]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java
Add SSL certificate pinning. Implements https://redmine.stoutner.com/issues/54.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / PinnedSslCertificateMismatchDialog.java
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java
new file mode 100644 (file)
index 0000000..aa5b627
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Copyright © 2017 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.http.SslCertificate;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.TabLayout;
+import android.support.v4.view.PagerAdapter;
+// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22.
+import android.support.v7.app.AppCompatDialogFragment;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.activities.MainWebViewActivity;
+import com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager;
+import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+// `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
+@SuppressLint("InflateParams")
+public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment {
+    // `layoutInflater` is used in `onCreateDialog()` and `pagerAdapter`.
+    private LayoutInflater layoutInflater;
+
+    // The current website SSL certificate variables are used in `onCreateDialog()` and `pagerAdapter()`.
+    private String currentSslIssuedToCNameString;
+    private String currentSslIssuedToONameString;
+    private String currentSslIssuedToUNameString;
+    private String currentSslIssuedByCNameString;
+    private String currentSslIssuedByONameString;
+    private String currentSslIssuedByUNameString;
+    private Date currentSslStartDate;
+    private Date currentSslEndDate;
+
+    // The public interface is used to send information back to the parent activity.
+    public interface PinnedSslCertificateMismatchListener {
+        void onSslMismatchBack();
+
+        void onSslMismatchProceed();
+    }
+
+    // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`.
+    private PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener pinnedSslCertificateMismatchListener;
+
+    // Check to make sure that the parent activity implements the listener.
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        try {
+            pinnedSslCertificateMismatchListener = (PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener) context;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(context.toString() + " must implement PinnedSslCertificateMismatchListener");
+        }
+    }
+
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Get the activity's layout inflater.
+        layoutInflater = getActivity().getLayoutInflater();
+
+        // Use `AlertDialog.Builder` to create the `AlertDialog`.
+        AlertDialog.Builder dialogBuilder;
+
+        // Set the style according to the theme.
+        if (MainWebViewActivity.darkTheme) {
+            // Set the dialog theme.
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
+        } else {
+            // Set the dialog theme.
+            dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
+
+            // Set the icon.
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
+        }
+
+        // Setup the neutral button.
+        dialogBuilder.setNeutralButton(R.string.update_ssl, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Initialize the `long` date variables.  If the date is `null`, a long value of `0` will be stored in the Domains database entry.
+                long currentSslStartDateLong = 0;
+                long currentSslEndDateLong = 0;
+
+                // Convert the `Dates` into `longs`.
+                if (currentSslStartDate != null) {
+                    currentSslStartDateLong = currentSslStartDate.getTime();
+                }
+
+                if (currentSslEndDate != null) {
+                    currentSslEndDateLong = currentSslEndDate.getTime();
+                }
+
+                // 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`.
+                DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(getContext(), null, null, 0);
+
+                // Update the pinned SSL certificate for this domain.
+                domainsDatabaseHelper.updateCertificate(MainWebViewActivity.domainSettingsDatabaseId, currentSslIssuedToCNameString, currentSslIssuedToONameString, currentSslIssuedToUNameString, currentSslIssuedByCNameString, currentSslIssuedByONameString,
+                        currentSslIssuedByUNameString, currentSslStartDateLong, currentSslEndDateLong);
+
+                // Update the pinned SSL certificate global variables to match the information that is now in the database.
+                MainWebViewActivity.pinnedDomainSslIssuedToCNameString = currentSslIssuedToCNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedToONameString = currentSslIssuedToONameString;
+                MainWebViewActivity.pinnedDomainSslIssuedToUNameString = currentSslIssuedToUNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByCNameString = currentSslIssuedByCNameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByONameString = currentSslIssuedByONameString;
+                MainWebViewActivity.pinnedDomainSslIssuedByUNameString = currentSslIssuedByUNameString;
+                MainWebViewActivity.pinnedDomainSslStartDate = currentSslStartDate;
+                MainWebViewActivity.pinnedDomainSslEndDate = currentSslEndDate;
+            }
+        });
+
+        // Setup the negative button.
+        dialogBuilder.setNegativeButton(R.string.back, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Call the `onSslMismatchBack` public interface to send the `WebView` back one page.
+                pinnedSslCertificateMismatchListener.onSslMismatchBack();
+            }
+        });
+
+        // Setup the positive button.
+        dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                // Call the `onSslMismatchProceed` public interface.
+                pinnedSslCertificateMismatchListener.onSslMismatchProceed();
+            }
+        });
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.ssl_certificate_mismatch);
+
+        // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
+        dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_linearlayout, null));
+
+        // Create an `AlertDialog` from the `AlertDialog.Builder`
+        final AlertDialog alertDialog = dialogBuilder.create();
+
+        // Show the `AlertDialog` so the items in the layout can be modified.
+        alertDialog.show();
+
+        //  Setup `wrapVerticalContentViewPager`.
+        WrapVerticalContentViewPager wrapVerticalContentViewPager = (WrapVerticalContentViewPager) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
+        wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
+
+        // Setup the `TabLayout` and connect it to the `WrapVerticalContentViewPager`.
+        TabLayout tabLayout = (TabLayout) alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout);
+        tabLayout.setupWithViewPager(wrapVerticalContentViewPager);
+
+        // `onCreateDialog` requires the return of an `AlertDialog`.
+        return alertDialog;
+    }
+
+    private class pagerAdapter extends PagerAdapter {
+        @Override
+        public boolean isViewFromObject(View view, Object object) {
+            // Check to see if the `View` and the `Object` are the same.
+            return (view == object);
+        }
+
+        @Override
+        public int getCount() {
+            // There are two tabs.
+            return 2;
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            // Return the current tab title.
+            if (position == 0) {  // The current SSL certificate tab.
+                return getString(R.string.current_ssl);
+            } else {  // The pinned SSL certificate tab.
+                return getString(R.string.pinned_ssl);
+            }
+        }
+
+        @Override
+        public Object instantiateItem(ViewGroup container, int position) {
+            // Inflate the `ScrollView` for this tab.
+            ViewGroup tabViewGroup = (ViewGroup) layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_scrollview, container, false);
+
+            // Get handles for the `TextViews`.
+            TextView issuedToCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_cname);
+            TextView issuedToONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_oname);
+            TextView issuedToUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_to_uname);
+            TextView issuedByCNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_cname);
+            TextView issuedByONameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_oname);
+            TextView issuedByUNameTextView = (TextView) tabViewGroup.findViewById(R.id.issued_by_uname);
+            TextView startDateTextView = (TextView) tabViewGroup.findViewById(R.id.start_date);
+            TextView endDateTextView = (TextView) tabViewGroup.findViewById(R.id.end_date);
+
+            // Setup the labels.
+            String cNameLabel = getString(R.string.common_name) + "  ";
+            String oNameLabel = getString(R.string.organization) + "  ";
+            String uNameLabel = getString(R.string.organizational_unit) + "  ";
+            String startDateLabel = getString(R.string.start_date) + "  ";
+            String endDateLabel = getString(R.string.end_date) + "  ";
+
+            // Get the current website SSL certificate.
+            SslCertificate sslCertificate = MainWebViewActivity.sslCertificate;
+
+            // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+            if (sslCertificate != null) {
+                currentSslIssuedToCNameString = sslCertificate.getIssuedTo().getCName();
+                currentSslIssuedToONameString = sslCertificate.getIssuedTo().getOName();
+                currentSslIssuedToUNameString = sslCertificate.getIssuedTo().getUName();
+                currentSslIssuedByCNameString = sslCertificate.getIssuedBy().getCName();
+                currentSslIssuedByONameString = sslCertificate.getIssuedBy().getOName();
+                currentSslIssuedByUNameString = sslCertificate.getIssuedBy().getUName();
+                currentSslStartDate = sslCertificate.getValidNotBeforeDate();
+                currentSslEndDate = sslCertificate.getValidNotAfterDate();
+            } else {
+                // Initialize the current website SSL certificate variables with blank information.
+                currentSslIssuedToCNameString = "";
+                currentSslIssuedToONameString = "";
+                currentSslIssuedToUNameString = "";
+                currentSslIssuedByCNameString = "";
+                currentSslIssuedByONameString = "";
+                currentSslIssuedByUNameString = "";
+            }
+
+            // Initialize the `SpannableStringBuilders`.
+            SpannableStringBuilder issuedToCNameStringBuilder;
+            SpannableStringBuilder issuedToONameStringBuilder;
+            SpannableStringBuilder issuedToUNameStringBuilder;
+            SpannableStringBuilder issuedByCNameStringBuilder;
+            SpannableStringBuilder issuedByONameStringBuilder;
+            SpannableStringBuilder issuedByUNameStringBuilder;
+            SpannableStringBuilder startDateStringBuilder;
+            SpannableStringBuilder endDateStringBuilder;
+
+            // Setup the `SpannableStringBuilders` for each tab.
+            if (position == 0) {  // Setup the current SSL certificate tab.
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCNameString);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToONameString);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUNameString);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCNameString);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByONameString);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUNameString);
+
+                // Set the dates if they aren't `null`.
+                if (currentSslStartDate == null) {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+                } else {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate));
+                }
+
+                if (currentSslEndDate == null) {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+                } else {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate));
+                }
+            } else {  // Setup the pinned SSL certificate tab.
+                issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToCNameString);
+                issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToONameString);
+                issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToUNameString);
+                issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByCNameString);
+                issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByONameString);
+                issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByUNameString);
+
+                // Set the dates if they aren't `null`.
+                if (MainWebViewActivity.pinnedDomainSslStartDate == null) {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
+                } else {
+                    startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslStartDate));
+                }
+
+                if (MainWebViewActivity.pinnedDomainSslEndDate == null) {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
+                } else {
+                    endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslEndDate));
+                }
+            }
+
+            // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
+            @SuppressWarnings("deprecation") ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
+
+            // Create a blue `ForegroundColorSpan`.
+            ForegroundColorSpan blueColorSpan;
+
+            // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
+            if (MainWebViewActivity.darkTheme) {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
+            } else {
+                //noinspection deprecation
+                blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
+            }
+
+            // Configure the spans to display conflicting information in red.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
+            if (currentSslIssuedToCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToCNameString)) {
+                issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedToONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToONameString)) {
+                issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedToUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToUNameString)) {
+                issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByCNameString)) {
+                issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByONameString)) {
+                issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if (currentSslIssuedByUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByUNameString)) {
+                issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if ((currentSslStartDate != null) && (MainWebViewActivity.pinnedDomainSslStartDate != null) && currentSslStartDate.equals(MainWebViewActivity.pinnedDomainSslStartDate)) {
+                startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            if ((currentSslEndDate != null) && (MainWebViewActivity.pinnedDomainSslEndDate != null) && currentSslEndDate.equals(MainWebViewActivity.pinnedDomainSslEndDate)) {
+                endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            } else {
+                endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            }
+
+            // Display the strings.
+            issuedToCNameTextView.setText(issuedToCNameStringBuilder);
+            issuedToONameTextView.setText(issuedToONameStringBuilder);
+            issuedToUNameTextView.setText(issuedToUNameStringBuilder);
+            issuedByCNameTextView.setText(issuedByCNameStringBuilder);
+            issuedByONameTextView.setText(issuedByONameStringBuilder);
+            issuedByUNameTextView.setText(issuedByUNameStringBuilder);
+            startDateTextView.setText(startDateStringBuilder);
+            endDateTextView.setText(endDateStringBuilder);
+
+            // Display the tab.
+            container.addView(tabViewGroup);
+
+            // Make it so.
+            return tabViewGroup;
+        }
+    }
+}