Bug 942515 - Show Untrusted Connection Error for SHA-1-based SSL certificates with notBefore >= 2016-01-01 r=keeler
authorRichard Barnes <rbarnes@mozilla.com>
Fri, 11 Sep 2015 14:52:30 -0400
changeset 262208 0516d4db29a9d76361dd51331036e0b059b4dd60
parent 262207 8a316d4c6458cfda9ef83b9d0aa0feea44a0adff
child 262209 bdb5b24b1f4e39412022f62354e6de3911cb34eb
push id29366
push userphilringnalda@gmail.com
push dateSun, 13 Sep 2015 18:58:26 +0000
treeherdermozilla-central@9ed17db42e3e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs942515
milestone43.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 942515 - Show Untrusted Connection Error for SHA-1-based SSL certificates with notBefore >= 2016-01-01 r=keeler
browser/app/profile/firefox.js
mobile/android/app/mobile.js
security/apps/AppTrustDomain.cpp
security/apps/AppTrustDomain.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/certverifier/OCSPVerificationTrustDomain.cpp
security/certverifier/OCSPVerificationTrustDomain.h
security/manager/ssl/SharedCertVerifier.h
security/manager/ssl/nsIX509CertDB.idl
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/moz.build
security/manager/ssl/tests/unit/pycert.py
security/manager/ssl/tests/unit/pykey.py
security/manager/ssl/tests/unit/test_cert_sha1.js
security/manager/ssl/tests/unit/test_cert_sha1/ca.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/ee-post_int-post.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/ee-post_int-pre.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/ee-pre_int-pre.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/int-post.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/int-pre.pem.certspec
security/manager/ssl/tests/unit/test_cert_sha1/moz.build
security/manager/ssl/tests/unit/xpcshell.ini
security/pkix/include/pkix/pkixtypes.h
security/pkix/lib/pkixbuild.cpp
security/pkix/lib/pkixcheck.cpp
security/pkix/lib/pkixcheck.h
security/pkix/test/gtest/moz.build
security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
security/pkix/test/gtest/pkixgtest.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1742,16 +1742,19 @@ pref("social.shareDirectory", "https://a
 pref("dom.identity.enabled", false);
 
 // Block insecure active content on https pages
 pref("security.mixed_content.block_active_content", true);
 
 // 1 = allow MITM for certificate pinning checks.
 pref("security.cert_pinning.enforcement_level", 1);
 
+// 2 = allow SHA-1 only before 2016-01-01
+pref("security.pki.sha1_enforcement_level", 2);
+
 // 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.
 pref("plain_text.wrap_long_lines", true);
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -479,16 +479,19 @@ 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);
 
+// Allow SHA-1 certificates only before 2016-01-01
+pref("security.pki.sha1_enforcement_level", 2);
+
 // 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
 pref("security.OCSP.enabled", 2);
 
 // Override some named colors to avoid inverse OS themes
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -240,17 +240,19 @@ AppTrustDomain::IsChainValid(const DERAr
                                                             mCertChain);
   if (srv != SECSuccess) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
   return Success;
 }
 
 Result
-AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA)
+AppTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm,
+                                              EndEntityOrCA,
+                                              Time)
 {
   // TODO: We should restrict signatures to SHA-256 or better.
   return Success;
 }
 
 Result
 AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
   EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -36,17 +36,18 @@ public:
                                  mozilla::pkix::Time time,
                                  mozilla::pkix::Duration validityDuration,
                     /*optional*/ const mozilla::pkix::Input* stapledOCSPresponse,
                     /*optional*/ const mozilla::pkix::Input* aiaExtension) override;
   virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
                               mozilla::pkix::Time time) override;
   virtual Result CheckSignatureDigestAlgorithm(
                    mozilla::pkix::DigestAlgorithm digestAlg,
-                   mozilla::pkix::EndEntityOrCA endEntityOrCA) override;
+                   mozilla::pkix::EndEntityOrCA endEntityOrCA,
+                   mozilla::pkix::Time notBefore) override;
   virtual Result CheckRSAPublicKeyModulusSizeInBits(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
                    unsigned int modulusSizeInBits) override;
   virtual Result VerifyRSAPKCS1SignedDigest(
                    const mozilla::pkix::SignedDigest& signedDigest,
                    mozilla::pkix::Input subjectPublicKeyInfo) override;
   virtual Result CheckECDSACurveIsAcceptable(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -28,22 +28,24 @@ namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
                            uint32_t certShortLifetimeInDays,
-                           PinningMode pinningMode)
+                           PinningMode pinningMode,
+                           SHA1Mode sha1Mode)
   : mOCSPDownloadConfig(odc)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
+  , mSHA1Mode(sha1Mode)
 {
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
 void
@@ -208,18 +210,18 @@ 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, nullptr, nullptr,
-                                       builtChain);
+                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                       nullptr, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -268,18 +270,18 @@ CertVerifier::VerifyCert(CERTCertificate
         if (pinningTelemetryInfo) {
           pinningTelemetryInfo->Reset();
         }
         NSSCertDBTrustDomain
           trustDomain(trustSSL, evOCSPFetching,
                       mOCSPCache, pinArg, ocspGETConfig,
                       mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
                       ValidityCheckingMode::CheckForEV,
-                      digestAlgorithmOptions[i], pinningTelemetryInfo, hostname,
-                      builtChain);
+                      digestAlgorithmOptions[i], mSHA1Mode,
+                      pinningTelemetryInfo, hostname, builtChain);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
         if (rv == Success) {
@@ -323,23 +325,29 @@ CertVerifier::VerifyCert(CERTCertificate
       for (size_t i=0; i<keySizeOptionsCount && rv != Success; i++) {
         for (size_t j=0; j<digestAlgorithmOptionsCount && 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) {
+            continue;
+          }
+
           NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                            mOCSPCache, pinArg, ocspGETConfig,
                                            mCertShortLifetimeInDays,
                                            mPinningMode, keySizeOptions[i],
                                            ValidityCheckingMode::CheckingOff,
                                            digestAlgorithmOptions[j],
-                                           pinningTelemetryInfo,
+                                           mSHA1Mode, pinningTelemetryInfo,
                                            hostname, builtChain);
           rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                             KeyUsage::digitalSignature,//(EC)DHE
                                             KeyUsage::keyEncipherment,//RSA
                                             KeyUsage::keyAgreement,//(EC)DH
                                             KeyPurposeId::id_kp_serverAuth,
                                             CertPolicyId::anyPolicy,
                                             stapledOCSPResponse,
@@ -351,52 +359,58 @@ CertVerifier::VerifyCert(CERTCertificate
             if (sigDigestStatus) {
               *sigDigestStatus = digestAlgorithmStatuses[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) {
+      if (sigDigestStatus && mSHA1Mode != SHA1Mode::Forbidden) {
         *sigDigestStatus = SignatureDigestStatus::AlreadyBad;
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, nullptr, nullptr,
-                                       builtChain);
+                                       AcceptAllAlgorithms, mSHA1Mode,
+                                       nullptr, nullptr, builtChain);
       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, nullptr, nullptr,
-                                       builtChain);
+                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                       nullptr, nullptr, builtChain);
       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,
@@ -411,18 +425,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, nullptr, nullptr,
-                                       builtChain);
+                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                       nullptr, nullptr, builtChain);
       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,
