bug 1239455 - rework telemetry for SHA-1 certificates to reflect possible policy states r=Cykesiopka,mgoodwin,rbarnes
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 13 Jan 2016 12:50:42 -0800
changeset 315966 bb6bfd172d6e
parent 315965 f13aab93f3a3
child 315967 fddcf8b3b088
push id5703
push userraliiev@mozilla.com
push dateMon, 07 Mar 2016 14:18:41 +0000
treeherdermozilla-beta@31e373ad5b5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka, mgoodwin, rbarnes
bugs1239455
milestone46.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 1239455 - rework telemetry for SHA-1 certificates to reflect possible policy states r=Cykesiopka,mgoodwin,rbarnes Before this patch, we were measuring where SHA-1 was being used in TLS certificates: nowhere, in end-entities, in intermediates, or in both. However, the possible SHA-1 policies don't differentiate between end-entities and intermediates and instead depended on whether or not each certificate has a notBefore value after 2015 (i.e. >= 0:00:00 1 January 2016 UTC). We need to gather telemetry on the possible policy configurations.
browser/app/profile/firefox.js
mobile/android/app/mobile.js
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsDataSignatureVerifier.cpp
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/nsSiteSecurityService.cpp
security/manager/ssl/nsUsageArrayHelper.cpp
security/manager/ssl/tests/unit/test_cert_sha1.js
toolkit/components/telemetry/Histograms.json
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1399,16 +1399,18 @@ pref("security.mixed_content.block_activ
 pref("security.insecure_password.ui.enabled", true);
 #else
 pref("security.insecure_password.ui.enabled", false);
 #endif
 
 // 1 = allow MITM for certificate pinning checks.
 pref("security.cert_pinning.enforcement_level", 1);
 
+// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
+// See the comment in CertVerifier.cpp.
 // 0 = allow SHA-1
 pref("security.pki.sha1_enforcement_level", 0);
 
 // Required blocklist freshness for OneCRL OCSP bypass
 // (default is 1.25x extensions.blocklist.interval, or 30 hours)
 pref("security.onecrl.maximum_staleness_in_seconds", 108000);
 
 // Override the Gecko-default value of false for Firefox.
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -493,16 +493,18 @@ pref("security.alternate_certificate_err
 pref("security.warn_viewing_mixed", false); // Warning is disabled.  See Bug 616712.
 
 // Block insecure active content on https pages
 pref("security.mixed_content.block_active_content", true);
 
 // Enable pinning
 pref("security.cert_pinning.enforcement_level", 1);
 
+// NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
+// See the comment in CertVerifier.cpp.
 // Allow SHA-1 certificates
 pref("security.pki.sha1_enforcement_level", 0);
 
 // Required blocklist freshness for OneCRL OCSP bypass
 // (default is 1.25x extensions.blocklist.interval, or 30 hours)
 pref("security.onecrl.maximum_staleness_in_seconds", 108000);
 
 // Only fetch OCSP for EV certificates
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -52,18 +52,40 @@ CertVerifier::~CertVerifier()
 void
 InitCertVerifierLog()
 {
   if (!gCertVerifierLog) {
     gCertVerifierLog = PR_NewLogModule("certverifier");
   }
 }
 
+Result
+IsCertChainRootBuiltInRoot(CERTCertList* chain, bool& result)
+{
+  if (!chain || CERT_LIST_EMPTY(chain)) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  CERTCertListNode* rootNode = CERT_LIST_TAIL(chain);
+  if (!rootNode) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  CERTCertificate* root = rootNode->cert;
+  if (!root) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  SECStatus srv = IsCertBuiltInRoot(root, result);
+  if (srv != SECSuccess) {
+    return MapPRErrorCodeToResult(PR_GetError());
+  }
+  return Success;
+}
+
 SECStatus
-IsCertBuiltInRoot(CERTCertificate* cert, bool& result) {
+IsCertBuiltInRoot(CERTCertificate* cert, bool& result)
+{
   result = false;
   ScopedPK11SlotList slots;
   slots = PK11_GetAllSlotsForCert(cert, nullptr);
   if (!slots) {
     if (PORT_GetError() == SEC_ERROR_NO_TOKEN) {
       // no list
       return SECSuccess;
     }
@@ -110,41 +132,56 @@ BuildCertChainForOneKeyUsage(NSSCertDBTr
     }
   }
   if (ocspStaplingStatus) {
     *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
   }
   return rv;
 }
 
+bool
+CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode)
+{
+  switch (mSHA1Mode) {
+    case SHA1Mode::Forbidden:
+      return mode != SHA1Mode::Forbidden;
+    case SHA1Mode::Before2016:
+      return mode != SHA1Mode::Forbidden && mode != SHA1Mode::Before2016;
+    case SHA1Mode::ImportedRoot:
+      return mode == SHA1Mode::Allowed;
+    case SHA1Mode::Allowed:
+      return false;
+    default:
+      MOZ_ASSERT(false, "unexpected SHA1Mode type");
+      return true;
+  }
+}
+
 static const unsigned int MIN_RSA_BITS = 2048;
 static const unsigned int MIN_RSA_BITS_WEAK = 1024;
 
 SECStatus
 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
                          Time time, void* pinArg, const char* hostname,
-                         const Flags flags,
+                 /*out*/ ScopedCERTCertList& builtChain,
+            /*optional*/ const Flags flags,
             /*optional*/ const SECItem* stapledOCSPResponseSECItem,
-        /*optional out*/ ScopedCERTCertList* builtChain,
         /*optional out*/ SECOidTag* evOidPolicy,
         /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
         /*optional out*/ KeySizeStatus* keySizeStatus,
-        /*optional out*/ SignatureDigestStatus* sigDigestStatus,
+        /*optional out*/ SHA1ModeResult* sha1ModeResult,
         /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
 {
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
 
   PR_ASSERT(cert);
   PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
   PR_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
-  PR_ASSERT(usage == certificateUsageSSLServer || !sigDigestStatus);
+  PR_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
 
-  if (builtChain) {
-    *builtChain = nullptr;
-  }
   if (evOidPolicy) {
     *evOidPolicy = SEC_OID_UNKNOWN;
   }
   if (ocspStaplingStatus) {
     if (usage != certificateUsageSSLServer) {
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
     }
@@ -154,22 +191,22 @@ CertVerifier::VerifyCert(CERTCertificate
   if (keySizeStatus) {
     if (usage != certificateUsageSSLServer) {
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
     }
     *keySizeStatus = KeySizeStatus::NeverChecked;
   }
 
-  if (sigDigestStatus) {
+  if (sha1ModeResult) {
     if (usage != certificateUsageSSLServer) {
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
       return SECFailure;
     }
-    *sigDigestStatus = SignatureDigestStatus::NeverChecked;
+    *sha1ModeResult = SHA1ModeResult::NeverChecked;
   }
 
   if (!cert ||
       (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
 
@@ -211,93 +248,125 @@ CertVerifier::VerifyCert(CERTCertificate
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                       nullptr, nullptr, builtChain);
+                                       SHA1Mode::Allowed, builtChain, nullptr,
+                                       nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageSSLServer: {
       // TODO: When verifying a certificate in an SSL handshake, we should
       // restrict the acceptable key usage based on the key exchange method
       // chosen by the server.
 
-      SignatureDigestOption digestAlgorithmOptions[] = {
-        DisableSHA1Everywhere,
-        DisableSHA1ForCA,
-        DisableSHA1ForEE,
-        AcceptAllAlgorithms
+      // These configurations are in order of most restrictive to least
+      // restrictive. This enables us to gather telemetry on the expected
+      // results of setting the default policy to a particular configuration.
+      SHA1Mode sha1ModeConfigurations[] = {
+        SHA1Mode::Forbidden,
+        SHA1Mode::Before2016,
+        SHA1Mode::ImportedRoot,
+        SHA1Mode::Allowed,
       };
 
-      SignatureDigestStatus digestAlgorithmStatuses[] = {
-        SignatureDigestStatus::GoodAlgorithmsOnly,
-        SignatureDigestStatus::WeakEECert,
-        SignatureDigestStatus::WeakCACert,
-        SignatureDigestStatus::WeakCAAndEE
+      SHA1ModeResult sha1ModeResults[] = {
+        SHA1ModeResult::SucceededWithoutSHA1,
+        SHA1ModeResult::SucceededWithSHA1Before2016,
+        SHA1ModeResult::SucceededWithImportedRoot,
+        SHA1ModeResult::SucceededWithSHA1,
       };
 
-      size_t digestAlgorithmOptionsCount = MOZ_ARRAY_LENGTH(digestAlgorithmStatuses);
+      size_t sha1ModeConfigurationsCount = MOZ_ARRAY_LENGTH(sha1ModeConfigurations);
 
-      static_assert(MOZ_ARRAY_LENGTH(digestAlgorithmOptions) ==
-                    MOZ_ARRAY_LENGTH(digestAlgorithmStatuses),
+      static_assert(MOZ_ARRAY_LENGTH(sha1ModeConfigurations) ==
+                    MOZ_ARRAY_LENGTH(sha1ModeResults),
                     "digestAlgorithm array lengths differ");
 
       rv = Result::ERROR_UNKNOWN_ERROR;
 
 #ifndef MOZ_NO_EV_CERTS
       // Try to validate for EV first.
       NSSCertDBTrustDomain::OCSPFetching evOCSPFetching
         = (mOCSPDownloadConfig == ocspOff) ||
           (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                                     : NSSCertDBTrustDomain::FetchOCSPForEV;
 
       CertPolicyId evPolicy;
       SECOidTag evPolicyOidTag;
       SECStatus srv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
-      for (size_t i=0;
-           i < digestAlgorithmOptionsCount && rv != Success && srv == SECSuccess;
+      for (size_t i = 0;
+           i < sha1ModeConfigurationsCount && rv != Success && srv == SECSuccess;
            i++) {
         // Because of the try-strict and fallback approach, we have to clear any
         // previously noted telemetry information
         if (pinningTelemetryInfo) {
           pinningTelemetryInfo->Reset();
         }
+        // Don't attempt verification if the SHA1 mode set by preferences
+        // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on.
+        // (To put it another way, only attempt verification if the SHA1 mode
+        // option we're on is as restrictive or more restrictive than
+        // mSHA1Mode.) This allows us to gather telemetry information while
+        // still enforcing the mode set by preferences.
+        if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[i])) {
+          continue;
+        }
         NSSCertDBTrustDomain
           trustDomain(trustSSL, evOCSPFetching,
                       mOCSPCache, pinArg, ocspGETConfig,
                       mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
                       ValidityCheckingMode::CheckForEV,
-                      digestAlgorithmOptions[i], mSHA1Mode,
-                      pinningTelemetryInfo, hostname, builtChain);
+                      sha1ModeConfigurations[i], builtChain,
+                      pinningTelemetryInfo, hostname);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
+        // If we succeeded with the SHA1Mode of only allowing imported roots to
+        // issue SHA1 certificates after 2015, if the chain we built doesn't
+        // terminate with an imported root, we must reject it. (This only works
+        // because we try SHA1 configurations in order of decreasing
+        // strictness.)
+        // Note that if there existed a certificate chain with a built-in root
+        // that had SHA1 certificates issued before 2016, it would have already
+        // been accepted. If such a chain had SHA1 certificates issued after
+        // 2015, it will only be accepted in the SHA1Mode::Allowed case.
+        if (rv == Success &&
+            sha1ModeConfigurations[i] == SHA1Mode::ImportedRoot) {
+          bool isBuiltInRoot = false;
+          rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
+          if (rv != Success) {
+            break;
+          }
+          if (isBuiltInRoot) {
+            rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+          }
+        }
         if (rv == Success) {
           MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
-                  ("cert is EV with status %i\n", digestAlgorithmStatuses[i]));
+                  ("cert is EV with status %i\n", sha1ModeResults[i]));
           if (evOidPolicy) {
             *evOidPolicy = evPolicyOidTag;
           }
-          if (sigDigestStatus) {
-            *sigDigestStatus = digestAlgorithmStatuses[i];
+          if (sha1ModeResult) {
+            *sha1ModeResult = sha1ModeResults[i];
           }
         }
       }
       if (rv == Success) {
         break;
       }
 #endif
 
@@ -318,100 +387,120 @@ CertVerifier::VerifyCert(CERTCertificate
       };
 
       static_assert(MOZ_ARRAY_LENGTH(keySizeOptions) ==
                     MOZ_ARRAY_LENGTH(keySizeStatuses),
                     "keySize array lengths differ");
 
       size_t keySizeOptionsCount = MOZ_ARRAY_LENGTH(keySizeStatuses);
 
-      for (size_t i=0; i<keySizeOptionsCount && rv != Success; i++) {
-        for (size_t j=0; j<digestAlgorithmOptionsCount && rv != Success; j++) {
-
+      for (size_t i = 0; i < keySizeOptionsCount && rv != Success; i++) {
+        for (size_t j = 0; j < sha1ModeConfigurationsCount && rv != Success;
+             j++) {
           // invalidate any telemetry info relating to failed chains
           if (pinningTelemetryInfo) {
             pinningTelemetryInfo->Reset();
           }
 
-          // If we're not going to do SHA-1 in any case, don't try
-          if (mSHA1Mode == SHA1Mode::Forbidden &&
-              digestAlgorithmOptions[i] != DisableSHA1Everywhere) {
+          // Don't attempt verification if the SHA1 mode set by preferences
+          // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on.
+          // (To put it another way, only attempt verification if the SHA1 mode
+          // option we're on is as restrictive or more restrictive than
+          // mSHA1Mode.) This allows us to gather telemetry information while
+          // still enforcing the mode set by preferences.
+          if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[j])) {
             continue;
           }
 
           NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                            mOCSPCache, pinArg, ocspGETConfig,
                                            mCertShortLifetimeInDays,
                                            mPinningMode, keySizeOptions[i],
                                            ValidityCheckingMode::CheckingOff,
-                                           digestAlgorithmOptions[j],
-                                           mSHA1Mode, pinningTelemetryInfo,
-                                           hostname, builtChain);
+                                           sha1ModeConfigurations[j],
+                                           builtChain, pinningTelemetryInfo,
+                                           hostname);
           rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                             KeyUsage::digitalSignature,//(EC)DHE
                                             KeyUsage::keyEncipherment,//RSA
                                             KeyUsage::keyAgreement,//(EC)DH
                                             KeyPurposeId::id_kp_serverAuth,
                                             CertPolicyId::anyPolicy,
                                             stapledOCSPResponse,
                                             ocspStaplingStatus);
+          // If we succeeded with the SHA1Mode of only allowing imported roots
+          // to issue SHA1 certificates after 2015, if the chain we built
+          // doesn't terminate with an imported root, we must reject it. (This
+          // only works because we try SHA1 configurations in order of
+          // decreasing strictness.)
+          // Note that if there existed a certificate chain with a built-in root
+          // that had SHA1 certificates issued before 2016, it would have
+          // already been accepted. If such a chain had SHA1 certificates issued
+          // after 2015, it will only be accepted in the SHA1Mode::Allowed case.
+          if (rv == Success &&
+              sha1ModeConfigurations[j] == SHA1Mode::ImportedRoot) {
+            bool isBuiltInRoot = false;
+            rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot);
+            if (rv != Success) {
+              break;
+            }
+            if (isBuiltInRoot) {
+              rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+            }
+          }
           if (rv == Success) {
             if (keySizeStatus) {
               *keySizeStatus = keySizeStatuses[i];
             }
-            if (sigDigestStatus) {
-              *sigDigestStatus = digestAlgorithmStatuses[j];
+            if (sha1ModeResult) {
+              *sha1ModeResult = sha1ModeResults[j];
             }
           }
         }
       }
 
       if (rv == Success) {
-        // If SHA-1 is forbidden by preference, don't accumulate SHA-1
-        // telemetry, to avoid skewing the results.
-        if (sigDigestStatus && mSHA1Mode == SHA1Mode::Forbidden) {
-          *sigDigestStatus = SignatureDigestStatus::NeverChecked;
-        }
-
         break;
       }
 
       if (keySizeStatus) {
         *keySizeStatus = KeySizeStatus::AlreadyBad;
       }
-      if (sigDigestStatus && mSHA1Mode != SHA1Mode::Forbidden) {
-        *sigDigestStatus = SignatureDigestStatus::AlreadyBad;
+      // Only collect CERT_CHAIN_SHA1_POLICY_STATUS telemetry indicating a
+      // failure when mSHA1Mode is the default.
+      // NB: When we change the default, we have to change this.
+      if (sha1ModeResult && mSHA1Mode == SHA1Mode::Allowed) {
+        *sha1ModeResult = SHA1ModeResult::Failed;
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, mSHA1Mode,
-                                       nullptr, nullptr, builtChain);
+                                       mSHA1Mode, builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                       nullptr, nullptr, builtChain);
+                                       SHA1Mode::Allowed, builtChain, nullptr,
+                                       nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -426,18 +515,18 @@ CertVerifier::VerifyCert(CERTCertificate
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                       nullptr, nullptr, builtChain);
+                                       SHA1Mode::Allowed, builtChain, nullptr,
+                                       nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment, // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -449,18 +538,18 @@ CertVerifier::VerifyCert(CERTCertificate
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                       nullptr, nullptr, builtChain);
+                                       SHA1Mode::Allowed, builtChain, nullptr,
+                                       nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -481,42 +570,42 @@ CertVerifier::VerifyCert(CERTCertificate
         keyUsage = KeyUsage::digitalSignature;
         eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, defaultOCSPFetching, mOCSPCache,
                                     pinArg, ocspGETConfig, mCertShortLifetimeInDays,
                                     pinningDisabled, MIN_RSA_BITS_WEAK,
                                     ValidityCheckingMode::CheckingOff,
-                                    AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                    nullptr, nullptr, builtChain);
+                                    SHA1Mode::Allowed, builtChain, nullptr,
+                                    nullptr);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, defaultOCSPFetching,
                                         mOCSPCache, pinArg, ocspGETConfig,
                                         mCertShortLifetimeInDays,
                                         pinningDisabled, MIN_RSA_BITS_WEAK,
                                         ValidityCheckingMode::CheckingOff,
-                                        AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                        nullptr, nullptr, builtChain);
+                                        SHA1Mode::Allowed, builtChain, nullptr,
+                                        nullptr);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   defaultOCSPFetching, mOCSPCache,
                                                   pinArg, ocspGETConfig,
                                                   mCertShortLifetimeInDays,
                                                   pinningDisabled,
                                                   MIN_RSA_BITS_WEAK,
                                                   ValidityCheckingMode::CheckingOff,
-                                                  AcceptAllAlgorithms, SHA1Mode::Allowed,
-                                                  nullptr, nullptr, builtChain);
+                                                  SHA1Mode::Allowed, builtChain,
+                                                  nullptr, nullptr);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
 
       break;
     }
@@ -534,50 +623,45 @@ CertVerifier::VerifyCert(CERTCertificate
 }
 
 SECStatus
 CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert,
                      /*optional*/ const SECItem* stapledOCSPResponse,
                                   Time time,
                      /*optional*/ void* pinarg,
                                   const char* hostname,
-                                  bool saveIntermediatesInPermanentDatabase,
-                                  Flags flags,
-                 /*optional out*/ ScopedCERTCertList* builtChain,
+                          /*out*/ ScopedCERTCertList& builtChain,
+                     /*optional*/ bool saveIntermediatesInPermanentDatabase,
+                     /*optional*/ Flags flags,
                  /*optional out*/ SECOidTag* evOidPolicy,
                  /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
                  /*optional out*/ KeySizeStatus* keySizeStatus,
-                 /*optional out*/ SignatureDigestStatus* sigDigestStatus,
+                 /*optional out*/ SHA1ModeResult* sha1ModeResult,
                  /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
 {
   PR_ASSERT(peerCert);
   // XXX: PR_ASSERT(pinarg)
   PR_ASSERT(hostname);
   PR_ASSERT(hostname[0]);
 
-  if (builtChain) {
-    *builtChain = nullptr;
-  }
   if (evOidPolicy) {
     *evOidPolicy = SEC_OID_UNKNOWN;
   }
 
   if (!hostname || !hostname[0]) {
     PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     return SECFailure;
   }
 
-  ScopedCERTCertList builtChainTemp;
   // CreateCertErrorRunnable assumes that CheckCertHostname is only called
   // if VerifyCert succeeded.
   SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg,
-                            hostname, flags, stapledOCSPResponse,
-                            &builtChainTemp, evOidPolicy, ocspStaplingStatus,
-                            keySizeStatus, sigDigestStatus,
-                            pinningTelemetryInfo);
+                            hostname, builtChain, flags, stapledOCSPResponse,
+                            evOidPolicy, ocspStaplingStatus, keySizeStatus,
+                            sha1ModeResult, pinningTelemetryInfo);
   if (rv != SECSuccess) {
     return rv;
   }
 
   Input peerCertInput;
   Result result = peerCertInput.Init(peerCert->derCert.data,
                                      peerCert->derCert.len);
   if (result != Success) {
@@ -620,19 +704,15 @@ CertVerifier::VerifySSLServerCert(CERTCe
       PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     } else {
       PR_SetError(MapResultToPRErrorCode(result), 0);
     }
     return SECFailure;
   }
 
   if (saveIntermediatesInPermanentDatabase) {
-    SaveIntermediateCerts(builtChainTemp);
-  }
-
-  if (builtChain) {
-    *builtChain = builtChainTemp.forget();
+    SaveIntermediateCerts(builtChain);
   }
 
   return SECSuccess;
 }
 
 } } // namespace mozilla::psm
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -17,24 +17,24 @@ namespace mozilla { namespace psm {
 // These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
 enum class KeySizeStatus {
   NeverChecked = 0,
   LargeMinimumSucceeded = 1,
   CompatibilityRisk = 2,
   AlreadyBad = 3,
 };
 
-// These values correspond to the CERT_CHAIN_SIGNATURE_DIGEST telemetry.
-enum class SignatureDigestStatus {
+// These values correspond to the CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
+enum class SHA1ModeResult {
   NeverChecked = 0,
-  GoodAlgorithmsOnly = 1,
-  WeakEECert = 2,
-  WeakCACert = 3,
-  WeakCAAndEE = 4,
-  AlreadyBad = 5,
+  SucceededWithoutSHA1 = 1,
+  SucceededWithSHA1Before2016 = 2,
+  SucceededWithImportedRoot = 3,
+  SucceededWithSHA1 = 4,
+  Failed = 5,
 };
 
 class PinningTelemetryInfo
 {
 public:
   // Should we accumulate pinning telemetry for the result?
   bool accumulateResult;
   Telemetry::ID certPinningResultHistogram;
@@ -68,51 +68,52 @@ public:
 
   // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV
   // Only one usage per verification is supported.
   SECStatus VerifyCert(CERTCertificate* cert,
                        SECCertificateUsage usage,
                        mozilla::pkix::Time time,
                        void* pinArg,
                        const char* hostname,
+               /*out*/ ScopedCERTCertList& builtChain,
                        Flags flags = 0,
        /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
-      /*optional out*/ ScopedCERTCertList* builtChain = nullptr,
       /*optional out*/ SECOidTag* evOidPolicy = nullptr,
       /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
       /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
-      /*optional out*/ SignatureDigestStatus* sigDigestStatus = nullptr,
+      /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
       /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
 
   SECStatus VerifySSLServerCert(
                     CERTCertificate* peerCert,
        /*optional*/ const SECItem* stapledOCSPResponse,
                     mozilla::pkix::Time time,
        /*optional*/ void* pinarg,
                     const char* hostname,
-                    bool saveIntermediatesInPermanentDatabase = false,
-                    Flags flags = 0,
-   /*optional out*/ ScopedCERTCertList* builtChain = nullptr,
+            /*out*/ ScopedCERTCertList& builtChain,
+       /*optional*/ bool saveIntermediatesInPermanentDatabase = false,
+       /*optional*/ Flags flags = 0,
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
-   /*optional out*/ SignatureDigestStatus* sigDigestStatus = nullptr,
+   /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
 
   enum PinningMode {
     pinningDisabled = 0,
     pinningAllowUserCAMITM = 1,
     pinningStrict = 2,
     pinningEnforceTestMode = 3
   };
 
   enum class SHA1Mode {
     Allowed = 0,
     Forbidden = 1,
-    OnlyBefore2016 = 2
+    Before2016 = 2,
+    ImportedRoot = 3,
   };
 
   enum OcspDownloadConfig {
     ocspOff = 0,
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
@@ -129,16 +130,22 @@ public:
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
   const SHA1Mode mSHA1Mode;
 
 private:
   OCSPCache mOCSPCache;
+
+  // Returns true if the configured SHA1 mode is more restrictive than the given
+  // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
+  // Forbidden. Next is Before2016, then ImportedRoot, then Allowed.
+  // (A mode is never more restrictive than itself.)
+  bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
 };
 
 void InitCertVerifierLog();
 SECStatus IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
 mozilla::pkix::Result CertListContainsExpectedKeys(
   const CERTCertList* certList, const char* hostname, mozilla::pkix::Time time,
   CertVerifier::PinningMode pinningMode);
 
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -45,35 +45,33 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
                                            OCSPFetching ocspFetching,
                                            OCSPCache& ocspCache,
              /*optional but shouldn't be*/ void* pinArg,
                                            CertVerifier::OcspGetConfig ocspGETConfig,
                                            uint32_t certShortLifetimeInDays,
                                            CertVerifier::PinningMode pinningMode,
                                            unsigned int minRSABits,
                                            ValidityCheckingMode validityCheckingMode,
-                                           SignatureDigestOption signatureDigestOption,
                                            CertVerifier::SHA1Mode sha1Mode,
+                                           ScopedCERTCertList& builtChain,
                               /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
-                              /*optional*/ const char* hostname,
-                              /*optional*/ ScopedCERTCertList* builtChain)
+                              /*optional*/ const char* hostname)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mValidityCheckingMode(validityCheckingMode)
-  , mSignatureDigestOption(signatureDigestOption)
   , mSHA1Mode(sha1Mode)
+  , mBuiltChain(builtChain)
   , mPinningTelemetryInfo(pinningTelemetryInfo)
   , mHostname(hostname)
-  , mBuiltChain(builtChain)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
 {
 }
 
 // If useRoots is true, we only use root certificates in the candidate list.
 // If useRoots is false, we only use non-root certificates in the list.
 static Result
@@ -801,68 +799,51 @@ NSSCertDBTrustDomain::IsChainValid(const
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
     if (!chainHasValidPins) {
       return Result::ERROR_KEY_PINNING_FAILURE;
     }
   }
 
-  if (mBuiltChain) {
-    *mBuiltChain = certList.forget();
-  }
+  mBuiltChain = certList.forget();
 
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm aAlg,
                                                     EndEntityOrCA endEntityOrCA,
                                                     Time notBefore)
 {
   // (new Date("2016-01-01T00:00:00Z")).getTime() / 1000
   static const Time JANUARY_FIRST_2016 = TimeFromEpochInSeconds(1451606400);
 
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
           ("NSSCertDBTrustDomain: CheckSignatureDigestAlgorithm"));
   if (aAlg == DigestAlgorithm::sha1) {
-    // First check based on SHA1Mode
     switch (mSHA1Mode) {
       case CertVerifier::SHA1Mode::Forbidden:
         MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("SHA-1 certificate rejected"));
         return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
-      case CertVerifier::SHA1Mode::OnlyBefore2016:
+      case CertVerifier::SHA1Mode::Before2016:
         if (JANUARY_FIRST_2016 <= notBefore) {
           MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Post-2015 SHA-1 certificate rejected"));
           return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
         }
         break;
       case CertVerifier::SHA1Mode::Allowed:
+      // Enforcing that the resulting chain uses an imported root is only
+      // possible at a higher level. This is done in CertVerifier::VerifyCert.
+      case CertVerifier::SHA1Mode::ImportedRoot:
       default:
         break;
     }
-
-    // Then check the signatureDigestOption values
-    if (mSignatureDigestOption == DisableSHA1Everywhere) {
-      MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("SHA-1 certificate rejected"));
-      return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
-    }
+  }
 
-    if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
-      MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("CA cert is SHA-1"));
-      return mSignatureDigestOption == DisableSHA1ForCA
-             ? Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
-             : Success;
-    } else {
-      MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("EE cert is SHA-1"));
-      return mSignatureDigestOption == DisableSHA1ForEE
-             ? Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
-             : Success;
-    }
-  }
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
   EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
 {
   if (modulusSizeInBits < mMinRSABits) {
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -36,23 +36,16 @@ SECStatus LoadLoadableRoots(/*optional*/
 
 void UnloadLoadableRoots(const char* modNameUTF8);
 
 // Caller must free the result with PR_Free
 char* DefaultServerNicknameForCert(CERTCertificate* cert);
 
 void SaveIntermediateCerts(const ScopedCERTCertList& certList);
 
-enum SignatureDigestOption {
-  AcceptAllAlgorithms,
-  DisableSHA1ForEE,
-  DisableSHA1ForCA,
-  DisableSHA1Everywhere,
-};
-
 class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain
 {
 
 public:
   typedef mozilla::pkix::Result Result;
 
   enum OCSPFetching {
     NeverFetchOCSP = 0,
@@ -64,21 +57,20 @@ public:
 
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
                        OCSPCache& ocspCache, void* pinArg,
                        CertVerifier::OcspGetConfig ocspGETConfig,
                        uint32_t certShortLifetimeInDays,
                        CertVerifier::PinningMode pinningMode,
                        unsigned int minRSABits,
                        ValidityCheckingMode validityCheckingMode,
-                       SignatureDigestOption signatureDigestOption,
                        CertVerifier::SHA1Mode sha1Mode,
+                       ScopedCERTCertList& builtChain,
           /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
-          /*optional*/ const char* hostname = nullptr,
-      /*optional out*/ ScopedCERTCertList* builtChain = nullptr);
+          /*optional*/ const char* hostname = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
 
   virtual Result GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                               const mozilla::pkix::CertPolicyId& policy,
                               mozilla::pkix::Input candidateCertDER,
@@ -151,20 +143,19 @@ private:
   const OCSPFetching mOCSPFetching;
   OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
   const CertVerifier::OcspGetConfig mOCSPGetConfig;
   const uint32_t mCertShortLifetimeInDays;
   CertVerifier::PinningMode mPinningMode;
   const unsigned int mMinRSABits;
   ValidityCheckingMode mValidityCheckingMode;
-  SignatureDigestOption mSignatureDigestOption;
   CertVerifier::SHA1Mode mSHA1Mode;
+  ScopedCERTCertList& mBuiltChain; // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname; // non-owning - only used for pinning checks
-  ScopedCERTCertList* mBuiltChain; // non-owning
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__NSSCertDBTrustDomain_h
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1217,47 +1217,47 @@ AuthCertificate(CertVerifier& certVerifi
   bool saveIntermediates =
     !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE);
 
   SECOidTag evOidPolicy;
   ScopedCERTCertList certList;
   CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
     CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
   KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
-  SignatureDigestStatus sigDigestStatus = SignatureDigestStatus::NeverChecked;
+  SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
   PinningTelemetryInfo pinningTelemetryInfo;
 
   int flags = 0;
   if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
       !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
     flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   }
 
   rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
                                         time, infoObject,
                                         infoObject->GetHostNameRaw(),
-                                        saveIntermediates, flags, &certList,
+                                        certList, saveIntermediates, flags,
                                         &evOidPolicy, &ocspStaplingStatus,
-                                        &keySizeStatus, &sigDigestStatus,
+                                        &keySizeStatus, &sha1ModeResult,
                                         &pinningTelemetryInfo);
   PRErrorCode savedErrorCode;
   if (rv != SECSuccess) {
     savedErrorCode = PR_GetError();
   }
 
   if (ocspStaplingStatus != CertVerifier::OCSP_STAPLING_NEVER_CHECKED) {
     Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, ocspStaplingStatus);
   }
   if (keySizeStatus != KeySizeStatus::NeverChecked) {
     Telemetry::Accumulate(Telemetry::CERT_CHAIN_KEY_SIZE_STATUS,
                           static_cast<uint32_t>(keySizeStatus));
   }
-  if (sigDigestStatus != SignatureDigestStatus::NeverChecked) {
-    Telemetry::Accumulate(Telemetry::CERT_CHAIN_SIGNATURE_DIGEST_STATUS,
-                          static_cast<uint32_t>(sigDigestStatus));
+  if (sha1ModeResult != SHA1ModeResult::NeverChecked) {
+    Telemetry::Accumulate(Telemetry::CERT_CHAIN_SHA1_POLICY_STATUS,
+                          static_cast<uint32_t>(sha1ModeResult));
   }
 
   if (pinningTelemetryInfo.accumulateForRoot) {
     Telemetry::Accumulate(Telemetry::CERT_PINNING_FAILURES_BY_CA,
                           pinningTelemetryInfo.rootBucket);
   }
 
   if (pinningTelemetryInfo.accumulateResult) {
--- a/security/manager/ssl/nsDataSignatureVerifier.cpp
+++ b/security/manager/ssl/nsDataSignatureVerifier.cpp
@@ -245,19 +245,17 @@ VerifyCertificate(CERTCertificate* cert,
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   return MapSECStatus(certVerifier->VerifyCert(cert,
                                                certificateUsageObjectSigner,
                                                Now(), pinArg,
                                                nullptr, // hostname
-                                               0, // flags
-                                               nullptr, // stapledOCSPResponse
-                                               &context->builtChain));
+                                               context->builtChain));
 }
 
 } // namespace
 
 NS_IMETHODIMP
 nsDataSignatureVerifier::VerifySignature(const char* aRSABuf,
                                          uint32_t aRSABufLen,
                                          const char* aPlaintext,
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -826,19 +826,18 @@ nsNSSCertificate::GetChain(nsIArray** _r
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   // We want to test all usages, but we start with server because most of the
   // time Firefox users care about server certs.
   if (certVerifier->VerifyCert(mCert.get(), certificateUsageSSLServer, now,
                                nullptr, /*XXX fixme*/
                                nullptr, /* hostname */
-                               CertVerifier::FLAG_LOCAL_ONLY,
-                               nullptr, /* stapledOCSPResponse */
-                               &nssChain) != SECSuccess) {
+                               nssChain,
+                               CertVerifier::FLAG_LOCAL_ONLY) != SECSuccess) {
     nssChain = nullptr;
     // keep going
   }
 
   // This is the whitelist of all non-SSLServer usages that are supported by
   // verifycert.
   const int otherUsagesToTest = certificateUsageSSLClient |
                                 certificateUsageSSLCA |
@@ -853,19 +852,18 @@ nsNSSCertificate::GetChain(nsIArray** _r
       continue;
     }
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("pipnss: PKIX attempting chain(%d) for '%s'\n",
             usage, mCert->nickname));
     if (certVerifier->VerifyCert(mCert.get(), usage, now,
                                  nullptr, /*XXX fixme*/
                                  nullptr, /*hostname*/
-                                 CertVerifier::FLAG_LOCAL_ONLY,
-                                 nullptr, /* stapledOCSPResponse */
-                                 &nssChain) != SECSuccess) {
+                                 nssChain,
+                                 CertVerifier::FLAG_LOCAL_ONLY) != SECSuccess) {
       nssChain = nullptr;
       // keep going
     }
   }
 
   if (!nssChain) {
     // There is not verified path for the chain, howeever we still want to 
     // present to the user as much of a possible chain as possible, in the case
@@ -1388,21 +1386,23 @@ nsNSSCertificate::hasValidEVOidTag(SECOi
     certVerifier(mozilla::psm::GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   validEV = false;
   resultOidTag = SEC_OID_UNKNOWN;
 
   uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
     mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
+  ScopedCERTCertList unusedBuiltChain;
   SECStatus rv = certVerifier->VerifyCert(mCert.get(),
     certificateUsageSSLServer, mozilla::pkix::Now(),
     nullptr /* XXX pinarg */,
     nullptr /* hostname */,
-    flags, nullptr /* stapledOCSPResponse */ , nullptr, &resultOidTag);
+    unusedBuiltChain,
+    flags, nullptr /* stapledOCSPResponse */, &resultOidTag);
 
   if (rv != SECSuccess) {
     resultOidTag = SEC_OID_UNKNOWN;
   }
   if (resultOidTag != SEC_OID_UNKNOWN) {
     validEV = true;
   }
   return NS_OK;
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -610,17 +610,17 @@ nsNSSCertificateDB::ImportEmailCertifica
       continue;
     }
 
     ScopedCERTCertList certChain;
 
     SECStatus rv = certVerifier->VerifyCert(node->cert,
                                             certificateUsageEmailRecipient,
                                             mozilla::pkix::Now(), ctx,
-                                            nullptr, 0, nullptr, &certChain);
+                                            nullptr, certChain);
 
     if (rv != SECSuccess) {
       nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
       DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, locker);
       continue;
     }
     rv = ImportCertsIntoPermanentStorage(certChain, certUsageEmailRecipient,
                                          false);
