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 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
61 fun displayDialog(sslError: SslError, webViewFragmentId: Long): SslCertificateErrorDialog {
62 // Get the various components of the SSL error message.
63 val primaryErrorInt = sslError.primaryError
64 val urlWithErrors = sslError.url
65 val sslCertificate = sslError.certificate
66 val issuedToCName = sslCertificate.issuedTo.cName
67 val issuedToOName = sslCertificate.issuedTo.oName
68 val issuedToUName = sslCertificate.issuedTo.uName
69 val issuedByCName = sslCertificate.issuedBy.cName
70 val issuedByOName = sslCertificate.issuedBy.oName
71 val issuedByUName = sslCertificate.issuedBy.uName
72 val startDate = sslCertificate.validNotBeforeDate
73 val endDate = sslCertificate.validNotAfterDate
75 // Create an arguments bundle.
76 val argumentsBundle = Bundle()
78 // Store the SSL error message components in the bundle.
79 argumentsBundle.putInt(PRIMARY_ERROR_INT, primaryErrorInt)
80 argumentsBundle.putString(URL_WITH_ERRORS, urlWithErrors)
81 argumentsBundle.putString(ISSUED_TO_CNAME, issuedToCName)
82 argumentsBundle.putString(ISSUED_TO_ONAME, issuedToOName)
83 argumentsBundle.putString(ISSUED_TO_UNAME, issuedToUName)
84 argumentsBundle.putString(ISSUED_BY_CNAME, issuedByCName)
85 argumentsBundle.putString(ISSUED_BY_ONAME, issuedByOName)
86 argumentsBundle.putString(ISSUED_BY_UNAME, issuedByUName)
87 argumentsBundle.putString(START_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
88 argumentsBundle.putString(END_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
89 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
91 // Create a new instance of the SSL certificate error dialog.
92 val thisSslCertificateErrorDialog = SslCertificateErrorDialog()
94 // Add the arguments bundle to the new dialog.
95 thisSslCertificateErrorDialog.arguments = argumentsBundle
97 // Return the new dialog.
98 return thisSslCertificateErrorDialog
102 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
103 // Get the variables from the bundle.
104 val primaryErrorInt = requireArguments().getInt(PRIMARY_ERROR_INT)
105 val urlWithErrors = requireArguments().getString(URL_WITH_ERRORS)
106 val issuedToCName = requireArguments().getString(ISSUED_TO_CNAME)
107 val issuedToOName = requireArguments().getString(ISSUED_TO_ONAME)
108 val issuedToUName = requireArguments().getString(ISSUED_TO_UNAME)
109 val issuedByCName = requireArguments().getString(ISSUED_BY_CNAME)
110 val issuedByOName = requireArguments().getString(ISSUED_BY_ONAME)
111 val issuedByUName = requireArguments().getString(ISSUED_BY_UNAME)
112 val startDate = requireArguments().getString(START_DATE)
113 val endDate = requireArguments().getString(END_DATE)
114 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
116 // Get the current position of this WebView fragment.
117 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
119 // Get the WebView tab fragment.
120 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
122 // Get the fragment view.
123 val fragmentView = webViewTabFragment.requireView()
125 // Get a handle for the current WebView.
126 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
128 // Get a handle for the SSL error handler.
129 val sslErrorHandler = nestedScrollWebView.sslErrorHandler
131 // Use an alert dialog builder to create the alert dialog.
132 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
135 dialogBuilder.setIcon(R.drawable.ssl_certificate)
138 dialogBuilder.setTitle(R.string.ssl_certificate_error)
141 dialogBuilder.setView(R.layout.ssl_certificate_error)
143 // Set the cancel button listener.
144 dialogBuilder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int ->
145 // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once.
146 if (sslErrorHandler != null) {
147 // Cancel the request.
148 sslErrorHandler.cancel()
150 // Reset the SSL error handler.
151 nestedScrollWebView.resetSslErrorHandler()
155 // Set the proceed button listener.
156 dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
157 // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once.
158 if (sslErrorHandler != null) {
159 // Proceed to the website.
160 sslErrorHandler.proceed()
162 // Reset the SSL error handler.
163 nestedScrollWebView.resetSslErrorHandler()
167 // Create an alert dialog from the builder.
168 val alertDialog = dialogBuilder.create()
170 // Get a handle for the shared preferences.
171 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
173 // Get the screenshot preference.
174 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
176 // Disable screenshots if not allowed.
177 if (!allowScreenshots) {
178 // Disable screenshots.
179 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
182 // Get a URI for the URL with errors.
183 val uriWithErrors = Uri.parse(urlWithErrors)
185 // The alert dialog must be shown before the contents can be modified.
188 // Get handles for the views.
189 val primaryErrorTextView = alertDialog.findViewById<TextView>(R.id.primary_error)!!
190 val urlTextView = alertDialog.findViewById<TextView>(R.id.url)!!
191 val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!!
192 val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!!
193 val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!!
194 val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!!
195 val issuedByTextView = alertDialog.findViewById<TextView>(R.id.issued_by_textview)!!
196 val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!!
197 val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!!
198 val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!!
199 val validDatesTextView = alertDialog.findViewById<TextView>(R.id.valid_dates_textview)!!
200 val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!!
201 val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!!
203 // Define the color spans.
204 val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
205 val redColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.red_text))
207 // Get the IP Addresses for the URI.
208 GetHostIpAddressesCoroutine.getAddresses(uriWithErrors.host!!, getString(R.string.ip_addresses), blueColorSpan, ipAddressesTextView)
210 // Setup the common strings.
211 val urlLabel = getString(R.string.url_label)
212 val cNameLabel = getString(R.string.common_name)
213 val oNameLabel = getString(R.string.organization)
214 val uNameLabel = getString(R.string.organizational_unit)
215 val startDateLabel = getString(R.string.start_date)
216 val endDateLabel = getString(R.string.end_date)
218 // Create a spannable string builder for each text view that needs multiple colors of text.
219 val urlStringBuilder = SpannableStringBuilder(urlLabel + urlWithErrors)
220 val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName)
221 val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName)
222 val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName)
223 val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName)
224 val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName)
225 val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName)
226 val startDateStringBuilder = SpannableStringBuilder(startDateLabel + startDate)
227 val endDateStringBuilder = SpannableStringBuilder(endDateLabel + endDate)
229 // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
230 urlStringBuilder.setSpan(blueColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
231 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
232 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
233 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
234 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
235 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
236 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
237 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
238 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
240 // Define the primary error string.
241 var primaryErrorString = ""
243 // Highlight the primary error in red and store it in the primary error string.
244 when (primaryErrorInt) {
245 SslError.SSL_IDMISMATCH -> {
246 // Change the URL span colors to red.
247 urlStringBuilder.setSpan(redColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
248 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
250 // Store the primary error string.
251 primaryErrorString = getString(R.string.cn_mismatch)
254 SslError.SSL_UNTRUSTED -> {
255 // Change the issued by text view text to red.
256 issuedByTextView.setTextColor(requireContext().getColor(R.color.red_text))
258 // Change the issued by span color to red.
259 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
260 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
261 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
263 // Store the primary error string.
264 primaryErrorString = getString(R.string.untrusted)
267 SslError.SSL_DATE_INVALID -> {
268 // Change the valid dates text view text to red.
269 validDatesTextView.setTextColor(requireContext().getColor(R.color.red_text))
271 // Change the date span colors to red.
272 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
273 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
275 // Store the primary error string.
276 primaryErrorString = getString(R.string.invalid_date)
279 SslError.SSL_NOTYETVALID -> {
280 // Change the start date span color to red.
281 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
283 // Store the primary error string.
284 primaryErrorString = getString(R.string.future_certificate)
287 SslError.SSL_EXPIRED -> {
288 // Change the end date span color to red.
289 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
291 // Store the primary error string.
292 primaryErrorString = getString(R.string.expired_certificate)
295 SslError.SSL_INVALID ->
296 // Store the primary error string.
297 primaryErrorString = getString(R.string.invalid_certificate)
300 // Display the strings.
301 primaryErrorTextView.text = primaryErrorString
302 urlTextView.text = urlStringBuilder
303 issuedToCNameTextView.text = issuedToCNameStringBuilder
304 issuedToONameTextView.text = issuedToONameStringBuilder
305 issuedToUNameTextView.text = issuedToUNameStringBuilder
306 issuedByCNameTextView.text = issuedByCNameStringBuilder
307 issuedByONameTextView.text = issuedByONameStringBuilder
308 issuedByUNameTextView.text = issuedByUNameStringBuilder
309 startDateTextView.text = startDateStringBuilder
310 endDateTextView.text = endDateStringBuilder
312 // Return the alert dialog.