@@ -434,18 +448,18 @@ CertVerifier::VerifyCert(CERTCertificate
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
-                                       AcceptAllAlgorithms, nullptr, nullptr,
-                                       builtChain);
+                                       AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                       nullptr, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -466,41 +480,41 @@ 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, nullptr, nullptr,
-                                    builtChain);
+                                    AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                    nullptr, nullptr, builtChain);
       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, nullptr, nullptr,
-                                        builtChain);
+                                        AcceptAllAlgorithms, SHA1Mode::Allowed,
+                                        nullptr, nullptr, builtChain);
         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,
+                                                  AcceptAllAlgorithms, SHA1Mode::Allowed,
                                                   nullptr, nullptr, builtChain);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
 
       break;
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -97,36 +97,43 @@ public:
 
   enum PinningMode {
     pinningDisabled = 0,
     pinningAllowUserCAMITM = 1,
     pinningStrict = 2,
     pinningEnforceTestMode = 3
   };
 
+  enum class SHA1Mode {
+    Allowed = 0,
+    Forbidden = 1,
+    OnlyBefore2016 = 2
+  };
+
   enum OcspDownloadConfig {
     ocspOff = 0,
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-               PinningMode pinningMode);
+               PinningMode pinningMode, SHA1Mode sha1Mode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
+  const SHA1Mode mSHA1Mode;
 
 private:
   OCSPCache mOCSPCache;
 };
 
 void InitCertVerifierLog();
 SECStatus IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
 mozilla::pkix::Result CertListContainsExpectedKeys(
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -46,29 +46,31 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
                                            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,
                               /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
                               /*optional*/ const char* hostname,
                               /*optional*/ ScopedCERTCertList* builtChain)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mValidityCheckingMode(validityCheckingMode)
   , mSignatureDigestOption(signatureDigestOption)
+  , mSHA1Mode(sha1Mode)
   , mPinningTelemetryInfo(pinningTelemetryInfo)
   , mHostname(hostname)
   , mBuiltChain(builtChain)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
 {
 }
 
@@ -808,32 +810,54 @@ NSSCertDBTrustDomain::IsChainValid(const
     *mBuiltChain = certList.forget();
   }
 
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm aAlg,
-                                                    EndEntityOrCA endEntityOrCA)
+                                                    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:
+        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:
+      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 SHA1"));
+      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 SHA1"));
+      MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("EE cert is SHA-1"));
       return mSignatureDigestOption == DisableSHA1ForEE
              ? Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
              : Success;
     }
   }
   return Success;
 }
 
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -64,34 +64,36 @@ 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 signatureDigestOption,
+                       CertVerifier::SHA1Mode sha1Mode,
           /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
           /*optional*/ const char* hostname = nullptr,
       /*optional out*/ ScopedCERTCertList* builtChain = 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,
                               /*out*/ mozilla::pkix::TrustLevel& trustLevel)
                               override;
 
   virtual Result CheckSignatureDigestAlgorithm(
                    mozilla::pkix::DigestAlgorithm digestAlg,
-                   mozilla::pkix::EndEntityOrCA endEntityOrCA) override;
+                   mozilla::pkix::EndEntityOrCA endEntityOrCA,
+                   mozilla::pkix::Time notBefore) override;
 
   virtual Result CheckRSAPublicKeyModulusSizeInBits(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
                    unsigned int modulusSizeInBits) override;
 
   virtual Result VerifyRSAPKCS1SignedDigest(
                    const mozilla::pkix::SignedDigest& signedDigest,
                    mozilla::pkix::Input subjectPublicKeyInfo) override;
@@ -150,16 +152,17 @@ private:
   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;
   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
--- a/security/certverifier/OCSPVerificationTrustDomain.cpp
+++ b/security/certverifier/OCSPVerificationTrustDomain.cpp
@@ -46,17 +46,17 @@ OCSPVerificationTrustDomain::CheckRevoca
                                              const Input*)
 {
   // We do not expect this to be called for OCSP signers
   return Result::FATAL_ERROR_LIBRARY_FAILURE;
 }
 
 Result
 OCSPVerificationTrustDomain::CheckSignatureDigestAlgorithm(
-  DigestAlgorithm aAlg, EndEntityOrCA aEEOrCA)
+  DigestAlgorithm aAlg, EndEntityOrCA aEEOrCA, Time notBefore)
 {
   // The reason for wrapping the NSSCertDBTrustDomain in an
   // OCSPVerificationTrustDomain is to allow us to bypass the weaker signature
   // algorithm check - thus all allowable signature digest algorithms should
   // always be accepted. This is only needed while we gather telemetry on SHA-1.
   return Success;
 }
 
--- a/security/certverifier/OCSPVerificationTrustDomain.h
+++ b/security/certverifier/OCSPVerificationTrustDomain.h
@@ -24,17 +24,18 @@ public:
   virtual Result GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                               const mozilla::pkix::CertPolicyId& policy,
                               mozilla::pkix::Input candidateCertDER,
                       /*out*/ mozilla::pkix::TrustLevel& trustLevel)
                               override;
 
   virtual Result CheckSignatureDigestAlgorithm(
                    mozilla::pkix::DigestAlgorithm digestAlg,
-                   mozilla::pkix::EndEntityOrCA endEntityOrCA) override;
+                   mozilla::pkix::EndEntityOrCA endEntityOrCA,
+                   mozilla::pkix::Time notBefore) override;
 
   virtual Result CheckRSAPublicKeyModulusSizeInBits(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
                    unsigned int modulusSizeInBits) override;
 
   virtual Result VerifyRSAPKCS1SignedDigest(
                    const mozilla::pkix::SignedDigest& signedDigest,
                    mozilla::pkix::Input subjectPublicKeyInfo) override;
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -16,18 +16,18 @@ class SharedCertVerifier : public mozill
 protected:
   ~SharedCertVerifier();
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                      OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
-                     PinningMode pinningMode)
+                     PinningMode pinningMode, SHA1Mode sha1Mode)
     : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
-                                 pinningMode)
+                                 pinningMode, sha1Mode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/nsIX509CertDB.idl
+++ b/security/manager/ssl/nsIX509CertDB.idl
@@ -41,17 +41,17 @@ interface nsIVerifySignedManifestCallbac
   void verifySignedManifestFinished(in nsresult rv,
                                     in nsIX509Cert aSignerCert);
 };
 
 /**
  * This represents a service to access and manipulate
  * X.509 certificates stored in a database.
  */
