2 * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser Android is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.dialogs
22 import android.app.Dialog
23 import android.content.DialogInterface
24 import android.net.Uri
25 import android.net.http.SslError
26 import android.os.Bundle
27 import android.text.SpannableStringBuilder
28 import android.text.Spanned
29 import android.text.style.ForegroundColorSpan
30 import android.view.WindowManager
31 import android.widget.TextView
33 import androidx.appcompat.app.AlertDialog
34 import androidx.fragment.app.DialogFragment
35 import androidx.preference.PreferenceManager
37 import com.stoutner.privacybrowser.R
38 import com.stoutner.privacybrowser.activities.MainWebViewActivity
39 import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine
40 import com.stoutner.privacybrowser.views.NestedScrollWebView
42 import java.text.DateFormat
44 // Define the class constants.
45 private const val PRIMARY_ERROR_INT = "primary_error_int"
46 private const val URL_WITH_ERRORS = "url_with_errors"
47 private const val ISSUED_TO_CNAME = "issued_to_cname"
48 private const val ISSUED_TO_ONAME = "issued_to_oname"
49 private const val ISSUED_TO_UNAME = "issued_to_uname"
50 private const val ISSUED_BY_CNAME = "issued_by_cname"
51 private const val ISSUED_BY_ONAME = "issued_by_oname"
52 private const val ISSUED_BY_UNAME = "issued_by_uname"
53 private const val START_DATE = "start_date"
54 private const val END_DATE = "end_date"
55 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
57 class SslCertificateErrorDialog : DialogFragment() {
59 fun displayDialog(sslError: SslError, webViewFragmentId: Long): SslCertificateErrorDialog {
60 // Get the various components of the SSL error message.
61 val primaryErrorInt = sslError.primaryError
62 val urlWithErrors = sslError.url
63 val sslCertificate = sslError.certificate
64 val issuedToCName = sslCertificate.issuedTo.cName
65 val issuedToOName = sslCertificate.issuedTo.oName
66 val issuedToUName = sslCertificate.issuedTo.uName
67 val issuedByCName = sslCertificate.issuedBy.cName
68 val issuedByOName = sslCertificate.issuedBy.oName
69 val issuedByUName = sslCertificate.issuedBy.uName
70 val startDate = sslCertificate.validNotBeforeDate
71 val endDate = sslCertificate.validNotAfterDate
73 // Create an arguments bundle.
74 val argumentsBundle = Bundle()
76 // Store the SSL error message components in the bundle.
77 argumentsBundle.putInt(PRIMARY_ERROR_INT, primaryErrorInt)
78 argumentsBundle.putString(URL_WITH_ERRORS, urlWithErrors)
79 argumentsBundle.putString(ISSUED_TO_CNAME, issuedToCName)
80 argumentsBundle.putString(ISSUED_TO_ONAME, issuedToOName)
81 argumentsBundle.putString(ISSUED_TO_UNAME, issuedToUName)
82 argumentsBundle.putString(ISSUED_BY_CNAME, issuedByCName)
83 argumentsBundle.putString(ISSUED_BY_ONAME, issuedByOName)
84 argumentsBundle.putString(ISSUED_BY_UNAME, issuedByUName)
85 argumentsBundle.putString(START_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
86 argumentsBundle.putString(END_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
87 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
89 // Create a new instance of the SSL certificate error dialog.
90 val thisSslCertificateErrorDialog = SslCertificateErrorDialog()
92 // Add the arguments bundle to the new dialog.
93 thisSslCertificateErrorDialog.arguments = argumentsBundle
95 // Return the new dialog.
96 return thisSslCertificateErrorDialog
100 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
101 // Get the variables from the bundle.
102 val primaryErrorInt = requireArguments().getInt(PRIMARY_ERROR_INT)
103 val urlWithErrors = requireArguments().getString(URL_WITH_ERRORS)
104 val issuedToCName = requireArguments().getString(ISSUED_TO_CNAME)
105 val issuedToOName = requireArguments().getString(ISSUED_TO_ONAME)
106 val issuedToUName = requireArguments().getString(ISSUED_TO_UNAME)
107 val issuedByCName = requireArguments().getString(ISSUED_BY_CNAME)
108 val issuedByOName = requireArguments().getString(ISSUED_BY_ONAME)
109 val issuedByUName = requireArguments().getString(ISSUED_BY_UNAME)
110 val startDate = requireArguments().getString(START_DATE)
111 val endDate = requireArguments().getString(END_DATE)
112 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
114 // Get the current position of this WebView fragment.
115 val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId)
117 // Get the WebView tab fragment.
118 val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition)
120 // Get the fragment view.
121 val fragmentView = webViewTabFragment.requireView()
123 // Get a handle for the current WebView.
124 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
126 // Get a handle for the SSL error handler.
127 val sslErrorHandler = nestedScrollWebView.sslErrorHandler
129 // Use an alert dialog builder to create the alert dialog.
130 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
133 dialogBuilder.setIcon(R.drawable.ssl_certificate)
136 dialogBuilder.setTitle(R.string.ssl_certificate_error)
139 dialogBuilder.setView(R.layout.ssl_certificate_error)
141 // Set the cancel button listener.
142 dialogBuilder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int ->
143 // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once.
144 if (sslErrorHandler != null) {
145 // Cancel the request.
146 sslErrorHandler.cancel()
148 // Reset the SSL error handler.
149 nestedScrollWebView.resetSslErrorHandler()
153 // Set the proceed button listener.
154 dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
155 // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once.
156 if (sslErrorHandler != null) {
157 // Proceed to the website.
158 sslErrorHandler.proceed()
160 // Reset the SSL error handler.
161 nestedScrollWebView.resetSslErrorHandler()
165 // Create an alert dialog from the builder.
166 val alertDialog = dialogBuilder.create()
168 // Get a handle for the shared preferences.
169 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
171 // Get the screenshot preference.
172 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
174 // Disable screenshots if not allowed.
175 if (!allowScreenshots) {
176 // Disable screenshots.
177 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
180 // Get a URI for the URL with errors.
181 val uriWithErrors = Uri.parse(urlWithErrors)
183 // The alert dialog must be shown before the contents can be modified.
186 // Get handles for the views.
187 val primaryErrorTextView = alertDialog.findViewById<TextView>(R.id.primary_error)!!
188 val urlTextView = alertDialog.findViewById<TextView>(R.id.url)!!
189 val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!!
190 val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!!
191 val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!!
192 val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!!
193 val issuedByTextView = alertDialog.findViewById<TextView>(R.id.issued_by_textview)!!
194 val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!!
195 val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!!
196 val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!!
197 val validDatesTextView = alertDialog.findViewById<TextView>(R.id.valid_dates_textview)!!
198 val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!!
199 val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!!
201 // Define the color spans.
202 val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
203 val redColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.red_text))
205 // Get the IP Addresses for the URI.
206 GetHostIpAddressesCoroutine.getAddresses(uriWithErrors.host!!, getString(R.string.ip_addresses), blueColorSpan, ipAddressesTextView)
208 // Setup the common strings.
209 val urlLabel = getString(R.string.url_label)
210 val cNameLabel = getString(R.string.common_name)
211 val oNameLabel = getString(R.string.organization)
212 val uNameLabel = getString(R.string.organizational_unit)
213 val startDateLabel = getString(R.string.start_date)
214 val endDateLabel = getString(R.string.end_date)
216 // Create a spannable string builder for each text view that needs multiple colors of text.
217 val urlStringBuilder = SpannableStringBuilder(urlLabel + urlWithErrors)
218 val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName)
219 val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName)
220 val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName)
221 val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName)
222 val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName)
223 val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName)
224 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + startDate)
225 val endDateStringBuilder = SpannableStringBuilder(endDateLabel + endDate)
227 // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
228 urlStringBuilder.setSpan(blueColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
229 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
230 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
231 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
232 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
233 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
234 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
235 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
236 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
238 // Define the primary error string.
239 var primaryErrorString = ""
241 // Highlight the primary error in red and store it in the primary error string.
242 when (primaryErrorInt) {
243 SslError.SSL_IDMISMATCH -> {
244 // Change the URL span colors to red.
245 urlStringBuilder.setSpan(redColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
246 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
248 // Store the primary error string.
249 primaryErrorString = getString(R.string.cn_mismatch)
252 SslError.SSL_UNTRUSTED -> {
253 // Change the issued by text view text to red.
254 issuedByTextView.setTextColor(requireContext().getColor(R.color.red_text))
256 // Change the issued by span color to red.
257 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
258 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
259 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
261 // Store the primary error string.
262 primaryErrorString = getString(R.string.untrusted)
265 SslError.SSL_DATE_INVALID -> {
266 // Change the valid dates text view text to red.
267 validDatesTextView.setTextColor(requireContext().getColor(R.color.red_text))
269 // Change the date span colors to red.
270 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
271 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
273 // Store the primary error string.
274 primaryErrorString = getString(R.string.invalid_date)
277 SslError.SSL_NOTYETVALID -> {
278 // Change the start date span color to red.
279 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
281 // Store the primary error string.
282 primaryErrorString = getString(R.string.future_certificate)
285 SslError.SSL_EXPIRED -> {
286 // Change the end date span color to red.
287 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
289 // Store the primary error string.
290 primaryErrorString = getString(R.string.expired_certificate)
293 SslError.SSL_INVALID ->
294 // Store the primary error string.
295 primaryErrorString = getString(R.string.invalid_certificate)
298 // Display the strings.
299 primaryErrorTextView.text = primaryErrorString
300 urlTextView.text = urlStringBuilder
301 issuedToCNameTextView.text = issuedToCNameStringBuilder
302 issuedToONameTextView.text = issuedToONameStringBuilder
303 issuedToUNameTextView.text = issuedToUNameStringBuilder
304 issuedByCNameTextView.text = issuedByCNameStringBuilder
305 issuedByONameTextView.text = issuedByONameStringBuilder
306 issuedByUNameTextView.text = issuedByUNameStringBuilder
307 startDateTextView.text = startDateStringBuilder
308 endDateTextView.text = endDateStringBuilder
310 // Return the alert dialog.