Add SSL certificate pinning. Implements https://redmine.stoutner.com/issues/54.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / PinnedSslCertificateMismatchDialog.java
1 /*
2  * Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser 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 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.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs;
21
22 import android.annotation.SuppressLint;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.net.http.SslCertificate;
28 import android.os.Bundle;
29 import android.support.annotation.NonNull;
30 import android.support.design.widget.TabLayout;
31 import android.support.v4.view.PagerAdapter;
32 // We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
33 import android.support.v7.app.AppCompatDialogFragment;
34 import android.text.SpannableStringBuilder;
35 import android.text.Spanned;
36 import android.text.style.ForegroundColorSpan;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.widget.TextView;
41
42 import com.stoutner.privacybrowser.R;
43 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
44 import com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager;
45 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
46
47 import java.text.DateFormat;
48 import java.util.Date;
49
50 // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
51 @SuppressLint("InflateParams")
52 public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment {
53     // `layoutInflater` is used in `onCreateDialog()` and `pagerAdapter`.
54     private LayoutInflater layoutInflater;
55
56     // The current website SSL certificate variables are used in `onCreateDialog()` and `pagerAdapter()`.
57     private String currentSslIssuedToCNameString;
58     private String currentSslIssuedToONameString;
59     private String currentSslIssuedToUNameString;
60     private String currentSslIssuedByCNameString;
61     private String currentSslIssuedByONameString;
62     private String currentSslIssuedByUNameString;
63     private Date currentSslStartDate;
64     private Date currentSslEndDate;
65
66     // The public interface is used to send information back to the parent activity.
67     public interface PinnedSslCertificateMismatchListener {
68         void onSslMismatchBack();
69
70         void onSslMismatchProceed();
71     }
72
73     // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`.
74     private PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener pinnedSslCertificateMismatchListener;
75
76     // Check to make sure that the parent activity implements the listener.
77     public void onAttach(Context context) {
78         super.onAttach(context);
79
80         try {
81             pinnedSslCertificateMismatchListener = (PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener) context;
82         } catch(ClassCastException exception) {
83             throw new ClassCastException(context.toString() + " must implement PinnedSslCertificateMismatchListener");
84         }
85     }
86
87     @NonNull
88     public Dialog onCreateDialog(Bundle savedInstanceState) {
89         // Get the activity's layout inflater.
90         layoutInflater = getActivity().getLayoutInflater();
91
92         // Use `AlertDialog.Builder` to create the `AlertDialog`.
93         AlertDialog.Builder dialogBuilder;
94
95         // Set the style according to the theme.
96         if (MainWebViewActivity.darkTheme) {
97             // Set the dialog theme.
98             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
99
100             // Set the icon.
101             dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
102         } else {
103             // Set the dialog theme.
104             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
105
106             // Set the icon.
107             dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
108         }
109
110         // Setup the neutral button.
111         dialogBuilder.setNeutralButton(R.string.update_ssl, new DialogInterface.OnClickListener() {
112             @Override
113             public void onClick(DialogInterface dialog, int which) {
114                 // Initialize the `long` date variables.  If the date is `null`, a long value of `0` will be stored in the Domains database entry.
115                 long currentSslStartDateLong = 0;
116                 long currentSslEndDateLong = 0;
117
118                 // Convert the `Dates` into `longs`.
119                 if (currentSslStartDate != null) {
120                     currentSslStartDateLong = currentSslStartDate.getTime();
121                 }
122
123                 if (currentSslEndDate != null) {
124                     currentSslEndDateLong = currentSslEndDate.getTime();
125                 }
126
127                 // Initialize the database handler.  The two `nulls` do not specify the database name or a `CursorFactory`.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
128                 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(getContext(), null, null, 0);
129
130                 // Update the pinned SSL certificate for this domain.
131                 domainsDatabaseHelper.updateCertificate(MainWebViewActivity.domainSettingsDatabaseId, currentSslIssuedToCNameString, currentSslIssuedToONameString, currentSslIssuedToUNameString, currentSslIssuedByCNameString, currentSslIssuedByONameString,
132                         currentSslIssuedByUNameString, currentSslStartDateLong, currentSslEndDateLong);
133
134                 // Update the pinned SSL certificate global variables to match the information that is now in the database.
135                 MainWebViewActivity.pinnedDomainSslIssuedToCNameString = currentSslIssuedToCNameString;
136                 MainWebViewActivity.pinnedDomainSslIssuedToONameString = currentSslIssuedToONameString;
137                 MainWebViewActivity.pinnedDomainSslIssuedToUNameString = currentSslIssuedToUNameString;
138                 MainWebViewActivity.pinnedDomainSslIssuedByCNameString = currentSslIssuedByCNameString;
139                 MainWebViewActivity.pinnedDomainSslIssuedByONameString = currentSslIssuedByONameString;
140                 MainWebViewActivity.pinnedDomainSslIssuedByUNameString = currentSslIssuedByUNameString;
141                 MainWebViewActivity.pinnedDomainSslStartDate = currentSslStartDate;
142                 MainWebViewActivity.pinnedDomainSslEndDate = currentSslEndDate;
143             }
144         });
145
146         // Setup the negative button.
147         dialogBuilder.setNegativeButton(R.string.back, new DialogInterface.OnClickListener() {
148             @Override
149             public void onClick(DialogInterface dialog, int which) {
150                 // Call the `onSslMismatchBack` public interface to send the `WebView` back one page.
151                 pinnedSslCertificateMismatchListener.onSslMismatchBack();
152             }
153         });
154
155         // Setup the positive button.
156         dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() {
157             @Override
158             public void onClick(DialogInterface dialog, int which) {
159                 // Call the `onSslMismatchProceed` public interface.
160                 pinnedSslCertificateMismatchListener.onSslMismatchProceed();
161             }
162         });
163
164         // Set the title.
165         dialogBuilder.setTitle(R.string.ssl_certificate_mismatch);
166
167         // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
168         dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_linearlayout, null));
169
170         // Create an `AlertDialog` from the `AlertDialog.Builder`
171         final AlertDialog alertDialog = dialogBuilder.create();
172
173         // Show the `AlertDialog` so the items in the layout can be modified.
174         alertDialog.show();
175
176         //  Setup `wrapVerticalContentViewPager`.
177         WrapVerticalContentViewPager wrapVerticalContentViewPager = (WrapVerticalContentViewPager) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
178         wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
179
180         // Setup the `TabLayout` and connect it to the `WrapVerticalContentViewPager`.
181         TabLayout tabLayout = (TabLayout) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout);
182         tabLayout.setupWithViewPager(wrapVerticalContentViewPager);
183
184         // `onCreateDialog` requires the return of an `AlertDialog`.
185         return alertDialog;
186     }
187
188     private class pagerAdapter extends PagerAdapter {
189         @Override
190         public boolean isViewFromObject(View view, Object object) {
191             // Check to see if the `View` and the `Object` are the same.
192             return (view == object);
193         }
194
195         @Override
196         public int getCount() {
197             // There are two tabs.
198             return 2;
199         }
200
201         @Override
202         public CharSequence getPageTitle(int position) {
203             // Return the current tab title.
204             if (position == 0) {  // The current SSL certificate tab.
205                 return getString(R.string.current_ssl);
206             } else {  // The pinned SSL certificate tab.
207                 return getString(R.string.pinned_ssl);
208             }
209         }
210
211         @Override
212         public Object instantiateItem(ViewGroup container, int position) {
213             // Inflate the `ScrollView` for this tab.
214             ViewGroup tabViewGroup = (ViewGroup) layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_scrollview, container, false);
215
216             // Get handles for the `TextViews`.
217             TextView issuedToCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_cname);
218             TextView issuedToONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_oname);
219             TextView issuedToUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_uname);
220             TextView issuedByCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_cname);
221             TextView issuedByONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_oname);
222             TextView issuedByUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_uname);
223             TextView startDateTextView = (TextView) tabViewGroup.findViewById(R.id.start_date);
224             TextView endDateTextView = (TextView) tabViewGroup.findViewById(R.id.end_date);
225
226             // Setup the labels.
227             String cNameLabel = getString(R.string.common_name) + "  ";
228             String oNameLabel = getString(R.string.organization) + "  ";
229             String uNameLabel = getString(R.string.organizational_unit) + "  ";
230             String startDateLabel = getString(R.string.start_date) + "  ";
231             String endDateLabel = getString(R.string.end_date) + "  ";
232
233             // Get the current website SSL certificate.
234             SslCertificate sslCertificate = MainWebViewActivity.sslCertificate;
235
236             // Extract the individual pieces of information from the current website SSL certificate if it is not null.
237             if (sslCertificate != null) {
238                 currentSslIssuedToCNameString = sslCertificate.getIssuedTo().getCName();
239                 currentSslIssuedToONameString = sslCertificate.getIssuedTo().getOName();
240                 currentSslIssuedToUNameString = sslCertificate.getIssuedTo().getUName();
241                 currentSslIssuedByCNameString = sslCertificate.getIssuedBy().getCName();
242                 currentSslIssuedByONameString = sslCertificate.getIssuedBy().getOName();
243                 currentSslIssuedByUNameString = sslCertificate.getIssuedBy().getUName();
244                 currentSslStartDate = sslCertificate.getValidNotBeforeDate();
245                 currentSslEndDate = sslCertificate.getValidNotAfterDate();
246             } else {
247                 // Initialize the current website SSL certificate variables with blank information.
248                 currentSslIssuedToCNameString = "";
249                 currentSslIssuedToONameString = "";
250                 currentSslIssuedToUNameString = "";
251                 currentSslIssuedByCNameString = "";
252                 currentSslIssuedByONameString = "";
253                 currentSslIssuedByUNameString = "";
254             }
255
256             // Initialize the `SpannableStringBuilders`.
257             SpannableStringBuilder issuedToCNameStringBuilder;
258             SpannableStringBuilder issuedToONameStringBuilder;
259             SpannableStringBuilder issuedToUNameStringBuilder;
260             SpannableStringBuilder issuedByCNameStringBuilder;
261             SpannableStringBuilder issuedByONameStringBuilder;
262             SpannableStringBuilder issuedByUNameStringBuilder;
263             SpannableStringBuilder startDateStringBuilder;
264             SpannableStringBuilder endDateStringBuilder;
265
266             // Setup the `SpannableStringBuilders` for each tab.
267             if (position == 0) {  // Setup the current SSL certificate tab.
268                 issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCNameString);
269                 issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToONameString);
270                 issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUNameString);
271                 issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCNameString);
272                 issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByONameString);
273                 issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUNameString);
274
275                 // Set the dates if they aren't `null`.
276                 if (currentSslStartDate == null) {
277                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
278                 } else {
279                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate));
280                 }
281
282                 if (currentSslEndDate == null) {
283                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
284                 } else {
285                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate));
286                 }
287             } else {  // Setup the pinned SSL certificate tab.
288                 issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToCNameString);
289                 issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToONameString);
290                 issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToUNameString);
291                 issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByCNameString);
292                 issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByONameString);
293                 issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByUNameString);
294
295                 // Set the dates if they aren't `null`.
296                 if (MainWebViewActivity.pinnedDomainSslStartDate == null) {
297                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
298                 } else {
299                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslStartDate));
300                 }
301
302                 if (MainWebViewActivity.pinnedDomainSslEndDate == null) {
303                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
304                 } else {
305                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslEndDate));
306                 }
307             }
308
309             // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
310             @SuppressWarnings("deprecation") ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
311
312             // Create a blue `ForegroundColorSpan`.
313             ForegroundColorSpan blueColorSpan;
314
315             // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
316             if (MainWebViewActivity.darkTheme) {
317                 //noinspection deprecation
318                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
319             } else {
320                 //noinspection deprecation
321                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
322             }
323
324             // Configure the spans to display conflicting information in red.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
325             if (currentSslIssuedToCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToCNameString)) {
326                 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
327             } else {
328                 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
329             }
330
331             if (currentSslIssuedToONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToONameString)) {
332                 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
333             } else {
334                 issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
335             }
336
337             if (currentSslIssuedToUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToUNameString)) {
338                 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
339             } else {
340                 issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
341             }
342
343             if (currentSslIssuedByCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByCNameString)) {
344                 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
345             } else {
346                 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
347             }
348
349             if (currentSslIssuedByONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByONameString)) {
350                 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
351             } else {
352                 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
353             }
354
355             if (currentSslIssuedByUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByUNameString)) {
356                 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
357             } else {
358                 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
359             }
360
361             if ((currentSslStartDate != null) && (MainWebViewActivity.pinnedDomainSslStartDate != null) && currentSslStartDate.equals(MainWebViewActivity.pinnedDomainSslStartDate)) {
362                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
363             } else {
364                 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
365             }
366
367             if ((currentSslEndDate != null) && (MainWebViewActivity.pinnedDomainSslEndDate != null) && currentSslEndDate.equals(MainWebViewActivity.pinnedDomainSslEndDate)) {
368                 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
369             } else {
370                 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
371             }
372
373             // Display the strings.
374             issuedToCNameTextView.setText(issuedToCNameStringBuilder);
375             issuedToONameTextView.setText(issuedToONameStringBuilder);
376             issuedToUNameTextView.setText(issuedToUNameStringBuilder);
377             issuedByCNameTextView.setText(issuedByCNameStringBuilder);
378             issuedByONameTextView.setText(issuedByONameStringBuilder);
379             issuedByUNameTextView.setText(issuedByUNameStringBuilder);
380             startDateTextView.setText(startDateStringBuilder);
381             endDateTextView.setText(endDateStringBuilder);
382
383             // Display the tab.
384             container.addView(tabViewGroup);
385
386             // Make it so.
387             return tabViewGroup;
388         }
389     }
390 }