Bug 1434300 - Implement the Symantec distrust plan from Bug 1409257 r?keeler r?fkiefer draft
authorJ.C. Jones <jjones@mozilla.com>
Wed, 21 Feb 2018 14:08:18 -0500
changeset 758020 f6c9341fde050d7079a8934636644aaf54bde922
parent 758019 76dd581c3642c106c8a2826c1414ecf797579560
child 758021 c1e4910567cf84ca810e0cef63dd5cf1e0c9dd79
push id99916
push userbmo:jjones@mozilla.com
push dateWed, 21 Feb 2018 19:10:27 +0000
reviewerskeeler, fkiefer
bugs1434300, 1409257, 1409259
milestone60.0a1
Bug 1434300 - Implement the Symantec distrust plan from Bug 1409257 r?keeler r?fkiefer The algorithm from https://hg.mozilla.org/mozilla-central/rev/595e27212723 (Bug 1409259) is adapted in this patch from nsNSSCallbacks into the TrustDomain decisions. This patch does not change the algorithm to use SPKI matching, nor add the additional whitelisted intermediates from DigiCert; that will be done in a separate commit. This patch also does not update the pre-existing browser chrome test. MozReview-Commit-ID: 1PdCAqo71bI
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/TrustOverrideUtils.h
security/manager/ssl/nsNSSCallbacks.cpp
security/manager/ssl/nsNSSCertValidity.h
security/manager/ssl/tests/unit/test_symantec_apple_google.js
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -18,29 +18,32 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsCRTGlue.h"
 #include "nsNSSCertificate.h"
+#include "nsNSSCertValidity.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nss.h"
 #include "pk11pub.h"
 #include "pkix/Result.h"
 #include "pkix/pkix.h"
 #include "pkix/pkixnss.h"
 #include "prerror.h"
 #include "secerr.h"
 
 #include "TrustOverrideUtils.h"
 #include "TrustOverride-StartComAndWoSignData.inc"
 #include "TrustOverride-GlobalSignData.inc"
+#include "TrustOverride-SymantecData.inc"
+#include "TrustOverride-AppleGoogleData.inc"
 
 using namespace mozilla;
 using namespace mozilla::pkix;
 
 extern LazyLogModule gCertVerifierLog;
 
 static const uint64_t ServerFailureDelaySeconds = 5 * 60;
 
@@ -848,36 +851,73 @@ NSSCertDBTrustDomain::IsChainValid(const
       // This chain is supposed to be complete, so this is an error. There
       // are no intermediates, so return before searching just as if the
       // search failed.
       return Result::ERROR_POLICY_VALIDATION_FAILED;
     }
 
     bool foundRequiredIntermediate = false;
     RefPtr<nsNSSCertList> intCertList = intCerts->GetCertList();
-    intCertList->ForEachCertificateInChain(
+    nsrv = intCertList->ForEachCertificateInChain(
       [&foundRequiredIntermediate] (nsCOMPtr<nsIX509Cert> aCert, bool aHasMore,
                                     /* out */ bool& aContinue) {
         // We need an owning handle when calling nsIX509Cert::GetCert().
         UniqueCERTCertificate nssCert(aCert->GetCert());
         if (CertMatchesStaticData(
             nssCert.get(),
             sGlobalSignExtendedValidationCASHA256G2SubjectBytes,
             sGlobalSignExtendedValidationCASHA256G2SPKIBytes)) {
           foundRequiredIntermediate = true;
           aContinue = false;
         }
         return NS_OK;
     });
 
+    if (NS_FAILED(nsrv)) {
+      return Result::FATAL_ERROR_LIBRARY_FAILURE;
+    }
+
     if (!foundRequiredIntermediate) {
       return Result::ERROR_POLICY_VALIDATION_FAILED;
     }
   }
 
