]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt
Remove AsyncTask from SSLCertificateErrorDialog. https://redmine.stoutner.com/issues/987
[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.widget.TextView
32
33 import androidx.appcompat.app.AlertDialog
34 import androidx.fragment.app.DialogFragment
35 import androidx.preference.PreferenceManager
36
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
41
42 import java.text.DateFormat
43
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"
56
57 class SslCertificateErrorDialog : DialogFragment() {
58     companion object {
59         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
60         @JvmStatic
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
74
75             // Create an arguments bundle.
76             val argumentsBundle = Bundle()
77
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)
90
91             // Create a new instance of the SSL certificate error dialog.
92             val thisSslCertificateErrorDialog = SslCertificateErrorDialog()
93
94             // Add the arguments bundle to the new dialog.
95             thisSslCertificateErrorDialog.arguments = argumentsBundle
96
97             // Return the new dialog.
98             return thisSslCertificateErrorDialog
99         }
100     }
101
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)
115
116         // Get the current position of this WebView fragment.
117         val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
118
119         // Get the WebView tab fragment.
120         val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
121
122         // Get the fragment view.
123         val fragmentView = webViewTabFragment.requireView()
124
125         // Get a handle for the current WebView.
126         val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
127
128         // Get a handle for the SSL error handler.
129         val sslErrorHandler = nestedScrollWebView.sslErrorHandler
130
131         // Use an alert dialog builder to create the alert dialog.
132         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
133
134         // Set the icon.
135         dialogBuilder.setIcon(R.drawable.ssl_certificate)
136
137         // Set the title.
138         dialogBuilder.setTitle(R.string.ssl_certificate_error)
139
140         // Set the view.
141         dialogBuilder.setView(R.layout.ssl_certificate_error)
142
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()
149
150                 // Reset the SSL error handler.
151                 nestedScrollWebView.resetSslErrorHandler()
152             }
153         }
154
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()
161
162                 // Reset the SSL error handler.
163                 nestedScrollWebView.resetSslErrorHandler()
164             }
165         }
166
167         // Create an alert dialog from the builder.
168         val alertDialog = dialogBuilder.create()
169
170         // Get a handle for the shared preferences.
171         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
172
173         // Get the screenshot preference.
174         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
175
176         // Disable screenshots if not allowed.
177         if (!allowScreenshots) {
178             // Disable screenshots.
179             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
180         }
181
182         // Get a URI for the URL with errors.
183         val uriWithErrors = Uri.parse(urlWithErrors)
184
185         // The alert dialog must be shown before the contents can be modified.
186         alertDialog.show()
187
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)!!
202
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))
206
207         // Get the IP Addresses for the URI.
208         GetHostIpAddressesCoroutine.getAddresses(uriWithErrors.host!!, getString(R.string.ip_addresses), blueColorSpan, ipAddressesTextView)
209
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)
217
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)
228
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)
239
240         // Define the primary error string.
241         var primaryErrorString = ""
242
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)
249
250                 // Store the primary error string.
251                 primaryErrorString = getString(R.string.cn_mismatch)
252             }
253
254             SslError.SSL_UNTRUSTED -> {
255                 // Change the issued by text view text to red.
256                 issuedByTextView.setTextColor(requireContext().getColor(R.color.red_text))
257
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)
262
263                 // Store the primary error string.
264                 primaryErrorString = getString(R.string.untrusted)
265             }
266
267             SslError.SSL_DATE_INVALID -> {
268                 // Change the valid dates text view text to red.
269                 validDatesTextView.setTextColor(requireContext().getColor(R.color.red_text))
270
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)
274
275                 // Store the primary error string.
276                 primaryErrorString = getString(R.string.invalid_date)
277             }
278
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)
282
283                 // Store the primary error string.
284                 primaryErrorString = getString(R.string.future_certificate)
285             }
286
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)
290
291                 // Store the primary error string.
292                 primaryErrorString = getString(R.string.expired_certificate)
293             }
294
295             SslError.SSL_INVALID ->
296                 // Store the primary error string.
297                 primaryErrorString = getString(R.string.invalid_certificate)
298         }
299
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
311
312         // Return the alert dialog.
313         return alertDialog
314     }
315 }