X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;ds=sidebyside;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fdialogs%2FViewSslCertificateDialog.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fdialogs%2FViewSslCertificateDialog.kt;h=2bebf905107d537d5bc0b253a03f14c61552ae9e;hb=031def95c6d9bfc14113fe86b4a5690233d93ce2;hp=0000000000000000000000000000000000000000;hpb=f3b9172adedd74f705ddc0beac80798ae84f2920;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt new file mode 100644 index 00000000..2bebf905 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.kt @@ -0,0 +1,297 @@ +/* + * 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 + } + } +} \ No newline at end of file