+  // See bug 1434300. If the root is a Symantec root, see if we distrust this
+  // path. Since we already have the root available, we can check that cheaply
+  // here before proceeding with the rest of the algorithm.
+
+  // This algorithm only applies if we are verifying in the context of a TLS
+  // handshake. To determine this, we check mHostname: If it isn't set, this is
+  // not TLS, so don't run the algorithm.
+  if (mHostname && CertDNIsInList(root.get(), RootSymantecDNs)) {
+    rootCert = nullptr; // Clear the state for Segment...
+    nsCOMPtr<nsIX509CertList> intCerts;
+    nsCOMPtr<nsIX509Cert> eeCert;
+
+    nsrv = nssCertList->SegmentCertificateChain(rootCert, intCerts, eeCert);
+    if (NS_FAILED(nsrv)) {
+      // This chain is supposed to be complete, so this is an error.
+      return Result::FATAL_ERROR_LIBRARY_FAILURE;
+    }
+
+    // PRTime is microseconds since the epoch, whereas JS time is milliseconds.
+    // (new Date("2016-06-01T00:00:00Z")).getTime() * 1000
+    static const PRTime JUNE_1_2016 = 1464739200000000;
+
+    bool isDistrusted = false;
+    nsrv = CheckForSymantecDistrust(intCerts, eeCert, JUNE_1_2016,
+                                    RootAppleAndGoogleDNs, isDistrusted);
+    if (NS_FAILED(nsrv)) {
+      return Result::FATAL_ERROR_LIBRARY_FAILURE;
+    }
+    if (isDistrusted) {
+      return Result::ERROR_UNKNOWN_ISSUER;
+    }
+  }
+
   mBuiltChain = Move(certList);
 
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm aAlg,
                                                     EndEntityOrCA endEntityOrCA,
--- a/security/certverifier/TrustOverrideUtils.h
+++ b/security/certverifier/TrustOverrideUtils.h
@@ -2,18 +2,22 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 TrustOverrides_h
 #define TrustOverrides_h
 
+#include "nsNSSCertificate.h"
+#include "nsNSSCertValidity.h"
 #include "mozilla/PodOperations.h"
 
+using namespace mozilla;
+
 struct DataAndLength {
   const uint8_t* data;
   uint32_t len;
 };
 
 template<size_t T>
 static bool
 CertDNIsInList(const CERTCertificate* aCert, const DataAndLength (&aDnList)[T])
@@ -42,9 +46,75 @@ CertMatchesStaticData(const CERTCertific
     return false;
   }
   return cert->derSubject.len == T &&
          mozilla::PodEqual(cert->derSubject.data, subject, T) &&
          cert->derPublicKey.len == R &&
          mozilla::PodEqual(cert->derPublicKey.data, spki, R);
 }
 