@@ -778,17 +778,17 @@ nsNSSCertificateDB::ImportValidCACertsIn
 
   for (node = CERT_LIST_HEAD(certList);
        !CERT_LIST_END(node,certList);
        node = CERT_LIST_NEXT(node)) {
     ScopedCERTCertList certChain;
     SECStatus rv = certVerifier->VerifyCert(node->cert,
                                             certificateUsageVerifyCA,
                                             mozilla::pkix::Now(), ctx,
-                                            nullptr, 0, nullptr, &certChain);
+                                            nullptr, certChain);
     if (rv != SECSuccess) {
       nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
       DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, proofOfLock);
       continue;
     }
 
     rv = ImportCertsIntoPermanentStorage(certChain, certUsageAnyCA, true);
     if (rv != SECSuccess) {
@@ -1346,21 +1346,23 @@ nsNSSCertificateDB::FindCertByEmailAddre
     return NS_ERROR_FAILURE; // no certs found
 
   CERTCertListNode *node;
   // search for a valid certificate
   for (node = CERT_LIST_HEAD(certlist);
        !CERT_LIST_END(node, certlist);
        node = CERT_LIST_NEXT(node)) {
 
+    ScopedCERTCertList unusedCertChain;
     SECStatus srv = certVerifier->VerifyCert(node->cert,
                                              certificateUsageEmailRecipient,
                                              mozilla::pkix::Now(),
                                              nullptr /*XXX pinarg*/,
-                                             nullptr /*hostname*/);
+                                             nullptr /*hostname*/,
+                                             unusedCertChain);
     if (srv == SECSuccess) {
       break;
     }
   }
 
   if (CERT_LIST_END(node, certlist)) {
     // no valid cert found
     return NS_ERROR_FAILURE;
@@ -1717,27 +1719,27 @@ VerifyCertAtTime(nsIX509Cert* aCert,
   SECStatus srv;
 
   if (aHostname && aUsage == certificateUsageSSLServer) {
     srv = certVerifier->VerifySSLServerCert(nssCert,
                                             nullptr, // stapledOCSPResponse
                                             aTime,
                                             nullptr, // Assume no context
                                             aHostname,
+                                            resultChain,
                                             false, // don't save intermediates
                                             aFlags,
-                                            &resultChain,
                                             &evOidPolicy);
   } else {
     srv = certVerifier->VerifyCert(nssCert, aUsage, aTime,
                                    nullptr, // Assume no context
                                    aHostname,
+                                   resultChain,
                                    aFlags,
                                    nullptr, // stapledOCSPResponse
-                                   &resultChain,
                                    &evOidPolicy);
   }
 
   PRErrorCode error = PR_GetError();
 
   nsCOMPtr<nsIX509CertList> nssCertList;
   // This adopts the list
   nssCertList = new nsNSSCertList(resultChain, locker);
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -857,18 +857,24 @@ void nsNSSComponent::setValidationOption
                            CertVerifier::pinningDisabled));
   if (pinningMode > CertVerifier::pinningEnforceTestMode) {
     pinningMode = CertVerifier::pinningDisabled;
   }
 
   CertVerifier::SHA1Mode sha1Mode = static_cast<CertVerifier::SHA1Mode>
       (Preferences::GetInt("security.pki.sha1_enforcement_level",
                            static_cast<int32_t>(CertVerifier::SHA1Mode::Allowed)));