-[scriptable, uuid(c9fdec46-5c4c-4b1d-a0ca-c2bc10151b69)]
+[scriptable, uuid(3fe3702b-766b-47dd-8f77-c08c3a339a74)]
 interface nsIX509CertDB : nsISupports {
 
   /**
    *  Constants that define which usages a certificate
    *  is trusted for.
    */
   const unsigned long UNTRUSTED       =      0;
   const unsigned long TRUSTED_SSL     = 1 << 0;
@@ -380,22 +380,31 @@ interface nsIX509CertDB : nsISupports {
    *  On success, the call returns 0, the chain built during verification,
    *  and whether the cert is good for EV usage.
    *  On failure, the call returns the PRErrorCode for the verification failure
    *
    *  @param aCert Obtain the stored trust of this certificate
    *  @param aUsage a integer representing the usage from NSS
    *  @param aFlags flags as described above
    *  @param aHostname the (optional) hostname to verify for
+   *  @param aTime the time at which to verify, in seconds since the epoch
    *  @param aVerifiedChain chain of verification up to the root if success
    *  @param aHasEVPolicy bool that signified that the cert was an EV cert
    *  @return 0 if success or the value or the error code for the verification
    *          failure
    */
   int32_t /*PRErrorCode*/
+    verifyCertAtTime(in nsIX509Cert aCert,
+                     in int64_t /*SECCertificateUsage*/ aUsage,
+                     in uint32_t aFlags,
+                     in string aHostname,
+                     in uint64_t aTime,
+                     out nsIX509CertList aVerifiedChain,
+                     out bool aHasEVPolicy);
+  int32_t /*PRErrorCode*/
     verifyCertNow(in nsIX509Cert aCert,
                   in int64_t /*SECCertificateUsage*/ aUsage,
                   in uint32_t aFlags,
                   in string aHostname,
                   out nsIX509CertList aVerifiedChain,
                   out bool aHasEVPolicy);
 
   // Clears the OCSP cache for the current certificate verification
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -9,16 +9,17 @@
 
 #include "nsNSSComponent.h"
 #include "nsNSSCertificateDB.h"
 
 #include "CertVerifier.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "pkix/pkixtypes.h"
+#include "pkix/Time.h"
 #include "nsNSSComponent.h"
 #include "mozilla/Base64.h"
 #include "nsCOMPtr.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSHelper.h"
 #include "nsNSSCertHelper.h"
 #include "nsCRT.h"
 #include "nsICertificateDialogs.h"
@@ -1693,39 +1694,36 @@ nsNSSCertificateDB::GetCerts(nsIX509Cert
   // nsNSSCertList 1) adopts certList, and 2) handles the nullptr case fine.
   // (returns an empty list) 
   nssCertList = new nsNSSCertList(certList, locker);
 
   nssCertList.forget(_retval);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsNSSCertificateDB::VerifyCertNow(nsIX509Cert* aCert,
-                                  int64_t /*SECCertificateUsage*/ aUsage,
-                                  uint32_t aFlags,
-                                  const char* aHostname,
-                                  nsIX509CertList** aVerifiedChain,
-                                  bool* aHasEVPolicy,
-                                  int32_t* /*PRErrorCode*/ _retval )
+nsresult
+VerifyCertAtTime(nsIX509Cert* aCert,
+                 int64_t /*SECCertificateUsage*/ aUsage,
+                 uint32_t aFlags,
+                 const char* aHostname,
+                 mozilla::pkix::Time aTime,
+                 nsIX509CertList** aVerifiedChain,
+                 bool* aHasEVPolicy,
+                 int32_t* /*PRErrorCode*/ _retval,
+                 const nsNSSShutDownPreventionLock& locker)
 {
   NS_ENSURE_ARG_POINTER(aCert);
   NS_ENSURE_ARG_POINTER(aHasEVPolicy);
   NS_ENSURE_ARG_POINTER(aVerifiedChain);
   NS_ENSURE_ARG_POINTER(_retval);
 
   *aVerifiedChain = nullptr;
   *aHasEVPolicy = false;
   *_retval = PR_UNKNOWN_ERROR;
 
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
 #ifndef MOZ_NO_EV_CERTS
   EnsureIdentityInfoLoaded();
 #endif
 
   ScopedCERTCertificate nssCert(aCert->GetCert());
   if (!nssCert) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -1735,25 +1733,25 @@ nsNSSCertificateDB::VerifyCertNow(nsIX50
 
   ScopedCERTCertList resultChain;
   SECOidTag evOidPolicy;
   SECStatus srv;
 
   if (aHostname && aUsage == certificateUsageSSLServer) {
     srv = certVerifier->VerifySSLServerCert(nssCert,
                                             nullptr, // stapledOCSPResponse
-                                            mozilla::pkix::Now(),
+                                            aTime,
                                             nullptr, // Assume no context
                                             aHostname,
                                             false, // don't save intermediates
                                             aFlags,
                                             &resultChain,
                                             &evOidPolicy);
   } else {
-    srv = certVerifier->VerifyCert(nssCert, aUsage, mozilla::pkix::Now(),
+    srv = certVerifier->VerifyCert(nssCert, aUsage, aTime,
                                    nullptr, // Assume no context
                                    aHostname,
                                    aFlags,
                                    nullptr, // stapledOCSPResponse
                                    &resultChain,
                                    &evOidPolicy);
   }
 
@@ -1775,16 +1773,55 @@ nsNSSCertificateDB::VerifyCertNow(nsIX50
     *_retval = error;
   }
   nssCertList.forget(aVerifiedChain);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsNSSCertificateDB::VerifyCertNow(nsIX509Cert* aCert,
+                                  int64_t /*SECCertificateUsage*/ aUsage,
+                                  uint32_t aFlags,
+                                  const char* aHostname,
+                                  nsIX509CertList** aVerifiedChain,
+                                  bool* aHasEVPolicy,
+                                  int32_t* /*PRErrorCode*/ _retval)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return ::VerifyCertAtTime(aCert, aUsage, aFlags, aHostname,
+                            mozilla::pkix::Now(),
+                            aVerifiedChain, aHasEVPolicy, _retval, locker);
+}
+
+NS_IMETHODIMP
+nsNSSCertificateDB::VerifyCertAtTime(nsIX509Cert* aCert,
+                                     int64_t /*SECCertificateUsage*/ aUsage,
+                                     uint32_t aFlags,
+                                     const char* aHostname,
+                                     uint64_t aTime,
+                                     nsIX509CertList** aVerifiedChain,
+                                     bool* aHasEVPolicy,
+                                     int32_t* /*PRErrorCode*/ _retval)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return ::VerifyCertAtTime(aCert, aUsage, aFlags, aHostname,
+                            mozilla::pkix::TimeFromEpochInSeconds(aTime),
+                            aVerifiedChain, aHasEVPolicy, _retval, locker);
+}
+
+NS_IMETHODIMP
 nsNSSCertificateDB::ClearOCSPCache()
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -883,26 +883,33 @@ void nsNSSComponent::setValidationOption
   CertVerifier::PinningMode pinningMode =
     static_cast<CertVerifier::PinningMode>
       (Preferences::GetInt("security.cert_pinning.enforcement_level",
                            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;
+  }
+
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
   uint32_t certShortLifetimeInDays;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
                                  lock);
   mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
                                                 certShortLifetimeInDays,
-                                                pinningMode);
+                                                pinningMode, sha1Mode);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
 // TLS 1.2 (max) when the prefs aren't set or set to invalid values.
 nsresult
 nsNSSComponent::setEnabledTLSVersions()
 {
   // keep these values in sync with security-prefs.js
@@ -1365,17 +1372,18 @@ nsNSSComponent::Observe(nsISupports* aSu
                                                 ALPN_ENABLED_DEFAULT));
     } else if (prefName.Equals("security.ssl.disable_session_identifiers")) {
       ConfigureTLSSessionIdentifiers();
     } else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
                prefName.EqualsLiteral("security.OCSP.require") ||
                prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
                prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
-               prefName.EqualsLiteral("security.cert_pinning.enforcement_level")) {
+               prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
+               prefName.EqualsLiteral("security.pki.sha1_enforcement_level")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
     } else {
       clearSessionCache = false;
     }
     if (clearSessionCache)
       SSL_ClearSessionCache();
   }
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -150,16 +150,30 @@ function setCertTrust(cert, trustString)
 function getXPCOMStatusFromNSS(statusNSS) {
   let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
                            .getService(Ci.nsINSSErrorsService);
   return nssErrorsService.getXPCOMFromNSSError(statusNSS);
 }
 
 // certdb implements nsIX509CertDB. See nsIX509CertDB.idl for documentation.
 // In particular, hostname is optional.
