From 757139ad59282fb8400bc641a4be574e0b88de49 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Fri, 26 Aug 2016 11:34:32 -0700 Subject: [PATCH] View SSL certificates and errors. --- .idea/dictionaries/soren.xml | 4 + .../main/assets/en/guide_clear_and_exit.html | 1 + .../BookmarksDatabaseHandler.java | 3 - .../CreateHomeScreenShortcut.java | 24 +- .../privacybrowser/MainWebViewActivity.java | 65 ++++- .../privacybrowser/SslCertificateError.java | 249 ++++++++++++++++++ .../privacybrowser/ViewSslCertificate.java | 153 +++++++++++ .../main/res/layout/ssl_certificate_error.xml | 126 +++++++++ .../main/res/layout/unencrypted_website.xml | 26 ++ app/src/main/res/layout/url_bar.xml | 3 +- .../main/res/layout/view_ssl_certificate.xml | 103 ++++++++ .../main/res/menu/webview_options_menu.xml | 16 +- app/src/main/res/values/strings.xml | 42 ++- 13 files changed, 773 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java create mode 100644 app/src/main/res/layout/ssl_certificate_error.xml create mode 100644 app/src/main/res/layout/unencrypted_website.xml create mode 100644 app/src/main/res/layout/view_ssl_certificate.xml diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index 24bc218d..47992f9b 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -15,9 +15,11 @@ checkedtextview chromebooks chromeversion + cname commitdiff coordinatorlayout displayorder + dname eadd edittext exynos @@ -33,6 +35,7 @@ lossless mozilla nojs + oname orbot panopticlick parentfolder @@ -54,6 +57,7 @@ techrepublic textview uids + uname webkay webkitversion whatismyip diff --git a/app/src/main/assets/en/guide_clear_and_exit.html b/app/src/main/assets/en/guide_clear_and_exit.html index c00ac623..1cab0796 100644 --- a/app/src/main/assets/en/guide_clear_and_exit.html +++ b/app/src/main/assets/en/guide_clear_and_exit.html @@ -41,6 +41,7 @@
  • Removes all form data.
  • Clears the cache, including disk files.
  • Clears the back/forward history.
  • +
  • Clears any saved SSL certificate preferences (SSL certificates with errors that are ignored).
  • Deletes the current URL.
  • Destroys the internal state of the WebView.
  • Closes Privacy Browser. For Android Lollipop and newer (version >= 5.0 or API >= 21), Privacy Browser is also removed from the recent app list.
  • diff --git a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java index 2a81097e..f03029a2 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java +++ b/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java @@ -25,9 +25,6 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.provider.ContactsContract; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; public class BookmarksDatabaseHandler extends SQLiteOpenHelper { private static final int SCHEMA_VERSION = 1; diff --git a/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java b/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java index 4a6b2fa7..35601387 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java +++ b/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java @@ -42,7 +42,7 @@ public class CreateHomeScreenShortcut extends DialogFragment { void onCreateHomeScreenShortcut(DialogFragment dialogFragment); } - //createHomeScreenShortcutListener is used in onAttach and and onCreateDialog. + //createHomeScreenShortcutListener is used in `onAttach` and and `onCreateDialog`. private CreateHomeScreenSchortcutListener createHomeScreenShortcutListener; // Check to make sure that the parent activity implements the listener. @@ -57,20 +57,20 @@ public class CreateHomeScreenShortcut extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + // Create a drawable version of the favorite icon. Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon); - // Get the activity's layout inflater. - LayoutInflater customDialogInflater = getActivity().getLayoutInflater(); - - // Use AlertDialog.Builder to create the AlertDialog. The style formats the color of the button text. + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); dialogBuilder.setTitle(R.string.create_shortcut); dialogBuilder.setIcon(favoriteIconDrawable); - // The parent view is "null" because it will be assigned by AlertDialog. - dialogBuilder.setView(customDialogInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)); - // Set an onClick listener on the negative button. + // Set an `onClick` listener on the negative button. dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -78,7 +78,7 @@ public class CreateHomeScreenShortcut extends DialogFragment { } }); - // Set an onClick listener on the positive button. + // Set an `onClick` listener on the positive button. dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -87,13 +87,13 @@ public class CreateHomeScreenShortcut extends DialogFragment { }); - // Create an AlertDialog from the AlertDialogBuilder. + // Create an `AlertDialog` from the `AlertDialog.Builder`. final AlertDialog alertDialog = dialogBuilder.create(); // Show the keyboard when the Dialog is displayed on the screen. alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - // We need to show the AlertDialog before we can call setOnKeyListener() below. + // We need to show the `AlertDialog` before we can call setOnKeyListener() below. alertDialog.show(); // Allow the "enter" key on the keyboard to create the shortcut. @@ -117,7 +117,7 @@ public class CreateHomeScreenShortcut extends DialogFragment { } }); - // onCreateDialog requires the return of an AlertDialog. + // `onCreateDialog` requires the return of an `AlertDialog`. return alertDialog; } } \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java index 4cad1873..0a824a15 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java @@ -30,6 +30,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.net.http.SslError; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; @@ -51,6 +52,7 @@ import android.view.View; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.DownloadListener; +import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; import android.webkit.WebStorage; import android.webkit.WebView; @@ -69,7 +71,7 @@ import java.util.HashMap; import java.util.Map; // 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, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener { +public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, SslCertificateError.SslCertificateErrorListener { // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`. // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`. public static Bitmap favoriteIcon; @@ -140,6 +142,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `adView` is used in `onCreate()` and `onConfigurationChanged()`. private View adView; + // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. + private SslErrorHandler sslErrorHandler; + @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") @@ -251,6 +256,17 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation urlTextBox.setText(formattedUrlString); } } + + // 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`. + DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error); + sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + } }); mainWebView.setWebChromeClient(new WebChromeClient() { @@ -533,50 +549,57 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); clearFormData.setEnabled(mainWebViewDatabase.hasFormData()); - // Select the current font size. + // Initialize font size variables. int fontSize = mainWebView.getSettings().getTextZoom(); - MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize); + String fontSizeTitle; MenuItem selectedFontSizeMenuItem; + + // Prepare the font size title and current size menu item. switch (fontSize) { case 50: - fontSizeMenuItem.setTitle(R.string.font_size_fifty_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent); break; case 75: - fontSizeMenuItem.setTitle(R.string.font_size_seventy_five_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent); break; case 100: - fontSizeMenuItem.setTitle(R.string.font_size_one_hundred_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); break; case 125: - fontSizeMenuItem.setTitle(R.string.font_size_one_hundred_twenty_five_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent); break; case 150: - fontSizeMenuItem.setTitle(R.string.font_size_one_hundred_fifty_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent); break; case 175: - fontSizeMenuItem.setTitle(R.string.font_size_one_hundred_seventy_five_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent); break; case 200: - fontSizeMenuItem.setTitle(R.string.font_size_two_hundred_percent); + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent); break; default: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); break; } + + // Set the font size title and select the current size menu item. + MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize); + fontSizeMenuItem.setTitle(fontSizeTitle); selectedFontSizeMenuItem.setChecked(true); // Only show `Refresh` if `swipeToRefresh` is disabled. @@ -744,7 +767,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation return true; case R.id.addToHomescreen: - // Show the CreateHomeScreenShortcut AlertDialog and name this instance "@string/create_shortcut". + // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`. DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut(); createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut)); @@ -840,6 +863,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Clear the back/forward history. mainWebView.clearHistory(); + // Clear any SSL certificate preferences. + MainWebViewActivity.mainWebView.clearSslPreferences(); + + // Clear `formattedUrlString`. formattedUrlString = null; // Destroy the internal state of the webview. @@ -905,6 +932,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation sendBroadcast(placeBookmarkShortcut); } + public void viewSslCertificate(View view) { + // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`. + DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate(); + viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate)); + } + + @Override + public void onSslErrorCancel() { + sslErrorHandler.cancel(); + } + + @Override + public void onSslErrorProceed() { + sslErrorHandler.proceed(); + } + // Override onBackPressed to handle the navigation drawer and mainWebView. @Override public void onBackPressed() { diff --git a/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java b/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java new file mode 100644 index 00000000..80c9c1e7 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java @@ -0,0 +1,249 @@ +/** + * Copyright 2015-2016 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.http.SslCertificate; +import android.net.http.SslError; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.widget.TextView; + +import org.w3c.dom.Text; + +import java.util.Date; + +public class SslCertificateError extends DialogFragment{ + private String primaryError; + private String urlWithError; + private String issuedToCName; + private String issuedToOName; + private String issuedToUName; + private String issuedByCName; + private String issuedByOName; + private String issuedByUName; + private String startDate; + private String endDate; + + public static SslCertificateError displayDialog(SslError error) { + // Get the various components of the SSL error message. + int primaryErrorIntForBundle = error.getPrimaryError(); + String urlWithErrorForBundle = error.getUrl(); + SslCertificate sslCertificate = error.getCertificate(); + String issuedToCNameForBundle = sslCertificate.getIssuedTo().getCName(); + String issuedToONameForBundle = sslCertificate.getIssuedTo().getOName(); + String issuedToUNameForBundle = sslCertificate.getIssuedTo().getUName(); + String issuedByCNameForBundle = sslCertificate.getIssuedBy().getCName(); + String issuedByONameForBundle = sslCertificate.getIssuedBy().getOName(); + String issuedByUNameForBundle = sslCertificate.getIssuedBy().getUName(); + Date startDateForBundle = sslCertificate.getValidNotBeforeDate(); + Date endDateForBundle = sslCertificate.getValidNotAfterDate(); + + // Store the SSL error message components in a `Bundle`. + Bundle argumentsBundle = new Bundle(); + argumentsBundle.putInt("PrimaryErrorInt", primaryErrorIntForBundle); + argumentsBundle.putString("UrlWithError", urlWithErrorForBundle); + argumentsBundle.putString("IssuedToCName", issuedToCNameForBundle); + argumentsBundle.putString("IssuedToOName", issuedToONameForBundle); + argumentsBundle.putString("IssuedToUName", issuedToUNameForBundle); + argumentsBundle.putString("IssuedByCName", issuedByCNameForBundle); + argumentsBundle.putString("IssuedByOName", issuedByONameForBundle); + argumentsBundle.putString("IssuedByUName", issuedByUNameForBundle); + argumentsBundle.putString("StartDate", startDateForBundle.toString()); + argumentsBundle.putString("EndDate", endDateForBundle.toString()); + + // Add the `Bundle` to this instance of `SslCertificateError`. + SslCertificateError thisSslCertificateErrorDialog = new SslCertificateError(); + thisSslCertificateErrorDialog.setArguments(argumentsBundle); + return thisSslCertificateErrorDialog; + } + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Save the components of the SSL error message in class variables. + urlWithError = getArguments().getString("UrlWithError"); + issuedToCName = getArguments().getString("IssuedToCName"); + issuedToOName = getArguments().getString("IssuedToOName"); + issuedToUName = getArguments().getString("IssuedToUName"); + issuedByCName = getArguments().getString("IssuedByCName"); + issuedByOName = getArguments().getString("IssuedByOName"); + issuedByUName = getArguments().getString("IssuedByUName"); + startDate = getArguments().getString("StartDate"); + endDate = getArguments().getString("EndDate"); + + // Get the appropriate string for `primaryError. + int primaryErrorInt = getArguments().getInt("PrimaryErrorInt"); + switch (primaryErrorInt) { + case SslError.SSL_NOTYETVALID: + primaryError = getString(R.string.future_certificate); + break; + + case SslError.SSL_EXPIRED: + primaryError = getString(R.string.expired_certificate); + break; + + case SslError.SSL_IDMISMATCH: + primaryError = getString(R.string.cn_mismatch); + break; + + case SslError.SSL_UNTRUSTED: + primaryError = getString(R.string.untrusted); + break; + + case SslError.SSL_DATE_INVALID: + primaryError = getString(R.string.invalid_date); + break; + + case SslError.SSL_INVALID: + primaryError = getString(R.string.invalid_certificate); + break; + } + } + + // The public interface is used to send information back to the parent activity. + public interface SslCertificateErrorListener { + void onSslErrorCancel(); + + void onSslErrorProceed(); + } + + // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. + private SslCertificateErrorListener sslCertificateErrorListener; + + // Check to make sure that the parent activity implements the listener. + public void onAttach(Activity parentActivity) { + super.onAttach(parentActivity); + + try { + sslCertificateErrorListener = (SslCertificateErrorListener) parentActivity; + } catch(ClassCastException exception) { + throw new ClassCastException(parentActivity.toString() + " must implement SslCertificateErrorListener"); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.ssl_certificate_error); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); + + // Set an `onClick` listener on the negative button. `null` doesn't do anything extra when the button is pressed. The `Dialog` will automatically close. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + sslCertificateErrorListener.onSslErrorCancel(); + } + }); + + // Set an `onClick` listener on the positive button. + dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + sslCertificateErrorListener.onSslErrorProceed(); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + AlertDialog alertDialog = dialogBuilder.create(); + + // We have to show the `AlertDialog` before we can modify the content. + alertDialog.show(); + + // Get handles for the `TextViews` + TextView primaryErrorTextView = (TextView) alertDialog.findViewById(R.id.primary_error); + TextView urlTextView = (TextView) alertDialog.findViewById(R.id.url_error_dialog); + TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname_error_dialog); + TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname_error_dialog); + TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname_error_dialog); + TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname_error_dialog); + TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname_error_dialog); + TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname_error_dialog); + TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date_error_dialog); + TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date_error_dialog); + + // Setup the common strings. + String urlLabel = getString(R.string.url_label) + " "; + 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) + " "; + + // Create a `SpannableStringBuilder` for each item. + SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithError); + SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); + SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); + SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); + SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCName); + SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByOName); + SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUName); + SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); + SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); + + // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue)); + + // Setup the spans to display the certificate information in blue. + // `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + urlStringBuilder.setSpan(blueColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + + // Display the strings. + primaryErrorTextView.setText(primaryError); + urlTextView.setText(urlStringBuilder); + issuedToCNameTextView.setText(issuedToCNameStringBuilder); + issuedToONameTextView.setText(issuedToONameStringBuilder); + issuedToUNameTextView.setText(issuedToUNameStringBuilder); + issuedByCNameTextView.setText(issuedByCNameStringBuilder); + issuedByONameTextView.setText(issuedByONameStringBuilder); + issuedByUNameTextView.setText(issuedByUNameStringBuilder); + startDateTextView.setText(startDateStringBuilder); + endDateTextView.setText(endDateStringBuilder); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java b/app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java new file mode 100644 index 00000000..8b370748 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java @@ -0,0 +1,153 @@ +/** + * Copyright 2016 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.http.SslCertificate; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.widget.TextView; + +import org.w3c.dom.Text; + +import java.util.Date; + +public class ViewSslCertificate extends DialogFragment { + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Create a drawable version of the favorite icon. + Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setIcon(favoriteIconDrawable); + + // Set an `onClick` listener on the negative button. Using `null` closes the dialog without doing anything else. + dialogBuilder.setNegativeButton(R.string.close, null); + + // Check to see if the website is encrypted. + if (MainWebViewActivity.mainWebView.getCertificate() == null) { // The website is not encrypted. + // Set the title. + dialogBuilder.setTitle(R.string.unencrypted_website); + + // Set the Layout. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website, null)); + + // Create an `AlertDialog` from the `AlertDialog.Builder` + final AlertDialog alertDialog = dialogBuilder.create(); + + // Show `alertDialog`. + alertDialog.show(); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + + } else { // Display the SSL certificate information + // Set the title. + dialogBuilder.setTitle(R.string.ssl_certificate); + + // Set the layout. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate, null)); + + // Create an `AlertDialog` from the `AlertDialog.Builder` + final AlertDialog alertDialog = dialogBuilder.create(); + + // We need to show the `AlertDialog` before we can modify items in the layout. + alertDialog.show(); + + // Get handles for the `TextViews`. + TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname); + TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname); + TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname); + TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname); + TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname); + TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname); + TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date); + TextView endDateTextView = (TextView) alertDialog.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 SSL certificate. + SslCertificate sslCertificate = MainWebViewActivity.mainWebView.getCertificate(); + + // Get the strings from the SSL certificate. + String issuedToCNameString = sslCertificate.getIssuedTo().getCName(); + String issuedToONameString = sslCertificate.getIssuedTo().getOName(); + String issuedToUNameString = sslCertificate.getIssuedTo().getUName(); + String issuedByCNameString = sslCertificate.getIssuedBy().getCName(); + String issuedByONameString = sslCertificate.getIssuedBy().getOName(); + String issuedByUNameString = sslCertificate.getIssuedBy().getUName(); + Date startDate = sslCertificate.getValidNotBeforeDate(); + Date endDate = sslCertificate.getValidNotAfterDate(); + + // Create a `SpannableStringBuilder` for each item. + SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCNameString); + SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToONameString); + SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUNameString); + SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCNameString); + SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByONameString); + SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUNameString); + SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate.toString()); + SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + endDate.toString()); + + // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue)); + + // Setup the spans to display the certificate information in blue. + // `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + endDateStringBuilder.setSpan(blueColorSpan, 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); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } + } +} diff --git a/app/src/main/res/layout/ssl_certificate_error.xml b/app/src/main/res/layout/ssl_certificate_error.xml new file mode 100644 index 00000000..c9626512 --- /dev/null +++ b/app/src/main/res/layout/ssl_certificate_error.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/unencrypted_website.xml b/app/src/main/res/layout/unencrypted_website.xml new file mode 100644 index 00000000..88b2709f --- /dev/null +++ b/app/src/main/res/layout/unencrypted_website.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/url_bar.xml b/app/src/main/res/layout/url_bar.xml index 4ee8cbda..19751aa5 100644 --- a/app/src/main/res/layout/url_bar.xml +++ b/app/src/main/res/layout/url_bar.xml @@ -39,7 +39,8 @@ android:layout_height="26dp" android:layout_width="26dp" android:layout_centerVertical="true" - android:contentDescription="@string/favorite_icon"/> + android:onClick="viewSslCertificate" + android:contentDescription="@string/favorite_icon" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/webview_options_menu.xml b/app/src/main/res/menu/webview_options_menu.xml index 9c189886..202437d1 100644 --- a/app/src/main/res/menu/webview_options_menu.xml +++ b/app/src/main/res/menu/webview_options_menu.xml @@ -78,7 +78,7 @@ @@ -86,43 +86,43 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 977d412d..ac11edcf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,33 @@ Favorite Icon URL or Search Terms + + View SSL Certificate + Unencrypted Website + Communicated with this website is not encrypted by an SSL certificate. + SSL Certificate + Close + Issued To + Issued By + Common Name (CN): + Organization (O): + Organizational Unit (OU): + Valid Dates + Start Date: + End Date: + + + SSL Certificate Error + Proceed + The certificate start date is in the future + The certificate is expired + The Common Name does not match the hostname + The certificate authority is not trusted + The date on the certificate is invalid + The certificate is invalid + URL + URL: + Navigation Drawer Navigation @@ -60,13 +87,14 @@ Clear Cookies Clear DOM Storage Clear Form Data - Font Size 50% - Font Size 75% - Font Size 100% - Font Size 125% - Font Size 150% - Font Size 175% - Font Size 200% + Font Size + 50% + 75% + 100% + 125% + 150% + 175% + 200% Share Add to Home Screen Refresh -- 2.43.0