bug 1352262 - make OCSP timeout values configurable r?Cykesiopka,jcj draft
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 31 Mar 2017 15:21:40 -0700
changeset 563087 f813d28e4ce2550c85a14abdb61d4928879e95bd
parent 562868 1a1069b27f40edbfbcf1aa81a5e7dfb39845a5fe
child 624384 81a111e65705c829eed933af28f742fb2c58edeb
push id54200
push userbmo:dkeeler@mozilla.com
push dateFri, 14 Apr 2017 22:52:37 +0000
reviewersCykesiopka, jcj
bugs1352262
milestone55.0a1
bug 1352262 - make OCSP timeout values configurable r?Cykesiopka,jcj The default OCSP timeout for soft-fail DV is still 2 seconds. This patch makes it configurable on the interval (0, 5] seconds. The default OCSP timeout for EV and hard-fail DV is still 10 seconds. This patch makes it configurable on the interval (0, 20] seconds. MozReview-Commit-ID: CPd8pwYrJhj
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/manager/ssl/SharedCertVerifier.h
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/security-prefs.js
security/manager/ssl/tests/unit/test_ocsp_timeout.js
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -80,25 +80,29 @@ CertificateTransparencyInfo::Reset()
   enabled = false;
   verifyResult.Reset();
   policyCompliance = CTPolicyCompliance::Unknown;
 }
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
+                           mozilla::TimeDuration ocspTimeoutSoft,
+                           mozilla::TimeDuration ocspTimeoutHard,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode,
                            SHA1Mode sha1Mode,
                            BRNameMatchingPolicy::Mode nameMatchingMode,
                            NetscapeStepUpPolicy netscapeStepUpPolicy,
                            CertificateTransparencyMode ctMode)
   : mOCSPDownloadConfig(odc)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
