bug 1302140 - add policy to disable SHA-1 except for certificates issued by non-built-in CAs r=jcj,rbarnes
☠☠ backed out by d8b95e0d8843 ☠ ☠
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 14 Sep 2016 15:11:15 -0700
changeset 357577 2df66e8b7411
parent 357576 26e0cc5ad055
child 357578 d01861ca9927
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-beta@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj, rbarnes
bugs1302140
milestone52.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 1302140 - add policy to disable SHA-1 except for certificates issued by non-built-in CAs r=jcj,rbarnes MozReview-Commit-ID: 2uwHPxk0VhZ
netwerk/base/security-prefs.js
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/tests/unit/test_cert_sha1.js
security/manager/ssl/tests/unit/test_ocsp_caching.js
security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
toolkit/components/telemetry/Histograms.json
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -50,18 +50,18 @@ pref("security.family_safety.mode", 2);
 
 pref("security.OCSP.enabled", 1);
 pref("security.OCSP.require", false);
 pref("security.OCSP.GET.enabled", false);
 
 pref("security.pki.cert_short_lifetime_in_days", 10);
 // NB: Changes to this pref affect CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
 // See the comment in CertVerifier.cpp.
-// 3 = allow SHA-1 for certificates issued before 2016 or by an imported root.
-pref("security.pki.sha1_enforcement_level", 3);
+// 4 = allow SHA-1 for certificates issued before 2016 or by an imported root.
+pref("security.pki.sha1_enforcement_level", 4);
 
 // security.pki.name_matching_mode controls how the platform matches hostnames
 // to name information in TLS certificates. The possible values are:
 // 0: always fall back to the subject common name if necessary (as in, if the
 //    subject alternative name extension is either not present or does not
 //    contain any DNS names or IP addresses)
 // 1: fall back to the subject common name for certificates valid before 23
 //    August 2016 if necessary
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -147,19 +147,19 @@ BuildCertChainForOneKeyUsage(NSSCertDBTr
 }
 
 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::Forbidden && mode != SHA1Mode::ImportedRoot;
+    case SHA1Mode::ImportedRootOrBefore2016:
       return mode == SHA1Mode::Allowed;
     case SHA1Mode::Allowed:
       return false;
     default:
       MOZ_ASSERT(false, "unexpected SHA1Mode type");
       return true;
   }
 }
@@ -278,25 +278,25 @@ CertVerifier::VerifyCert(CERTCertificate
       // restrict the acceptable key usage based on the key exchange method
       // chosen by the server.
 
       // 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::ImportedRootOrBefore2016,
         SHA1Mode::Allowed,
       };
 
       SHA1ModeResult sha1ModeResults[] = {
         SHA1ModeResult::SucceededWithoutSHA1,
-        SHA1ModeResult::SucceededWithSHA1Before2016,
         SHA1ModeResult::SucceededWithImportedRoot,
+        SHA1ModeResult::SucceededWithImportedRootOrSHA1Before2016,
         SHA1ModeResult::SucceededWithSHA1,
       };
 
       size_t sha1ModeConfigurationsCount = MOZ_ARRAY_LENGTH(sha1ModeConfigurations);
 
       static_assert(MOZ_ARRAY_LENGTH(sha1ModeConfigurations) ==
                     MOZ_ARRAY_LENGTH(sha1ModeResults),
                     "digestAlgorithm array lengths differ");
