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 280893 bb6bfd172d6e40b5d6a87d8118faf860c02f8545
parent 280892 f13aab93f3a33ca6dc515e19559bd836706b4970
child 280894 fddcf8b3b08835eb75c1bd986df3af677bae3f34
push id17143
push usercbook@mozilla.com
push dateThu, 21 Jan 2016 11:30:19 +0000
treeherderfx-team@bd50a0b3f94d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka, mgoodwin, rbarnes
bugs1239455
milestone46.0a1
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": {