Bug 1549605 - Add an indicator in the identity popup for when the site is verified by an imported root certificate. r=nhnt11
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 16 May 2019 06:34:21 +0000
changeset 535947 56f2b4cb0818691269e68dc1a1bb4230b7a77e03
parent 535946 76a573b6acf4b7c0c7e0b875306a7eb6fbaa64e6
child 535948 38b3d475e3c1592b5e27ba53f377671887d9a9d9
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnhnt11
bugs1549605
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1549605 - Add an indicator in the identity popup for when the site is verified by an imported root certificate. r=nhnt11 Differential Revision: https://phabricator.services.mozilla.com/D30136
browser/base/content/browser-siteIdentity.js
browser/base/content/test/siteIdentity/browser.ini
browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
browser/components/controlcenter/content/identityPanel.inc.xul
browser/locales/en-US/chrome/browser/browser.dtd
browser/themes/shared/controlcenter/panel.inc.css
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -135,16 +135,21 @@ var gIdentityHandler = {
     return this._identityPopupContentSupp =
       document.getElementById("identity-popup-content-supplemental");
   },
   get _identityPopupContentVerif() {
     delete this._identityPopupContentVerif;
     return this._identityPopupContentVerif =
       document.getElementById("identity-popup-content-verifier");
   },
+  get _identityPopupCustomRootLearnMore() {
+    delete this._identityPopupCustomRootLearnMore;
+    return this._identityPopupCustomRootLearnMore =
+      document.getElementById("identity-popup-custom-root-learn-more");
+  },
   get _identityPopupMixedContentLearnMore() {
     delete this._identityPopupMixedContentLearnMore;
     return this._identityPopupMixedContentLearnMore =
       [...document.querySelectorAll(".identity-popup-mcb-learn-more")];
   },
   get _identityPopupInsecureLoginFormsLearnMore() {
     delete this._identityPopupInsecureLoginFormsLearnMore;
     return this._identityPopupInsecureLoginFormsLearnMore =
@@ -505,16 +510,28 @@ var gIdentityHandler = {
     }
     if (this._uriHasHost && this._isSecure) {
       return "verifiedDomain";
     }
     return "unknownIdentity";
   },
 
   /**
+   * Returns whether the issuer of the current certificate chain is
+   * built-in (returns false) or imported (returns true).
+   */
+  _hasCustomRoot() {
+    let issuerCert = null;
+    // Walk the whole chain to get the last cert.
+    for (issuerCert of this._secInfo.succeededCertChain.getEnumerator());
+
+    return !issuerCert.isBuiltInRoot;
+  },
+
+  /**
    * Updates the identity block user interface with the data from this object.
    */
   refreshIdentityBlock() {
     if (!this._identityBox) {
       return;
     }
 
     let icon_label = "";
@@ -690,34 +707,39 @@ var gIdentityHandler = {
     }
 
     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
     this._identityPopupMixedContentLearnMore.forEach(
       e => e.setAttribute("href", baseURL + "mixed-content"));
     this._identityPopupInsecureLoginFormsLearnMore
         .setAttribute("href", baseURL + "insecure-password");
+    this._identityPopupCustomRootLearnMore
+        .setAttribute("href", baseURL + "enterprise-roots");
 
     // This is in the properties file because the expander used to switch its tooltip.
     this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
 
+    let customRoot = false;
+
     // Determine connection security information.
     let connection = "not-secure";
     if (this._isSecureInternalUI) {
       connection = "chrome";
     } else if (this._pageExtensionPolicy) {
       connection = "extension";
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
     } else if (this._isCertUserOverridden) {
       connection = "secure-cert-user-overridden";
     } else if (this._isSecure) {
       connection = "secure";
+      customRoot = this._hasCustomRoot();
     }
 
     // Determine if there are insecure login forms.
     let loginforms = "secure";
     if (this._hasInsecureLoginForms) {
       loginforms = "insecure";
     }
 
@@ -757,16 +779,17 @@ var gIdentityHandler = {
 
     for (let id of elementIDs) {
       let element = document.getElementById(id);
       updateAttribute(element, "connection", connection);
       updateAttribute(element, "loginforms", loginforms);
       updateAttribute(element, "ciphers", ciphers);
       updateAttribute(element, "mixedcontent", mixedcontent);
       updateAttribute(element, "isbroken", this._isBroken);
+      updateAttribute(element, "customroot", customRoot);
     }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
     let host = this.getHostForDisplay();
     let owner = "";
 
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -41,16 +41,17 @@ support-files =
 tags = mcb
 support-files =
   file_csp_block_all_mixedcontent.html
   file_csp_block_all_mixedcontent.js
 [browser_identity_UI.js]
 [browser_identityBlock_focus.js]
 support-files = ../permissions/permissions.html
 [browser_identityPopup_clearSiteData.js]
+[browser_identityPopup_custom_roots.js]
 [browser_identityPopup_focus.js]
 [browser_insecureLoginForms.js]
 support-files =
   insecure_opener.html
   !/toolkit/components/passwordmgr/test/browser/form_basic.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test.html
   !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
 [browser_mcb_redirect.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Test that the UI for imported root certificates shows up correctly in the identity popup.
+ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+
+// This test is incredibly simple, because our test framework already
+// imports root certificates by default, so we just visit example.com
+// and verify that the custom root certificates UI is visible.
+add_task(async function test_https() {
+  await BrowserTestUtils.withNewTab("https://example.com", async function() {
+    let promisePanelOpen = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    gIdentityHandler._identityBox.click();
+    await promisePanelOpen;
+    let customRootWarning = document.getElementById("identity-popup-security-decription-custom-root");
+    ok(BrowserTestUtils.is_visible(customRootWarning), "custom root warning is visible");
+
+    let securityView = document.getElementById("identity-popup-securityView");
+    let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
+    document.getElementById("identity-popup-security-expander").click();
+    await shown;
+
+    let subPanelInfo = document.getElementById("identity-popup-content-verifier-unknown");
+    ok(BrowserTestUtils.is_visible(subPanelInfo), "custom root warning in sub panel is visible");
+  });
+});
+
+// Also check that there are conditions where this isn't shown.
+add_task(async function test_http() {
+  await BrowserTestUtils.withNewTab("http://example.com", async function() {
+    let promisePanelOpen = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+    gIdentityHandler._identityBox.click();
+    await promisePanelOpen;
+    let customRootWarning = document.getElementById("identity-popup-security-decription-custom-root");
+    ok(BrowserTestUtils.is_hidden(customRootWarning), "custom root warning is hidden");
+
+    let securityView = document.getElementById("identity-popup-securityView");
+    let shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
+    document.getElementById("identity-popup-security-expander").click();
+    await shown;
+
+    let subPanelInfo = document.getElementById("identity-popup-content-verifier-unknown");
+    ok(BrowserTestUtils.is_hidden(subPanelInfo), "custom root warning in sub panel is hidden");
+  });
+});
--- a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -46,25 +46,29 @@ add_task(async function testSiteSecurity
     is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
 
     // 2. Access the Site Security section.
     let securityView = document.getElementById("identity-popup-securityView");
     shown = BrowserTestUtils.waitForEvent(securityView, "ViewShown");
     EventUtils.sendString(" ");
     await shown;
 
-    // 3. First press of tab should focus the Back button.
-    let backButton = gIdentityHandler._identityPopup.querySelector(".subviewbutton-back");
-    // Wait for focus to move somewhere. We use focusin because focus doesn't bubble.
+    // 3. Custom root learn more info should be focused by default
+    // This is probably not present in real-world scenarios, but needs to be present in our test infrastructure.
+    let customRootLearnMore = document.getElementById("identity-popup-custom-root-learn-more");
+    is(Services.focus.focusedElement, customRootLearnMore, "learn more option for custom roots is focused");
+
+    // 4. First press of tab should move to the More Information button.
+    let moreInfoButton = document.getElementById("identity-popup-more-info");
     let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "focusin");
     EventUtils.sendKey("tab");
     await focused;
-    is(Services.focus.focusedElement, backButton);
+    is(Services.focus.focusedElement, moreInfoButton, "more info button is focused");
 
-    // 4. Second press of tab should move to the More Information button.
-    let moreInfoButton = document.getElementById("identity-popup-more-info");
+    // 5. Second press of tab should focus the Back button.
+    let backButton = gIdentityHandler._identityPopup.querySelector(".subviewbutton-back");
+    // Wait for focus to move somewhere. We use focusin because focus doesn't bubble.
     focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "focusin");
     EventUtils.sendKey("tab");
     await focused;
-    isnot(Services.focus.focusedElement, backButton);
-    is(Services.focus.focusedElement, moreInfoButton);
+    is(Services.focus.focusedElement, backButton, "back button is focused");
   });
 });
