]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt
First wrong button text in View Headers in night theme. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SslCertificateErrorDialog.kt
1 /*
2  * Copyright 2016-2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
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.webkit.SslErrorHandler
32 import android.widget.TextView
33
34 import androidx.appcompat.app.AlertDialog
35 import androidx.fragment.app.DialogFragment
36 import androidx.preference.PreferenceManager
37
38 import com.stoutner.privacybrowser.R
39 import com.stoutner.privacybrowser.activities.MainWebViewActivity
40 import com.stoutner.privacybrowser.coroutines.GetHostIpAddressesCoroutine
41 import com.stoutner.privacybrowser.views.NestedScrollWebView
42
43 import java.text.DateFormat
44
45 // Define the class constants.
46 private const val PRIMARY_ERROR_INT = "primary_error_int"
47 private const val URL_WITH_ERRORS = "url_with_errors"
48 private const val ISSUED_TO_CNAME = "issued_to_cname"
49 private const val ISSUED_TO_ONAME = "issued_to_oname"
50 private const val ISSUED_TO_UNAME = "issued_to_uname"
51 private const val ISSUED_BY_CNAME = "issued_by_cname"
52 private const val ISSUED_BY_ONAME = "issued_by_oname"
53 private const val ISSUED_BY_UNAME = "issued_by_uname"
54 private const val START_DATE = "start_date"
55 private const val END_DATE = "end_date"
56 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
57
58 class SslCertificateErrorDialog : DialogFragment() {
59     companion object {
60         fun displayDialog(sslError: SslError, webViewFragmentId: Long): SslCertificateErrorDialog {
61             // Get the various components of the SSL error message.
62             val primaryErrorInt = sslError.primaryError
63             val urlWithErrors = sslError.url
64             val sslCertificate = sslError.certificate
65             val issuedToCName = sslCertificate.issuedTo.cName
66             val issuedToOName = sslCertificate.issuedTo.oName
67             val issuedToUName = sslCertificate.issuedTo.uName
68             val issuedByCName = sslCertificate.issuedBy.cName
69             val issuedByOName = sslCertificate.issuedBy.oName
70             val issuedByUName = sslCertificate.issuedBy.uName
71             val startDate = sslCertificate.validNotBeforeDate
72             val endDate = sslCertificate.validNotAfterDate
73
74             // Create an arguments bundle.
75             val argumentsBundle = Bundle()
76
77             // Store the SSL error message components in the bundle.
78             argumentsBundle.putInt(PRIMARY_ERROR_INT, primaryErrorInt)
79             argumentsBundle.putString(URL_WITH_ERRORS, urlWithErrors)
80             argumentsBundle.putString(ISSUED_TO_CNAME, issuedToCName)
81             argumentsBundle.putString(ISSUED_TO_ONAME, issuedToOName)
82             argumentsBundle.putString(ISSUED_TO_UNAME, issuedToUName)
83             argumentsBundle.putString(ISSUED_BY_CNAME, issuedByCName)
84             argumentsBundle.putString(ISSUED_BY_ONAME, issuedByOName)
85             argumentsBundle.putString(ISSUED_BY_UNAME, issuedByUName)
86             argumentsBundle.putString(START_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
87             argumentsBundle.putString(END_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
88             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
89
90             // Create a new instance of the SSL certificate error dialog.
91             val thisSslCertificateErrorDialog = SslCertificateErrorDialog()
92
93             // Add the arguments bundle to the new dialog.
94             thisSslCertificateErrorDialog.arguments = argumentsBundle
95
96             // Return the new dialog.
97             return thisSslCertificateErrorDialog
98         }
99     }
100
101     // Define the class variables.
102     private var sslErrorHandler: SslErrorHandler? = null
103
104     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
105         try {
106             // Get the variables from the bundle.
107             val primaryErrorInt = requireArguments().getInt(PRIMARY_ERROR_INT)
108             val urlWithErrors = requireArguments().getString(URL_WITH_ERRORS)
109             val issuedToCName = requireArguments().getString(ISSUED_TO_CNAME)
110             val issuedToOName = requireArguments().getString(ISSUED_TO_ONAME)
111             val issuedToUName = requireArguments().getString(ISSUED_TO_UNAME)
112             val issuedByCName = requireArguments().getString(ISSUED_BY_CNAME)
113             val issuedByOName = requireArguments().getString(ISSUED_BY_ONAME)
114             val issuedByUName = requireArguments().getString(ISSUED_BY_UNAME)
115             val startDate = requireArguments().getString(START_DATE)
116             val endDate = requireArguments().getString(END_DATE)
117             val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
118
119             // Get the current position of this WebView fragment.
120             val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId)
121
122             // Get the WebView tab fragment.
123             val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition)
124
125             // Get the fragment view.
126             val fragmentView = webViewTabFragment.requireView()
127
128             // Get a handle for the current WebView.
129             val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
130
131             // Get a handle for the SSL error handler.
132             sslErrorHandler = nestedScrollWebView.sslErrorHandler
133
134             // Use an alert dialog builder to create the alert dialog.
135             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
136
137             // Set the icon.
138             dialogBuilder.setIcon(R.drawable.ssl_certificate)
139
140             // Set the title.
141             dialogBuilder.setTitle(R.string.ssl_certificate_error)
142
143             // Set the view.
144             dialogBuilder.setView(R.layout.ssl_certificate_error)
145
146             // Set the cancel button listener.
147             dialogBuilder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int ->
148                 // Check to make sure the SSL error handler is not null.  This might happen if multiple dialogs are displayed at once.
149                 if (sslErrorHandler != null) {
150                     // Cancel the request.
151                     sslErrorHandler!!.cancel()
152
153                     // Reset the SSL error handler.
154                     nestedScrollWebView.resetSslErrorHandler()
155                 }
156             }
157
158             // Set the proceed button listener.
159             dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
160                 // Check to make sure the SSL error handler is not null.  This might happen if multiple dialogs are displayed at once.
161                 if (sslErrorHandler != null) {
162                     // Proceed to the website.
163                     sslErrorHandler!!.proceed()
164
165                     // Reset the SSL error handler.
166                     nestedScrollWebView.resetSslErrorHandler()
167                 }
168             }
169
170             // Create an alert dialog from the builder.
171             val alertDialog = dialogBuilder.create()
172
173             // Get a handle for the shared preferences.
174             val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
175
176             // Get the screenshot preference.
177             val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
178
179             // Disable screenshots if not allowed.
180             if (!allowScreenshots) {
181                 // Disable screenshots.
182                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
183             }
184
185             // Get a URI for the URL with errors.
186             val uriWithErrors = Uri.parse(urlWithErrors)
187
188             // The alert dialog must be shown before the contents can be modified.
189             alertDialog.show()
190
191             // Get handles for the views.
192             val primaryErrorTextView = alertDialog.findViewById<TextView>(R.id.primary_error)!!
193             val urlTextView = alertDialog.findViewById<TextView>(R.id.url)!!
194             val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!!
195             val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!!
196             val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!!
197             val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!!
198             val issuedByTextView = alertDialog.findViewById<TextView>(R.id.issued_by_textview)!!
199             val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!!
200             val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!!
201             val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!!
202             val validDatesTextView = alertDialog.findViewById<TextView>(R.id.valid_dates_textview)!!
203             val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!!
204             val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!!
205
206             // Define the color spans.
207             val blueColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.alt_blue_text))
208             val redColorSpan = ForegroundColorSpan(requireContext().getColor(R.color.red_text))
209
210             // Get the IP Addresses for the URI.
211             GetHostIpAddressesCoroutine.getAddresses(uriWithErrors.host!!, getString(R.string.ip_addresses), blueColorSpan, ipAddressesTextView)
212
213             // Setup the common strings.
214             val urlLabel = getString(R.string.url_label)
215             val cNameLabel = getString(R.string.common_name)
216             val oNameLabel = getString(R.string.organization)
217             val uNameLabel = getString(R.string.organizational_unit)
218             val startDateLabel = getString(R.string.start_date)
219             val endDateLabel = getString(R.string.end_date)
220
221             // Create a spannable string builder for each text view that needs multiple colors of text.
222             val urlStringBuilder = SpannableStringBuilder(urlLabel + urlWithErrors)
223             val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName)
224             val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName)
225             val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName)
226             val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName)
227             val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName)
228             val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName)
229             val startDateStringBuilder = SpannableStringBuilder(startDateLabel + startDate)
230             val endDateStringBuilder = SpannableStringBuilder(endDateLabel + endDate)
231
232             // Setup the spans to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
233             urlStringBuilder.setSpan(blueColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
234             issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
235             issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
236             issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
237             issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
238             issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
239             issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
240             startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
241             endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
242
243             // Define the primary error string.
244             var primaryErrorString = ""
245
246             // Highlight the primary error in red and store it in the primary error string.
247             when (primaryErrorInt) {
248                 SslError.SSL_IDMISMATCH -> {
249                     // Change the URL span colors to red.
250                     urlStringBuilder.setSpan(redColorSpan, urlLabel.length, urlStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
251                     issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
252
253                     // Store the primary error string.
254                     primaryErrorString = getString(R.string.cn_mismatch)
255                 }
256
257                 SslError.SSL_UNTRUSTED -> {
258                     // Change the issued by text view text to red.
259                     issuedByTextView.setTextColor(requireContext().getColor(R.color.red_text))
260
261                     // Change the issued by span color to red.
262                     issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
263                     issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
264                     issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
265
266                     // Store the primary error string.
267                     primaryErrorString = getString(R.string.untrusted)
268                 }
269
270                 SslError.SSL_DATE_INVALID -> {
271                     // Change the valid dates text view text to red.
272                     validDatesTextView.setTextColor(requireContext().getColor(R.color.red_text))
273
274                     // Change the date span colors to red.
275                     startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
276                     endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
277
278                     // Store the primary error string.
279                     primaryErrorString = getString(R.string.invalid_date)
280                 }
281
282                 SslError.SSL_NOTYETVALID -> {
283                     // Change the start date span color to red.
284                     startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
285
286                     // Store the primary error string.
287                     primaryErrorString = getString(R.string.future_certificate)
288                 }
289
290                 SslError.SSL_EXPIRED -> {
291                     // Change the end date span color to red.
292                     endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
293
294                     // Store the primary error string.
295                     primaryErrorString = getString(R.string.expired_certificate)
296                 }
297
298                 SslError.SSL_INVALID ->
299                     // Store the primary error string.
300                     primaryErrorString = getString(R.string.invalid_certificate)
301             }
302
303             // Display the strings.
304             primaryErrorTextView.text = primaryErrorString
305             urlTextView.text = urlStringBuilder
306             issuedToCNameTextView.text = issuedToCNameStringBuilder
307             issuedToONameTextView.text = issuedToONameStringBuilder
308             issuedToUNameTextView.text = issuedToUNameStringBuilder
309             issuedByCNameTextView.text = issuedByCNameStringBuilder
310             issuedByONameTextView.text = issuedByONameStringBuilder
311             issuedByUNameTextView.text = issuedByUNameStringBuilder
312             startDateTextView.text = startDateStringBuilder
313             endDateTextView.text = endDateStringBuilder
314
315             // Return the alert dialog.
316             return alertDialog
317         } catch (exception: Exception) {  // The app was restarted while the dialog was displayed.
318             // Dismiss this new instance of the dialog as soon as it is displayed.
319             dismiss()
320
321             // Use an alert dialog builder to create an empty alert dialog.
322             val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
323
324             // Return the empty alert dialog.
325             return dialogBuilder.create()
326             }
327     }
328
329     override fun onSaveInstanceState(outState: Bundle) {
330         // Run the default commands.
331         super.onSaveInstanceState(outState)
332
333         // Cancel the request if the SSL error handler is not null.  This resets the WebView so it is not waiting on a response to the error handler if it is restarted in the background.
334         // Otherwise, after restart, the dialog is no longer displayed, but the error handler is still pending and there is no way to cause the dialog to redisplay for that URL in that tab.
335         if (sslErrorHandler != null)
336             sslErrorHandler!!.cancel()
337     }
338 }