bug 1302140 - add policy to disable SHA-1 except for certificates issued by non-built-in CAs r?jcj,rbarnes draft
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 14 Sep 2016 15:11:15 -0700
changeset 416338 8bd20da6ad55c8a246c31381a0eb21e66d1ebebe
parent 415108 fd0564234eca242b7fb753a110312679020f8059
child 531824 6c643683b864d122e04c64a08240469460874eeb
push id30110
push userdkeeler@mozilla.com
push dateWed, 21 Sep 2016 22:32:51 +0000
reviewersjcj, rbarnes
bugs1302140
milestone52.0a1
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,22 +147,24 @@ 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;
+    // MSVC warns unless we explicitly handle this now-unused option.
+    case SHA1Mode::UsedToBeBefore2016ButNowIsForbidden:
     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;
@@ -278,25 +280,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 +343,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 +426,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 +455,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,28 +830,32 @@ 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.
       case CertVerifier::SHA1Mode::ImportedRoot:
       default:
         break;
+      // MSVC warns unless we explicitly handle this now-unused option.
+      case CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden:
+        MOZ_ASSERT_UNREACHABLE("unexpected SHA1Mode type");
+        return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
   }
 
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
--- 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
@@ -8079,17 +8079,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": {