Bug 1128607 - Add freshness check for OneCRL (r=keeler)
authorMark Goodwin <mgoodwin@mozilla.com>
Thu, 07 May 2015 18:54:05 +0100
changeset 274224 a4e5010cb3d1ef01aecd5e7aee74b42670be5bc7
parent 274223 2124a617b3bc9c9eb4e7145b7aa3eeec09a3f62a
child 274225 b40e0753d6d3b83555f6d11cd4d36f5998aa1dfa
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1128607
milestone40.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 1128607 - Add freshness check for OneCRL (r=keeler)
browser/app/profile/firefox.js
security/certverifier/NSSCertDBTrustDomain.cpp
security/manager/boot/public/nsICertBlocklist.idl
security/manager/boot/src/CertBlocklist.cpp
security/manager/boot/src/CertBlocklist.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1735,16 +1735,20 @@ 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);
 
+// Required blocklist freshness for OneCRL OCSP bypass
+// (default should be at least as large as extensions.blocklist.interval)
+pref("security.onecrl.maximum_staleness_in_seconds", 0);
+
 // 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()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // The request URL of the GeoLocation backend.
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -439,25 +439,33 @@ NSSCertDBTrustDomain::CheckRevocation(En
     PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
            ("NSSCertDBTrustDomain: no cached OCSP response"));
   }
   // At this point, if and only if cachedErrorResult is Success, there was no
   // cached response.
   PR_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) ||
             (cachedResponsePresent && cachedResponseResult != Success));
 
+  // If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs
+  bool blocklistIsFresh;
+  nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh);
+  if (NS_FAILED(nsrv)) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+
   // TODO: We still need to handle the fallback for expired responses. But,
   // if/when we disable OCSP fetching by default, it would be ambiguous whether
   // security.OCSP.enable==0 means "I want the default" or "I really never want
   // you to ever fetch OCSP."
 
   if ((mOCSPFetching == NeverFetchOCSP) ||
       (endEntityOrCA == EndEntityOrCA::MustBeCA &&
        (mOCSPFetching == FetchOCSPForDVHardFail ||
-        mOCSPFetching == FetchOCSPForDVSoftFail))) {
+        mOCSPFetching == FetchOCSPForDVSoftFail ||
+        blocklistIsFresh))) {
     // We're not going to be doing any fetching, so if there was a cached
     // "unknown" response, say so.
     if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) {
       return Result::ERROR_OCSP_UNKNOWN_CERT;
     }
     // If we're doing hard-fail, we want to know if we have a cached response
     // that has expired.
     if (mOCSPFetching == FetchOCSPForDVHardFail &&
--- a/security/manager/boot/public/nsICertBlocklist.idl
+++ b/security/manager/boot/public/nsICertBlocklist.idl
@@ -9,17 +9,17 @@ interface nsIX509Cert;
 
 %{C++
 #define NS_CERTBLOCKLIST_CONTRACTID "@mozilla.org/security/certblocklist;1"
 %}
 
 /**
  * Represents a service to add certificates as explicitly blocked/distrusted.
  */
-[scriptable, uuid(fed30090-c190-11e4-8830-0800200c9a66)]
+[scriptable, uuid(e0654480-f433-11e4-b939-0800200c9a66)]
 interface nsICertBlocklist : nsISupports {
   /**
    * Add details of a revoked certificate :
    * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
    */
    void revokeCertByIssuerAndSerial(in string issuer, in string serialNumber);
 
   /**
@@ -45,9 +45,17 @@ interface nsICertBlocklist : nsISupports
    boolean isCertRevoked([const, array, size_is(issuer_length)] in octet issuer,
                           in unsigned long issuer_length,
                           [const, array, size_is(serial_length)] in octet serial,
                           in unsigned long serial_length,
                           [const, array, size_is(subject_length)] in octet subject,
                           in unsigned long subject_length,
                           [const, array, size_is(pubkey_length)] in octet pubkey,
                           in unsigned long pubkey_length);
+
+   /**
+    * Check that the blocklist data is current. Specifically, that the current
+    * time is no more than security.onecrl.maximum_staleness_in_seconds seconds
+    * after the last blocklist update (as stored in the
+    * app.update.lastUpdateTime.blocklist-background-update-timer pref)
+    */
+   boolean isBlocklistFresh();
 };
--- a/security/manager/boot/src/CertBlocklist.cpp
+++ b/security/manager/boot/src/CertBlocklist.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
 
 #include "CertBlocklist.h"
 #include "mozilla/Base64.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsCRTGlue.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsICryptoHash.h"
 #include "nsIFileStreams.h"
 #include "nsILineInputStream.h"
 #include "nsIX509Cert.h"
@@ -17,18 +18,27 @@
 #include "nsNetUtil.h"
 #include "nsTHashtable.h"
 #include "nsThreadUtils.h"
 #include "pkix/Input.h"
 #include "prlog.h"
 
 NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist)
 