+function checkCertErrorGenericAtTime(certdb, cert, expectedError, usage, time,
+                                     /*optional*/ hasEVPolicy,
+                                     /*optional*/ hostname) {
+  do_print(`cert cn=${cert.commonName}`);
+  do_print(`cert issuer cn=${cert.issuerCommonName}`);
+  let verifiedChain = {};
+  let error = certdb.verifyCertAtTime(cert, usage, NO_FLAGS, hostname, time,
+                                      verifiedChain, hasEVPolicy || {});
+  Assert.equal(error, expectedError,
+               "Actual and expected error should match");
+}
+
+// certdb implements nsIX509CertDB. See nsIX509CertDB.idl for documentation.
+// In particular, hostname is optional.
 function checkCertErrorGeneric(certdb, cert, expectedError, usage,
                                /*optional*/ hasEVPolicy,
                                /*optional*/ hostname) {
   do_print(`cert cn=${cert.commonName}`);
   do_print(`cert issuer cn=${cert.issuerCommonName}`);
   let verifiedChain = {};
   let error = certdb.verifyCertNow(cert, usage, NO_FLAGS, hostname,
                                    verifiedChain, hasEVPolicy || {});
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += ['tlsserver']
 TEST_DIRS += [
     'test_cert_eku',
     'test_cert_embedded_null',
     'test_cert_keyUsage',
+    'test_cert_sha1',
     'test_cert_signatures',
     'test_cert_trust',
     'test_cert_version',
     'test_ev_certs',
     'test_intermediate_basic_usage_constraints',
     'test_keysize',
     'test_keysize_ev',
     'test_name_constraints',
--- a/security/manager/ssl/tests/unit/pycert.py
+++ b/security/manager/ssl/tests/unit/pycert.py
@@ -11,17 +11,17 @@ signed x509 certificate with the desired
 The input format is as follows:
 
 issuer:<issuer distinguished name specification>
 subject:<subject distinguished name specification>
 [version:{1,2,3,4}]
 [validity:<YYYYMMDD-YYYYMMDD|duration in days>]
 [issuerKey:<key specification>]
 [subjectKey:<key specification>]
-[signature:{sha256WithRSAEncryption,ecdsaWithSHA256}]
+[signature:{sha1WithRSAEncryption,sha256WithRSAEncryption,ecdsaWithSHA256}]
 [extension:<extension name:<extension-specific data>>]
 [...]
 
 Known extensions are:
 basicConstraints:[cA],[pathLenConstraint]
 keyUsage:[digitalSignature,nonRepudiation,keyEncipherment,
           dataEncipherment,keyAgreement,keyCertSign,cRLSign]
 extKeyUsage:[serverAuth,clientAuth,codeSigning,emailProtection
@@ -254,24 +254,32 @@ def stringToDN(string, tag=None):
     name.setComponentByPosition(0, rdns)
     return name
 
 def stringToAlgorithmIdentifier(string):
     """Helper function that converts a description of an algorithm
     to a representation usable by the pyasn1 package"""
     algorithmIdentifier = rfc2459.AlgorithmIdentifier()
     algorithm = None
-    if string == 'sha256WithRSAEncryption':
+    name = None
+    if string == 'sha1WithRSAEncryption':
+        name = 'SHA-1'
+        algorithm = rfc2459.sha1WithRSAEncryption
+    elif string == 'sha256WithRSAEncryption':
+        name = 'SHA-256'
         algorithm = univ.ObjectIdentifier('1.2.840.113549.1.1.11')
     elif string == 'ecdsaWithSHA256':
+        # Note that this value is only used by pykey.py to tell if
+        # ECDSA is allowed.  It does not conform to the pyECC syntax.
+        name = 'SHA-256'
         algorithm = univ.ObjectIdentifier('1.2.840.10045.4.3.2')
     else:
         raise UnknownAlgorithmTypeError(string)
     algorithmIdentifier.setComponentByName('algorithm', algorithm)
-    return algorithmIdentifier
+    return (algorithmIdentifier, name)
 
 def datetimeToTime(dt):
     """Takes a datetime object and returns an rfc2459.Time object with
     that time as its value as a GeneralizedTime"""
     time = rfc2459.Time()
     time.setComponentByName('generalTime', useful.GeneralizedTime(dt.strftime('%Y%m%d%H%M%SZ')))
     return time
 
@@ -532,17 +540,18 @@ class Certificate:
 
     def getNotAfter(self):
         return datetimeToTime(self.notAfter)
 
     def getSubject(self):
         return stringToDN(self.subject)
 
     def toDER(self):
-        signatureOID = self.getSignature()
+        (signatureOID, hashAlg) = self.getSignature()
+
         tbsCertificate = rfc2459.TBSCertificate()
         tbsCertificate.setComponentByName('version', self.getVersion())
         tbsCertificate.setComponentByName('serialNumber', self.getSerialNumber())
         tbsCertificate.setComponentByName('signature', signatureOID)
         tbsCertificate.setComponentByName('issuer', self.getIssuer())
         tbsCertificate.setComponentByName('validity', self.getValidity())
         tbsCertificate.setComponentByName('subject', self.getSubject())
         tbsCertificate.setComponentByName('subjectPublicKeyInfo',
@@ -554,17 +563,18 @@ class Certificate:
             for extension in self.extensions:
                 extensions.setComponentByPosition(count, extension)
                 count += 1
             tbsCertificate.setComponentByName('extensions', extensions)
         certificate = rfc2459.Certificate()
         certificate.setComponentByName('tbsCertificate', tbsCertificate)
         certificate.setComponentByName('signatureAlgorithm', signatureOID)
         tbsDER = encoder.encode(tbsCertificate)
-        certificate.setComponentByName('signatureValue', self.issuerKey.sign(tbsDER))
+
+        certificate.setComponentByName('signatureValue', self.issuerKey.sign(tbsDER, hashAlg))
         return encoder.encode(certificate)
 
     def toPEM(self):
         output = '-----BEGIN CERTIFICATE-----'
         der = self.toDER()
         b64 = base64.b64encode(der)
         while b64:
             output += '\n' + b64[:64]
--- a/security/manager/ssl/tests/unit/pykey.py
+++ b/security/manager/ssl/tests/unit/pykey.py
@@ -61,16 +61,22 @@ class UnknownBaseError(Exception):
 
 class UnknownKeySpecificationError(UnknownBaseError):
     """Helper exception type to handle unknown key specifications."""
 
     def __init__(self, value):
         UnknownBaseError.__init__(self, value)
         self.category = 'key specification'
 
+class ParameterError(UnknownBaseError):
+    """Exception type indicating that the key was misconfigured"""
+
+    def __init__(self, value):
+        UnknownBaseError.__init__(self, value)
+        self.category = 'key parameter'
 
 class RSAPublicKey(univ.Sequence):
     """Helper type for encoding an RSA public key"""
     componentType = namedtype.NamedTypes(
         namedtype.NamedType('N', univ.Integer()),
         namedtype.NamedType('E', univ.Integer()))
 
 
@@ -549,22 +555,22 @@ class RSAKey:
         spki.setComponentByName('algorithm', algorithmIdentifier)
         rsaKey = RSAPublicKey()
         rsaKey.setComponentByName('N', univ.Integer(self.RSA_N))
         rsaKey.setComponentByName('E', univ.Integer(self.RSA_E))
         subjectPublicKey = univ.BitString(byteStringToHexifiedBitString(encoder.encode(rsaKey)))
         spki.setComponentByName('subjectPublicKey', subjectPublicKey)
         return spki
 
-    def sign(self, data):
+    def sign(self, data, digest):
         """Returns a hexified bit string representing a
         signature by this key over the specified data.
         Intended for use with pyasn1.type.univ.BitString"""
         rsaPrivateKey = rsa.PrivateKey(self.RSA_N, self.RSA_E, self.RSA_D, self.RSA_P, self.RSA_Q)
-        signature = rsa.sign(data, rsaPrivateKey, 'SHA-256')
+        signature = rsa.sign(data, rsaPrivateKey, digest)
         return byteStringToHexifiedBitString(signature)
 
 
 ecPublicKey = univ.ObjectIdentifier('1.2.840.10045.2.1')
 secp256k1 = univ.ObjectIdentifier('1.3.132.0.10')
 secp224r1 = univ.ObjectIdentifier('1.3.132.0.33')
 secp256r1 = univ.ObjectIdentifier('1.2.840.10045.3.1.7')
 secp384r1 = univ.ObjectIdentifier('1.3.132.0.34')
@@ -660,20 +666,24 @@ class ECCKey:
         _, _, points = encoding.Decoder(encoded).int(8).int(2).point(2).out()
         # '04' indicates that the points are in uncompressed form.
         hexifiedBitString = "'%s%s%s'H" % ('04', longToEvenLengthHexString(points[0]),
                                            longToEvenLengthHexString(points[1]))
         subjectPublicKey = univ.BitString(hexifiedBitString)
         spki.setComponentByName('subjectPublicKey', subjectPublicKey)
         return spki
 
-    def sign(self, data):
+    def sign(self, data, digest):
         """Returns a hexified bit string representing a
         signature by this key over the specified data.
         Intended for use with pyasn1.type.univ.BitString"""
+        # This should really only be used with SHA-256
+        if digest != "SHA-256":
+            raise ParameterError(digest)
+
         # There is some non-determinism in ECDSA signatures. Work around
         # this by patching ecc.ecdsa.urandom to not be random.
         with mock.patch('ecc.ecdsa.urandom', side_effect=notRandom):
             # For some reason Key.sign returns an encoded point.
             # Decode it so we can encode it as a BITSTRING consisting
             # of a SEQUENCE of two INTEGERs.
             # Also patch in secp256k1 if applicable.
             if self.keyOID == secp256k1:
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1.js
@@ -0,0 +1,89 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Tests the rejection of SHA-1 certificates based on the preference
+// security.pki.sha1_enforcement_level.
+
+"use strict";
+
+do_get_profile(); // must be called before getting nsIX509CertDB
+const certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+
+// We need to test as if we are at a fixed time, so that we are testing the
+// 2016 checks on SHA-1, not the notBefore checks.
+//
+// (new Date("2016-03-01")).getTime() / 1000
+const VALIDATION_TIME = 1456790400;
+
+function certFromFile(certName) {
+  return constructCertFromFile("test_cert_sha1/" + certName + ".pem");
+}
+
+function loadCertWithTrust(certName, trustString) {
+  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   <--- (b)
+  //  |       +----- post-2016  <--- (c)
+  //  |
+  //  +--- 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
+
+  // 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
+  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);
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/ca.pem.certspec
@@ -0,0 +1,6 @@
+issuer:ca
+subject:ca
+validity:20100101-20500101
+extension:keyUsage:keyCertSign,cRLSign
+extension:basicConstraints:cA,
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/ee-post_int-post.pem.certspec
@@ -0,0 +1,4 @@
+issuer:int-post
+subject:ee-post
+validity:20160102-20170201
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/ee-post_int-pre.pem.certspec
@@ -0,0 +1,4 @@
+issuer:int-pre
+subject:ee-post
+validity:20160101-20170201
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/ee-pre_int-pre.pem.certspec
@@ -0,0 +1,4 @@
+issuer:int-pre
+subject:ee-pre
+validity:20150101-20170201
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/int-post.pem.certspec
@@ -0,0 +1,6 @@
+issuer:ca
+subject:int-post
+validity:20160101-20260101
+extension:keyUsage:keyCertSign,cRLSign
+extension:basicConstraints:cA,
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/int-pre.pem.certspec
@@ -0,0 +1,6 @@
+issuer:ca
+subject:int-pre
+validity:20100101-20200101
+extension:keyUsage:keyCertSign,cRLSign
+extension:basicConstraints:cA,
+signature:sha1WithRSAEncryption
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_sha1/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+test_certificates = (
+    'ca.pem',
+    'int-pre.pem',
+    'ee-pre_int-pre.pem',
+    'ee-post_int-pre.pem',
+    'int-post.pem',
+    'ee-post_int-post.pem',
+)
+
+for test_certificate in test_certificates:
+    input_file = test_certificate + '.certspec'
+    GENERATED_FILES += [test_certificate]
+    props = GENERATED_FILES[test_certificate]
+    props.script = '../pycert.py'
+    props.inputs = [input_file]
+    TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_cert_sha1 += ['!%s' % test_certificate]
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -7,16 +7,17 @@ support-files =
   test_signed_apps/**
   tlsserver/**
   test_cert_signatures/**
   test_certviewer_invalid_oids/**
   test_ev_certs/**
   test_getchain/**
   test_intermediate_basic_usage_constraints/**
   test_name_constraints/**
+  test_cert_sha1/**
   test_cert_trust/**
   test_cert_version/**
   test_cert_eku/**
   test_cert_embedded_null/**
   test_ocsp_url/**
   test_ocsp_fetch_method/**
   test_keysize/**
   test_keysize_ev/**
@@ -102,16 +103,18 @@ tags = addons
 [test_cert_eku-OS_SA.js]
 [test_cert_eku-OS_TS.js]
 [test_cert_eku-SA.js]
 [test_cert_eku-SA_TS.js]
 [test_cert_eku-TS.js]
 
 [test_cert_embedded_null.js]
 
+[test_cert_sha1.js]
+
 [test_pinning.js]
 run-sequentially = hardcoded ports
 # This test can take longer than 300 seconds on B2G emulator debug builds, so
 # give it enough time to finish. See bug 1081128.
 requesttimeoutfactor = 2
 [test_ocsp_url.js]
 # OCSP requests in this test time out on slow B2G Emulator debug builds.
 # See Bug 1147725.
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -273,17 +273,18 @@ public:
                     /*optional*/ const Input* aiaExtension) = 0;
 
   // Check that the given digest algorithm is acceptable for use in signatures.
   //
   // Return Success if the algorithm is acceptable,
   // Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
   // acceptable, or another error code if another error occurred.
   virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg,
-                                               EndEntityOrCA endEntityOrCA) = 0;
+                                               EndEntityOrCA endEntityOrCA,
+                                               Time notBefore) = 0;
 
   // Check that the RSA public key size is acceptable.
   //
   // Return Success if the key size is acceptable,
   // Result::ERROR_INADEQUATE_KEY_SIZE if the key size is not acceptable,
   // or another error code if another error occurred.
   virtual Result CheckRSAPublicKeyModulusSizeInBits(
                    EndEntityOrCA endEntityOrCA,
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -221,19 +221,19 @@ PathBuildingStep::Check(Input potentialI
   // responders are allowed to forget about expired certificates, and many OCSP
   // responders return an error when asked for the status of an expired
   // certificate.
   if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
     CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
                   subject.GetSerialNumber());
     Time notBefore(Time::uninitialized);
     Time notAfter(Time::uninitialized);
-    // This should never fail. If we're here, we've already checked that the
-    // given time is in the certificate's validity period.
-    rv = CheckValidity(subject.GetValidity(), time, &notBefore, &notAfter);
+    // This should never fail. If we're here, we've already parsed the validity
+    // and checked that the given time is in the certificate's validity period.
+    rv = ParseValidity(subject.GetValidity(), &notBefore, &notAfter);
     if (rv != Success) {
       return rv;
     }
     Duration validityDuration(notAfter, notBefore);
     rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
                                      validityDuration, stapledOCSPResponse,
                                      subject.GetAuthorityInfoAccess());
     if (rv != Success) {
--- a/security/pkix/lib/pkixcheck.cpp
+++ b/security/pkix/lib/pkixcheck.cpp
@@ -30,16 +30,17 @@
 namespace mozilla { namespace pkix {
 
 // 4.1.1.2 signatureAlgorithm
 // 4.1.2.3 signature
 
 Result
 CheckSignatureAlgorithm(TrustDomain& trustDomain,
                         EndEntityOrCA endEntityOrCA,
+                        Time notBefore,
                         const der::SignedDataWithSignature& signedData,
                         Input signatureValue)
 {
   // 4.1.1.2. signatureAlgorithm
   der::PublicKeyAlgorithm publicKeyAlg;
   DigestAlgorithm digestAlg;
   Reader signatureAlgorithmReader(signedData.algorithm);
   Result rv = der::SignatureAlgorithmIdentifierValue(signatureAlgorithmReader,
@@ -86,17 +87,18 @@ CheckSignatureAlgorithm(TrustDomain& tru
   // SHA-1 and/or too-small RSA keys. With this in mind, we ask the trust
   // domain early on if it knows it will reject the signature purely based on
   // the digest algorithm and/or the RSA key size (if an RSA signature). This
   // is a good optimization because it completely avoids calling
   // trustDomain.FindIssuers (which may be slow) for such rejected certs, and
   // more generally it short-circuits any path building with them (which, of
   // course, is even slower).
 
-  rv = trustDomain.CheckSignatureDigestAlgorithm(digestAlg, endEntityOrCA);
+  rv = trustDomain.CheckSignatureDigestAlgorithm(digestAlg, endEntityOrCA,
+                                                 notBefore);
   if (rv != Success) {
     return rv;
   }
 
   switch (publicKeyAlg) {
     case der::PublicKeyAlgorithm::RSA_PKCS1:
     {
       // The RSA computation may give a result that requires fewer bytes to
@@ -120,17 +122,17 @@ CheckSignatureAlgorithm(TrustDomain& tru
   }
 
   return Success;
 }
 
 // 4.1.2.5 Validity
 
 Result
-CheckValidity(Input encodedValidity, Time time,
+ParseValidity(Input encodedValidity,
               /*optional out*/ Time* notBeforeOut,
               /*optional out*/ Time* notAfterOut)
 {
   Reader validity(encodedValidity);
   Time notBefore(Time::uninitialized);
   if (der::TimeChoice(validity, notBefore) != Success) {
     return Result::ERROR_INVALID_DER_TIME;
   }
@@ -143,30 +145,37 @@ CheckValidity(Input encodedValidity, Tim
   if (der::End(validity) != Success) {
     return Result::ERROR_INVALID_DER_TIME;
   }
 
   if (notBefore > notAfter) {
     return Result::ERROR_INVALID_DER_TIME;
   }
 
+  if (notBeforeOut) {
+    *notBeforeOut = notBefore;
+  }
+  if (notAfterOut) {
+    *notAfterOut = notAfter;
+  }
+
+  return Success;
+}
+
+Result
+CheckValidity(Time time, Time notBefore, Time notAfter)
+{
   if (time < notBefore) {
     return Result::ERROR_NOT_YET_VALID_CERTIFICATE;
   }
 
   if (time > notAfter) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
 
-  if (notBeforeOut) {
-    *notBeforeOut = notBefore;
-  }
-  if (notAfterOut) {
-    *notAfterOut = notAfter;
-  }
   return Success;
 }
 
 // 4.1.2.7 Subject Public Key Info
 
 Result
 CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain,
                           EndEntityOrCA endEntityOrCA)
@@ -839,29 +848,40 @@ CheckIssuerIndependentProperties(TrustDo
   // processing we do on a distrusted cert, in case it is trying to exploit
   // some bug in our processing.
   rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(),
                                 trustLevel);
   if (rv != Success) {
     return rv;
   }
 
+  // IMPORTANT: We parse the validity interval here, so that we can use the
+  // notBefore and notAfter values in checks for things that might be deprecated
+  // over time. However, we must not fail for semantic errors until the end of
+  // this method, in order to preserve error ranking.
+  Time notBefore(Time::uninitialized);
+  Time notAfter(Time::uninitialized);
+  rv = ParseValidity(cert.GetValidity(), &notBefore, &notAfter);
+  if (rv != Success) {
+    return rv;
+  }
+
   if (trustLevel == TrustLevel::TrustAnchor &&
       endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
       requiredEKUIfPresent == KeyPurposeId::id_kp_OCSPSigning) {
     // OCSP signer certificates can never be trust anchors, especially
     // since we don't support designated OCSP responders. All of the checks
     // below that are dependent on trustLevel rely on this overriding of the
     // trust level for OCSP signers.
     trustLevel = TrustLevel::InheritsTrust;
   }
 
   switch (trustLevel) {
     case TrustLevel::InheritsTrust:
-      rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA,
+      rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA, notBefore,
                                    cert.GetSignedData(), cert.GetSignature());
       if (rv != Success) {
         return rv;
       }
       break;
 
     case TrustLevel::TrustAnchor:
       // We don't even bother checking signatureAlgorithm or signature for
@@ -940,21 +960,19 @@ CheckIssuerIndependentProperties(TrustDo
 
   // 4.2.1.13. CRL Distribution Points is not supported, though the
   //           TrustDomain's CheckRevocation method may parse it and process it
   //           on its own.
 
   // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation
   //           about policy enforcement in pkix.h.
 
-  // IMPORTANT: This check must come after the other checks in order for error
-  // ranking to work correctly.
-  Time notBefore(Time::uninitialized);
-  Time notAfter(Time::uninitialized);
-  rv = CheckValidity(cert.GetValidity(), time, &notBefore, &notAfter);
+  // IMPORTANT: Even though we parse validity above, we wait until this point to
+  // check it, so that error ranking works correctly.
+  rv = CheckValidity(time, notBefore, notAfter);
   if (rv != Success) {
     return rv;
   }
 
   rv = trustDomain.CheckValidityIsAcceptable(notBefore, notAfter, endEntityOrCA,
                                              requiredEKUIfPresent);
   if (rv != Success) {
     return rv;
--- a/security/pkix/lib/pkixcheck.h
+++ b/security/pkix/lib/pkixcheck.h
@@ -40,15 +40,21 @@ Result CheckIssuerIndependentProperties(
           const CertPolicyId& requiredPolicy,
           unsigned int subCACount,
           /*out*/ TrustLevel& trustLevel);
 
 Result CheckNameConstraints(Input encodedNameConstraints,
                             const BackCert& firstChild,
                             KeyPurposeId requiredEKUIfPresent);
 
-Result CheckValidity(Input encodedValidity, Time time,
+// ParseValidity and CheckValidity are usually used together.  First you parse
+// the dates from the DER Validity sequence, then you compare them to the time
+// at which you are validating.  They are separate so that the notBefore and
+// notAfter times can be used for other things before they are checked against
+// the time of validation.
+Result ParseValidity(Input encodedValidity,
                      /*optional out*/ Time* notBeforeOut = nullptr,
                      /*optional out*/ Time* notAfterOut = nullptr);
+Result CheckValidity(Time time, Time notBefore, Time notAfter);
 
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix_pkixcheck_h
--- a/security/pkix/test/gtest/moz.build
+++ b/security/pkix/test/gtest/moz.build
@@ -6,16 +6,17 @@
 
 SOURCES += [
     'pkixbuild_tests.cpp',
     'pkixcert_extension_tests.cpp',
     'pkixcert_signature_algorithm_tests.cpp',
     'pkixcheck_CheckKeyUsage_tests.cpp',
     'pkixcheck_CheckSignatureAlgorithm_tests.cpp',
     'pkixcheck_CheckValidity_tests.cpp',
+    'pkixcheck_ParseValidity_tests.cpp',
 
     # The naming conventions are described in ./README.txt.
 
     'pkixder_input_tests.cpp',
     'pkixder_pki_types_tests.cpp',
     'pkixder_universal_types_tests.cpp',
     'pkixgtest.cpp',
     'pkixnames_tests.cpp',
--- a/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
+++ b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
@@ -27,16 +27,17 @@
 
 using namespace mozilla::pkix;
 using namespace mozilla::pkix::test;
 
 namespace mozilla { namespace pkix {
 
 extern Result CheckSignatureAlgorithm(
                 TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
+                Time notBefore,
                 const der::SignedDataWithSignature& signedData,
                 Input signatureValue);
 
 } } // namespace mozilla::pkix
 
 struct CheckSignatureAlgorithmTestParams
 {
   ByteString signatureAlgorithmValue;
@@ -198,17 +199,18 @@ public:
   explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
              unsigned int publicKeySizeInBits)
     : publicKeySizeInBits(publicKeySizeInBits)
     , checkedDigestAlgorithm(false)
     , checkedModulusSizeInBits(false)
   {
   }
 
-  Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA) override
+  Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+    override
   {
     checkedDigestAlgorithm = true;
     return Success;
   }
 
   Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
                                             unsigned int modulusSizeInBits)
     override
@@ -221,16 +223,17 @@ public:
 
   const unsigned int publicKeySizeInBits;
   bool checkedDigestAlgorithm;
   bool checkedModulusSizeInBits;
 };
 
 TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
 {
+  const Time now(Now());
   const CheckSignatureAlgorithmTestParams& params(GetParam());
 
   Input signatureValueInput;
   ASSERT_EQ(Success,
             signatureValueInput.Init(params.signatureValue.data(),
                                      params.signatureValue.length()));
 
   pkixcheck_CheckSignatureAlgorithm_TrustDomain
@@ -243,17 +246,17 @@ TEST_P(pkixcheck_CheckSignatureAlgorithm
 
   ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
   ASSERT_EQ(Success,
             signedData.signature.Init(dummySignature.data(),
                                       dummySignature.length()));
 
   ASSERT_EQ(params.expectedResult,
             CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
-                                    signedData, signatureValueInput));
+                                    now, signedData, signatureValueInput));
   ASSERT_EQ(params.expectedResult == Success,
             trustDomain.checkedDigestAlgorithm);
   ASSERT_EQ(params.expectedResult == Success,
             trustDomain.checkedModulusSizeInBits);
 }
 
 INSTANTIATE_TEST_CASE_P(
   pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
--- a/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
+++ b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
@@ -51,101 +51,77 @@ static const Time NOW(YMDHMS(2016, 12, 3
   0x17, 13,                               /* tag, length */ \
   '2', '1', '0', '1', '0', '1',           /* 2021-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
 static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
 
 class pkixcheck_CheckValidity : public ::testing::Test { };
 
-TEST_F(pkixcheck_CheckValidity, BothEmptyNull)
-{
-  static const uint8_t DER[] = {
-    0x17/*UTCTime*/, 0/*length*/,
-    0x17/*UTCTime*/, 0/*length*/,
-  };
-  static const Input validity(DER);
-  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, CheckValidity(validity, NOW));
-}
-
-TEST_F(pkixcheck_CheckValidity, NotBeforeEmptyNull)
-{
-  static const uint8_t DER[] = {
-    0x17/*UTCTime*/, 0x00/*length*/,
-    NEWER_UTCTIME
-  };
-  static const Input validity(DER);
-  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, CheckValidity(validity, NOW));
-}
-
-TEST_F(pkixcheck_CheckValidity, NotAfterEmptyNull)
-{
-  static const uint8_t DER[] = {
-    NEWER_UTCTIME,
-    0x17/*UTCTime*/, 0x00/*length*/,
-  };
-  static const Input validity(DER);
-  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, CheckValidity(validity, NOW));
-}
-
 static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = {
   OLDER_UTCTIME,
   NEWER_UTCTIME,
 };
 static const Input
 OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA);
 
 TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME)
 {
-  ASSERT_EQ(Success, CheckValidity(OLDER_UTCTIME_NEWER_UTCTIME, NOW));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+  ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
 }
 
 TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_GENERALIZEDTIME)
 {
   static const uint8_t DER[] = {
     OLDER_GENERALIZEDTIME,
     NEWER_GENERALIZEDTIME,
   };
   static const Input validity(DER);
-  ASSERT_EQ(Success, CheckValidity(validity, NOW));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+  ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
 }
 
 TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_UTCTIME)
 {
   static const uint8_t DER[] = {
     OLDER_GENERALIZEDTIME,
     NEWER_UTCTIME,
   };
   static const Input validity(DER);
-  ASSERT_EQ(Success, CheckValidity(validity, NOW));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+  ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
 }
 
 TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_GENERALIZEDTIME)
 {
   static const uint8_t DER[] = {
     OLDER_UTCTIME,
     NEWER_GENERALIZEDTIME,
   };
   static const Input validity(DER);
-  ASSERT_EQ(Success, CheckValidity(validity, NOW));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+  ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
 }
 
 TEST_F(pkixcheck_CheckValidity, InvalidBeforeNotBefore)
 {
-  ASSERT_EQ(Result::ERROR_NOT_YET_VALID_CERTIFICATE,
-            CheckValidity(OLDER_UTCTIME_NEWER_UTCTIME, PAST_TIME));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+  ASSERT_EQ(Result::ERROR_NOT_YET_VALID_CERTIFICATE, CheckValidity(PAST_TIME, notBefore, notAfter));
 }
 
 TEST_F(pkixcheck_CheckValidity, InvalidAfterNotAfter)
 {
-  ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE,
-            CheckValidity(OLDER_UTCTIME_NEWER_UTCTIME, FUTURE_TIME));
+  static Time notBefore(Time::uninitialized);
+  static Time notAfter(Time::uninitialized);
+  ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+  ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, CheckValidity(FUTURE_TIME, notBefore, notAfter));
 }
