Bug 1576150 - Update pageInfo's security tab. r=frg a=frg
authorIan Neal <iann_cvs@blueyonder.co.uk>
Sat, 28 Sep 2019 18:13:22 +0200
changeset 32313 0def45bb7f5de5ae0262c11f228d93bd79d9424a
parent 32312 9c857eef5a5b1ce89a6da3442b7fffd369879168
child 32314 3f35f2caac8c39b0cf3b348660b9f4c8356fd642
push id219
push userfrgrahl@gmx.net
push dateSat, 28 Sep 2019 16:19:15 +0000
treeherdercomm-esr60@5c0d3f86a9a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrg, frg
bugs1576150
Bug 1576150 - Update pageInfo's security tab. r=frg a=frg
suite/browser/pageinfo/pageInfo.xul
suite/browser/pageinfo/security.js
suite/locales/en-US/chrome/browser/pageInfo.dtd
suite/locales/en-US/chrome/browser/pageInfo.properties
suite/themes/classic/mac/navigator/pageInfo.css
suite/themes/classic/navigator/pageInfo.css
suite/themes/modern/navigator/pageInfo.css
--- a/suite/browser/pageinfo/pageInfo.xul
+++ b/suite/browser/pageinfo/pageInfo.xul
@@ -28,16 +28,17 @@
   <script type="application/javascript" src="chrome://navigator/content/pageinfo/feeds.js"/>
   <script type="application/javascript" src="chrome://navigator/content/pageinfo/permissions.js"/>
   <script type="application/javascript" src="chrome://navigator/content/pageinfo/security.js"/>
   <script type="application/javascript" src="chrome://help/content/contextHelp.js"/>
   <script type="application/javascript" src="chrome://communicator/content/tasksOverlay.js"/>
 
   <stringbundleset id="pageinfobundleset">
     <stringbundle id="pageinfobundle" src="chrome://navigator/locale/pageInfo.properties"/>
+    <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
   </stringbundleset>
 
   <commandset id="pageInfoCommandSet">
     <command id="cmd_close"     oncommand="window.close();"/>
     <command id="cmd_help"      oncommand="doHelpButton();"/>
     <command id="cmd_copy"      oncommand="doCopy(false);"/>
     <command id="cmd_selectall" oncommand="doSelectAll();"/>
 
@@ -429,16 +430,25 @@
                 <row><!-- Verifier -->
                   <label control="security-identity-verifier-value"
                         id="security-identity-verifier-label"
                         class="fieldLabel"
                         value="&securityView.identity.verifier;"/>
                   <textbox id="security-identity-verifier-value"
                           class="fieldValue" readonly="true"/>
                 </row>
+                <!-- Certificate Validity -->
+                <row id="security-identity-validity-row">
+                  <label id="security-identity-validity-label"
+                         class="fieldLabel"
+                         value="&securityView.identity.validity;"
+                         control="security-identity-validity-value"/>
+                  <textbox id="security-identity-validity-value"
+                           class="fieldValue" readonly="true" />
+                </row>
               </rows>
             </grid>
           </hbox>
           <spacer flex="1"/>
           <hbox pack="end"><!-- Cert button -->
             <button id="security-view-cert" label="&securityView.certView;"
                     accesskey="&securityView.accesskey;"
                     oncommand="security.viewCert();"/>
@@ -501,16 +511,17 @@
 
         <!-- Technical Details section -->
         <groupbox id="security-technical-groupbox" flex="1">
           <caption id="security-technical" label="&securityView.technical.header;" />
           <vbox flex="1">
             <label id="security-technical-shortform" class="fieldValue"/>
             <description id="security-technical-longform1" class="fieldLabel"/>
             <description id="security-technical-longform2" class="fieldLabel"/>
+            <description id="security-technical-certificate-transparency" class="fieldLabel"/>
           </vbox>
         </groupbox>
       </vbox>
 
       <!-- Others added by overlay -->
     </tabpanels>
   </tabbox>
 </window>
--- a/suite/browser/pageinfo/security.js
+++ b/suite/browser/pageinfo/security.js
@@ -1,83 +1,130 @@
 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+                                  "resource://gre/modules/PluralForm.jsm");