+// Implements the graduated Symantec distrust algorithm from Bug 1409257.
+// This accepts a pre-segmented certificate chain (e.g. SegmentCertificateChain)
+// as |intCerts| and |eeCert|, and pre-assumes that the root has been identified
+// as being affected (this is to avoid duplicate Segment operations in the
+// NSSCertDBTrustDomain). If |permitAfterDate| is non-zero, this algorithm
+// returns "not distrusted" if the NotBefore date of |eeCert| is after
+// the |permitAfterDate|. Then each of the |intCerts| is evaluated against a
+// |whitelist| of SPKI entries, and if a match is found, then this returns
+// "not distrusted." Otherwise, due to the precondition holding, the chain is
+// "distrusted."
+template<size_t T>
+static nsresult
+CheckForSymantecDistrust(const nsCOMPtr<nsIX509CertList>& intCerts,
+                         const nsCOMPtr<nsIX509Cert>& eeCert,
+                         const PRTime& permitAfterDate,
+                         const DataAndLength (&whitelist)[T],
+                         /* out */ bool& isDistrusted)
+{
+    // PRECONDITION: The rootCert is already verified as being one of the
+    // affected Symantec roots
+
+    // Check the preference to see if this is enabled before proceeding.
+    // TODO in Bug 1437754
+
+    isDistrusted = true;
+
+    // Only check the validity period if we're asked
+    if (permitAfterDate > 0) {
+      // We need to verify the age of the end entity
+      nsCOMPtr<nsIX509CertValidity> validity;
+      nsresult rv = eeCert->GetValidity(getter_AddRefs(validity));
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      PRTime notBefore;
+      rv = validity->GetNotBefore(&notBefore);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+
+      // If the end entity's notBefore date is after the permitAfter date, this
+      // algorithm doesn't apply, so exit false before we do any iterating.
+      if (notBefore >= permitAfterDate) {
+        isDistrusted = false;
+        return NS_OK;
+      }
+    }
+
+    // Look for one of the intermediates to be in the whitelist
+    RefPtr<nsNSSCertList> intCertList = intCerts->GetCertList();
+
+    return intCertList->ForEachCertificateInChain(
+      [&isDistrusted, &whitelist] (nsCOMPtr<nsIX509Cert> aCert, bool aHasMore,
+                                   /* out */ bool& aContinue) {
+        // We need an owning handle when calling nsIX509Cert::GetCert().
+        UniqueCERTCertificate nssCert(aCert->GetCert());
+        if (CertDNIsInList(nssCert.get(), whitelist)) {
+          // In the whitelist
+          isDistrusted = false;
+          aContinue = false;
+        }
+        return NS_OK;
+    });
+}
+
 #endif // TrustOverrides_h
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1235,17 +1235,17 @@ DetermineEVAndCTStatusAndSetNewCert(RefP
   if (rv == Success) {
     sslStatus->SetCertificateTransparencyInfo(certificateTransparencyInfo);
     sslStatus->SetSucceededCertChain(Move(builtChain));
   }
 }
 
 static nsresult
 IsCertificateDistrustImminent(nsIX509CertList* aCertList,
-                              /* out */ bool& aResult) {
+                              /* out */ bool& isDistrusted) {
   if (!aCertList) {
     return NS_ERROR_INVALID_POINTER;
   }
 
   nsCOMPtr<nsIX509Cert> rootCert;
   nsCOMPtr<nsIX509CertList> intCerts;
   nsCOMPtr<nsIX509Cert> eeCert;
 
@@ -1259,52 +1259,40 @@ IsCertificateDistrustImminent(nsIX509Cer
   // that gets the 'imminent distrust' treatment; this is so that the distrust
   // UX code does not become stale, as it will need regular use. See Bug 1409257
   // for context. Please do not remove this when adjusting the rest of the
   // method.
   UniqueCERTCertificate nssEECert(eeCert->GetCert());
   if (!nssEECert) {
     return NS_ERROR_FAILURE;
   }
-  aResult = CertDNIsInList(nssEECert.get(), TestImminentDistrustEndEntityDNs);
-  if (aResult) {
+  isDistrusted = CertDNIsInList(nssEECert.get(),
+                                TestImminentDistrustEndEntityDNs);
+  if (isDistrusted) {
     // Exit early
     return NS_OK;
   }
 
+  UniqueCERTCertificate nssRootCert(rootCert->GetCert());
+  if (!nssRootCert) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Proceed with the Symantec imminent distrust algorithm. This algorithm is
   // to be removed in Firefox 63, when the validity period check will also be
   // removed from the code in NSSCertDBTrustDomain.
-
-  // We need an owning handle when calling nsIX509Cert::GetCert().
-  UniqueCERTCertificate nssRootCert(rootCert->GetCert());
-  // If the root is not one of the Symantec roots, exit false
-  if (!CertDNIsInList(nssRootCert.get(), RootSymantecDNs)) {
-    aResult = false;
-    return NS_OK;
-  }
-
-  // Look for one of the intermediates to be in the whitelist
-  bool foundInWhitelist = false;
-  RefPtr<nsNSSCertList> intCertList = intCerts->GetCertList();
+  if (CertDNIsInList(nssRootCert.get(), RootSymantecDNs)) {
+    static const PRTime NULL_TIME = 0;
 
-  intCertList->ForEachCertificateInChain(
-    [&foundInWhitelist] (nsCOMPtr<nsIX509Cert> aCert, bool aHasMore,
-                         /* out */ bool& aContinue) {
-      // We need an owning handle when calling nsIX509Cert::GetCert().
-      UniqueCERTCertificate nssCert(aCert->GetCert());
-      if (CertDNIsInList(nssCert.get(), RootAppleAndGoogleDNs)) {
-        foundInWhitelist = true;
-        aContinue = false;
-      }
-      return NS_OK;
-  });
-
-  // If this chain did not match the whitelist, exit true
-  aResult = !foundInWhitelist;
+    rv = CheckForSymantecDistrust(intCerts, eeCert, NULL_TIME,
+                                  RootAppleAndGoogleDNs, isDistrusted);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
   return NS_OK;
 }
 
 void HandshakeCallback(PRFileDesc* fd, void* client_data) {
   SECStatus rv;
 
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
 
--- a/security/manager/ssl/nsNSSCertValidity.h
+++ b/security/manager/ssl/nsNSSCertValidity.h
@@ -18,17 +18,17 @@ public:
   explicit nsX509CertValidity(const mozilla::UniqueCERTCertificate& cert);
 
 protected:
   virtual ~nsX509CertValidity() {}
 
 private:
   nsresult FormatTime(const PRTime& aTime,
                       PRTimeParamFn aParamFn,
-                      const nsTimeFormatSelector aTimeFormatSelector,
+                      const mozilla::nsTimeFormatSelector aTimeFormatSelector,
                       nsAString& aFormattedTimeDate);
 
   PRTime mNotBefore;
   PRTime mNotAfter;
   bool mTimesInitialized;
 
   nsX509CertValidity(const nsX509CertValidity& x) = delete;
   nsX509CertValidity& operator=(const nsX509CertValidity& x) = delete;
--- a/security/manager/ssl/tests/unit/test_symantec_apple_google.js
+++ b/security/manager/ssl/tests/unit/test_symantec_apple_google.js
@@ -1,17 +1,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/. */
 "use strict";
 
-// Tests handling of certificates issued by Symantec. If such
-// certificates have a notBefore before 1 June 2016, and are not
-// issued by an Apple or Google intermediate, they should emit a
-// warning to the console.
+// Tests handling of certificates issued by Symantec. If such certificates were
+// issued by an Apple or Google intermediate, they are whitelisted. Otherwise,
+// If they have a notBefore before 1 June 2016, they should be distrusted, while
+// those from that date or later emit a warning to the console.
 
 function shouldBeImminentlyDistrusted(aTransportSecurityInfo) {
   let isDistrust = aTransportSecurityInfo.securityState &
                      Ci.nsIWebProgressListener.STATE_CERT_DISTRUST_IMMINENT;
   Assert.ok(isDistrust, "This host should be imminently distrusted");
 }
 
 function shouldNotBeImminentlyDistrusted(aTransportSecurityInfo) {
@@ -32,9 +32,9 @@ add_connection_test("symantec-whitelist-
                     PRErrorCodeSuccess, null, shouldNotBeImminentlyDistrusted);
 
 // Not-whitelisted certs after the cutoff are to be distrusted
 add_connection_test("symantec-not-whitelisted-after-cutoff.example.com",
                     PRErrorCodeSuccess, null, shouldBeImminentlyDistrusted);
 
 // Not whitelisted certs before the cutoff are to be distrusted
 add_connection_test("symantec-not-whitelisted-before-cutoff.example.com",
-                    PRErrorCodeSuccess, null, shouldBeImminentlyDistrusted);
+                    SEC_ERROR_UNKNOWN_ISSUER, null, null);