@@ -341,25 +341,16 @@ CertVerifier::VerifyCert(CERTCertificate
                       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) {
@@ -433,25 +424,16 @@ CertVerifier::VerifyCert(CERTCertificate
           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) {
@@ -471,33 +453,36 @@ CertVerifier::VerifyCert(CERTCertificate
 
       if (rv == Success) {
         break;
       }
 
       if (keySizeStatus) {
         *keySizeStatus = KeySizeStatus::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::ImportedRoot) {
+      // The telemetry probe CERT_CHAIN_SHA1_POLICY_STATUS gives us feedback on
+      // the result of setting a specific policy. However, we don't want noise
+      // from users who have manually set the policy to Allowed or Forbidden, so
+      // we only collect for ImportedRoot or ImportedRootOrBefore2016.
+      if (sha1ModeResult &&
+          (mSHA1Mode == SHA1Mode::ImportedRoot ||
+           mSHA1Mode == SHA1Mode::ImportedRootOrBefore2016)) {
         *sha1ModeResult = SHA1ModeResult::Failed;
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       mSHA1Mode, mNetscapeStepUpPolicy,
+                                       SHA1Mode::Allowed, mNetscapeStepUpPolicy,
                                        builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -22,18 +22,18 @@ enum class KeySizeStatus {
   CompatibilityRisk = 2,
   AlreadyBad = 3,
 };
 
 // These values correspond to the CERT_CHAIN_SHA1_POLICY_STATUS telemetry.
 enum class SHA1ModeResult {
   NeverChecked = 0,
   SucceededWithoutSHA1 = 1,
-  SucceededWithSHA1Before2016 = 2,
-  SucceededWithImportedRoot = 3,
+  SucceededWithImportedRoot = 2,
+  SucceededWithImportedRootOrSHA1Before2016 = 3,
   SucceededWithSHA1 = 4,
   Failed = 5,
 };
 
 enum class NetscapeStepUpPolicy : uint32_t;
 
 class PinningTelemetryInfo
 {
@@ -105,18 +105,22 @@ public:
     pinningAllowUserCAMITM = 1,
     pinningStrict = 2,
     pinningEnforceTestMode = 3
   };
 
   enum class SHA1Mode {
     Allowed = 0,
     Forbidden = 1,
-    Before2016 = 2,
+    // There used to be a policy that only allowed SHA1 for certificates issued
+    // before 2016. This is no longer available. If a user has selected this
+    // policy in about:config, it now maps to Forbidden.
+    UsedToBeBefore2016ButNowIsForbidden = 2,
     ImportedRoot = 3,
+    ImportedRootOrBefore2016 = 4,
   };
 
   enum OcspDownloadConfig {
     ocspOff = 0,
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
@@ -140,18 +144,18 @@ public:
   const BRNameMatchingPolicy::Mode mNameMatchingMode;
   const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
 
 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.)
+  // Forbidden. Next is ImportedRoot, then ImportedRootOrBefore2016, then
+  // Allowed. (A mode is never more restrictive than itself.)
   bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
 };
 
 mozilla::pkix::Result 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
@@ -830,17 +830,17 @@ NSSCertDBTrustDomain::CheckSignatureDige
 
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
           ("NSSCertDBTrustDomain: CheckSignatureDigestAlgorithm"));
   if (aAlg == DigestAlgorithm::sha1) {
     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::Before2016:
+      case CertVerifier::SHA1Mode::ImportedRootOrBefore2016:
         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.
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1511,24 +1511,30 @@ void nsNSSComponent::setValidationOption
   }
 
   CertVerifier::SHA1Mode sha1Mode = static_cast<CertVerifier::SHA1Mode>
       (Preferences::GetInt("security.pki.sha1_enforcement_level",
                            static_cast<int32_t>(CertVerifier::SHA1Mode::Allowed)));
   switch (sha1Mode) {
     case CertVerifier::SHA1Mode::Allowed:
     case CertVerifier::SHA1Mode::Forbidden:
-    case CertVerifier::SHA1Mode::Before2016:
+    case CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden:
     case CertVerifier::SHA1Mode::ImportedRoot:
+    case CertVerifier::SHA1Mode::ImportedRootOrBefore2016:
       break;
     default:
       sha1Mode = CertVerifier::SHA1Mode::Allowed;
       break;
   }
 
+  // Convert a previously-available setting to a safe one.
+  if (sha1Mode == CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden) {
+    sha1Mode = CertVerifier::SHA1Mode::Forbidden;
+  }
+
   BRNameMatchingPolicy::Mode nameMatchingMode =
     static_cast<BRNameMatchingPolicy::Mode>
       (Preferences::GetInt("security.pki.name_matching_mode",
                            static_cast<int32_t>(BRNameMatchingPolicy::Mode::DoNotEnforce)));
   switch (nameMatchingMode) {
     case BRNameMatchingPolicy::Mode::Enforce:
     case BRNameMatchingPolicy::Mode::EnforceAfter23August2015:
     case BRNameMatchingPolicy::Mode::EnforceAfter23August2016:
--- a/security/manager/ssl/tests/unit/test_cert_sha1.js
+++ b/security/manager/ssl/tests/unit/test_cert_sha1.js
@@ -26,102 +26,117 @@ function loadCertWithTrust(certName, tru
   addCertFromFile(certdb, "test_cert_sha1/" + certName + ".pem", trustString);
 }
 
 function checkEndEntity(cert, expectedResult) {
   checkCertErrorGenericAtTime(certdb, cert, expectedResult,
                               certificateUsageSSLServer, VALIDATION_TIME);
 }
 
-function checkIntermediate(cert, expectedResult) {
-  checkCertErrorGenericAtTime(certdb, cert, expectedResult,
-                              certificateUsageSSLCA, VALIDATION_TIME);
-}
-
 function run_test() {
   loadCertWithTrust("ca", "CTu,,");
   loadCertWithTrust("int-pre", ",,");
   loadCertWithTrust("int-post", ",,");
 
   // Test cases per pref setting
   //
   // root  intermed.  end entity
   // ===========================
   // root
   //  |
-  //  +--- pre-2016             <--- (a)
+  //  +--- pre-2016
   //  |       |
-  //  |       +----- pre-2016   <--- (b)
-  //  |       +----- post-2016  <--- (c)
+  //  |       +----- pre-2016   <--- (a)
+  //  |       +----- post-2016  <--- (b)
   //  |
-  //  +--- post-2016            <--- (d)
+  //  +--- post-2016
   //          |
-  //          +----- post-2016  <--- (e)
+  //          +----- post-2016  <--- (c)
   //
   // Expected outcomes (accept / reject):
   //
-  //                     a   b   c   d   e
-  // Allowed=0          Acc Acc Acc Acc Acc
-  // Forbidden=1        Rej Rej Rej Rej Rej
-  // Before2016=2       Acc Acc Rej Rej Rej
+  //                              a      b      c
+  // Allowed (0)                  Accept Accept Accept
+  // Forbidden (1)                Reject Reject Reject
+  // (2) is no longer available and is treated as Forbidden (1) internally.
+  // ImportedRoot (3)             Reject Reject Reject (for built-in roots)
+  // ImportedRoot                 Accept Accept Accept (for non-built-in roots)
+  // ImportedRootOrBefore2016 (4) Accept Reject Reject (for built-in roots)
+  // ImportedRootOrBefore2016     Accept Accept Accept (for non-built-in roots)
+  //
+  // The pref setting of ImportedRoot accepts usage of SHA-1 only for
+  // certificates issued by non-built-in roots. By default, the testing
+  // certificates are all considered issued by a non-built-in root. However, we
+  // have the ability to treat a given non-built-in root as built-in. We test
+  // both of these situations below.
   //
-  // The pref setting of ImportedRoot (3) accepts usage of SHA-1 for
-  // certificates valid before 2016 issued by built-in roots or SHA-1 for
-  // certificates issued any time by non-built-in roots. By default, the testing
-  // certificates are all considered issued by a non-built-in root. However, we
-  // now have the ability to treat a given non-built-in root as built-in. We
-  // test both of these situations below.
+  // As a historical note, a policy option (Before2016) was previously available
+  // that only allowed SHA-1 for certificates with a notBefore before 2016.
+  // However, to enable the policy of only allowing SHA-1 from non-built-in
+  // roots in the most straightforward way (while still having a time-based
+  // policy that users could enable if this new policy were problematic),
+  // Before2016 was shifted to also allow SHA-1 from non-built-in roots, hence
+  // ImportedRootOrBefore2016.
+  //
+  // A note about intermediate certificates: the certificate verifier has the
+  // ability to directly verify a given certificate for the purpose of issuing
+  // TLS web server certificates. However, when asked to do so, the certificate
+  // verifier does not take into account the currently configured SHA-1 policy.
+  // This is in part due to implementation complexity and because this isn't
+  // actually how TLS web server certificates are verified in the TLS handshake
+  // (which makes a full implementation that supports heeding the SHA-1 policy
+  // unnecessary).
 
   // 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);
 
   // SHA-1 forbidden
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 1);
-  checkIntermediate(certFromFile("int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   checkEndEntity(certFromFile("ee-pre_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   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
+  // SHA-1 forbidden (test the case where the pref has been set to 2)
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 2);
-  checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
-  checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+  checkEndEntity(certFromFile("ee-pre_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
   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. First
-  // test with the test root considered a built-in (on debug only - this
-  // functionality is disabled on non-debug builds).
+  // SHA-1 allowed only when issued by an imported root. First test with the
+  // test root considered a built-in (on debug only - this functionality is
+  // disabled on non-debug builds).
   Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 3);
   if (isDebugBuild) {
     let root = certFromFile("ca");
     Services.prefs.setCharPref("security.test.built_in_root_hash", root.sha256Fingerprint);
-    checkIntermediate(certFromFile("int-pre"), PRErrorCodeSuccess);
-    checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+    checkEndEntity(certFromFile("ee-pre_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
     checkEndEntity(certFromFile("ee-post_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
-    // This should fail but it doesn't, because the implementation makes no
-    // effort to enforce that when verifying a certificate for the capability
-    // of issuing TLS server auth certificates (i.e. the
-    // "certificateUsageSSLCA" usage), if SHA-1 was necessary, then the root of
-    // trust is an imported certificate. We don't really care, though, because
-    // the platform doesn't actually make trust decisions in this way and the
-    // ability to even verify a certificate for this purpose is intended to go
-    // away in bug 1257362.
-    // checkIntermediate(certFromFile("int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
     checkEndEntity(certFromFile("ee-post_int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
     Services.prefs.clearUserPref("security.test.built_in_root_hash");
   }
 
-  // SHA-1 still allowed only before 2016 or when issued by an imported root.
+  // SHA-1 still allowed only when issued by an imported root.
   // Now test with the test root considered a non-built-in.
-  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);
+
+  // SHA-1 allowed before 2016 or when issued by an imported root. First test
+  // with the test root considered a built-in.
+  Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 4);
+  if (isDebugBuild) {
+    let root = certFromFile("ca");
+    Services.prefs.setCharPref("security.test.built_in_root_hash", root.sha256Fingerprint);
+    checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+    checkEndEntity(certFromFile("ee-post_int-pre"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+    checkEndEntity(certFromFile("ee-post_int-post"), SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED);
+    Services.prefs.clearUserPref("security.test.built_in_root_hash");
+  }
+
+  // SHA-1 still only allowed before 2016 or when issued by an imported root.
+  // Now test with the test root considered a non-built-in.
+  checkEndEntity(certFromFile("ee-pre_int-pre"), PRErrorCodeSuccess);
+  checkEndEntity(certFromFile("ee-post_int-pre"), PRErrorCodeSuccess);
   checkEndEntity(certFromFile("ee-post_int-post"), PRErrorCodeSuccess);
 }
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -57,17 +57,17 @@ function add_ocsp_test(aHost, aExpectedR
               " OCSP request" + (aResponses.length == 1 ? "" : "s"));
       });
 }
 
 function run_test() {
   do_get_profile();
   Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
   Services.prefs.setIntPref("security.OCSP.enabled", 1);
-  Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 3);
+  Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 4);
   add_tls_server_setup("OCSPStaplingServer", "ocsp_certs");
 
   let ocspResponder = new HttpServer();
   ocspResponder.registerPrefixHandler("/", function(request, response) {
 
     do_print("gFetchCount: " + gFetchCount);
     let responseFunction = gResponsePattern[gFetchCount];
     Assert.notEqual(undefined, responseFunction);
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
@@ -26,17 +26,17 @@ function add_ocsp_test(aHost, aExpectedR
             "Should have made " + aExpectedRequestCount +
             " fallback OCSP request" + (aExpectedRequestCount == 1 ? "" : "s"));
     });
 }
 
 do_get_profile();
 Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
 Services.prefs.setIntPref("security.OCSP.enabled", 1);
-Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 3);
+Services.prefs.setIntPref("security.pki.sha1_enforcement_level", 4);
 var args = [["good", "default-ee", "unused"],
              ["expiredresponse", "default-ee", "unused"],
              ["oldvalidperiod", "default-ee", "unused"],
              ["revoked", "default-ee", "unused"],
              ["unknown", "default-ee", "unused"],
             ];
 var ocspResponses = generateOCSPResponses(args, "ocsp_certs");
 // Fresh response, certificate is good.
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8171,17 +8171,17 @@
     "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_SHA1_POLICY_STATUS": {
     "expires_in_version": "default",
     "kind": "enumerated",
     "n_values": 6,
-    "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"
+    "description": "1 = No SHA1 signatures, 2 = SHA1 certificates issued by an imported root, 3 = SHA1 certificates issued before 2016, 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": {