-
-TEST_F(pkixcheck_CheckValidity, InvalidNotAfterBeforeNotBefore)
-{
-  static const uint8_t DER[] = {
-    NEWER_UTCTIME,
-    OLDER_UTCTIME,
-  };
-  static const Input validity(DER);
-  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, CheckValidity(validity, NOW));
-}
new file mode 100644
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+#define OLDER_UTCTIME \
+  0x17, 13,                               /* tag, length */ \
+  '9', '9', '0', '1', '0', '1',           /* (19)99-01-01 */ \
+  '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
+
+#define NEWER_UTCTIME \
+  0x17, 13,                               /* tag, length */ \
+  '2', '1', '0', '1', '0', '1',           /* 2021-01-01 */ \
+  '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
+
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+
+class pkixcheck_ParseValidity : public ::testing::Test { };
+
+TEST_F(pkixcheck_ParseValidity, BothEmptyNull)
+{
+  static const uint8_t DER[] = {
+    0x17/*UTCTime*/, 0/*length*/,
+    0x17/*UTCTime*/, 0/*length*/,
+  };
+  static const Input validity(DER);
+  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull)
+{
+  static const uint8_t DER[] = {
+    0x17/*UTCTime*/, 0x00/*length*/,
+    NEWER_UTCTIME
+  };
+  static const Input validity(DER);
+  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull)
+{
+  static const uint8_t DER[] = {
+    NEWER_UTCTIME,
+    0x17/*UTCTime*/, 0x00/*length*/,
+  };
+  static const Input validity(DER);
+  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore)
+{
+  static const uint8_t DER[] = {
+    NEWER_UTCTIME,
+    OLDER_UTCTIME,
+  };
+  static const Input validity(DER);
+  ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
--- a/security/pkix/test/gtest/pkixgtest.h
+++ b/security/pkix/test/gtest/pkixgtest.h
@@ -121,17 +121,18 @@ public:
   Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override
   {
     ADD_FAILURE();
     return NotReached("DigestBuf should not be called",
                       Result::FATAL_ERROR_LIBRARY_FAILURE);
   }
 
   Result CheckSignatureDigestAlgorithm(DigestAlgorithm,
-                                       EndEntityOrCA) override
+                                       EndEntityOrCA,
+                                       Time) override
   {
     ADD_FAILURE();
     return NotReached("CheckSignatureDigestAlgorithm should not be called",
                       Result::FATAL_ERROR_LIBRARY_FAILURE);
   }
 
   Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
   {
@@ -174,17 +175,18 @@ public:
 class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
 {
   Result DigestBuf(Input item, DigestAlgorithm digestAlg,
                    /*out*/ uint8_t* digestBuf, size_t digestBufLen) override
   {
     return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
   }
 
-  Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA) override
+  Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+                                       override
   {
     return Success;
   }
 
   Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
   {
     return Success;
   }