]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
Add SSL certificate pinning. Implements https://redmine.stoutner.com/issues/54.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebViewActivity.java
index f1110c57565e81cf67773cc95445653d79ced64d..975814a65a5814b53ace7490d03be6e7eba06b21 100644 (file)
@@ -34,7 +34,6 @@ import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.Typeface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -63,7 +62,6 @@ import android.text.Editable;
 import android.text.Spanned;
 import android.text.TextWatcher;
 import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
 import android.util.Patterns;
 import android.view.ContextMenu;
 import android.view.GestureDetector;
@@ -97,6 +95,7 @@ import com.stoutner.privacybrowser.BuildConfig;
 import com.stoutner.privacybrowser.R;
 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
+import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
@@ -106,6 +105,7 @@ import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
@@ -113,6 +113,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -120,22 +121,22 @@ import java.util.Set;
 
 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
-        SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
+        PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener {
 
-    // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsSettingsActivity`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
+    // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
     // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`,
     // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
     public static boolean darkTheme;
 
-    // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `ViewSslCertificateDialog`.
-    // It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`.
+    // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
+    // and `ViewSslCertificateDialog`.  It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`.
     public static Bitmap favoriteIconBitmap;
 
     // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
     public static String formattedUrlString;
 
-    // `sslCertificate` is public static so it can be accessed from `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
+    // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`, and `ViewSslCertificateDialog`.  It is also used in `onCreate()`.
     public static SslCertificate sslCertificate;
 
     // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`.  It is also used in `onCreate()`.
@@ -150,11 +151,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`.  It is also used in `onRestart()`
     public static boolean reloadOnRestartBoolean;
 
+    // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`.  They are also used in `onCreate()` and `applyDomainSettings()`.
+    public static int domainSettingsDatabaseId;
+    public static boolean pinnedDomainSslCertificate;
+    public static String pinnedDomainSslIssuedToCNameString;
+    public static String pinnedDomainSslIssuedToONameString;
+    public static String pinnedDomainSslIssuedToUNameString;
+    public static String pinnedDomainSslIssuedByCNameString;
+    public static String pinnedDomainSslIssuedByONameString;
+    public static String pinnedDomainSslIssuedByUNameString;
+    public static Date pinnedDomainSslStartDate;
+    public static Date pinnedDomainSslEndDate;
+
 
     // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`.
     private ActionBar appBar;
 
-    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`.
+    // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
     private boolean navigatingHistory;
 
     // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
@@ -167,7 +180,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     private CoordinatorLayout rootCoordinatorLayout;
 
     // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`
-    // and `setDisplayWebpageImages()`.
+    // `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
     private WebView mainWebView;
 
     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
@@ -216,7 +229,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `adBlockerEnabled` is used in `onCreate()` and `applyAppSettings()`.
     private boolean adBlockerEnabled;
 
-    // `privacyBrowserRuntime` is used in `onCreate()` and `applyAppSettings()`.
+    // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
     private Runtime privacyBrowserRuntime;
 
     // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`.
@@ -234,9 +247,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
     private boolean translucentNavigationBarOnFullscreen;
 
-    // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`.
+    // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
     private String currentDomainName;
 
+    // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+    private boolean ignorePinnedSslCertificate;
+
     // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
     private boolean waitingForOrbot;
 
@@ -252,7 +268,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `waitingForOrbotData` is used in `onCreate()` and `applyAppSettings()`.
     private String waitingForOrbotHTMLString;
 
-    // `privateDataDirectoryString` is used in `onCreate()` and `onNavigationItemSelected()`.
+    // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
     private String privateDataDirectoryString;
 
     // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
@@ -282,9 +298,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`.
     private ForegroundColorSpan finalGrayColorSpan;
 
-    // `boldStyleSpan` is used in `onCreate()` and `highlightUrlText()`.
-    private StyleSpan boldStyleSpan;
-
     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
     private View adView;
 
@@ -300,6 +313,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`.
     private boolean urlIsLoading;
 
+
     @Override
     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.  The whole premise of Privacy Browser is built around an understanding of these dangers.
     @SuppressLint("SetJavaScriptEnabled")
@@ -344,7 +358,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
         initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
         finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
-        boldStyleSpan = new StyleSpan(Typeface.BOLD);
 
         // Get a handle for `urlTextBox`.
         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext);
@@ -358,7 +371,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     urlTextBox.getText().removeSpan(redColorSpan);
                     urlTextBox.getText().removeSpan(initialGrayColorSpan);
                     urlTextBox.getText().removeSpan(finalGrayColorSpan);
