From 5fb34c1fa70b7c42a0fc3c0b5af8e856d3af2695 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Tue, 22 Aug 2017 22:34:33 -0700 Subject: [PATCH] Add SSL certificate pinning. Implements https://redmine.stoutner.com/issues/54. --- .idea/dictionaries/soren.xml | 10 + app/src/main/assets/de/about_licenses.html | 1 + app/src/main/assets/en/about_licenses.html | 1 + app/src/main/assets/en/images/ic_vpn_lock.png | Bin 0 -> 2807 bytes app/src/main/assets/es/about_licenses.html | 1 + app/src/main/assets/it/about_licenses.html | 1 + .../activities/DomainsActivity.java | 35 +- .../activities/MainWebViewActivity.java | 186 ++++- .../WrapVerticalContentViewPager.java | 61 ++ .../CreateHomeScreenShortcutDialog.java | 9 +- .../PinnedSslCertificateMismatchDialog.java | 390 ++++++++++ .../dialogs/SslCertificateErrorDialog.java | 10 +- .../fragments/DomainSettingsFragment.java | 716 ++++++++++++++++-- .../fragments/DomainsListFragment.java | 35 +- .../helpers/DomainsDatabaseHelper.java | 107 ++- .../ssl_certificate_disabled_dark.xml | 18 + .../ssl_certificate_disabled_light.xml | 18 + .../drawable/ssl_certificate_enabled_dark.xml | 18 + .../ssl_certificate_enabled_light.xml | 18 + .../res/layout/about_coordinatorlayout.xml | 2 +- app/src/main/res/layout/add_domain_dialog.xml | 3 +- .../res/layout/domain_settings_fragment.xml | 322 +++++++- ..._ssl_certificate_mismatch_linearlayout.xml | 39 + ...ed_ssl_certificate_mismatch_scrollview.xml | 103 +++ .../main/res/layout/view_ssl_certificate.xml | 14 +- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values/attrs.xml | 2 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 10 + app/src/main/res/values/styles.xml | 8 + 31 files changed, 1998 insertions(+), 143 deletions(-) create mode 100644 app/src/main/assets/en/images/ic_vpn_lock.png create mode 100644 app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java create mode 100644 app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java create mode 100644 app/src/main/res/drawable/ssl_certificate_disabled_dark.xml create mode 100644 app/src/main/res/drawable/ssl_certificate_disabled_light.xml create mode 100644 app/src/main/res/drawable/ssl_certificate_enabled_dark.xml create mode 100644 app/src/main/res/drawable/ssl_certificate_enabled_light.xml create mode 100644 app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml create mode 100644 app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml index 4ee70c2d..16a47ed1 100644 --- a/.idea/dictionaries/soren.xml +++ b/.idea/dictionaries/soren.xml @@ -77,6 +77,7 @@ panopticlick parameterized parentfolder + pinnedsslcertificate programatically proxying qwant @@ -96,6 +97,14 @@ snackbar snackbars softkeyboard + sslenddate + sslissuedbycommonname + sslissuedbyorganization + sslissuedbyorganizationalunit + sslissuedtocommonname + sslissuedtoorganization + sslissuedtoorganizationalunit + sslstartdate subdomain subdomains subfolders @@ -107,6 +116,7 @@ uidh uids uname + uncheck useragent useragentname useragentstring diff --git a/app/src/main/assets/de/about_licenses.html b/app/src/main/assets/de/about_licenses.html index 32692288..7899ba46 100644 --- a/app/src/main/assets/de/about_licenses.html +++ b/app/src/main/assets/de/about_licenses.html @@ -116,6 +116,7 @@

ic_vertical_align_bottom.

ic_vertical_align_top.

ic_visibility_off.

+

ic_vpn_lock.

ic_web.


diff --git a/app/src/main/assets/en/about_licenses.html b/app/src/main/assets/en/about_licenses.html index 18a7edb0..cad98dc9 100644 --- a/app/src/main/assets/en/about_licenses.html +++ b/app/src/main/assets/en/about_licenses.html @@ -110,6 +110,7 @@

ic_vertical_align_bottom.

ic_vertical_align_top.

ic_visibility_off.

+

ic_vpn_lock.

ic_web.