-  if (sha1Mode > CertVerifier::SHA1Mode::OnlyBefore2016) {
-    sha1Mode = CertVerifier::SHA1Mode::Allowed;
+  switch (sha1Mode) {
+    case CertVerifier::SHA1Mode::Allowed:
+    case CertVerifier::SHA1Mode::Forbidden:
+    case CertVerifier::SHA1Mode::Before2016:
+    case CertVerifier::SHA1Mode::ImportedRoot:
+      break;
+    default:
+      sha1Mode = CertVerifier::SHA1Mode::Allowed;
   }
 
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
   uint32_t certShortLifetimeInDays;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -375,21 +375,21 @@ nsNSSSocketInfo::IsAcceptableForHost(con
   // can associate the correct certificate chain with the HTTP transactions it
   // is trying to join onto this connection.
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   if (!certVerifier) {
     return NS_OK;
   }
   nsAutoCString hostnameFlat(PromiseFlatCString(hostname));
   CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY;
+  ScopedCERTCertList unusedBuiltChain;
   SECStatus rv = certVerifier->VerifySSLServerCert(nssCert, nullptr,
                                                    mozilla::pkix::Now(),
                                                    nullptr, hostnameFlat.get(),
-                                                   false, flags, nullptr,
-                                                   nullptr);
+                                                   unusedBuiltChain, false, flags);
   if (rv != SECSuccess) {
     return NS_OK;
   }
 
   // All tests pass
   *_retval = true;
   return NS_OK;
 }
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -707,19 +707,20 @@ nsSiteSecurityService::ProcessPKPHeader(
 
   mozilla::pkix::Time now(mozilla::pkix::Now());
   ScopedCERTCertList certList;
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
   if (certVerifier->VerifySSLServerCert(nssCert, nullptr, // stapled ocsp
                                         now, nullptr, // pinarg
                                         host.get(), // hostname
+                                        certList,
                                         false, // don't store intermediates
-                                        CertVerifier::FLAG_LOCAL_ONLY,
-                                        &certList) != SECSuccess) {
+                                        CertVerifier::FLAG_LOCAL_ONLY)
+      != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
 
   CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
   if (CERT_LIST_END(rootNode, certList)) {
     return NS_ERROR_FAILURE;
   }
   bool isBuiltIn = false;
--- a/security/manager/ssl/nsUsageArrayHelper.cpp
+++ b/security/manager/ssl/nsUsageArrayHelper.cpp
@@ -98,19 +98,21 @@ nsUsageArrayHelper::check(uint32_t previ
     break;
   case certificateUsageStatusResponder:
     typestr = "VerifyStatusResponder";
     break;
   default:
     MOZ_CRASH("unknown cert usage passed to check()");
   }
 
+  ScopedCERTCertList unusedBuiltChain;
   SECStatus rv = certVerifier->VerifyCert(mCert, aCertUsage, time,
                                           nullptr /*XXX:wincx*/,
-                                          nullptr /*hostname*/, flags);
+                                          nullptr /*hostname*/,
+                                          unusedBuiltChain, flags);
 
   if (rv == SECSuccess) {
     typestr.Append(suffix);
     nsAutoString verifyDesc;
     m_rv = nssComponent->GetPIPNSSBundleString(typestr.get(), verifyDesc);
     if (NS_SUCCEEDED(m_rv)) {
       outUsages[aCounter++] = ToNewUnicode(verifyDesc);
     }
--- a/security/manager/ssl/tests/unit/test_cert_sha1.js
+++ b/security/manager/ssl/tests/unit/test_cert_sha1.js
@@ -54,19 +54,22 @@ function run_test() {
   //  |
   //  +--- post-2016            <--- (d)
   //          |
   //          +----- post-2016  <--- (e)
   //
   // Expected outcomes (accept / reject):
   //
   //                     a   b   c   d   e
-  // allowed=0          Acc Acc Acc Acc Acc
-  // forbidden=1        Rej Rej Rej Rej Rej
-  // onlyBefore2016=2   Acc Acc Rej Rej Rej
+  // Allowed=0          Acc Acc Acc Acc Acc
+  // Forbidden=1        Rej Rej Rej Rej Rej
+  // Before2016=2       Acc Acc Rej Rej Rej
+  //
+  // The pref setting of ImportedRoot (3) accepts everything because the
+  // testing root is an imported one. This will be addressed in bug 1240118.
 
   // SHA-1 allowed
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 0);
   checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-pre"), PRErrorCodeSuccess);
   checkIntermediate(certFromFile("int-post"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-post"), PRErrorCodeSuccess);
@@ -81,9 +84,18 @@ function run_test() {
 
   // SHA-1 allowed only before 2016
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 2);
   checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   checkIntermediate(certFromFile("int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   checkEndEntity(certFromFile("ee-post_int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+
+  // SHA-1 allowed only before 2016 or when issued by an imported root (which
+  // happens to be all of our test certificates).
+  Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 3);
+  checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
+  checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+  checkEndEntity(certFromFile("ee-post_int-pre"), PRErrorCodeSuccess);
+  checkIntermediate(certFromFile("int-post"), PRErrorCodeSuccess);
+  checkEndEntity(certFromFile("ee-post_int-post"), PRErrorCodeSuccess);
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8011,21 +8011,21 @@
     "description": "Certificate pinning test results by host for Mozilla operational sites"
   },
   "CERT_CHAIN_KEY_SIZE_STATUS": {
     "expires_in_version": "default",
     "kind": "enumerated",
     "n_values": 4,
     "description": "Does enforcing a larger minimum RSA key size cause verification failures? 1 = no, 2 = yes, 3 = another error prevented finding a verified chain"
   },
-  "CERT_CHAIN_SIGNATURE_DIGEST_STATUS": {
+  "CERT_CHAIN_SHA1_POLICY_STATUS": {
     "expires_in_version": "default",
     "kind": "enumerated",
     "n_values": 6,
-    "description": "Information on weak signature digest algorithms in the chain: 1 = Only good algorithms, 2 = a weak algorithm was present in an end entity, 3 = a weak algorithm was present in a CA cert, 4 = a weak algorithm was present in both EE and CA certs, 5 = another error prevented signature algorithm from being determined"
+    "description": "1 = No SHA1 signatures, 2 = SHA1 certificates issued before 2016, 3 = SHA1 certificates issued by an imported root, 4 = SHA1 certificates issued after 2015, 5 = another error prevented successful verification"
   },
   "WEAVE_CONFIGURED": {
     "expires_in_version": "default",
     "kind": "boolean",
     "description": "If any version of Firefox Sync is configured for this device",
     "releaseChannelCollection": "opt-out"
   },
   "WEAVE_CONFIGURED_MASTER_PASSWORD": {