+using namespace mozilla;
+using namespace mozilla::pkix;
+
+#define PREF_BACKGROUND_UPDATE_TIMER "app.update.lastUpdateTime.blocklist-background-update-timer"
+#define PREF_MAX_STALENESS_IN_SECONDS "security.onecrl.maximum_staleness_in_seconds"
+
 static PRLogModuleInfo* gCertBlockPRLog;
 
+uint32_t CertBlocklist::sLastBlocklistUpdate = 0U;
+uint32_t CertBlocklist::sMaxStaleness = 0U;
+
 CertBlocklistItem::CertBlocklistItem(const uint8_t* DNData,
                                      size_t DNLength,
                                      const uint8_t* otherData,
                                      size_t otherLength,
                                      CertBlocklistItemMechanism itemMechanism)
   : mIsCurrent(false)
   , mItemMechanism(itemMechanism)
 {
@@ -64,21 +74,21 @@ CertBlocklistItem::~CertBlocklistItem()
 
 nsresult
 CertBlocklistItem::ToBase64(nsACString& b64DNOut, nsACString& b64OtherOut)
 {
   nsDependentCSubstring DNString(reinterpret_cast<char*>(mDNData),
                                  mDNLength);
   nsDependentCSubstring otherString(reinterpret_cast<char*>(mOtherData),
                                     mOtherLength);
-  nsresult rv = mozilla::Base64Encode(DNString, b64DNOut);
+  nsresult rv = Base64Encode(DNString, b64DNOut);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  rv = mozilla::Base64Encode(otherString, b64OtherOut);
+  rv = Base64Encode(otherString, b64OtherOut);
   return rv;
 }
 
 bool
 CertBlocklistItem::operator==(const CertBlocklistItem& aItem) const
 {
   if (aItem.mItemMechanism != mItemMechanism) {
     return false;
@@ -115,33 +125,54 @@ CertBlocklist::CertBlocklist()
 {
   if (!gCertBlockPRLog) {
     gCertBlockPRLog = PR_NewLogModule("CertBlock");
   }
 }
 
 CertBlocklist::~CertBlocklist()
 {
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_BACKGROUND_UPDATE_TIMER,
+                                  this);
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_MAX_STALENESS_IN_SECONDS,
+                                  this);
 }
 
 nsresult
 CertBlocklist::Init()
 {
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG, ("CertBlocklist::Init"));
 
   // Init must be on main thread for getting the profile directory
   if (!NS_IsMainThread()) {
     PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
            ("CertBlocklist::Init - called off main thread"));
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
+  // Register preference callbacks
+  nsresult rv =
+      Preferences::RegisterCallbackAndCall(CertBlocklist::PreferenceChanged,
+                                           PREF_BACKGROUND_UPDATE_TIMER,
+                                           this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Preferences::RegisterCallbackAndCall(CertBlocklist::PreferenceChanged,
+                                            PREF_MAX_STALENESS_IN_SECONDS,
+                                            this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   // Get the profile directory
-  nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                       getter_AddRefs(mBackingFile));
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(mBackingFile));
   if (NS_FAILED(rv) || !mBackingFile) {
     PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
            ("CertBlocklist::Init - couldn't get profile dir"));
     // Since we're returning NS_OK here, set mBackingFile to a safe value.
     // (We need initialization to succeed and CertBlocklist to be in a
     // well-defined state if the profile directory doesn't exist.)
     mBackingFile = nullptr;
     return NS_OK;
@@ -157,17 +188,17 @@ CertBlocklist::Init()
   }
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
          ("CertBlocklist::Init certList path: %s", path.get()));
 
   return NS_OK;
 }
 
 nsresult