-                    urlTextBox.getText().removeSpan(boldStyleSpan);
                 } else {  // The user has stopped editing `urlTextBox`.
                     // Reapply the highlighting.
                     highlightUrlText();
@@ -718,9 +730,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Update the URL in urlTextBox when the page starts to load.
             @Override
             public void onPageStarted(WebView view, String url, Bitmap favicon) {
-                // Reset `webViewTitle`
-                webViewTitle = getString(R.string.no_title);
-
                 // Check to see if we are waiting on Orbot.
                 if (!waitingForOrbot) {  // We are not waiting on Orbot, so we need to process the URL.
                     // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
@@ -799,20 +808,100 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                         }
                     }
 
-                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog`.
+                    // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
                     sslCertificate = mainWebView.getCertificate();
+
+                    // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
+                    if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) {
+                        // Initialize the current SSL certificate variables.
+                        String currentWebsiteIssuedToCName = "";
+                        String currentWebsiteIssuedToOName = "";
+                        String currentWebsiteIssuedToUName = "";
+                        String currentWebsiteIssuedByCName = "";
+                        String currentWebsiteIssuedByOName = "";
+                        String currentWebsiteIssuedByUName = "";
+                        Date currentWebsiteSslStartDate = null;
+                        Date currentWebsiteSslEndDate = null;
+
+
+                        // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+                        if (sslCertificate != null) {
+                            currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
+                            currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
+                            currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
+                            currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
+                            currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
+                            currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
+                            currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
+                            currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
+                        }
+
+                        // Initialize `String` variables to store the SSL certificate dates.  `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
+                        String currentWebsiteSslStartDateString = "";
+                        String currentWebsiteSslEndDateString = "";
+                        String pinnedDomainSslStartDateString = "";
+                        String pinnedDomainSslEndDateString = "";
+
+                        // Convert the `Dates` to `Strings` if they are not `null`.
+                        if (currentWebsiteSslStartDate != null) {
+                            currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
+                        }
+
+                        if (currentWebsiteSslEndDate != null) {
+                            currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
+                        }
+
+                        if (pinnedDomainSslStartDate != null) {
+                            pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
+                        }
+
+                        if (pinnedDomainSslEndDate != null) {
+                            pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
+                        }
+
+                        // Check to see if the pinned SSL certificate matches the current website certificate.
+                        if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) || !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) ||
+                                !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) || !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
+                                !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {  // The pinned SSL certificate doesn't match the current domain certificate.
+                            //Display the pinned SSL certificate mismatch `AlertDialog`.
+                            AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
+                            pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_mismatch));
+                        }
+                    }
                 }
             }
 
             // Handle SSL Certificate errors.
             @Override
             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
-                // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
-                sslErrorHandler = handler;
-
-                // Display the SSL error `AlertDialog`.
-                AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
-                sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
+                // Get the current website SSL certificate.
+                SslCertificate currentWebsiteSslCertificate = error.getCertificate();
+
+                // Extract the individual pieces of information from the current website SSL certificate.
+                String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
+                String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
+                String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
+                String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
+                String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
+                String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
+                Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
+                Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
+
+                // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
+                if (pinnedDomainSslCertificate &&
+                        currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) && currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) &&
+                        currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) && currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
+                        currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {  // An SSL certificate is pinned and matches the current domain certificate.
+                    // Proceed to the website without displaying an error.
+                    handler.proceed();
+                } else {  // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
+                    // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
+                    sslErrorHandler = handler;
+
+                    // Display the SSL error `AlertDialog`.
+                    AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
+                    sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
+                }
             }
         });
 
@@ -1115,6 +1204,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
         MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);
         MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
+        MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
         MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
         MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
         MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
@@ -1130,13 +1220,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         // Enable third-party cookies if first-party cookies are enabled.
         toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled);
 
-        // Enable DOM Storage if JavaScript is enabled.
+        // Enable `DOM Storage` if JavaScript is enabled.
         toggleDomStorageMenuItem.setEnabled(javaScriptEnabled);
 
-        // Enable Clear Cookies if there are any.
+        // Enable `Clear Cookies` if there are any.
         clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
 
-        // Enable Clear Form Data is there is any.
+        // Get a count of the number of files in the `Local Storage` directory.
+        File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
+        int localStorageDirectoryNumberOfFiles = 0;
+        if (localStorageDirectory.exists()) {
+            localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
+        }
+
+        // Get a count of the number of files in the `IndexedDB` directory.
+        File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
+        int indexedDBDirectoryNumberOfFiles = 0;
+        if (indexedDBDirectory.exists()) {
+            indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
+        }
+
+        // Enable `Clear DOM Storage` if there is any.
+        clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
+
+        // Enable `Clear Form Data` is there is any.
         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
         clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
 