--- a/browser/components/controlcenter/content/identityPanel.inc.xul
+++ b/browser/components/controlcenter/content/identityPanel.inc.xul
@@ -24,26 +24,29 @@
 
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox class="identity-popup-security-content" flex="1">
           <label class="plain">
             <label class="identity-popup-headline">&identity.connection;</label>
           </label>
           <description class="identity-popup-connection-not-secure"
-                       when-connection="not-secure secure-cert-user-overridden">&identity.connectionNotSecure;</description>
+                       when-connection="not-secure secure-cert-user-overridden secure-custom-root">&identity.connectionNotSecure;</description>
           <description class="identity-popup-connection-secure"
                        when-connection="secure secure-ev">&identity.connectionSecure;</description>
           <description when-connection="chrome">&identity.connectionInternal;</description>
           <description when-connection="file">&identity.connectionFile;</description>
           <description when-connection="extension">&identity.extensionPage;</description>
 
           <vbox id="identity-popup-security-descriptions">
             <description class="identity-popup-warning-gray"
                          when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
+           <description  id="identity-popup-security-decription-custom-root"
+                         class="identity-popup-warning-gray"
+                         when-customroot="true">&identity.customRoot;</description>
             <description class="identity-popup-warning-yellow"
                          when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
             <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
             <description class="identity-popup-warning-yellow"
                          when-ciphers="weak">&identity.weakEncryption;</description>
             <description when-loginforms="insecure">&identity.insecureLoginForms2;</description>
           </vbox>
         </vbox>