-CertBlocklist::EnsureBackingFileInitialized(mozilla::MutexAutoLock& lock)
+CertBlocklist::EnsureBackingFileInitialized(MutexAutoLock& lock)
 {
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
          ("CertBlocklist::EnsureBackingFileInitialized"));
   if (mBackingFileIsInitialized || !mBackingFile) {
     return NS_OK;
   }
 
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
@@ -257,55 +288,55 @@ CertBlocklist::EnsureBackingFileInitiali
 // void revokeCertBySubjectAndPubKey(in string subject, in string pubKeyHash);
 NS_IMETHODIMP
 CertBlocklist::RevokeCertBySubjectAndPubKey(const char* aSubject,
                                             const char* aPubKeyHash)
 {
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
          ("CertBlocklist::RevokeCertBySubjectAndPubKey - subject is: %s and pubKeyHash: %s",
           aSubject, aPubKeyHash));
-  mozilla::MutexAutoLock lock(mMutex);
+  MutexAutoLock lock(mMutex);
 
   return AddRevokedCertInternal(nsDependentCString(aSubject),
                                 nsDependentCString(aPubKeyHash),
                                 BlockBySubjectAndPubKey,
                                 CertNewFromBlocklist, lock);
 }
 
 // void revokeCertByIssuerAndSerial(in string issuer, in string serialNumber);
 NS_IMETHODIMP
 CertBlocklist::RevokeCertByIssuerAndSerial(const char* aIssuer,
                                            const char* aSerialNumber)
 {
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
          ("CertBlocklist::RevokeCertByIssuerAndSerial - issuer is: %s and serial: %s",
           aIssuer, aSerialNumber));