@@ -1391,6 +1498,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                                         // Delete the DOM Storage.
                                         WebStorage webStorage = WebStorage.getInstance();
                                         webStorage.deleteAllData();
+
+                                        // Manually remove `IndexedDB` if it exists.
+                                        try {
+                                            privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                                        } catch (IOException e) {
+                                            // Do nothing if an error is thrown.
+                                        }
                                 }
                             }
                         })
@@ -1471,12 +1585,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
             case R.id.share:
                 // Setup the share string.
-                String shareString;
-                if (webViewTitle != null) {
-                    shareString = webViewTitle + " – " + urlTextBox.getText().toString();
-                } else {
-                    shareString = urlTextBox.getText().toString();
-                }
+                String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
 
                 // Create the share intent.
                 Intent shareIntent = new Intent();
@@ -1495,17 +1604,15 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 // Show the Find on Page `RelativeLayout`.
                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
 
-                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
-                // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
-                findOnPageEditText.postDelayed(new Runnable()
-                {
+                // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.  http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
+                findOnPageEditText.postDelayed(new Runnable() {
                     @Override
                     public void run()
                     {
                         // Set the focus on `findOnPageEditText`.
                         findOnPageEditText.requestFocus();
 
-                        // Display the keyboard.
+                        // Display the keyboard.  `0` sets no input flags.
                         inputMethodManager.showSoftInput(findOnPageEditText, 0);
                     }
                 }, 200);
@@ -1657,10 +1764,16 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                     WebStorage webStorage = WebStorage.getInstance();
                     webStorage.deleteAllData();
 
-                    // Manually delete the DOM storage directory, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
+                    // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
                     try {
                         // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
                         privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+                        // We have to use multiple commands because `Runtime.exec()` does not like `*`.
+                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+                        privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+                        privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
                     } catch (IOException e) {
                         // Do nothing if an error is thrown.
                     }
@@ -2081,6 +2194,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
         sslErrorHandler.proceed();
     }
 
+    @Override
+    public void onSslMismatchBack() {
+        if (mainWebView.canGoBack()) {  // There is a back page in the history.
+            // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
+            navigatingHistory = true;
+
+            // Go back.
+            mainWebView.goBack();
+        } else {  // There are no pages to go back to.
+            // Load a blank page
+            loadUrl("");
+        }
+    }
+
+    @Override
+    public void onSslMismatchProceed() {
+        // Do not check the pinned SSL certificate for this domain again until the domain changes.
+        ignorePinnedSslCertificate = true;
+    }
+
     @Override
     public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
         // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded.
@@ -2390,6 +2523,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
             // Set the new `hostname` as the `currentDomainName`.
             currentDomainName = hostName;
 
+            // Reset `ignorePinnedSslCertificate`.
+            ignorePinnedSslCertificate = false;
+
             // Reset `favoriteIconBitmap` and display it in the `appbar`.
             favoriteIconBitmap = favoriteIconDefaultBitmap;
             favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
@@ -2456,6 +2592,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 currentHostDomainSettingsCursor.moveToFirst();
 
                 // Get the settings from the cursor.
+                domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
                 javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
                 firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
                 thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
@@ -2464,6 +2601,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
                 int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
                 displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+                pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+                pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+                pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+                pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+                pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+                pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+                pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+
+                // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
+                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
+                    pinnedDomainSslStartDate = null;
+                } else {
+                    pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+                }
+
+                // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
+                if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
+                    pinnedDomainSslEndDate = null;
+                } else {
+                    pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+                }
 
                 // Close `currentHostDomainSettingsCursor`.
                 currentHostDomainSettingsCursor.close();
@@ -2540,6 +2698,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
                 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
 
+                // Reset the pinned SSL certificate information.
+                domainSettingsDatabaseId = -1;
+                pinnedDomainSslCertificate = false;
+                pinnedDomainSslIssuedToCNameString = "";
+                pinnedDomainSslIssuedToONameString = "";
+                pinnedDomainSslIssuedToUNameString = "";
+                pinnedDomainSslIssuedByCNameString = "";
+                pinnedDomainSslIssuedByONameString = "";
+                pinnedDomainSslIssuedByUNameString = "";
+                pinnedDomainSslStartDate = null;
+                pinnedDomainSslEndDate = null;
+
                 // Set third-party cookies status if API >= 21.
                 if (Build.VERSION.SDK_INT >= 21) {
                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
@@ -2670,7 +2840,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
 
         if (urlString.startsWith("http://")) {  // Highlight connections that are not encrypted.
             urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-            // urlTextBox.getText().setSpan(boldStyleSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         } else if (urlString.startsWith("https://")) {  // Highlight connections that are encrypted.
             urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         }