Bug 1305289 - Certificate Transparency - basic UI indicator; r=Dolske,keeler draft
authorSergei Chernov <sergei.cv@ndivi.com>
Wed, 28 Sep 2016 20:19:56 +0300
changeset 429058 2bfa8895246f862f3e49c0c9674a8e862137f8a6
parent 428736 c845bfd0accb7e0c29b41713255963b08006e701
child 534902 4932165b36f8b1d0b9dea02fec602da65d370001
push id33488
push usersergei.cv@ndivi.com
push dateTue, 25 Oct 2016 08:28:51 +0000
reviewersDolske, keeler
bugs1305289
milestone52.0a1
Bug 1305289 - Certificate Transparency - basic UI indicator; r=Dolske,keeler MozReview-Commit-ID: b0SUW2WNJT
browser/base/content/pageinfo/pageInfo.xul
browser/base/content/pageinfo/security.js
security/manager/locales/en-US/chrome/pippki/pippki.properties
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsISSLStatus.idl
security/manager/ssl/nsSSLStatus.cpp
security/manager/ssl/nsSSLStatus.h
--- a/browser/base/content/pageinfo/pageInfo.xul
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -412,16 +412,17 @@
       
       <!-- Technical Details section -->
       <groupbox id="security-technical-groupbox" flex="1">
         <caption id="security-technical" label="&securityView.technical.header;" />
         <vbox id="security-technical-box" 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>
       <hbox pack="end">
         <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
       </hbox>
     </vbox>
     <!-- Others added by overlay -->
   </deck>
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -60,17 +60,18 @@ var security = {
         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;
       }
@@ -90,28 +91,50 @@ var security = {
         case nsISSLStatus.TLS_VERSION_1_2:
           retval.version = "TLS 1.2"
           break;
         case nsISSLStatus.TLS_VERSION_1_3:
           retval.version = "TLS 1.3"
           break;
       }
 
+      // Select status text to display for Certificate Transparency.
+      switch (status.certificateTransparencyStatus) {
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+          // CT compliance checks were not performed,
+          // do not display any status text.
+          retval.certificateTransparency = null;
+          break;
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NONE:
+          retval.certificateTransparency = "None";
+          break;
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_OK:
+          retval.certificateTransparency = "OK";
+          break;
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG:
+          retval.certificateTransparency = "UnknownLog";
+          break;
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_INVALID:
+          retval.certificateTransparency = "Invalid";
+          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 (window.opener.gBrowser)
       return window.opener.gBrowser.securityUI;
     return null;
@@ -278,16 +301,26 @@ function securityOnLoad(uri, windowInfo)
       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);
+
+  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")
--- a/security/manager/locales/en-US/chrome/pippki/pippki.properties
+++ b/security/manager/locales/en-US/chrome/pippki/pippki.properties
@@ -104,16 +104,20 @@ pageInfo_Privacy_None4=The page you are 
 # %3$S is protocol version like "SSL 3" or "TLS 1.2"
 pageInfo_EncryptionWithBitsAndProtocol=Connection Encrypted (%1$S, %2$S bit keys, %3$S)
 pageInfo_BrokenEncryption=Broken Encryption (%1$S, %2$S bit keys, %3$S)
 pageInfo_Privacy_Encrypted1=The page you are viewing was encrypted before being transmitted over the Internet.
 pageInfo_Privacy_Encrypted2=Encryption makes it difficult for unauthorized people to view information traveling between computers. It is therefore unlikely that anyone read this page as it traveled across the network.
 pageInfo_MixedContent=Connection Partially Encrypted
 pageInfo_MixedContent2=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
 pageInfo_WeakCipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website’s behavior.
+pageInfo_CertificateTransparency_None=This website does not supply Certificate Transparency audit records.
+pageInfo_CertificateTransparency_OK=This website supplies publicly auditable Certificate Transparency records.
+pageInfo_CertificateTransparency_UnknownLog=This website claims to have Certificate Transparency audit records, but the records were issued by an unknown party and cannot be verified.
+pageInfo_CertificateTransparency_Invalid=This website supplies Certificate Transparency audit records, but the records failed verification.
 
 # Cert Viewer
 # LOCALIZATION NOTE(certViewerTitle): Title used for the Certificate Viewer.
 # %1$S is a string representative of the certificate being viewed.
 certViewerTitle=Certificate Viewer: “%1$S”
 notPresent=<Not Part Of Certificate>
 
 # Token Manager
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1412,16 +1412,18 @@ AuthCertificate(CertVerifier& certVerifi
       } else {
         evStatus = nsNSSCertificate::ev_status_valid;
       }
 
       status->SetServerCert(nsc, evStatus);
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
              ("AuthCertificate setting NEW cert %p\n", nsc.get()));
     }
+
+    status->SetCertificateTransparencyInfo(certificateTransparencyInfo);
   }
 
   if (rv != Success) {
     // Certificate validation failed; store the peer certificate chain on
     // infoObject so it can be used for error reporting.
     infoObject->SetFailedCertChain(Move(peerCertChain));
     PR_SetError(MapResultToPRErrorCode(rv), 0);
   }