-  mozilla::MutexAutoLock lock(mMutex);
+  MutexAutoLock lock(mMutex);
 
   return AddRevokedCertInternal(nsDependentCString(aIssuer),
                                 nsDependentCString(aSerialNumber),
                                 BlockByIssuerAndSerial,
                                 CertNewFromBlocklist, lock);
 }
 
 nsresult
 CertBlocklist::AddRevokedCertInternal(const nsACString& aEncodedDN,
                                       const nsACString& aEncodedOther,
                                       CertBlocklistItemMechanism aMechanism,
                                       CertBlocklistItemState aItemState,
-                                      mozilla::MutexAutoLock& /*proofOfLock*/)
+                                      MutexAutoLock& /*proofOfLock*/)
 {
     nsCString decodedDN;
     nsCString decodedOther;
 
-    nsresult rv = mozilla::Base64Decode(aEncodedDN, decodedDN);
+    nsresult rv = Base64Decode(aEncodedDN, decodedDN);
     if (NS_FAILED(rv)) {
       return rv;
     }
-    rv = mozilla::Base64Decode(aEncodedOther, decodedOther);
+    rv = Base64Decode(aEncodedOther, decodedOther);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     CertBlocklistItem item(reinterpret_cast<const uint8_t*>(decodedDN.get()),
                            decodedDN.Length(),
                            reinterpret_cast<const uint8_t*>(decodedOther.get()),
                            decodedOther.Length(),
@@ -447,17 +478,17 @@ WriteIssuer(nsCStringHashKey* aHashKey, 
 // revoked serial numbers, indented by one space.
 //
 // lines starting with a # character are ignored
 NS_IMETHODIMP
 CertBlocklist::SaveEntries()
 {
   PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
       ("CertBlocklist::SaveEntries - not initialized"));
-  mozilla::MutexAutoLock lock(mMutex);
+  MutexAutoLock lock(mMutex);
   if (!mModified) {
     return NS_OK;
   }
 
   nsresult rv = EnsureBackingFileInitialized(lock);
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -527,29 +558,29 @@ CertBlocklist::IsCertRevoked(const uint8
                              const uint8_t* aSerial,
                              uint32_t aSerialLength,
                              const uint8_t* aSubject,
                              uint32_t aSubjectLength,
                              const uint8_t* aPubKey,
                              uint32_t aPubKeyLength,
                              bool* _retval)
 {
-  mozilla::MutexAutoLock lock(mMutex);
+  MutexAutoLock lock(mMutex);
 
   nsresult rv = EnsureBackingFileInitialized(lock);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  mozilla::pkix::Input issuer;
-  mozilla::pkix::Input serial;
-  if (issuer.Init(aIssuer, aIssuerLength) != mozilla::pkix::Success) {
+  Input issuer;
+  Input serial;
+  if (issuer.Init(aIssuer, aIssuerLength) != Success) {
     return NS_ERROR_FAILURE;
   }
-  if (serial.Init(aSerial, aSerialLength) != mozilla::pkix::Success) {
+  if (serial.Init(aSerial, aSerialLength) != Success) {
     return NS_ERROR_FAILURE;
   }
 
   CertBlocklistItem issuerSerial(aIssuer, aIssuerLength, aSerial, aSerialLength,
                                  BlockByIssuerAndSerial);
   *_retval = mBlocklist.Contains(issuerSerial);
 
   if (*_retval) {
@@ -580,8 +611,48 @@ CertBlocklist::IsCertRevoked(const uint8
                                   static_cast<size_t>(aSubjectLength),
                                   reinterpret_cast<const uint8_t*>(hashString.get()),
                                   hashString.Length(),
                                   BlockBySubjectAndPubKey);
   *_retval = mBlocklist.Contains(subjectPubKey);
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+CertBlocklist::IsBlocklistFresh(bool* _retval)
+{
+  MutexAutoLock lock(mMutex);
+  *_retval = false;
+
+  uint32_t now = uint32_t(PR_Now() / PR_USEC_PER_SEC);
+
+  if (now > sLastBlocklistUpdate) {
+    int64_t interval = now - sLastBlocklistUpdate;
+    PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
+           ("CertBlocklist::IsBlocklistFresh we're after the last BlocklistUpdate "
+            "interval is %i, staleness %u", interval, sMaxStaleness));
+    *_retval = sMaxStaleness > interval;
+  }
+  PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
+         ("CertBlocklist::IsBlocklistFresh ? %s", *_retval ? "true" : "false"));
+  return NS_OK;
+}
+
+
+/* static */
+void
+CertBlocklist::PreferenceChanged(const char* aPref, void* aClosure)
+
+{
+  CertBlocklist* blocklist = reinterpret_cast<CertBlocklist*>(aClosure);
+  MutexAutoLock lock(blocklist->mMutex);
+
+  PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
+         ("CertBlocklist::PreferenceChanged %s changed", aPref));
+  if (strcmp(aPref, PREF_BACKGROUND_UPDATE_TIMER) == 0) {
+    sLastBlocklistUpdate = Preferences::GetUint(PREF_BACKGROUND_UPDATE_TIMER,
+                                                uint32_t(0));
+  } else if (strcmp(aPref, PREF_MAX_STALENESS_IN_SECONDS) == 0) {
+    sMaxStaleness = Preferences::GetUint(PREF_MAX_STALENESS_IN_SECONDS,
+                                         uint32_t(0));
+  }
+}
--- a/security/manager/boot/src/CertBlocklist.h
+++ b/security/manager/boot/src/CertBlocklist.h
@@ -73,12 +73,15 @@ private:
   bool mModified;
   bool mBackingFileIsInitialized;
   // call EnsureBackingFileInitialized before operations that read or
   // modify CertBlocklist data
   nsresult EnsureBackingFileInitialized(mozilla::MutexAutoLock& lock);
   nsCOMPtr<nsIFile> mBackingFile;
 
 protected:
+  static void PreferenceChanged(const char* aPref, void* aClosure);
+  static uint32_t sLastBlocklistUpdate;
+  static uint32_t sMaxStaleness;
   virtual ~CertBlocklist();
 };
 
 #endif // CertBlocklist_h