+
 var security = {
   init: function(uri, windowInfo) {
     this.uri = uri;
     this.windowInfo = windowInfo;
   },
 
   // Display the server certificate (static)
-  viewCert : function () {
+  viewCert : function() {
     var cert = security._cert;
     viewCertHelper(window, cert);
   },
 
   _getSecurityInfo : function() {
     // We don't have separate info for a frame, return null until further notice
     // (see bug 138479)
     if (!this.windowInfo.isTopWindow)
       return null;
 
     var hostName = this.windowInfo.hostName;
 
     var ui = security._getSecurityUI();
     if (!ui)
       return null;
 
-    var isBroken = ui &&
+    var isBroken =
       (ui.state & Ci.nsIWebProgressListener.STATE_IS_BROKEN);
-    var isInsecure = ui &&
+    var isMixed =
+      (ui.state & (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+                   Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+    var isInsecure =
       (ui.state & Ci.nsIWebProgressListener.STATE_IS_INSECURE);
-    var isEV = ui &&
+    var isEV =
       (ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
-    var status = ui ? ui.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus : null;
+    var status = ui.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
 
     if (!isInsecure && status) {
       status.QueryInterface(Ci.nsISSLStatus);
       var cert = status.serverCert;
       var issuerName = cert.issuerOrganization || cert.issuerName;
 
       var retval = {
         hostName : hostName,
         cAName : issuerName,
         encryptionAlgorithm : undefined,
         encryptionStrength : undefined,
+        version: undefined,
         isBroken : isBroken,
+        isMixed : isMixed,
         isEV : isEV,
-        cert : cert
+        cert : cert,
+        certificateTransparency : undefined,
       };
 
+      var version;
       try {
         retval.encryptionAlgorithm = status.cipherName;
         retval.encryptionStrength = status.secretKeyLength;
+        version = status.protocolVersion;
       }
       catch (e) {
       }
 
+      switch (version) {
+        case Ci.nsISSLStatus.SSL_VERSION_3:
+          retval.version = "SSL 3";
+          break;
+        case Ci.nsISSLStatus.TLS_VERSION_1:
+          retval.version = "TLS 1.0";
+          break;
+        case Ci.nsISSLStatus.TLS_VERSION_1_1:
+          retval.version = "TLS 1.1";
+          break;
+        case Ci.nsISSLStatus.TLS_VERSION_1_2:
+          retval.version = "TLS 1.2";
+          break;
+        case Ci.nsISSLStatus.TLS_VERSION_1_3:
+          retval.version = "TLS 1.3";
+          break;
+      }
+
+      // Select the status text to display for Certificate Transparency.
+      // Since we do not yet enforce the CT Policy on secure connections,
+      // we must not complain on policy discompliance (it might be viewed
+      // as a security issue by the user).
+      switch (status.certificateTransparencyStatus) {
+        case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+        case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
+        case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
+          retval.certificateTransparency = null;
+          break;
+        case Ci.nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
+          retval.certificateTransparency = "Compliant";
+          break;
+      }
+
       return retval;
     }
     return {
       hostName : hostName,
       cAName : "",
       encryptionAlgorithm : "",
       encryptionStrength : 0,
+      version: "",
       isBroken : isBroken,
+      isMixed : isMixed,
       isEV : isEV,
-      cert : null
+      cert : null,
+      certificateTransparency : null,
     };
   },
 
   // Find the secureBrowserUI object (if present)
   _getSecurityUI : function() {
-    if ("gBrowser" in window.opener)
+    if (window.opener.gBrowser)
       return window.opener.gBrowser.securityUI;
     return null;
   },
 
   /**
    * Open the cookie manager window
    */
   viewCookies : function()
@@ -103,61 +150,76 @@ var security = {
 
   _cert : null
 };
 
 function securityOnLoad(uri, windowInfo) {
   security.init(uri, windowInfo);
 
   var info = security._getSecurityInfo();
-  if (!info)
+  if (!info || uri.scheme === "about") {
+    document.getElementById("securityTab").hidden = true;
+    document.getElementById("securityBox").hidden = true;
     return;
+  }
+
+  document.getElementById("securityTab").hidden = false;
+  document.getElementById("securityBox").hidden = false;
 
   const pageInfoBundle = document.getElementById("pageinfobundle");
 
   /* Set Identity section text */
   setText("security-identity-domain-value", info.hostName);
 
-  var owner, verifier, generalPageIdentityString, identityClass;
+  var owner;
+  var verifier;
+  var generalPageIdentityString;
+  var identityClass;
+  var validity;
   if (info.cert && !info.isBroken) {
+    validity = info.cert.validity.notAfterLocalDay;
+
     // Try to pull out meaningful values.  Technically these fields are optional
     // so we'll employ fallbacks where appropriate.  The EV spec states that Org
     // fields must be specified for subject and issuer so that case is simpler.
     if (info.isEV) {
       owner = info.cert.organization;
       verifier = info.cAName;
       generalPageIdentityString =
         pageInfoBundle.getFormattedString("generalSiteIdentity",
                                           [owner, verifier]);
       identityClass = "verifiedIdentity";
-    }
-    else {
-      // Technically, a non-EV cert might specify an owner in the O field or not,
-      // depending on the CA's issuing policies.  However we don't have any programmatic
-      // way to tell those apart, and no policy way to establish which organization
-      // vetting standards are good enough (that's what EV is for) so we default to
-      // treating these certs as domain-validated only.
+    } else {
+      // Technically, a non-EV cert might specify an owner in the O field or
+      // not, depending on the CA's issuing policies.  However we don't have any
+      // programmatic way to tell those apart, and no policy way to establish
+      // which organization vetting standards are good enough (that's what EV is
+      // for) so we default to treating these certs as domain-validated only.
       owner = pageInfoBundle.getString("securityNoOwner");
       verifier = info.cAName || info.cert.issuerCommonName || info.cert.issuerName;
       generalPageIdentityString = owner;
       identityClass = "verifiedDomain";
     }
-  }
-  else {
+  } else {
     // We don't have valid identity credentials.
     owner = pageInfoBundle.getString("securityNoOwner");
     verifier = pageInfoBundle.getString("notSet");
     generalPageIdentityString = owner;
     identityClass = "";
   }
 
   setText("security-identity-owner-value", owner);
   setText("security-identity-verifier-value", verifier);
   setText("general-security-identity", generalPageIdentityString);
   document.getElementById("identity-icon").className = identityClass;
+  if (validity) {
+    setText("security-identity-validity-value", validity);
+  } else {
+    document.getElementById("security-identity-validity-row").hidden = true;
+  }
 
   /* Manage the View Cert button*/
   if (info.cert)
     security._cert = info.cert;
   document.getElementById("security-view-cert").collapsed = !info.cert;
 
   /* Set Privacy & History section text */
   var yesStr = pageInfoBundle.getString("yes");
@@ -166,57 +228,71 @@ function securityOnLoad(uri, windowInfo)
   var hasCookies = hostHasCookies(uri);
   setText("security-privacy-cookies-value", hasCookies ? yesStr : noStr);
   document.getElementById("security-view-cookies").disabled = !hasCookies;
   var hasPasswords = realmHasPasswords(uri);
   setText("security-privacy-passwords-value", hasPasswords ? yesStr : noStr);
   document.getElementById("security-view-password").disabled = !hasPasswords;
 
   var visitCount = previousVisitCount(info.hostName);
-  if(visitCount > 1) {
-    setText("security-privacy-history-value",
-            pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
-  }
-  else if (visitCount == 1) {
-    setText("security-privacy-history-value",
-            pageInfoBundle.getString("securityOneVisit"));
-  }
-  else {
-    setText("security-privacy-history-value", noStr);
-  }
+  let visitCountStr = visitCount > 0
+    ? PluralForm.get(visitCount, pageInfoBundle.getString("securityVisitsNumber"))
+        .replace("#1", visitCount.toLocaleString())
+    : pageInfoBundle.getString("securityNoVisits");
+  setText("security-privacy-history-value", visitCountStr);
 
   /* Set the Technical Detail section messages */
+  const pkiBundle = document.getElementById("pkiBundle");
   var hdr;
   var msg1;
   var msg2;
 
   if (info.isBroken) {
-    hdr = pageInfoBundle.getString("securityMixedContent");
-    msg1 = pageInfoBundle.getString("securityMixed1");
-    msg2 = pageInfoBundle.getString("securityNone2");
-  }
-  else if (info.encryptionStrength) {
-    hdr = pageInfoBundle.getFormattedString("securityEncryptionWithBits",
-                         [info.encryptionAlgorithm, info.encryptionStrength]);
-    msg1 = pageInfoBundle.getString("securityEncryption1");
-    msg2 = pageInfoBundle.getString("securityEncryption2");
+    if (info.isMixed) {
+      hdr = pkiBundle.getString("pageInfo_MixedContent");
+      msg1 = pkiBundle.getString("pageInfo_MixedContent2");
+    } else {
+      hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption",
+                                         [info.encryptionAlgorithm,
+                                          info.encryptionStrength + "",
+                                          info.version]);
+      msg1 = pkiBundle.getString("pageInfo_WeakCipher");
+    }
+    msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+  } else if (info.encryptionStrength > 0) {
+    hdr = pkiBundle.getFormattedString("pageInfo_EncryptionWithBitsAndProtocol",
+                                       [info.encryptionAlgorithm,
+                                        info.encryptionStrength + "",
+                                        info.version]);
+    msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+    msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
     security._cert = info.cert;
-  }
-  else {
-    hdr = pageInfoBundle.getString("securityNoEncryption");
-    if (info.hostName)
-      msg1 = pageInfoBundle.getFormattedString("securityNone1", [info.hostName]);
-    else
-      msg1 = pageInfoBundle.getString("securityNone3");
-    msg2 = pageInfoBundle.getString("securityNone2");
+  } else {
+    hdr = pkiBundle.getString("pageInfo_NoEncryption");
+    if (info.hostName != null) {
+      msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.hostName]);
+    } else {
+      msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+    }
+    msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
   }
   setText("security-technical-shortform", hdr);
   setText("security-technical-longform1", msg1);
   setText("security-technical-longform2", msg2);
   setText("general-security-privacy", hdr);
+
+  const ctStatus =
+    document.getElementById("security-technical-certificate-transparency");
+  if (info.certificateTransparency) {
+    ctStatus.hidden = false;
+    ctStatus.value = pkiBundle.getString(
+      "pageInfo_CertificateTransparency_" + info.certificateTransparency);
+  } else {
+    ctStatus.hidden = true;
+  }
 }
 
 function setText(id, value)
 {
   var element = document.getElementById(id);
   if (!element)
     return;
   if (element.localName == "textbox" || element.localName == "label")
@@ -233,26 +309,17 @@ function viewCertHelper(parent, cert)
   var cd = Cc[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
   cd.viewCert(parent, cert);
 }
 
 /**
  * Return true iff we have cookies for uri.
  */
 function hostHasCookies(aUri) {
-  var hostName;
-  try {
-    hostName = aUri.asciiHost;
-  }
-  catch (e) {
-  }
-  if (!hostName)
-    return false;
-
-  return Services.cookies.countCookiesFromHost(hostName) > 0;
+  return Services.cookies.countCookiesFromHost(aUri.asciiHost) > 0;
 }
 
 /**
  * Return true iff realm (proto://host:port) (extracted from uri) has
  * saved passwords
  */
 function realmHasPasswords(aUri) {
   return Services.logins.countLogins(aUri.prePath, "", "") > 0;
--- a/suite/locales/en-US/chrome/browser/pageInfo.dtd
+++ b/suite/locales/en-US/chrome/browser/pageInfo.dtd
@@ -91,16 +91,17 @@
 <!ENTITY  securityView.accesskey "V">
 <!ENTITY  securityView.unknown   "Unknown">
 
 
 <!ENTITY  securityView.identity.header   "Website Identity">
 <!ENTITY  securityView.identity.owner    "Owner:">
 <!ENTITY  securityView.identity.domain   "Website:">
 <!ENTITY  securityView.identity.verifier "Verified by:">
+<!ENTITY  securityView.identity.validity "Expires on:">
 
 <!ENTITY  securityView.privacy.header                   "Privacy &amp; History">
 <!ENTITY  securityView.privacy.history                  "Have I visited this website before today?">
 <!ENTITY  securityView.privacy.cookies                  "Is this website storing information (cookies) on my computer?">
 <!ENTITY  securityView.privacy.viewCookies              "View Cookies">
 <!ENTITY  securityView.privacy.viewCookies.accessKey    "k">
 <!ENTITY  securityView.privacy.passwords                "Have I saved any passwords for this website?">
 <!ENTITY  securityView.privacy.viewPasswords            "View Saved Passwords">
--- a/suite/locales/en-US/chrome/browser/pageInfo.properties
+++ b/suite/locales/en-US/chrome/browser/pageInfo.properties
@@ -62,23 +62,17 @@ linkX=Simple XLink
 linkScript=Script
 linkScriptInline=inline
 
 feedRss=RSS
 feedAtom=Atom
 feedXML=XML
 
 securityNoOwner=This website does not supply ownership information.
-securityOneVisit=Yes, once
-securityNVisits=Yes, %S times
-securityNoEncryption=Connection Not Encrypted
-securityNone1=The website %S does not support encryption for the page you are viewing.
-securityNone2=Information sent over the Internet without encryption can be seen by other people while it is in transit.
-securityNone3=The page you are viewing is not encrypted.
-# LOCALIZATION NOTE (securityEncryptionWithBits): %1$S is the name of the encryption standard,
-# %2$S is the key size of the cipher.
-securityEncryptionWithBits=Connection Encrypted (%1$S, %2$S bit keys)
-securityEncryption1=The page you are viewing was encrypted before being transmitted over the Internet.
-securityEncryption2=Encryption makes it very difficult for unauthorized people to view information traveling between computers. It is therefore very unlikely that anyone read this page as it traveled across the Internet.
-securityMixedContent=Connection Partially Encrypted
-securityMixed1=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
+# LOCALIZATION NOTE (securityVisitsNumber):
+# Semi-colon list of plural forms.
+# See: https://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of visits and can be used in all plural forms as needed, e.g.
+# for '1': 'Yes, #1 time'
+securityVisitsNumber=Yes, once;Yes, #1 times
+securityNoVisits=No
 
 permissions.useDefault=Use Default
--- a/suite/themes/classic/mac/navigator/pageInfo.css
+++ b/suite/themes/classic/mac/navigator/pageInfo.css
@@ -164,16 +164,17 @@ treechildren::-moz-tree-cell-text(broken
 
 .fieldValue {
   font-weight: bold;
 }
 
 #identity-icon {
   width: 64px;
   height: 64px;
+  max-height: 64px;
   list-style-image: url("chrome://navigator/skin/icons/identity.png");
   -moz-image-region: rect(0px, 64px, 64px, 0px);
 }
 
 #identity-icon.verifiedDomain {
   -moz-image-region: rect(64px, 64px, 128px, 0px);
 }
 
--- a/suite/themes/classic/navigator/pageInfo.css
+++ b/suite/themes/classic/navigator/pageInfo.css
@@ -107,16 +107,17 @@ treechildren::-moz-tree-cell-text(broken
 /* Security Tab */
 .fieldValue {
   font-weight: bold;
 }
 
 #identity-icon {
   width: 64px;
   height: 64px;
+  max-height: 64px;
   list-style-image: url("chrome://navigator/skin/icons/identity.png");
   -moz-image-region: rect(0px, 64px, 64px, 0px);
 }
 
 #identity-icon.verifiedDomain {
   -moz-image-region: rect(64px, 64px, 128px, 0px);
 }
 
--- a/suite/themes/modern/navigator/pageInfo.css
+++ b/suite/themes/modern/navigator/pageInfo.css
@@ -103,16 +103,17 @@ treechildren::-moz-tree-cell-text(broken
 /* Security Tab */
 .fieldValue {
   font-weight: bold;
 }
 
 #identity-icon {
   width: 64px;
   height: 64px;
+  max-height: 64px;
   list-style-image: url("chrome://navigator/skin/icons/identity.png");
   -moz-image-region: rect(0px, 64px, 64px, 0px);
 }
 
 #identity-icon.verifiedDomain {
   -moz-image-region: rect(64px, 64px, 128px, 0px);
 }