/* * Copyright © 2016-2021 Soren Stoutner . * * This file is part of Privacy Browser . * * Privacy Browser is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Privacy Browser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Privacy Browser. If not, see . */ package com.stoutner.privacybrowser.dialogs import android.annotation.SuppressLint import android.app.Dialog import android.content.res.Configuration import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.ForegroundColorSpan import android.view.WindowManager import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.activities.MainWebViewActivity import com.stoutner.privacybrowser.views.NestedScrollWebView import java.text.DateFormat import java.util.Calendar // Define the class constants. private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id" class ViewSslCertificateDialog : DialogFragment() { companion object { // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin. @JvmStatic fun displayDialog(webViewFragmentId: Long): ViewSslCertificateDialog { // Create an arguments bundle. val argumentsBundle = Bundle() // Store the WebView fragment ID in the bundle. argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId) // Create a new instance of the view SSL certificate dialog. val viewSslCertificateDialog = ViewSslCertificateDialog() // Add the bundle to the new dialog. viewSslCertificateDialog.arguments = argumentsBundle // Return the new dialog. return viewSslCertificateDialog } } // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog. @SuppressLint("InflateParams") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // Get the current position of this WebView fragment. val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(requireArguments().getLong(WEBVIEW_FRAGMENT_ID)) // Get the WebView tab fragment. val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition) // Get the fragment view. val fragmentView = webViewTabFragment.requireView() // Get a handle for the current nested scroll WebView. val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview) // Use a builder to create the alert dialog. val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) // Create a drawable version of the favorite icon. val favoriteIconDrawable: Drawable = BitmapDrawable(resources, nestedScrollWebView.favoriteOrDefaultIcon) // Set the icon. dialogBuilder.setIcon(favoriteIconDrawable) // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.close, null) // Get the SSL certificate. val sslCertificate = nestedScrollWebView.certificate // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) // Get the screenshot preference. val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) // Check to see if the website is encrypted. if (sslCertificate == null) { // The website is not encrypted. // Set the title. dialogBuilder.setTitle(R.string.unencrypted_website) // Set the Layout. The parent view is `null` because it will be assigned by the alert dialog. dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null)) // Create an alert dialog from the builder. val alertDialog = dialogBuilder.create() // Disable screenshots if not allowed. if (!allowScreenshots) { // Disable screenshots. alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } // Return the alert dialog. return alertDialog } else { // The website is encrypted. // Set the title. dialogBuilder.setTitle(R.string.ssl_certificate) // Set the layout. The parent view is `null` because it will be assigned by the alert dialog. dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate_dialog, null)) // Create an alert dialog from the builder. val alertDialog = dialogBuilder.create() // Disable screenshots if not allowed. if (!allowScreenshots) { // Disable screenshots. alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } // The alert dialog must be shown before items in the layout can be modified. alertDialog.show() // Get handles for the text views. val domainTextView = alertDialog.findViewById(R.id.domain)!! val ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses)!! val issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname)!! val issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname)!! val issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname)!! val issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname)!! val issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname)!! val issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname)!! val startDateTextView = alertDialog.findViewById(R.id.start_date)!! val endDateTextView = alertDialog.findViewById(R.id.end_date)!! // Setup the labels. val domainLabel = getString(R.string.domain_label) + " " val ipAddressesLabel = getString(R.string.ip_addresses) + " " val cNameLabel = getString(R.string.common_name) + " " val oNameLabel = getString(R.string.organization) + " " val uNameLabel = getString(R.string.organizational_unit) + " " val startDateLabel = getString(R.string.start_date) + " " val endDateLabel = getString(R.string.end_date) + " " // Convert the URL to a URI. val uri = Uri.parse(nestedScrollWebView.url) // Extract the domain name from the URI. val domainString = uri.host // Get the strings from the SSL certificate. val issuedToCName = sslCertificate.issuedTo.cName val issuedToOName = sslCertificate.issuedTo.oName val issuedToUName = sslCertificate.issuedTo.uName val issuedByCName = sslCertificate.issuedBy.cName val issuedByOName = sslCertificate.issuedBy.oName val issuedByUName = sslCertificate.issuedBy.uName val startDate = sslCertificate.validNotBeforeDate val endDate = sslCertificate.validNotAfterDate // Create spannable string builders for each text view that needs multiple colors of text. val domainStringBuilder = SpannableStringBuilder(domainLabel + domainString) val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses) val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName) val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName) val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName) val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName) val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName) val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName) val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)) val endDateStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)) // Define the color spans. val blueColorSpan: ForegroundColorSpan val redColorSpan: ForegroundColorSpan // Get the current theme status. val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK // Set the color spans according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) { @Suppress("DEPRECATION") blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.blue_700)) @Suppress("DEPRECATION") redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_a700)) } else { @Suppress("DEPRECATION") blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.violet_700)) @Suppress("DEPRECATION") redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_900)) } // Format the domain string and issued to CName colors. if (domainString == issuedToCName) { // The domain and issued to CName match. // Set the strings to be blue. domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } else if (issuedToCName.startsWith("*.")) { // The issued to CName begins with a wildcard. // Remove the initial `*.`. val baseCertificateDomain = issuedToCName.substring(2) // Setup a copy of the domain string to test subdomains. var domainStringSubdomain = domainString!! // Define a domain names match variable. var domainNamesMatch = false // Check all the subdomains against the base certificate domain. while (!domainNamesMatch && domainStringSubdomain.contains(".")) { // Stop checking if we know that the domain names match or if we run out of subdomains. // Test the subdomain against the base certificate domain. if (domainStringSubdomain == baseCertificateDomain) { domainNamesMatch = true } // Strip out the lowest subdomain. domainStringSubdomain = domainStringSubdomain.substring(domainStringSubdomain.indexOf(".") + 1) } // Format the domain and issued to CName. if (domainNamesMatch) { // The domain is a subdomain of the wildcard certificate. // Set the strings to be blue. domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } else { // The domain is not a subdomain of the wildcard certificate. // Set the string to be red. domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } } else { // The strings do not match and issued to CName does not begin with a wildcard. // Set the strings to be red. domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } // Set the IP addresses, issued to, and issued by spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) // Get the current date. val currentDate = Calendar.getInstance().time // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. if (startDate.after(currentDate)) { // The certificate start date is in the future. startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } else { // The certificate start date is in the past. startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. if (endDate.before(currentDate)) { // The certificate end date is in the past. endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } else { // The certificate end date is in the future. endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } // Display the strings. domainTextView.text = domainStringBuilder ipAddressesTextView.text = ipAddressesStringBuilder issuedToCNameTextView.text = issuedToCNameStringBuilder issuedToONameTextView.text = issuedToONameStringBuilder issuedToUNameTextView.text = issuedToUNameStringBuilder issuedByCNameTextView.text = issuedByCNameStringBuilder issuedByONameTextView.text = issuedByONameStringBuilder issuedByUNameTextView.text = issuedByUNameStringBuilder startDateTextView.text = startDateStringBuilder endDateTextView.text = endDateStringBuilder // Return the alert dialog. return alertDialog } } }