diff --git a/app/src/main/assets/en/images/ic_vpn_lock.png b/app/src/main/assets/en/images/ic_vpn_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..5adb9cfc1528099be69b949df574f18dfba45a1e GIT binary patch literal 2807 zcmVUqw+HF$^6v~ z*rEp94AjxD{IPN;(2=j|0aI$g10+}K9^i3cRYTS{K1+K3$GK`BFy=<$D{?c^tWSvf z&P3o-jc4M%dxLYW16DX480E;ys{=Qo)|7V;-*t=v@3XINz&0Bg|LeUvLpq;{NoSoU z9RrS7#P~}4p+S0g#iX~;P{)8ne#DWL^*S)cAib%;8<8~cHPkWShz{U2j;yR(ETsFr zNSfQxm=;#S72E^N0!~ESsQthU%U@qb!y%^~9tcy)fL00_E zjC?)`8E4pFQsnQ?1M?c`Z?=?&GiC#iV7@BmT68O`8@R{wd)0xv8@d#ZiNH6JX_@cJ zbzuPa6hP;uEZ5ZCIp-l$E#C#>$^cHGSS?x|I5|$yyf+f`0V{yV5a&%*xrkyp*=mSR zyy{y47uYg@4rIW0j-~X9$g{o`aDgoYxD~j>Qd&i17+B+52^ZKffF;Oj z)hhEKu-ms1E-*EKcOmD}isbjU%|2v1S|u?tfNtO?$Zp#z^95wba3wJ@fNvvPlB>*j zki#66C95N7E)vnAs_X-<0q*y#hjX&D05xQrQIYl&+F}@~^nTi)@*!I#UIBdAwj!4K z1L`MZ_8@)y7T}jubhMv@t^rI#=KMwH4&bN0l_&$cUBFk7#hyS#&@q6?$iOI2A#@Dj zI^@J{phBn`Kriq`mx=`ZplSeD6Z(D~Y@YnJMeOWG)c~%vs7O2Tv~LAs_*2#|&cpx7 z7G3eZ5p_DS3;*TNl%2p^;uKYB0hX!1cc8()fvb?%eZCn%blI8Z%Uwv&A>S5Uwickt zLL}~xuX5M|%uiFWl_zjM@OI_bEeg4B)LAX*?hK zdjL5l9E>0vy{HeCH<)IYWB`*j(ug}ZKL9KsbO%}3i~4ld_`ZQ81JFtMUkRUw?jSp= z!6=S4%_zwLbP`Uh;Ls9eL-ooUX9l3t0_<<`|6z8ZkLu|p<1Qe{6|8eLxyr7WW_Pd# zweiK5@aqjc2>ca)@%CH&sG2ytLOy!GU7F|&7*9u?nrX_Re>SOY!>{2=FiRr_}1gRC|TK(`+%n-PE*L2SHzN2r#mGXu~$*pMYeOxcVe zG2Xr-RLiswEkJA>dIid61XYT{kU&Db_*_wJwr_>khWO>d=fhDi)+q!Yas)r5BL4(6dA zg=I5h1V*G8z&ed|7Mm&(p=?t`Tj(6cTkA}V23@H8?dwS3Xxp5IY>x1~CU6LQ7d@vj zsG-lfHZn|W0Y-q|DW>DfQ`Synf?2?O6rUXdp0tr^D~E$lGjp+tA}O@XnBY>4XE%~l zMALrsn#t1~>T;E)E@kQhI)Rrobh*)1#;sa_ZNMgt^sYD1r4?0f0hXeE-|!*i1`{i& z_?%a2q<0DGCyD}x(fQ%5!@?Rs6BPqkje5wHZrrv4Rb&Brji}BP)DknU208uQn(%tJIM70UhPH1Mjeu{|QK6>$_rgWQfBj zcshZ{G{65p;N7MLXOokW0g28_Y(jRs+Tb?Lyo(?=80h|W3uR`VEHAPOJ9iBC0P8d4 z?^9j@KAYitTgh)XvKB`par|^&XgPL_U^?Y90-Zr$Qwklc`8=|{)a{n??*YC~`NjmQ za-XGga>}h_d$~I>zq-E3ETm=0(jpaAHe=U^;*4(K_hfs?hmn9twx}VqgtH8EDuV2$ zAaj0IW*}FjWO><*z}pZ*IZ7Kz@$;9=SNmWB|`lu{)V- z*fD}(WG& zR&)b+fc!(xd1Eoz{RC;U1qt7F0pr7}71aRhz<05;9#Sf1;MO}gid+`?C{n;rIm|1o zypEk2PHE`^ZYI00pv-1qtxD4m z{2Kq2WmEnMTuvJf^s-VzVhcQj|H?CwH-MYai=G~ca5i#ntnbRyksU~PX7$WoyR!&* zgkp~o27zlS$w;a literal 0 HcmV?d00001 diff --git a/app/src/main/assets/es/about_licenses.html b/app/src/main/assets/es/about_licenses.html index 1c50064f..55df760e 100644 --- a/app/src/main/assets/es/about_licenses.html +++ b/app/src/main/assets/es/about_licenses.html @@ -115,6 +115,7 @@

ic_vertical_align_bottom.

ic_vertical_align_top.

ic_visibility_off.

+

ic_vpn_lock.

ic_web.


diff --git a/app/src/main/assets/it/about_licenses.html b/app/src/main/assets/it/about_licenses.html index efe8d205..23ee075a 100644 --- a/app/src/main/assets/it/about_licenses.html +++ b/app/src/main/assets/it/about_licenses.html @@ -118,6 +118,7 @@

ic_vertical_align_bottom.

ic_vertical_align_top.

ic_visibility_off.

+

ic_vpn_lock.

ic_web.


diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java index 0e023d47..f71a1384 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java @@ -21,6 +21,7 @@ package com.stoutner.privacybrowser.activities; import android.content.Context; import android.database.Cursor; +import android.net.http.SslCertificate; import android.os.Bundle; import android.os.Handler; import android.support.design.widget.FloatingActionButton; @@ -38,6 +39,7 @@ import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.EditText; import android.widget.ListView; +import android.widget.RadioButton; import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; @@ -518,6 +520,9 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo EditText customUserAgentEditText = (EditText) findViewById(R.id.domain_settings_custom_user_agent_edittext); Spinner fontSizeSpinner = (Spinner) findViewById(R.id.domain_settings_font_size_spinner); Spinner displayWebpageImagesSpinner = (Spinner) findViewById(R.id.domain_settings_display_webpage_images_spinner); + Switch pinnedSslCertificateSwitch = (Switch) findViewById(R.id.domain_settings_pinned_ssl_certificate_switch); + RadioButton savedSslCertificateRadioButton = (RadioButton) findViewById(R.id.saved_ssl_certificate_radiobutton); + RadioButton currentWebsiteCertificateRadioButton = (RadioButton) findViewById(R.id.current_website_certificate_radiobutton); // Extract the data for the domain settings. String domainNameString = domainNameEditText.getText().toString(); @@ -529,6 +534,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition(); int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition(); int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition(); + boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked(); // Get the data for the `Spinners` from the entry values string arrays. String userAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[userAgentPositionInt]; @@ -541,8 +547,33 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo } // Save the domain settings. - domainsDatabaseHelper.saveDomain(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, userAgentString, fontSizeInt, - displayWebpageImagesInt); + if (savedSslCertificateRadioButton.isChecked()) { // The current certificate is being used. + // Update the database except for the certificate. + domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, + formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate); + } else if (currentWebsiteCertificateRadioButton.isChecked()) { // The certificate is being updated with the current website certificate. + // Get the current website SSL certificate. + SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate; + + // Store the values from the SSL certificate. + String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName(); + String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName(); + String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName(); + String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName(); + String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName(); + String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName(); + long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime(); + long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime(); + + // Update the database. + domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, + userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit, startDateLong, + endDateLong); + } else { // No certificate is selected. + // Update the database, with PINNED_SSL_CERTIFICATE set to false. + domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, + userAgentString, fontSizeInt, displayWebpageImagesInt, false); + } } private void populateDomainsListView(final int highlightedDomainDatabaseId) { diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 0d8534cb..975814a6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -95,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; @@ -112,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; @@ -119,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`, `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()`. @@ -149,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`. @@ -166,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()`. @@ -233,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; @@ -296,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") @@ -790,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)); + } } }); @@ -1507,8 +1605,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation 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() - { + findOnPageEditText.postDelayed(new Runnable() { @Override public void run() { @@ -2097,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. @@ -2406,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)); @@ -2472,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); @@ -2480,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(); @@ -2556,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); diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java new file mode 100644 index 00000000..8b1d9782 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/definitions/WrapVerticalContentViewPager.java @@ -0,0 +1,61 @@ +package com.stoutner.privacybrowser.definitions; + +/* + * Copyright © 2017 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 . + */ + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; + +public class WrapVerticalContentViewPager extends ViewPager { + // Setup the default constructors. + public WrapVerticalContentViewPager(Context context) { + super(context); + } + + public WrapVerticalContentViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Perform an initial `super.onMeasure`, which populates `getChildCount`. + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + // Initialize `maximumHeight`. + int maximumHeight = 0; + + // Find the maximum height of each of the child views. + for (int i = 0; i < getChildCount(); i++) { + View childView = getChildAt(i); + + // Measure the child view height with no constraints. + childView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + + // Store the child's height if it is larger than `maximumHeight`. + if (childView.getMeasuredHeight() > maximumHeight) { + maximumHeight = childView.getMeasuredHeight(); + } + } + + // Perform a final `super.onMeasure` to set the `maximumHeight`. + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maximumHeight, MeasureSpec.EXACTLY)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java index dbda6c79..47418332 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java @@ -86,13 +86,8 @@ public class CreateHomeScreenShortcutDialog extends AppCompatDialogFragment { // Set the view. 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. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing if `Cancel` is clicked. - } - }); + // Setup the negative button. Using `null` closes the dialog without doing anything else. + dialogBuilder.setNegativeButton(R.string.cancel, null); // Set an `onClick` listener on the positive button. dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { 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 index 00000000..aa5b6279 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/PinnedSslCertificateMismatchDialog.java @@ -0,0 +1,390 @@ +/* + * Copyright © 2017 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.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; + } + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java index f0d5a69f..e966b2ec 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java @@ -138,11 +138,19 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { // Use `AlertDialog.Builder` to create the `AlertDialog`. AlertDialog.Builder dialogBuilder; - // Set the style according to the theme. + // Set the style and icon according to the theme. if (MainWebViewActivity.darkTheme) { + // Set the style. dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark); } else { + // Set the style. dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light); } // Set the title. diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java index b47dec32..2a768542 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java @@ -22,12 +22,19 @@ package com.stoutner.privacybrowser.fragments; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; import android.database.Cursor; +import android.net.http.SslCertificate; import android.os.Build; import android.os.Bundle; // We have to use `android.support.v4.app.Fragment` until minimum API >= 23. Otherwise we cannot call `getContext()`. import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextWatcher; +import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,6 +45,7 @@ import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RadioButton; import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; @@ -46,11 +54,15 @@ import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.activities.MainWebViewActivity; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; + public class DomainSettingsFragment extends Fragment { // `DATABASE_ID` is used by activities calling this fragment. public static final String DATABASE_ID = "database_id"; - // `databaseId` is public statis so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`. + // `databaseId` is public static so it can be accessed from `DomainsActivity`. It is also used in `onCreate()` and `onCreateView()`. public static int databaseId; @Override @@ -68,11 +80,21 @@ public class DomainSettingsFragment extends Fragment { // Inflate `domain_settings_fragment`. `false` does not attach it to the root `container`. View domainSettingsView = inflater.inflate(R.layout.domain_settings_fragment, container, false); - // Get a handle for the `Context`. + // Get a handle for the `Context` and the `Resources`. Context context = getContext(); + final Resources resources = getResources(); + + // Get a handle for the shared preference. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + // Store the default user agent string values. + final String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); + final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + boolean defaultDisplayWebpageImagesBoolean = sharedPreferences.getBoolean("display_website_images", true); // Get handles for the views in the fragment. - EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext); + final EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext); Switch javaScriptEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_javascript_switch); final ImageView javaScriptImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_javascript_imageview); Switch firstPartyCookiesEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_switch); @@ -88,8 +110,43 @@ public class DomainSettingsFragment extends Fragment { final TextView userAgentTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_user_agent_textview); final EditText customUserAgentEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_custom_user_agent_edittext); Spinner fontSizeSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_font_size_spinner); + final TextView fontSizeTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_font_size_textview); final ImageView displayWebpageImagesImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_imageview); Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_spinner); + final TextView displayImagesTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_textview); + final ImageView pinnedSslCertificateImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_imageview); + Switch pinnedSslCertificateSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch); + final LinearLayout savedSslCertificateLinearLayout = (LinearLayout) domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout); + final RadioButton savedSslCertificateRadioButton = (RadioButton) domainSettingsView.findViewById(R.id.saved_ssl_certificate_radiobutton); + final TextView savedSslCertificateIssuedToCNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_cname); + TextView savedSslCertificateIssuedToONameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_oname); + TextView savedSslCertificateIssuedToUNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_to_uname); + TextView savedSslCertificateIssuedByCNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_cname); + TextView savedSslCertificateIssuedByONameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_oname); + TextView savedSslCertificateIssuedByUNameTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_issued_by_uname); + TextView savedSslCertificateStartDateTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_start_date); + TextView savedSslCertificateEndDateTextView = (TextView) domainSettingsView.findViewById(R.id.saved_ssl_certificate_end_date); + final LinearLayout currentWebsiteCertificateLinearLayout = (LinearLayout) domainSettingsView.findViewById(R.id.current_website_certificate_linearlayout); + final RadioButton currentWebsiteCertificateRadioButton = (RadioButton) domainSettingsView.findViewById(R.id.current_website_certificate_radiobutton); + final TextView currentWebsiteCertificateIssuedToCNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_cname); + TextView currentWebsiteCertificateIssuedToONameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_oname); + TextView currentWebsiteCertificateIssuedToUNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_to_uname); + TextView currentWebsiteCertificateIssuedByCNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_cname); + TextView currentWebsiteCertificateIssuedByONameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_oname); + TextView currentWebsiteCertificateIssuedByUNameTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_issued_by_uname); + TextView currentWebsiteCertificateStartDateTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_start_date); + TextView currentWebsiteCertificateEndDateTextView = (TextView) domainSettingsView.findViewById(R.id.current_website_certificate_end_date); + final TextView noCurrentWebsiteCertificateTextView = (TextView) domainSettingsView.findViewById(R.id.no_current_website_certificate); + + // Setup the SSL certificate labels. + final 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 + final SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate; // 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(context, null, null, 0); @@ -108,6 +165,26 @@ public class DomainSettingsFragment extends Fragment { final String currentUserAgentString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); int fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); int displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); + int pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)); + final String savedSslCertificateIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); + String savedSslCertificateIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); + String savedSslCertificateIssuedToUNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); + String savedSslCertificateIssuedByCNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); + String savedSslCertificateIssuedByONameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); + String savedSslCertificateIssuedByUNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + + // Initialize the saved SSL certificate date variables. + Date savedSslCertificateStartDate = null; + Date savedSslCertificateEndDate = null; + + // Only get the saved SSL certificate dates from the cursor if they are not set to `0`. + if (domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) != 0) { + savedSslCertificateStartDate = new Date(domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); + } + + if (domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) != 0) { + savedSslCertificateEndDate = new Date(domainCursor.getLong(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + } // Create `ArrayAdapters` for the `Spinners`and their `entry values`. ArrayAdapter userAgentArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_user_agent_entries, R.layout.spinner_item); @@ -126,30 +203,127 @@ public class DomainSettingsFragment extends Fragment { fontSizeSpinner.setAdapter(fontSizeArrayAdapter); displayWebpageImagesSpinner.setAdapter(displayImagesArrayAdapter); + // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + SpannableStringBuilder savedSslCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString); + SpannableStringBuilder savedSslCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedToONameString); + SpannableStringBuilder savedSslCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedToUNameString); + SpannableStringBuilder savedSslCertificateIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedByCNameString); + SpannableStringBuilder savedSslCertificateIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + savedSslCertificateIssuedByONameString); + SpannableStringBuilder savedSslCertificateIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + savedSslCertificateIssuedByUNameString); + + // Initialize the `SpannableStringBuilders` for the SSL certificate dates. + SpannableStringBuilder savedSslCertificateStartDateStringBuilder; + SpannableStringBuilder savedSslCertificateEndDateStringBuilder; + + // Leave the SSL certificate dates empty if they are `null`. + if (savedSslCertificateStartDate == null) { + savedSslCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel); + } else { + savedSslCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslCertificateStartDate)); + } + + if (savedSslCertificateEndDate == null) { + savedSslCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel); + } else { + savedSslCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(savedSslCertificateEndDate)); + } + + // Create a red `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + final ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + + // Create a blue `ForegroundColorSpan`. + final 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)); + } + // Set the domain name from the the database cursor. domainNameEditText.setText(domainNameString); + // Update the certificates' `Common Name` color when the domain name text changes. + domainNameEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing. + } + + @Override + public void afterTextChanged(Editable s) { + // Get the new domain name. + String newDomainName = domainNameEditText.getText().toString(); + + // Check the saved SSL certificate against the new domain name. + boolean savedSslCertificateMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, savedSslCertificateIssuedToCNameString); + + // Create a `SpannableStringBuilder` for the saved certificate `Common Name`. + SpannableStringBuilder savedSslCertificateCommonNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString); + + // Format the saved certificate `Common Name` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSslCertificateMatchesNewDomainName) { + savedSslCertificateCommonNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { + savedSslCertificateCommonNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Update `savedSslCertificateIssuedToCNameTextView`. + savedSslCertificateIssuedToCNameTextView.setText(savedSslCertificateCommonNameStringBuilder); + + // Update the current website certificate if it exists. + if (currentWebsiteSslCertificate != null) { + // Get the current website certificate `Common Name`. + String currentWebsiteCertificateCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName(); + + // Check the current website certificate against the new domain name. + boolean currentWebsiteCertificateMatchesNewDomainName = checkDomainNameAgainstCertificate(newDomainName, currentWebsiteCertificateCommonName); + + // Create a `SpannableStringBuilder` for the current website certificate `Common Name`. + SpannableStringBuilder currentWebsiteCertificateCommonNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateCommonName); + + // Format the current certificate `Common Name` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentWebsiteCertificateMatchesNewDomainName) { + currentWebsiteCertificateCommonNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { + currentWebsiteCertificateCommonNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentWebsiteCertificateCommonNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Update `currentWebsiteCertificateIssuedToCNameTextView`. + currentWebsiteCertificateIssuedToCNameTextView.setText(currentWebsiteCertificateCommonNameStringBuilder); + } + } + }); + // Set the JavaScript status. if (javaScriptEnabledInt == 1) { // JavaScript is enabled. javaScriptEnabledSwitch.setChecked(true); - javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.javascript_enabled)); + javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled)); } else { // JavaScript is disabled. javaScriptEnabledSwitch.setChecked(false); - javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.privacy_mode)); + javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode)); } // Set the first-party cookies status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons. if (firstPartyCookiesEnabledInt == 1) { // First-party cookies are enabled. firstPartyCookiesEnabledSwitch.setChecked(true); - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_enabled)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled)); } else { // First-party cookies are disabled. firstPartyCookiesEnabledSwitch.setChecked(false); // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark)); } else { - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light)); } } @@ -160,15 +334,15 @@ public class DomainSettingsFragment extends Fragment { // Set the third-party cookies status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons. if (thirdPartyCookiesEnabledInt == 1) { // Both first-party and third-party cookies are enabled. thirdPartyCookiesEnabledSwitch.setChecked(true); - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning)); } else { // First party cookies are enabled but third-party cookies are disabled. thirdPartyCookiesEnabledSwitch.setChecked(false); // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark)); } else { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light)); } } } else { // First-party cookies are disabled. @@ -184,9 +358,9 @@ public class DomainSettingsFragment extends Fragment { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_dark)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_dark)); } else { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_light)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_light)); } } } else { // Third-party cookies cannot be configured for API <= 21. @@ -199,16 +373,16 @@ public class DomainSettingsFragment extends Fragment { // Set the DOM storage status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons. if (domStorageEnabledInt == 1) { // Both JavaScript and DOM storage are enabled. domStorageEnabledSwitch.setChecked(true); - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled)); } else { // JavaScript is enabled but DOM storage is disabled. // Set the DOM storage switch to off. domStorageEnabledSwitch.setChecked(false); // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark)); } else { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light)); } } } else { // JavaScript is disabled. @@ -224,25 +398,25 @@ public class DomainSettingsFragment extends Fragment { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_dark)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark)); } else { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_light)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_light)); } } // Set the form data status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons. if (formDataEnabledInt == 1) { // Form data is enabled. formDataEnabledSwitch.setChecked(true); - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_enabled)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled)); } else { // Form data is disabled. // Set the form data switch to off. formDataEnabledSwitch.setChecked(false); // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_dark)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_dark)); } else { - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_light)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_light)); } } @@ -252,13 +426,6 @@ public class DomainSettingsFragment extends Fragment { WebView bareWebView = (WebView) bareWebViewLayout.findViewById(R.id.bare_webview); final String webViewDefaultUserAgentString = bareWebView.getSettings().getUserAgentString(); - // Get a handle for the shared preference. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - - // Store the default user agent string values. - final String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); - final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); - // Get the position of the user agent in `userAgentEntryValuesArrayAdapter`. int userAgentArrayPosition = userAgentEntryValuesArrayAdapter.getPosition(currentUserAgentString); @@ -319,47 +486,251 @@ public class DomainSettingsFragment extends Fragment { int fontSizeArrayPosition = fontSizeEntryValuesArrayAdapter.getPosition(String.valueOf(fontSizeInt)); fontSizeSpinner.setSelection(fontSizeArrayPosition); + // Set the default font size text. + int defaultFontSizeArrayPosition = fontSizeEntryValuesArrayAdapter.getPosition(defaultFontSizeString); + fontSizeTextView.setText(fontSizeArrayAdapter.getItem(defaultFontSizeArrayPosition)); + + // Set the display options for `fontSizeTextView`. + if (fontSizeArrayPosition == 0) { // System default font size is selected. Display `fontSizeTextView`. + fontSizeTextView.setVisibility(View.VISIBLE); + } else { // A custom font size is specified. Hide `fontSizeTextView`. + fontSizeTextView.setVisibility(View.GONE); + } + // Set the selected display website images mode. displayWebpageImagesSpinner.setSelection(displayImagesInt); - // Set the display website images icon. + // Set the default display images text. + if (defaultDisplayWebpageImagesBoolean) { + displayImagesTextView.setText(displayImagesArrayAdapter.getItem(1)); + } else { + displayImagesTextView.setText(displayImagesArrayAdapter.getItem(2)); + } + + // Set the display website images icon and `TextView` settings. switch (displayImagesInt) { case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: if (MainWebViewActivity.displayWebpageImagesBoolean) { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light)); } } else { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light)); } } + + // Show `displayImagesTextView`. + displayImagesTextView.setVisibility(View.VISIBLE); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light)); } + + // Hide `displayImagesTextView`. + displayImagesTextView.setVisibility(View.GONE); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light)); } + + // Hide `displayImagesTextView`. + displayImagesTextView.setVisibility(View.GONE); break; } + + // Set the pinned SSL certificate icon. + if (pinnedSslCertificateInt == 1) { // Pinned SSL certificate is enabled. + // Check the switch. + pinnedSslCertificateSwitch.setChecked(true); + + // Set the icon according to the theme. + if (MainWebViewActivity.darkTheme) { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark)); + } else { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light)); + } + } else { // Pinned SSL certificate is disabled. + // Uncheck the switch. + pinnedSslCertificateSwitch.setChecked(false); + + // Set the icon according to the theme. + if (MainWebViewActivity.darkTheme) { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark)); + } else { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light)); + } + } + + // Store the current date. + Date currentDate = Calendar.getInstance().getTime(); + + // Setup the `StringBuilders` to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + savedSslCertificateIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslCertificateIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + savedSslCertificateIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslCertificateIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + savedSslCertificateIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + savedSslCertificateIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), savedSslCertificateIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + savedSslCertificateIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), savedSslCertificateIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Check the certificate `Common Name` against the domain name. + boolean savedSSlCertificateCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, savedSslCertificateIssuedToCNameString); + + // Format the `issuedToCommonName` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (savedSSlCertificateCommonNameMatchesDomainName) { + savedSslCertificateIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), savedSslCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { + savedSslCertificateIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), savedSslCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if ((savedSslCertificateStartDate != null) && savedSslCertificateStartDate.after(currentDate)) { // The certificate start date is in the future. + savedSslCertificateStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), savedSslCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // The certificate start date is in the past. + savedSslCertificateStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), savedSslCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if ((savedSslCertificateEndDate != null) && savedSslCertificateEndDate.before(currentDate)) { // The certificate end date is in the past. + savedSslCertificateEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), savedSslCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // The certificate end date is in the future. + savedSslCertificateEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), savedSslCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Display the current website SSL certificate strings. + savedSslCertificateIssuedToCNameTextView.setText(savedSslCertificateIssuedToCNameStringBuilder); + savedSslCertificateIssuedToONameTextView.setText(savedSslCertificateIssuedToONameStringBuilder); + savedSslCertificateIssuedToUNameTextView.setText(savedSslCertificateIssuedToUNameStringBuilder); + savedSslCertificateIssuedByCNameTextView.setText(savedSslCertificateIssuedByCNameStringBuilder); + savedSslCertificateIssuedByONameTextView.setText(savedSslCertificateIssuedByONameStringBuilder); + savedSslCertificateIssuedByUNameTextView.setText(savedSslCertificateIssuedByUNameStringBuilder); + savedSslCertificateStartDateTextView.setText(savedSslCertificateStartDateStringBuilder); + savedSslCertificateEndDateTextView.setText(savedSslCertificateEndDateStringBuilder); + + // Populate the current website SSL certificate if there is one. + if (currentWebsiteSslCertificate != null) { + // Get the strings from the SSL certificate. + String currentWebsiteCertificateIssuedToCNameString = currentWebsiteSslCertificate.getIssuedTo().getCName(); + String currentWebsiteCertificateIssuedToONameString = currentWebsiteSslCertificate.getIssuedTo().getOName(); + String currentWebsiteCertificateIssuedToUNameString = currentWebsiteSslCertificate.getIssuedTo().getUName(); + String currentWebsiteCertificateIssuedByCNameString = currentWebsiteSslCertificate.getIssuedBy().getCName(); + String currentWebsiteCertificateIssuedByONameString = currentWebsiteSslCertificate.getIssuedBy().getOName(); + String currentWebsiteCertificateIssuedByUNameString = currentWebsiteSslCertificate.getIssuedBy().getUName(); + Date currentWebsiteCertificateStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate(); + Date currentWebsiteCertificateEndDate = currentWebsiteSslCertificate.getValidNotAfterDate(); + + // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + SpannableStringBuilder currentWebsiteCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateIssuedToCNameString); + SpannableStringBuilder currentWebsiteCertificateIssuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentWebsiteCertificateIssuedToONameString); + SpannableStringBuilder currentWebsiteCertificateIssuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentWebsiteCertificateIssuedToUNameString); + SpannableStringBuilder currentWebsiteCertificateIssuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentWebsiteCertificateIssuedByCNameString); + SpannableStringBuilder currentWebsiteCertificateIssuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentWebsiteCertificateIssuedByONameString); + SpannableStringBuilder currentWebsiteCertificateIssuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentWebsiteCertificateIssuedByUNameString); + SpannableStringBuilder currentWebsiteCertificateStartDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentWebsiteCertificateStartDate)); + SpannableStringBuilder currentWebsiteCertificateEndDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentWebsiteCertificateEndDate)); + + // Setup the `StringBuilders` to display the general certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + currentWebsiteCertificateIssuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentWebsiteCertificateIssuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + currentWebsiteCertificateIssuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentWebsiteCertificateIssuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + currentWebsiteCertificateIssuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + currentWebsiteCertificateIssuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), currentWebsiteCertificateIssuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + currentWebsiteCertificateIssuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), currentWebsiteCertificateIssuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Check the certificate `Common Name` against the domain name. + boolean currentWebsiteCertificateCommonNameMatchesDomainName = checkDomainNameAgainstCertificate(domainNameString, currentWebsiteCertificateIssuedToCNameString); + + // Format the `issuedToCommonName` color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentWebsiteCertificateCommonNameMatchesDomainName) { + currentWebsiteCertificateIssuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { + currentWebsiteCertificateIssuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), currentWebsiteCertificateIssuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Format the start date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentWebsiteCertificateStartDate.after(currentDate)) { // The certificate start date is in the future. + currentWebsiteCertificateStartDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), currentWebsiteCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // The certificate start date is in the past. + currentWebsiteCertificateStartDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), currentWebsiteCertificateStartDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Format the end date color. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + if (currentWebsiteCertificateEndDate.before(currentDate)) { // The certificate end date is in the past. + currentWebsiteCertificateEndDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), currentWebsiteCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // The certificate end date is in the future. + currentWebsiteCertificateEndDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), currentWebsiteCertificateEndDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + + // Display the current website SSL certificate strings. + currentWebsiteCertificateIssuedToCNameTextView.setText(currentWebsiteCertificateIssuedToCNameStringBuilder); + currentWebsiteCertificateIssuedToONameTextView.setText(currentWebsiteCertificateIssuedToONameStringBuilder); + currentWebsiteCertificateIssuedToUNameTextView.setText(currentWebsiteCertificateIssuedToUNameStringBuilder); + currentWebsiteCertificateIssuedByCNameTextView.setText(currentWebsiteCertificateIssuedByCNameStringBuilder); + currentWebsiteCertificateIssuedByONameTextView.setText(currentWebsiteCertificateIssuedByONameStringBuilder); + currentWebsiteCertificateIssuedByUNameTextView.setText(currentWebsiteCertificateIssuedByUNameStringBuilder); + currentWebsiteCertificateStartDateTextView.setText(currentWebsiteCertificateStartDateStringBuilder); + currentWebsiteCertificateEndDateTextView.setText(currentWebsiteCertificateEndDateStringBuilder); + } + + // Set the initial display status for the SSL certificates. + if (pinnedSslCertificateSwitch.isChecked()) { + // Set the visibility of the saved SSL certificate. + if (savedSslCertificateIssuedToCNameString == null) { + savedSslCertificateLinearLayout.setVisibility(View.GONE); + } else { + savedSslCertificateLinearLayout.setVisibility(View.VISIBLE); + } + + // Set the visibility of the current website SSL certificate. + if (currentWebsiteSslCertificate == null) { + // Hide the SSL certificate. + currentWebsiteCertificateLinearLayout.setVisibility(View.GONE); + + // Show the instruction. + noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE); + } else { + // Show the SSL certificate. + currentWebsiteCertificateLinearLayout.setVisibility(View.VISIBLE); + + // Hide the instruction. + noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); + } + + // Set the status of the radio buttons. + if (savedSslCertificateLinearLayout.getVisibility() == View.VISIBLE) { // The saved SSL certificate is displayed. + savedSslCertificateRadioButton.setChecked(true); + currentWebsiteCertificateRadioButton.setChecked(false); + } else if (currentWebsiteCertificateLinearLayout.getVisibility() == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. + currentWebsiteCertificateRadioButton.setChecked(true); + savedSslCertificateRadioButton.setChecked(false); + } else { // Neither SSL certificate is visible. + savedSslCertificateRadioButton.setChecked(false); + currentWebsiteCertificateRadioButton.setChecked(false); + } + } else { // `pinnedSslCertificateSwitch` is not checked. + // Hide the SSl certificates and instructions. + savedSslCertificateLinearLayout.setVisibility(View.GONE); + currentWebsiteCertificateLinearLayout.setVisibility(View.GONE); + noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); + + // Uncheck the radio buttons. + savedSslCertificateRadioButton.setChecked(false); + currentWebsiteCertificateRadioButton.setChecked(false); + } // Set the `javaScriptEnabledSwitch` `OnCheckedChangeListener()`. @@ -368,34 +739,34 @@ public class DomainSettingsFragment extends Fragment { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // JavaScript is enabled. // Update the JavaScript icon. - javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.javascript_enabled)); + javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled)); // Enable the DOM storage `Switch`. domStorageEnabledSwitch.setEnabled(true); // Update the DOM storage icon. if (domStorageEnabledSwitch.isChecked()) { // DOM storage is enabled. - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled)); } else { // DOM storage is disabled. // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark)); } else { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light)); } } } else { // JavaScript is disabled. // Update the JavaScript icon. - javaScriptImageView.setImageDrawable(getResources().getDrawable(R.drawable.privacy_mode)); + javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode)); // Disable the DOM storage `Switch`. domStorageEnabledSwitch.setEnabled(false); // Set the DOM storage icon according to the theme. if (MainWebViewActivity.darkTheme) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_dark)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark)); } else { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_ghosted_light)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_light)); } } } @@ -407,28 +778,28 @@ public class DomainSettingsFragment extends Fragment { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { // First-party cookies are enabled. // Update the first-party cookies icon. - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_enabled)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_enabled)); // Enable the third-party cookies `Switch`. thirdPartyCookiesEnabledSwitch.setEnabled(true); // Update the third-party cookies icon. if (thirdPartyCookiesEnabledSwitch.isChecked()) { // Third-party cookies are enabled. - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning)); } else { // Third-party cookies are disabled. // Set the third-party cookies icon according to the theme. if (MainWebViewActivity.darkTheme) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark)); } else { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light)); } } } else { // First-party cookies are disabled. // Update the first-party cookies icon according to the theme. if (MainWebViewActivity.darkTheme) { - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark)); } else { - firstPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light)); + firstPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light)); } // Disable the third-party cookies `Switch`. @@ -436,9 +807,9 @@ public class DomainSettingsFragment extends Fragment { // Set the third-party cookies icon according to the theme. if (MainWebViewActivity.darkTheme) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_dark)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_dark)); } else { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_ghosted_light)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_ghosted_light)); } } } @@ -450,13 +821,13 @@ public class DomainSettingsFragment extends Fragment { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Update the icon. if (isChecked) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_warning)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_warning)); } else { // Update the third-party cookies icon according to the theme. if (MainWebViewActivity.darkTheme) { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_dark)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_dark)); } else { - thirdPartyCookiesImageView.setImageDrawable(getResources().getDrawable(R.drawable.cookies_disabled_light)); + thirdPartyCookiesImageView.setImageDrawable(resources.getDrawable(R.drawable.cookies_disabled_light)); } } } @@ -468,13 +839,13 @@ public class DomainSettingsFragment extends Fragment { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Update the icon. if (isChecked) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_enabled)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled)); } else { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_dark)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark)); } else { - domStorageImageView.setImageDrawable(getResources().getDrawable(R.drawable.dom_storage_disabled_light)); + domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light)); } } } @@ -486,13 +857,13 @@ public class DomainSettingsFragment extends Fragment { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Update the icon. if (isChecked) { - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_enabled)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_enabled)); } else { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_dark)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_dark)); } else { - formDataImageView.setImageDrawable(getResources().getDrawable(R.drawable.form_data_disabled_light)); + formDataImageView.setImageDrawable(resources.getDrawable(R.drawable.form_data_disabled_light)); } } } @@ -503,7 +874,7 @@ public class DomainSettingsFragment extends Fragment { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { // Store the new user agent string. - String newUserAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[position]; + String newUserAgentString = resources.getStringArray(R.array.domain_settings_user_agent_entry_values)[position]; // Set the new user agent. switch (newUserAgentString) { @@ -566,46 +937,73 @@ public class DomainSettingsFragment extends Fragment { } }); - // Set the `displayImagesSwitch` `onItemClickListener()`. + // Set the `fontSizeSpinner` `onItemSelectedListener()`. + fontSizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // Update the display options for `fontSizeTextView`. + if (position == 0) { // System default font size has been selected. Display `fontSizeTextView`. + fontSizeTextView.setVisibility(View.VISIBLE); + } else { // A custom font size has been selected. Hide `fontSizeTextView`. + fontSizeTextView.setVisibility(View.GONE); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Do nothing. + } + }); + + // Set the `displayWebpageImagesSpinner` `onItemSelectedListener()`. displayWebpageImagesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update the icon. + // Update the icon and the visibility of `displayImagesTextView`. switch (position) { case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT: if (MainWebViewActivity.displayWebpageImagesBoolean) { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light)); } } else { // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light)); } } + + // Show `displayImagesTextView`. + displayImagesTextView.setVisibility(View.VISIBLE); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED: // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_enabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light)); } + + // Hide `displayImagesTextView`. + displayImagesTextView.setVisibility(View.GONE); break; case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED: // Set the icon according to the theme. if (MainWebViewActivity.darkTheme) { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_dark)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark)); } else { - displayWebpageImagesImageView.setImageDrawable(getResources().getDrawable(R.drawable.images_disabled_light)); + displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_light)); } + + // Hide `displayImagesTextView`. + displayImagesTextView.setVisibility(View.GONE); break; } } @@ -615,7 +1013,181 @@ public class DomainSettingsFragment extends Fragment { // Do nothing. } }); + + // Set the `pinnedSSLCertificateSwitch` `onCheckedChangeListener()`. + pinnedSslCertificateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + // Update the icon + if (isChecked) { // Pinned SSL certificate is enabled. + // Set the icon according to the theme. + if (MainWebViewActivity.darkTheme) { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_dark)); + } else { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_enabled_light)); + } + + // Update the visibility of the saved SSL certificate. + if (savedSslCertificateIssuedToCNameString == null) { + savedSslCertificateLinearLayout.setVisibility(View.GONE); + } else { + savedSslCertificateLinearLayout.setVisibility(View.VISIBLE); + } + + // Update the visibility of the current website SSL certificate. + if (currentWebsiteSslCertificate == null) { + // Hide the SSL certificate. + currentWebsiteCertificateLinearLayout.setVisibility(View.GONE); + + // Show the instruction. + noCurrentWebsiteCertificateTextView.setVisibility(View.VISIBLE); + } else { + // Show the SSL certificate. + currentWebsiteCertificateLinearLayout.setVisibility(View.VISIBLE); + + // Hide the instruction. + noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); + } + + // Set the status of the radio buttons. + if (savedSslCertificateLinearLayout.getVisibility() == View.VISIBLE) { // The saved SSL certificate is displayed. + savedSslCertificateRadioButton.setChecked(true); + currentWebsiteCertificateRadioButton.setChecked(false); + } else if (currentWebsiteCertificateLinearLayout.getVisibility() == View.VISIBLE) { // The saved SSL certificate is hidden but the current website SSL certificate is visible. + currentWebsiteCertificateRadioButton.setChecked(true); + savedSslCertificateRadioButton.setChecked(false); + } else { // Neither SSL certificate is visible. + savedSslCertificateRadioButton.setChecked(false); + currentWebsiteCertificateRadioButton.setChecked(false); + } + } else { // Pinned SSL certificate is disabled. + // Set the icon according to the theme. + if (MainWebViewActivity.darkTheme) { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_dark)); + } else { + pinnedSslCertificateImageView.setImageDrawable(resources.getDrawable(R.drawable.ssl_certificate_disabled_light)); + } + + // Hide the SSl certificates and instructions. + savedSslCertificateLinearLayout.setVisibility(View.GONE); + currentWebsiteCertificateLinearLayout.setVisibility(View.GONE); + noCurrentWebsiteCertificateTextView.setVisibility(View.GONE); + + // Uncheck the radio buttons. + savedSslCertificateRadioButton.setChecked(false); + currentWebsiteCertificateRadioButton.setChecked(false); + } + } + }); + + savedSslCertificateLinearLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + savedSslCertificateRadioButton.setChecked(true); + currentWebsiteCertificateRadioButton.setChecked(false); + } + }); + + savedSslCertificateRadioButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + savedSslCertificateRadioButton.setChecked(true); + currentWebsiteCertificateRadioButton.setChecked(false); + } + }); + + currentWebsiteCertificateLinearLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + currentWebsiteCertificateRadioButton.setChecked(true); + savedSslCertificateRadioButton.setChecked(false); + } + }); + + currentWebsiteCertificateRadioButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + currentWebsiteCertificateRadioButton.setChecked(true); + savedSslCertificateRadioButton.setChecked(false); + } + }); return domainSettingsView; } + + private boolean checkDomainNameAgainstCertificate(String domainName, String certificateCommonName) { + // Initialize `domainNamesMatch`. + boolean domainNamesMatch = false; + + // Check if the domains match. + if (domainName.equals(certificateCommonName)) { + domainNamesMatch = true; + } + + // Check various wildcard permutations if `domainName` and `certificateCommonName` are not empty. `noinspection ConstantCondition` removes Android Studio's incorrect lint warning that `domainName` can never be `null`. + //noinspection ConstantConditions + if ((domainName != null) && (certificateCommonName != null)) { + // If `domainName` starts with a wildcard, check the base domain against all the subdomains of `certificateCommonName`. + if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2)) { + // Remove the initial `*.`. + String baseDomainName = domainName.substring(2); + + // Setup a copy of `certificateCommonName` to test subdomains. + String certificateCommonNameSubdomain = certificateCommonName; + + // Check all the subdomains in `certificateCommonNameSubdomains` against `baseDomainName`. + while (!domainNamesMatch && certificateCommonNameSubdomain.contains(".")) { // Stop checking if we know that `domainNamesMatch` is `true` or if we run out of `.`. + // Test the `certificateCommonNameSubdomain` against `baseDomainName`. + if (certificateCommonNameSubdomain.equals(baseDomainName)) { + domainNamesMatch = true; + } + + // Strip out the lowest subdomain of `certificateCommonNameSubdomain`. + try { + certificateCommonNameSubdomain = certificateCommonNameSubdomain.substring(certificateCommonNameSubdomain.indexOf(".") + 1); + } catch (IndexOutOfBoundsException e) { // `certificateCommonNameSubdomain` ends with `.`. + certificateCommonNameSubdomain = ""; + } + } + } + + // If `certificateCommonName` starts with a wildcard, check the base common name against all the subdomains of `domainName`. + if (!domainNamesMatch && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) { + // Remove the initial `*.`. + String baseCertificateCommonName = certificateCommonName.substring(2); + + // Setup a copy of `domainName` to test subdomains. + String domainNameSubdomain = domainName; + + // Check all the subdomains in `domainNameSubdomain` against `baseCertificateCommonName`. + while (!domainNamesMatch && domainNameSubdomain.contains(".") && (domainNameSubdomain.length() > 2)) { + // Test the `domainNameSubdomain` against `baseCertificateCommonName`. + if (domainNameSubdomain.equals(baseCertificateCommonName)) { + domainNamesMatch = true; + } + + // Strip out the lowest subdomain of `domainNameSubdomain`. + try { + domainNameSubdomain = domainNameSubdomain.substring(domainNameSubdomain.indexOf(".") + 1); + } catch (IndexOutOfBoundsException e) { // `domainNameSubdomain` ends with `.`. + domainNameSubdomain = ""; + } + } + } + + // If both names start with a wildcard, check if the root of one contains the root of the other. + if (!domainNamesMatch && domainName.startsWith("*.") && (domainName.length() > 2) && certificateCommonName.startsWith("*.") && (certificateCommonName.length() > 2)) { + // Remove the wildcards. + String rootDomainName = domainName.substring(2); + String rootCertificateCommonName = certificateCommonName.substring(2); + + // Check if one name ends with the contents of the other. If so, there will be overlap in the their wildcard subdomains. + if (rootDomainName.endsWith(rootCertificateCommonName) || rootCertificateCommonName.endsWith(rootDomainName)) { + domainNamesMatch = true; + } + } + } + + return domainNamesMatch; + } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java index 553c107d..34349d79 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java @@ -19,6 +19,7 @@ package com.stoutner.privacybrowser.fragments; +import android.net.http.SslCertificate; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; // We have to use `android.support.v4.app.Fragment` until minimum API >= 23. Otherwise we cannot call `getContext()`. @@ -30,6 +31,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ListView; +import android.widget.RadioButton; import android.widget.Spinner; import android.widget.Switch; @@ -78,6 +80,9 @@ public class DomainsListFragment extends Fragment { EditText customUserAgentEditText = (EditText) domainSettingsFragmentView.findViewById(R.id.domain_settings_custom_user_agent_edittext); Spinner fontSizeSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_font_size_spinner); Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_display_webpage_images_spinner); + Switch pinnedSslCertificateSwitch = (Switch) domainSettingsFragmentView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch); + RadioButton savedSslCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.saved_ssl_certificate_radiobutton); + RadioButton currentWebsiteCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.current_website_certificate_radiobutton); // Extract the data for the domain settings. String domainNameString = domainNameEditText.getText().toString(); @@ -89,6 +94,7 @@ public class DomainsListFragment extends Fragment { int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition(); int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition(); int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition(); + boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked(); // Get the data for the `Spinners` from the entry values string arrays. String userAgentString = getResources().getStringArray(R.array.domain_settings_user_agent_entry_values)[userAgentPositionInt]; @@ -101,8 +107,33 @@ public class DomainsListFragment extends Fragment { } // Save the domain settings. - domainsDatabaseHelper.saveDomain(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean, userAgentString, fontSizeInt, - displayWebpageImagesInt); + if (savedSslCertificateRadioButton.isChecked()) { // The current certificate is being used. + // Update the database except for the certificate. + domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, + formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate); + } else if (currentWebsiteCertificateRadioButton.isChecked()) { // The certificate is being updated with the current website certificate. + // Get the current website SSL certificate. + SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate; + + // Store the values from the SSL certificate. + String issuedToCommonName = currentWebsiteSslCertificate.getIssuedTo().getCName(); + String issuedToOrganization = currentWebsiteSslCertificate.getIssuedTo().getOName(); + String issuedToOrganizationalUnit = currentWebsiteSslCertificate.getIssuedTo().getUName(); + String issuedByCommonName = currentWebsiteSslCertificate.getIssuedBy().getCName(); + String issuedByOrganization = currentWebsiteSslCertificate.getIssuedBy().getOName(); + String issuedByOrganizationalUnit = currentWebsiteSslCertificate.getIssuedBy().getUName(); + long startDateLong = currentWebsiteSslCertificate.getValidNotBeforeDate().getTime(); + long endDateLong = currentWebsiteSslCertificate.getValidNotAfterDate().getTime(); + + // Update the database. + domainsDatabaseHelper.updateDomainWithCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, + formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, + issuedByOrganizationalUnit, startDateLong, endDateLong); + } else { // No certificate is selected. + // Update the database, with PINNED_SSL_CERTIFICATE set to false. + domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, + formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, false); + } } // Store the new `currentDomainDatabaseId`, converting it from `long` to `int` to match the format of the domains database. diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java index bb1a9c5c..3100d694 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java @@ -26,7 +26,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DomainsDatabaseHelper extends SQLiteOpenHelper { - private static final int SCHEMA_VERSION = 2; + private static final int SCHEMA_VERSION = 3; private static final String DOMAINS_DATABASE = "domains.db"; private static final String DOMAINS_TABLE = "domains"; @@ -40,6 +40,15 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { public static final String USER_AGENT = "useragent"; public static final String FONT_SIZE = "fontsize"; public static final String DISPLAY_IMAGES = "displayimages"; + public static final String PINNED_SSL_CERTIFICATE = "pinnedsslcertificate"; + public static final String SSL_ISSUED_TO_COMMON_NAME = "sslissuedtocommonname"; + public static final String SSL_ISSUED_TO_ORGANIZATION = "sslissuedtoorganization"; + public static final String SSL_ISSUED_TO_ORGANIZATIONAL_UNIT = "sslissuedtoorganizationalunit"; + public static final String SSL_ISSUED_BY_COMMON_NAME = "sslissuedbycommonname"; + public static final String SSL_ISSUED_BY_ORGANIZATION = "sslissuedbyorganization"; + public static final String SSL_ISSUED_BY_ORGANIZATIONAL_UNIT = "sslissuedbyorganizationalunit"; + public static final String SSL_START_DATE = "sslstartdate"; + public static final String SSL_END_DATE = "sslenddate"; public static final int DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT = 0; public static final int DISPLAY_WEBPAGE_IMAGES_ENABLED = 1; @@ -53,7 +62,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase domainsDatabase) { // Setup the SQL string to create the `domains` table. - final String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" + + String CREATE_DOMAINS_TABLE = "CREATE TABLE " + DOMAINS_TABLE + " (" + _ID + " INTEGER PRIMARY KEY, " + DOMAIN_NAME + " TEXT, " + ENABLE_JAVASCRIPT + " BOOLEAN, " + @@ -63,9 +72,18 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { ENABLE_FORM_DATA + " BOOLEAN, " + USER_AGENT + " TEXT, " + FONT_SIZE + " INTEGER, " + - DISPLAY_IMAGES + " INTEGER);"; - - // Create the `domains` table if it doesn't exist. + DISPLAY_IMAGES + " INTEGER, " + + PINNED_SSL_CERTIFICATE + " BOOLEAN, " + + SSL_ISSUED_TO_COMMON_NAME + " TEXT, " + + SSL_ISSUED_TO_ORGANIZATION + " TEXT, " + + SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT, " + + SSL_ISSUED_BY_COMMON_NAME + " TEXT, " + + SSL_ISSUED_BY_ORGANIZATION + " TEXT, " + + SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT, " + + SSL_START_DATE + " INTEGER, " + + SSL_END_DATE + " INTEGER);"; + + // Make it so. domainsDatabase.execSQL(CREATE_DOMAINS_TABLE); } @@ -77,6 +95,19 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { case 1: // Add the `DISPLAY_IMAGES` column. domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + DISPLAY_IMAGES + " INTEGER"); + + // Upgrade from `SCHEMA_VERSION` 2. + case 2: + // Add the SSL certificate columns. + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + PINNED_SSL_CERTIFICATE + " BOOLEAN"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_COMMON_NAME + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_ORGANIZATION + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_TO_ORGANIZATIONAL_UNIT + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_COMMON_NAME + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_ORGANIZATION + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_START_DATE + " INTEGER"); + domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_END_DATE + " INTEGER"); } } @@ -158,8 +189,36 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { return newDomainDatabaseId; } - public void saveDomain(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize, - int displayImages) { + public void updateDomainExceptCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize, + int displayImages, boolean pinnedSslCertificate) { + // Store the domain data in a `ContentValues`. + ContentValues domainContentValues = new ContentValues(); + + // Add entries for each field in the database. + domainContentValues.put(DOMAIN_NAME, domainName); + domainContentValues.put(ENABLE_JAVASCRIPT, javaScriptEnabled); + domainContentValues.put(ENABLE_FIRST_PARTY_COOKIES, firstPartyCookiesEnabled); + domainContentValues.put(ENABLE_THIRD_PARTY_COOKIES, thirdPartyCookiesEnabled); + domainContentValues.put(ENABLE_DOM_STORAGE, domStorageEnabled); + domainContentValues.put(ENABLE_FORM_DATA, formDataEnabled); + domainContentValues.put(USER_AGENT, userAgent); + domainContentValues.put(FONT_SIZE, fontSize); + domainContentValues.put(DISPLAY_IMAGES, displayImages); + domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate); + + // Get a writable database handle. + SQLiteDatabase domainsDatabase = this.getWritableDatabase(); + + // Update the row for `databaseId`. The last argument is `null` because there are no `whereArgs`. + domainsDatabase.update(DOMAINS_TABLE, domainContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + domainsDatabase.close(); + } + + public void updateDomainWithCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize, + int displayImages, boolean pinnedSslCertificate, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization, + String sslIssuedByOrganizationalUnit, long sslStartDate, long sslEndDate) { // Store the domain data in a `ContentValues`. ContentValues domainContentValues = new ContentValues(); @@ -173,6 +232,40 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper { domainContentValues.put(USER_AGENT, userAgent); domainContentValues.put(FONT_SIZE, fontSize); domainContentValues.put(DISPLAY_IMAGES, displayImages); + domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate); + domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName); + domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization); + domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, sslIssuedToOrganizationalUnit); + domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, sslIssuedByCommonName); + domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, sslIssuedByOrganization); + domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, sslIssuedByOrganizationalUnit); + domainContentValues.put(SSL_START_DATE, sslStartDate); + domainContentValues.put(SSL_END_DATE, sslEndDate); + + // Get a writable database handle. + SQLiteDatabase domainsDatabase = this.getWritableDatabase(); + + // Update the row for `databaseId`. The last argument is `null` because there are no `whereArgs`. + domainsDatabase.update(DOMAINS_TABLE, domainContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + domainsDatabase.close(); + } + + public void updateCertificate(int databaseId, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization, String sslIssuedByOrganizationalUnit, + long sslStartDate, long sslEndDate) { + // Store the domain data in a `ContentValues`. + ContentValues domainContentValues = new ContentValues(); + + // Add entries for each field in the certificate. + domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName); + domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization); + domainContentValues.put(SSL_ISSUED_TO_ORGANIZATIONAL_UNIT, sslIssuedToOrganizationalUnit); + domainContentValues.put(SSL_ISSUED_BY_COMMON_NAME, sslIssuedByCommonName); + domainContentValues.put(SSL_ISSUED_BY_ORGANIZATION, sslIssuedByOrganization); + domainContentValues.put(SSL_ISSUED_BY_ORGANIZATIONAL_UNIT, sslIssuedByOrganizationalUnit); + domainContentValues.put(SSL_START_DATE, sslStartDate); + domainContentValues.put(SSL_END_DATE, sslEndDate); // Get a writable database handle. SQLiteDatabase domainsDatabase = this.getWritableDatabase(); diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml new file mode 100644 index 00000000..3f6998c1 --- /dev/null +++ b/app/src/main/res/drawable/ssl_certificate_disabled_dark.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ssl_certificate_disabled_light.xml b/app/src/main/res/drawable/ssl_certificate_disabled_light.xml new file mode 100644 index 00000000..c8010697 --- /dev/null +++ b/app/src/main/res/drawable/ssl_certificate_disabled_light.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml new file mode 100644 index 00000000..ba20d431 --- /dev/null +++ b/app/src/main/res/drawable/ssl_certificate_enabled_dark.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ssl_certificate_enabled_light.xml b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml new file mode 100644 index 00000000..9fc00fe8 --- /dev/null +++ b/app/src/main/res/drawable/ssl_certificate_enabled_light.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/layout/about_coordinatorlayout.xml b/app/src/main/res/layout/about_coordinatorlayout.xml index 9e43e766..4c9c4aeb 100644 --- a/app/src/main/res/layout/about_coordinatorlayout.xml +++ b/app/src/main/res/layout/about_coordinatorlayout.xml @@ -63,8 +63,8 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/add_domain_dialog.xml b/app/src/main/res/layout/add_domain_dialog.xml index 176c542e..dff393cb 100644 --- a/app/src/main/res/layout/add_domain_dialog.xml +++ b/app/src/main/res/layout/add_domain_dialog.xml @@ -50,6 +50,5 @@ android:text="@string/domain_name_already_exists" android:textColor="?attr/redText" android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" - android:layout_marginBottom="12dp" /> + android:layout_marginEnd="8dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings_fragment.xml b/app/src/main/res/layout/domain_settings_fragment.xml index 1d7bf639..b27a1c41 100644 --- a/app/src/main/res/layout/domain_settings_fragment.xml +++ b/app/src/main/res/layout/domain_settings_fragment.xml @@ -92,6 +92,7 @@ android:id="@+id/domain_settings_javascript_imageview" android:layout_height="wrap_content" android:layout_width="wrap_content" + android:layout_marginTop="1dp" android:layout_marginEnd="10dp" android:layout_gravity="center_vertical" tools:ignore="contentDescription" /> @@ -268,47 +269,316 @@ + android:orientation="vertical" + android:layout_marginTop="14dp" + android:layout_marginBottom="14dp" > - + android:layout_width="match_parent" + android:orientation="horizontal" > - + + + + + + android:layout_marginStart="45dp" + android:layout_marginEnd="36dp" + android:textSize="13sp" /> + + android:orientation="vertical" + android:layout_marginTop="14dp" + android:layout_marginBottom="14dp" > - + android:layout_width="match_parent" + android:orientation="horizontal" > + + + + + + + + + + + - + + android:orientation="horizontal" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml new file mode 100644 index 00000000..115008ad --- /dev/null +++ b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_linearlayout.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml new file mode 100644 index 00000000..2782c495 --- /dev/null +++ b/app/src/main/res/layout/pinned_ssl_certificate_mismatch_scrollview.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_ssl_certificate.xml b/app/src/main/res/layout/view_ssl_certificate.xml index d33ab53e..f1ce5c1b 100644 --- a/app/src/main/res/layout/view_ssl_certificate.xml +++ b/app/src/main/res/layout/view_ssl_certificate.xml @@ -31,8 +31,8 @@ + android:layout_width="wrap_content" /> + android:textColor="?attr/sslTitle" /> + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + android:textColor="?attr/sslTitle" /> Dominios Configuración de dominio Añadir dominio + El nombre de dominio ya existe Añadir Nombre de dominio Configuración de dominio guardada diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a2a56174..c94d1443 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -187,6 +187,7 @@ Domini Impostazioni Domini Aggiungi Dominio + Il nome del Dominio è già esistente Aggiungi Nome del Dominio Impostazioni Domini Salvate diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index a756278c..6e4ad1fb 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -23,10 +23,12 @@ + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index ff782d97..821407a6 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -32,6 +32,7 @@ #FF2196F3 #FF1E88E5 #FF1976D2 + #881976D2 #FF1565C0 #FF0D47A1 #FF082B61 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c825d38a..4bb849c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,6 +88,12 @@ URL URL: + + Update SSL + SSL Certificate Mismatch + Current SSL + Pinned SSL + Navigation Drawer Navigation @@ -201,6 +207,10 @@ Images enabled Images disabled + Pinned SSL certificate + Saved SSL certificate + Current website SSL certificate + Load an encrypted website before opening Domain Settings to populate the current website SSL certificate. Privacy Browser Guide diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b97a3beb..b46ea05b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -32,9 +32,11 @@ @color/blue_700 @color/blue_800 @color/blue_800 + @color/blue_700 @color/blue_900 @color/black @color/red_a700 + @style/PrivacyBrowserTabLayoutDialogLight @@ -87,6 +89,11 @@ @color/white + + @@ -106,6 +113,7 @@ @color/blue_600 @color/blue_600 @color/blue_600 + @color/blue_400 @color/blue_700 @color/gray_200 @color/red_900 -- 2.43.0