@@ -178,16 +181,19 @@
                      when-connection="secure-ev">&identity.connectionVerified2;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
                      when-connection="secure secure-ev secure-cert-user-overridden"/>
+        <description id="identity-popup-content-verifier-unknown"
+                     class="identity-popup-warning-gray"
+                     when-customroot="true">&identity.description.customRoot; <label id="identity-popup-custom-root-learn-more" is="text-link" class="plain" value="&identity.learnMore;"/></description>
 
         <!-- Remove Certificate Exception -->
         <button when-connection="secure-cert-user-overridden"
                 label="&identity.removeCertException.label;"
                 accesskey="&identity.removeCertException.accesskey;"
                 class="panel-button"
                 oncommand="gIdentityHandler.removeCertException()"/>
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -785,16 +785,17 @@ you can use these alternative items. Oth
 <!ENTITY identity.connection "Connection">
 <!ENTITY identity.connectionSecure "Secure Connection">
 <!ENTITY identity.connectionNotSecure "Connection Is Not Secure">
 <!ENTITY identity.connectionFile "This page is stored on your computer.">
 <!ENTITY identity.connectionVerified2 "You are securely connected to this site, owned by:">
 <!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
 <!ENTITY identity.extensionPage "This page is loaded from an extension.">
 <!ENTITY identity.insecureLoginForms2 "Logins entered on this page could be compromised.">
+<!ENTITY identity.customRoot "Connection verified by a certificate issuer that is not recognized by Mozilla.">
 
 <!-- Strings for connection state warnings. -->
 <!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
 <!ENTITY identity.activeLoaded "You have disabled protection on this page.">
 <!ENTITY identity.weakEncryption "This page uses weak encryption.">
 
 <!-- Strings for connection state warnings in the subview. -->
@@ -803,16 +804,17 @@ you can use these alternative items. Oth
 <!ENTITY identity.description.weakCipher "Your connection to this website uses weak encryption and is not private.">
 <!ENTITY identity.description.weakCipher2 "Other people can view your information or modify the website’s behavior.">
 <!ENTITY identity.description.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
 <!ENTITY identity.description.passiveLoaded "Your connection is not private and information you share with the site could be viewed by others.">
 <!ENTITY identity.description.passiveLoaded2 "This website contains content that is not secure (such as images).">
 <!ENTITY identity.description.passiveLoaded3 "Although &brandShortName; has blocked some content, there is still content on the page that is not secure (such as images).">
 <!ENTITY identity.description.activeLoaded "This website contains content that is not secure (such as scripts) and your connection to it is not private.">
 <!ENTITY identity.description.activeLoaded2 "Information you share with this site could be viewed by others (like passwords, messages, credit cards, etc.).">
+<!ENTITY identity.description.customRoot "Mozilla does not recognize this certificate issuer. It may have been added from your operating system or by an administrator.">
 
 <!ENTITY identity.enableMixedContentBlocking.label "Enable protection">
 <!ENTITY identity.enableMixedContentBlocking.accesskey "E">
 <!ENTITY identity.disableMixedContentBlocking.label "Disable protection for now">
 <!ENTITY identity.disableMixedContentBlocking.accesskey "D">
 <!ENTITY identity.learnMore "Learn More">
 
 <!ENTITY identity.removeCertException.label "Remove Exception">
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -1,16 +1,16 @@
 %if 0
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
 /* Hide all conditional elements by default. */
-:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
+:-moz-any([when-connection],[when-customroot],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
   display: none;
 }
 
 #identity-popup {
   --identity-popup-width: 33rem;
 }
 
 #protections-popup {
@@ -19,16 +19,17 @@
 
 /* This is used by screenshots tests to hide intermittently different
  * identity popup shadows (see bug 1425253). */
 #identity-popup.no-shadow {
   -moz-window-shadow: none;
 }
 
 /* Show the right elements for the right connection states. */
+#identity-popup[customroot=true] [when-customroot=true],
 #identity-popup[connection=not-secure] [when-connection~=not-secure],
 #identity-popup[connection=secure-cert-user-overridden] [when-connection~=secure-cert-user-overridden],
 #identity-popup[connection=secure-ev] [when-connection~=secure-ev],
 #identity-popup[connection=secure] [when-connection~=secure],
 #identity-popup[connection=chrome] [when-connection~=chrome],
 #identity-popup[connection=file] [when-connection~=file],
 #identity-popup[connection=extension] [when-connection~=extension],
 /* Show insecure login forms messages when needed. */