+  , mOCSPTimeoutSoft(ocspTimeoutSoft)
+  , mOCSPTimeoutHard(ocspTimeoutHard)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mSHA1Mode(sha1Mode)
   , mNameMatchingMode(nameMatchingMode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
   , mCTMode(ctMode)
 {
   LoadKnownCTLogs();
@@ -527,16 +531,17 @@ CertVerifier::VerifyCert(CERTCertificate
   }
 
   switch (usage) {
     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,
+                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
                                        originAttributes,
                                        builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
@@ -603,16 +608,17 @@ CertVerifier::VerifyCert(CERTCertificate
         // previously noted telemetry information
         if (pinningTelemetryInfo) {
           pinningTelemetryInfo->Reset();
         }
 
         NSSCertDBTrustDomain
           trustDomain(trustSSL, evOCSPFetching,
                       mOCSPCache, pinArg, ocspGETConfig,
+                      mOCSPTimeoutSoft, mOCSPTimeoutHard,
                       mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
                       ValidityCheckingMode::CheckForEV,
                       sha1ModeConfigurations[i], mNetscapeStepUpPolicy,
                       originAttributes, builtChain, pinningTelemetryInfo,
                       hostname);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
@@ -689,16 +695,17 @@ CertVerifier::VerifyCert(CERTCertificate
 
           // invalidate any telemetry info relating to failed chains
           if (pinningTelemetryInfo) {
             pinningTelemetryInfo->Reset();
           }
 
           NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                            mOCSPCache, pinArg, ocspGETConfig,
+                                           mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                            mCertShortLifetimeInDays,
                                            mPinningMode, keySizeOptions[i],
                                            ValidityCheckingMode::CheckingOff,
                                            sha1ModeConfigurations[j],
                                            mNetscapeStepUpPolicy,
                                            originAttributes, builtChain,
                                            pinningTelemetryInfo, hostname);
           rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
@@ -753,32 +760,34 @@ CertVerifier::VerifyCert(CERTCertificate
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
+                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed, mNetscapeStepUpPolicy,
                                        originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
+                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
                                        originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
@@ -797,16 +806,17 @@ CertVerifier::VerifyCert(CERTCertificate
     }
 
     case certificateUsageEmailRecipient: {
       // 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,
+                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
                                        originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
@@ -822,16 +832,17 @@ CertVerifier::VerifyCert(CERTCertificate
                             CertPolicyId::anyPolicy, stapledOCSPResponse);
       }
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
+                                       mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
                                        ValidityCheckingMode::CheckingOff,
                                        SHA1Mode::Allowed,
                                        NetscapeStepUpPolicy::NeverMatch,
                                        originAttributes, builtChain, nullptr,
                                        nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
@@ -856,43 +867,48 @@ CertVerifier::VerifyCert(CERTCertificate
         eku = KeyPurposeId::anyExtendedKeyUsage;
       } else {
         endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
         keyUsage = KeyUsage::digitalSignature;
         eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, defaultOCSPFetching, mOCSPCache,
-                                    pinArg, ocspGETConfig, mCertShortLifetimeInDays,
+                                    pinArg, ocspGETConfig, mOCSPTimeoutSoft,
+                                    mOCSPTimeoutHard, mCertShortLifetimeInDays,
                                     pinningDisabled, MIN_RSA_BITS_WEAK,
                                     ValidityCheckingMode::CheckingOff,
                                     SHA1Mode::Allowed,
                                     NetscapeStepUpPolicy::NeverMatch,
                                     originAttributes, builtChain, nullptr,
                                     nullptr);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, defaultOCSPFetching,
                                         mOCSPCache, pinArg, ocspGETConfig,
+                                        mOCSPTimeoutSoft, mOCSPTimeoutHard,
                                         mCertShortLifetimeInDays,
                                         pinningDisabled, MIN_RSA_BITS_WEAK,
                                         ValidityCheckingMode::CheckingOff,
                                         SHA1Mode::Allowed,
                                         NetscapeStepUpPolicy::NeverMatch,
                                         originAttributes, builtChain, nullptr,
                                         nullptr);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
-                                                  defaultOCSPFetching, mOCSPCache,
-                                                  pinArg, ocspGETConfig,
+                                                  defaultOCSPFetching,
+                                                  mOCSPCache, pinArg,
+                                                  ocspGETConfig,
+                                                  mOCSPTimeoutSoft,
+                                                  mOCSPTimeoutHard,
                                                   mCertShortLifetimeInDays,
                                                   pinningDisabled,
                                                   MIN_RSA_BITS_WEAK,
                                                   ValidityCheckingMode::CheckingOff,
                                                   SHA1Mode::Allowed,
                                                   NetscapeStepUpPolicy::NeverMatch,
                                                   originAttributes, builtChain,
                                                   nullptr, nullptr);
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -8,16 +8,17 @@
 #define CertVerifier_h
 
 #include "BRNameMatchingPolicy.h"
 #include "CTPolicyEnforcer.h"
 #include "CTVerifyResult.h"
 #include "OCSPCache.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "pkix/pkixtypes.h"
 
 #if defined(_MSC_VER)
 #pragma warning(push)
 // Silence "RootingAPI.h(718): warning C4324: 'js::DispatchWrapper<T>':
 // structure was padded due to alignment specifier with [ T=void * ]"
 #pragma warning(disable:4324)
@@ -180,29 +181,33 @@ public:
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
   enum class CertificateTransparencyMode {
     Disabled = 0,
     TelemetryOnly = 1,
   };
 
-  CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
-               OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
+  CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc, OcspGetConfig ogc,
+               mozilla::TimeDuration ocspTimeoutSoft,
+               mozilla::TimeDuration ocspTimeoutHard,
+               uint32_t certShortLifetimeInDays,
                PinningMode pinningMode, SHA1Mode sha1Mode,
                BRNameMatchingPolicy::Mode nameMatchingMode,
                NetscapeStepUpPolicy netscapeStepUpPolicy,
                CertificateTransparencyMode ctMode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
+  const mozilla::TimeDuration mOCSPTimeoutSoft;
+  const mozilla::TimeDuration mOCSPTimeoutHard;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
   const SHA1Mode mSHA1Mode;
   const BRNameMatchingPolicy::Mode mNameMatchingMode;
   const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   const CertificateTransparencyMode mCTMode;
 
 private:
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -44,31 +44,35 @@ static const uint64_t ServerFailureDelay
 
 namespace mozilla { namespace psm {
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
                                            OCSPFetching ocspFetching,
                                            OCSPCache& ocspCache,
              /*optional but shouldn't be*/ void* pinArg,
                                            CertVerifier::OcspGetConfig ocspGETConfig,
+                                           TimeDuration ocspTimeoutSoft,
+                                           TimeDuration ocspTimeoutHard,
                                            uint32_t certShortLifetimeInDays,
                                            CertVerifier::PinningMode pinningMode,
                                            unsigned int minRSABits,
                                            ValidityCheckingMode validityCheckingMode,
                                            CertVerifier::SHA1Mode sha1Mode,
                                            NetscapeStepUpPolicy netscapeStepUpPolicy,
                                            const OriginAttributes& originAttributes,
                                            UniqueCERTCertList& builtChain,
                               /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
                               /*optional*/ const char* hostname)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
+  , mOCSPTimeoutSoft(ocspTimeoutSoft)
+  , mOCSPTimeoutHard(ocspTimeoutHard)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mValidityCheckingMode(validityCheckingMode)
   , mSHA1Mode(sha1Mode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
   , mOriginAttributes(originAttributes)
   , mBuiltChain(builtChain)
@@ -265,35 +269,35 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
 
 Result
 NSSCertDBTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg,
                                 /*out*/ uint8_t* digestBuf, size_t digestBufLen)
 {
   return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
 }
 
-static TimeDuration
-OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching)
+TimeDuration
+NSSCertDBTrustDomain::GetOCSPTimeout() const
 {
-  switch (ocspFetching) {
+  switch (mOCSPFetching) {
     case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail:
-      return TimeDuration::FromSeconds(2);
+      return mOCSPTimeoutSoft;
     case NSSCertDBTrustDomain::FetchOCSPForEV:
     case NSSCertDBTrustDomain::FetchOCSPForDVHardFail:
-      return TimeDuration::FromSeconds(10);
+      return mOCSPTimeoutHard;
     // The rest of these are error cases. Assert in debug builds, but return
-    // the default value corresponding to 2 seconds in release builds.
+    // the soft timeout value in release builds.
     case NSSCertDBTrustDomain::NeverFetchOCSP:
     case NSSCertDBTrustDomain::LocalOnlyOCSPForEV:
       MOZ_ASSERT_UNREACHABLE("we should never see this OCSPFetching type here");
       break;
   }
 
   MOZ_ASSERT_UNREACHABLE("we're not handling every OCSPFetching type");
-  return TimeDuration::FromSeconds(2);
+  return mOCSPTimeoutSoft;
 }
 
 // Copied and modified from CERT_GetOCSPAuthorityInfoAccessLocation and
 // CERT_GetGeneralNameByType. Returns a non-Result::Success result on error,
 // Success with url == nullptr when an OCSP URI was not found, and Success with
 // url != nullptr when an OCSP URI was found. The output url will be owned
 // by the arena.
 static Result
@@ -560,17 +564,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
       siBuffer,
       ocspRequest,
       static_cast<unsigned int>(ocspRequestLength)
     };
     // Owned by arena
     SECItem* responseSECItem = nullptr;
     Result tempRV =
       DoOCSPRequest(arena, url, mOriginAttributes, &ocspRequestItem,
-                    OCSPFetchingTypeToTimeoutTime(mOCSPFetching),
+                    GetOCSPTimeout(),
                     mOCSPGetConfig == CertVerifier::ocspGetEnabled,
                     responseSECItem);
     MOZ_ASSERT((tempRV != Success) || responseSECItem);
     if (tempRV != Success) {
       rv = tempRV;
     } else if (response.Init(responseSECItem->data, responseSECItem->len)
                  != Success) {
       rv = Result::ERROR_OCSP_MALFORMED_RESPONSE; // too big
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NSSCertDBTrustDomain_h
 #define NSSCertDBTrustDomain_h
 
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/TimeStamp.h"
 #include "nsICertBlocklist.h"
 #include "nsString.h"
 #include "pkix/pkixtypes.h"
 #include "secmodt.h"
 
 namespace mozilla { namespace psm {
 
 enum class ValidityCheckingMode {
@@ -72,16 +73,18 @@ public:
     FetchOCSPForDVHardFail = 2,
     FetchOCSPForEV = 3,
     LocalOnlyOCSPForEV = 4,
   };
 
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
                        OCSPCache& ocspCache, void* pinArg,
                        CertVerifier::OcspGetConfig ocspGETConfig,
+                       mozilla::TimeDuration ocspTimeoutSoft,
+                       mozilla::TimeDuration ocspTimeoutHard,
                        uint32_t certShortLifetimeInDays,
                        CertVerifier::PinningMode pinningMode,
                        unsigned int minRSABits,
                        ValidityCheckingMode validityCheckingMode,
                        CertVerifier::SHA1Mode sha1Mode,
                        NetscapeStepUpPolicy netscapeStepUpPolicy,
                        const OriginAttributes& originAttributes,
                        UniqueCERTCertList& builtChain,
@@ -172,22 +175,25 @@ private:
   enum EncodedResponseSource {
     ResponseIsFromNetwork = 1,
     ResponseWasStapled = 2
   };
   Result VerifyAndMaybeCacheEncodedOCSPResponse(
     const mozilla::pkix::CertID& certID, mozilla::pkix::Time time,
     uint16_t maxLifetimeInDays, mozilla::pkix::Input encodedResponse,
     EncodedResponseSource responseSource, /*out*/ bool& expired);
+  TimeDuration GetOCSPTimeout() const;
 
   const SECTrustType mCertDBTrustType;
   const OCSPFetching mOCSPFetching;
   OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
   const CertVerifier::OcspGetConfig mOCSPGetConfig;
+  const mozilla::TimeDuration mOCSPTimeoutSoft;
+  const mozilla::TimeDuration mOCSPTimeoutHard;
   const uint32_t mCertShortLifetimeInDays;
   CertVerifier::PinningMode mPinningMode;
   const unsigned int mMinRSABits;
   ValidityCheckingMode mValidityCheckingMode;
   CertVerifier::SHA1Mode mSHA1Mode;
   NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   const OriginAttributes& mOriginAttributes;
   UniqueCERTCertList& mBuiltChain; // non-owning
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -2,34 +2,39 @@
  * 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/. */
 
 #ifndef SharedCertVerifier_h
 #define SharedCertVerifier_h
 
 #include "CertVerifier.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
 
 namespace mozilla { namespace psm {
 
 class SharedCertVerifier : public mozilla::psm::CertVerifier
 {
 protected:
   ~SharedCertVerifier();
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
-                     OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
+                     OcspGetConfig ogc,
+                     mozilla::TimeDuration ocspSoftTimeout,
+                     mozilla::TimeDuration ocspHardTimeout,
+                     uint32_t certShortLifetimeInDays,
                      PinningMode pinningMode, SHA1Mode sha1Mode,
                      BRNameMatchingPolicy::Mode nameMatchingMode,
                      NetscapeStepUpPolicy netscapeStepUpPolicy,
                      CertificateTransparencyMode ctMode)
-    : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
+    : mozilla::psm::CertVerifier(odc, osc, ogc, ocspSoftTimeout,
+                                 ocspHardTimeout, certShortLifetimeInDays,
                                  pinningMode, sha1Mode, nameMatchingMode,
                                  netscapeStepUpPolicy, ctMode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/Casting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/PublicSSL.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsCRT.h"
 #include "nsClientAuthRemember.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsICertOverrideService.h"
 #include "nsIFile.h"
@@ -125,21 +126,28 @@ bool EnsureNSSInitializedChromeOrContent
   if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
     return false;
   }
 
   mozilla::psm::DisableMD5();
   return true;
 }
 
+static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_DEFAULT = 2000;
+static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX = 5000;
+static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_DEFAULT = 10000;
+static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_MAX = 20000;
+
 static void
 GetRevocationBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc,
                                /*out*/ CertVerifier::OcspStrictConfig* osc,
                                /*out*/ CertVerifier::OcspGetConfig* ogc,
                                /*out*/ uint32_t* certShortLifetimeInDays,
+                               /*out*/ TimeDuration& softTimeout,
+                               /*out*/ TimeDuration& hardTimeout,
                                const MutexAutoLock& /*proofOfLock*/)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(odc);
   MOZ_ASSERT(osc);
   MOZ_ASSERT(ogc);
   MOZ_ASSERT(certShortLifetimeInDays);
 
@@ -164,16 +172,30 @@ GetRevocationBehaviorFromPrefs(/*out*/ C
 
   // If we pass in just 0 as the second argument to Preferences::GetUint, there
   // are two function signatures that match (given that 0 can be intepreted as
   // a null pointer). Thus the compiler will complain without the cast.
   *certShortLifetimeInDays =
     Preferences::GetUint("security.pki.cert_short_lifetime_in_days",
                          static_cast<uint32_t>(0));
 
+  uint32_t softTimeoutMillis =
+    Preferences::GetUint("security.OCSP.timeoutMilliseconds.soft",
+                         OCSP_TIMEOUT_MILLISECONDS_SOFT_DEFAULT);
+  softTimeoutMillis = std::min(softTimeoutMillis,
+                               OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX);
+  softTimeout = TimeDuration::FromMilliseconds(softTimeoutMillis);
+
+  uint32_t hardTimeoutMillis =
+    Preferences::GetUint("security.OCSP.timeoutMilliseconds.hard",
+                         OCSP_TIMEOUT_MILLISECONDS_HARD_DEFAULT);
+  hardTimeoutMillis = std::min(hardTimeoutMillis,
+                               OCSP_TIMEOUT_MILLISECONDS_HARD_MAX);
+  hardTimeout = TimeDuration::FromMilliseconds(hardTimeoutMillis);
+
   SSL_ClearSessionCache();
 }
 
 nsNSSComponent::nsNSSComponent()
   : mutex("nsNSSComponent.mutex")
   , mNSSInitialized(false)
 #ifndef MOZ_NO_SMART_CARDS
   , mThreadList(nullptr)
@@ -1517,20 +1539,23 @@ void nsNSSComponent::setValidationOption
       netscapeStepUpPolicy = NetscapeStepUpPolicy::AlwaysMatch;
       break;
   }
 
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
   uint32_t certShortLifetimeInDays;
+  TimeDuration softTimeout;
+  TimeDuration hardTimeout;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
-                                 lock);
-  mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
+                                 softTimeout, hardTimeout, lock);
+  mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc, softTimeout,
+                                                hardTimeout,
                                                 certShortLifetimeInDays,
                                                 pinningMode, sha1Mode,
                                                 nameMatchingMode,
                                                 netscapeStepUpPolicy,
                                                 ctMode);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
@@ -1914,17 +1939,19 @@ nsNSSComponent::Observe(nsISupports* aSu
                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.ssl.enable_ocsp_must_staple") ||
                prefName.EqualsLiteral("security.pki.certificate_transparency.mode") ||
                prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
                prefName.EqualsLiteral("security.pki.sha1_enforcement_level") ||
                prefName.EqualsLiteral("security.pki.name_matching_mode") ||
-               prefName.EqualsLiteral("security.pki.netscape_step_up_policy")) {
+               prefName.EqualsLiteral("security.pki.netscape_step_up_policy") ||
+               prefName.EqualsLiteral("security.OCSP.timeoutMilliseconds.soft") ||
+               prefName.EqualsLiteral("security.OCSP.timeoutMilliseconds.hard")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
 #ifdef DEBUG
     } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
       MutexAutoLock lock(mutex);
       mTestBuiltInRootHash = Preferences::GetString("security.test.built_in_root_hash");
 #endif // DEBUG
     } else if (prefName.Equals(kFamilySafetyModePref)) {
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -45,16 +45,18 @@ pref("security.password_lifetime",      
 // (This is only relevant to Windows 8.1)
 pref("security.family_safety.mode", 2);
 
 pref("security.enterprise_roots.enabled", false);
 
 pref("security.OCSP.enabled", 1);
 pref("security.OCSP.require", false);
 pref("security.OCSP.GET.enabled", false);
+pref("security.OCSP.timeoutMilliseconds.soft", 2000);
+pref("security.OCSP.timeoutMilliseconds.hard", 10000);
 
 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 = only allow SHA-1 for certificates issued by an imported root.
 pref("security.pki.sha1_enforcement_level", 3);
 
 // security.pki.name_matching_mode controls how the platform matches hostnames
--- a/security/manager/ssl/tests/unit/test_ocsp_timeout.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_timeout.js
@@ -30,59 +30,61 @@ function run_test() {
 
   add_tls_server_setup("OCSPStaplingServer", "ocsp_certs");
 
   let socket = Cc["@mozilla.org/network/server-socket;1"]
                  .createInstance(Ci.nsIServerSocket);
   socket.init(8888, true, -1);
   socket.asyncListen(gSocketListener);
 
-  add_tests_in_mode(true);
-  add_tests_in_mode(false);
+  add_one_test(false, "security.OCSP.timeoutMilliseconds.soft", 1000);
+  add_one_test(false, "security.OCSP.timeoutMilliseconds.soft", 2000);
+  add_one_test(false, "security.OCSP.timeoutMilliseconds.soft", 4000);
+
+  add_one_test(true, "security.OCSP.timeoutMilliseconds.hard", 3000);
+  add_one_test(true, "security.OCSP.timeoutMilliseconds.hard", 10000);
+  add_one_test(true, "security.OCSP.timeoutMilliseconds.hard", 15000);
 
   add_test(function() { socket.close(); run_next_test(); });
   run_next_test();
 }
 
-function add_tests_in_mode(useHardFail) {
+function add_one_test(useHardFail, timeoutPrefName, timeoutMilliseconds) {
   let startTime;
   add_test(function () {
     Services.prefs.setBoolPref("security.OCSP.require", useHardFail);
+    Services.prefs.setIntPref(timeoutPrefName, timeoutMilliseconds);
     startTime = new Date();
     run_next_test();
   });
 
   add_connection_test("ocsp-stapling-none.example.com", useHardFail
                       ? SEC_ERROR_OCSP_SERVER_ERROR
                       : PRErrorCodeSuccess, clearSessionCache);
 
-  // Reset state
   add_test(function() {
     let endTime = new Date();
     let timeDifference = endTime - startTime;
     do_print(`useHardFail = ${useHardFail}`);
     do_print(`startTime = ${startTime.getTime()} (${startTime})`);
     do_print(`endTime = ${endTime.getTime()} (${endTime})`);
     do_print(`timeDifference = ${timeDifference}ms`);
-
-    // With OCSP hard-fail on, we timeout after 10 seconds.
-    // With OCSP soft-fail, we timeout after 2 seconds.
     // Date() is not guaranteed to be monotonic, so add extra fuzz time to
     // prevent intermittent failures (this only appeared to be a problem on
     // Windows XP). See Bug 1121117.
     const FUZZ_MS = 300;
-    if (useHardFail) {
-      ok(timeDifference + FUZZ_MS > 10000,
-         "Automatic OCSP timeout should be about 10s for hard-fail");
-    } else {
-      ok(timeDifference + FUZZ_MS > 2000,
-         "Automatic OCSP timeout should be about 2s for soft-fail");
-    }
+    ok(timeDifference + FUZZ_MS > timeoutMilliseconds,
+       `OCSP timeout should be ~${timeoutMilliseconds}s for ` +
+       `${useHardFail ? "hard" : "soft"}-fail`);
     // Make sure we didn't wait too long.
     // (Unfortunately, we probably can't have a tight upper bound on
     // how long is too long for this test, because we might be running
     // on slow hardware.)
     ok(timeDifference < 60000,
        "Automatic OCSP timeout shouldn't be more than 60s");
+
+    // Reset state
     clearOCSPCache();
+    Services.prefs.clearUserPref("security.OCSP.require");
+    Services.prefs.clearUserPref(timeoutPrefName);
     run_next_test();
   });
 }