--- a/security/manager/ssl/nsISSLStatus.idl
+++ b/security/manager/ssl/nsISSLStatus.idl
@@ -18,16 +18,23 @@ interface nsISSLStatus : nsISupports {
 
   const short SSL_VERSION_3   = 0;
   const short TLS_VERSION_1   = 1;
   const short TLS_VERSION_1_1 = 2;
   const short TLS_VERSION_1_2 = 3;
   const short TLS_VERSION_1_3 = 4;
   readonly attribute unsigned short protocolVersion;
 
+  const short CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE = 0;
+  const short CERTIFICATE_TRANSPARENCY_NONE           = 1;
+  const short CERTIFICATE_TRANSPARENCY_OK             = 2;
+  const short CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG    = 3;
+  const short CERTIFICATE_TRANSPARENCY_INVALID        = 4;
+  readonly attribute unsigned short certificateTransparencyStatus;
+
   readonly attribute boolean isDomainMismatch;
   readonly attribute boolean isNotValidAtThisTime;
 
   /* Note: To distinguish between
    *         "unstrusted because missing or untrusted issuer"
    *       and
    *         "untrusted because self signed"
    *       query nsIX509Cert::isSelfSigned
--- a/security/manager/ssl/nsSSLStatus.cpp
+++ b/security/manager/ssl/nsSSLStatus.cpp
@@ -1,19 +1,21 @@
 /* -*- Mode: C++; 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/. */
 
+#include "mozilla/Casting.h"
 #include "nsSSLStatus.h"
 #include "plstr.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObjectInputStream.h"
+#include "SignedCertificateTimestamp.h"
 #include "ssl.h"
 
 NS_IMETHODIMP
 nsSSLStatus::GetServerCert(nsIX509Cert** aServerCert)
 {
   NS_ENSURE_ARG_POINTER(aServerCert);
 
   nsCOMPtr<nsIX509Cert> cert = mServerCert;
@@ -82,16 +84,26 @@ nsSSLStatus::GetProtocolVersion(uint16_t
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   *aProtocolVersion = mProtocolVersion;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSSLStatus::GetCertificateTransparencyStatus(
+  uint16_t* aCertificateTransparencyStatus)
+{
+  NS_ENSURE_ARG_POINTER(aCertificateTransparencyStatus);
+
+  *aCertificateTransparencyStatus = mCertificateTransparencyStatus;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSSLStatus::GetIsDomainMismatch(bool* aIsDomainMismatch)
 {
   NS_ENSURE_ARG_POINTER(aIsDomainMismatch);
 
   *aIsDomainMismatch = mHaveCertErrorBits && mIsDomainMismatch;
   return NS_OK;
 }
 
@@ -145,18 +157,29 @@ nsSSLStatus::Read(nsIObjectInputStream* 
 
   mServerCert = do_QueryInterface(cert);
   if (!mServerCert) {
     return NS_NOINTERFACE;
   }
 
   rv = aStream->Read16(&mCipherSuite);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = aStream->Read16(&mProtocolVersion);
+
+  // The code below is a workaround to allow serializing new fields
+  // while preserving binary compatibility with older streams. For more details
+  // on the binary compatibility requirement, refer to bug 1248628.
+  // Here, we take advantage of the fact that mProtocolVersion was originally
+  // stored as a 16 bits integer, but the highest 8 bits were never used.
+  // These bits are now used for stream versioning.
+  uint16_t protocolVersionAndStreamFormatVersion;
+  rv = aStream->Read16(&protocolVersionAndStreamFormatVersion);
   NS_ENSURE_SUCCESS(rv, rv);
+  mProtocolVersion = protocolVersionAndStreamFormatVersion & 0xFF;
+  const uint8_t streamFormatVersion =
+    (protocolVersionAndStreamFormatVersion >> 8) & 0xFF;
 
   rv = aStream->ReadBoolean(&mIsDomainMismatch);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->ReadBoolean(&mIsNotValidAtThisTime);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->ReadBoolean(&mIsUntrusted);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->ReadBoolean(&mIsEV);
@@ -164,30 +187,43 @@ nsSSLStatus::Read(nsIObjectInputStream* 
 
   rv = aStream->ReadBoolean(&mHasIsEVStatus);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->ReadBoolean(&mHaveCipherSuiteAndProtocol);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->ReadBoolean(&mHaveCertErrorBits);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Added in version 1 (see bug 1305289).
+  if (streamFormatVersion >= 1) {
+    rv = aStream->Read16(&mCertificateTransparencyStatus);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSSLStatus::Write(nsIObjectOutputStream* aStream)
 {
+  // The current version of the binary stream format.
+  const uint8_t STREAM_FORMAT_VERSION = 1;
+
   nsresult rv = aStream->WriteCompoundObject(mServerCert,
                                              NS_GET_IID(nsIX509Cert),
                                              true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aStream->Write16(mCipherSuite);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = aStream->Write16(mProtocolVersion);
+
+  uint16_t protocolVersionAndStreamFormatVersion =
+    mozilla::AssertedCast<uint8_t>(mProtocolVersion) |
+    (STREAM_FORMAT_VERSION << 8);
+  rv = aStream->Write16(protocolVersionAndStreamFormatVersion);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aStream->WriteBoolean(mIsDomainMismatch);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->WriteBoolean(mIsNotValidAtThisTime);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->WriteBoolean(mIsUntrusted);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -196,16 +232,20 @@ nsSSLStatus::Write(nsIObjectOutputStream
 
   rv = aStream->WriteBoolean(mHasIsEVStatus);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->WriteBoolean(mHaveCipherSuiteAndProtocol);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aStream->WriteBoolean(mHaveCertErrorBits);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Added in version 1.
+  rv = aStream->Write16(mCertificateTransparencyStatus);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSSLStatus::GetInterfaces(uint32_t* aCount, nsIID*** aArray)
 {
   *aCount = 0;
   *aArray = nullptr;
@@ -257,16 +297,18 @@ nsSSLStatus::GetClassIDNoAlloc(nsCID* aC
 {
   *aClassIDNoAlloc = kSSLStatusCID;
   return NS_OK;
 }
 
 nsSSLStatus::nsSSLStatus()
 : mCipherSuite(0)
 , mProtocolVersion(0)
+, mCertificateTransparencyStatus(nsISSLStatus::
+    CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE)
 , mIsDomainMismatch(false)
 , mIsNotValidAtThisTime(false)
 , mIsUntrusted(false)
 , mIsEV(false)
 , mHasIsEVStatus(false)
 , mHaveCipherSuiteAndProtocol(false)
 , mHaveCertErrorBits(false)
 {
@@ -295,8 +337,60 @@ nsSSLStatus::SetServerCert(nsNSSCertific
     nsresult rv = aServerCert->GetIsExtendedValidation(&mIsEV);
     if (NS_FAILED(rv)) {
       return;
     }
     mHasIsEVStatus = true;
   }
 #endif
 }
+
+void
+nsSSLStatus::SetCertificateTransparencyInfo(
+  const mozilla::psm::CertificateTransparencyInfo& info)
+{
+  using mozilla::ct::SignedCertificateTimestamp;
+
+  if (!info.enabled) {
+    // CT disabled.
+    mCertificateTransparencyStatus =
+      nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
+    return;
+  }
+
+  if (!info.processedSCTs) {
+    // No SCTs processed on the connection.
+    mCertificateTransparencyStatus =
+      nsISSLStatus::CERTIFICATE_TRANSPARENCY_NONE;
+    return;
+  }
+
+  bool hasOKSCTs = false;
+  bool hasUnknownLogSCTs = false;
+  bool hasInvalidSCTs = false;
+  for (const SignedCertificateTimestamp& sct : info.verifyResult.scts) {
+    switch (sct.verificationStatus) {
+      case SignedCertificateTimestamp::VerificationStatus::OK:
+        hasOKSCTs = true;
+        break;
+      case SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+        hasUnknownLogSCTs = true;
+        break;
+      case SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+      case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+        hasInvalidSCTs = true;
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unexpected SCT::VerificationStatus type");
+    }
+  }
+
+  if (hasOKSCTs) {
+    mCertificateTransparencyStatus =
+      nsISSLStatus::CERTIFICATE_TRANSPARENCY_OK;
+  } else if (hasUnknownLogSCTs) {
+    mCertificateTransparencyStatus =
+      nsISSLStatus::CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG;
+  } else if (hasInvalidSCTs) {
+    mCertificateTransparencyStatus =
+      nsISSLStatus::CERTIFICATE_TRANSPARENCY_INVALID;
+  }
+}
--- a/security/manager/ssl/nsSSLStatus.h
+++ b/security/manager/ssl/nsSSLStatus.h
@@ -2,16 +2,17 @@
  *
  * 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/. */
 
 #ifndef _NSSSLSTATUS_H
 #define _NSSSLSTATUS_H
 
+#include "CertVerifier.h" // For CertificateTransparencyInfo
 #include "nsISSLStatus.h"
 #include "nsCOMPtr.h"
 #include "nsXPIDLString.h"
 #include "nsIX509Cert.h"
 #include "nsISerializable.h"
 #include "nsIClassInfo.h"
 #include "nsNSSCertificate.h" // For EVStatus
 
@@ -32,19 +33,23 @@ public:
 
   void SetServerCert(nsNSSCertificate* aServerCert,
                      nsNSSCertificate::EVStatus aEVStatus);
 
   bool HasServerCert() {
     return mServerCert != nullptr;
   }
 
+  void SetCertificateTransparencyInfo(
+    const mozilla::psm::CertificateTransparencyInfo& info);
+
   /* public for initilization in this file */
   uint16_t mCipherSuite;
   uint16_t mProtocolVersion;
+  uint16_t mCertificateTransparencyStatus;
 
   bool mIsDomainMismatch;
   bool mIsNotValidAtThisTime;
   bool mIsUntrusted;
   bool mIsEV;
 
   bool mHasIsEVStatus;
   bool mHaveCipherSuiteAndProtocol;