Bug 1264562 - Part 5: Double key OCSP cache with firstPartyDomain (adapted from Tor Browser patch #13670) r=keeler draft
authorJonathan Hao <jhao@mozilla.com>
Tue, 18 Oct 2016 17:08:39 +0800
changeset 427464 06b90eaebaf8e9a15402d465652de7944fa90466
parent 427463 bf96292f6f53968ba581c55191f3fa8e00cc3c96
child 534466 70851d5fbab5c058f458948abdd57e216735cc7b
push id33016
push userbmo:jhao@mozilla.com
push dateThu, 20 Oct 2016 10:18:14 +0000
reviewerskeeler
bugs1264562, 13670
milestone52.0a1
Bug 1264562 - Part 5: Double key OCSP cache with firstPartyDomain (adapted from Tor Browser patch #13670) r=keeler
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/OCSPCache.cpp
security/certverifier/OCSPCache.h
security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -416,17 +416,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
     // no stapled OCSP response
     mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NONE;
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("NSSCertDBTrustDomain: no stapled OCSP response"));
   }
 
   Result cachedResponseResult = Success;
   Time cachedResponseValidThrough(Time::uninitialized);
-  bool cachedResponsePresent = mOCSPCache.Get(certID,
+  bool cachedResponsePresent = mOCSPCache.Get(certID, mFirstPartyDomain,
                                               cachedResponseResult,
                                               cachedResponseValidThrough);
   if (cachedResponsePresent) {
     if (cachedResponseResult == Success && cachedResponseValidThrough >= time) {
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("NSSCertDBTrustDomain: cached OCSP response: good"));
       return Success;
     }
@@ -583,17 +583,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
 
   if (response.GetLength() == 0) {
     Result error = rv;
     if (attemptedRequest) {
       Time timeout(time);
       if (timeout.AddSeconds(ServerFailureDelaySeconds) != Success) {
         return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow
       }
-      rv = mOCSPCache.Put(certID, error, time, timeout);
+      rv = mOCSPCache.Put(certID, mFirstPartyDomain, error, time, timeout);
       if (rv != Success) {
         return rv;
       }
     }
     if (mOCSPFetching != FetchOCSPForDVSoftFail) {
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
              ("NSSCertDBTrustDomain: returning SECFailure after "
               "OCSP request failure"));
@@ -688,17 +688,18 @@ NSSCertDBTrustDomain::VerifyAndMaybeCach
     }
   }
   if (responseSource == ResponseIsFromNetwork ||
       rv == Success ||
       rv == Result::ERROR_REVOKED_CERTIFICATE ||
       rv == Result::ERROR_OCSP_UNKNOWN_CERT) {
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
            ("NSSCertDBTrustDomain: caching OCSP response"));
-    Result putRV = mOCSPCache.Put(certID, rv, thisUpdate, validThrough);
+    Result putRV = mOCSPCache.Put(certID, mFirstPartyDomain, rv, thisUpdate,
+                                  validThrough);
     if (putRV != Success) {
       return putRV;
     }
   }
 
   return rv;
 }
 
--- a/security/certverifier/OCSPCache.cpp
+++ b/security/certverifier/OCSPCache.cpp
@@ -33,29 +33,54 @@
 #include "secerr.h"
 
 extern mozilla::LazyLogModule gCertVerifierLog;
 
 using namespace mozilla::pkix;
 
 namespace mozilla { namespace psm {
 
+static SECStatus
+DigestLength(UniquePK11Context& context, uint32_t length)
+{
+  // Restrict length to 2 bytes because it should be big enough for all
+  // inputs this code will actually see and that it is well-defined and
+  // type-size-independent.
+  if (length >= 65536) {
+    return SECFailure;
+  }
+  unsigned char array[2];
+  array[0] = length & 255;
+  array[1] = (length >> 8) & 255;
+
+  return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
+}
+
 // Let derIssuer be the DER encoding of the issuer of aCert.
 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
 // Let serialNumber be the bytes of the serial number of aCert.
-// The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
-// Because the DER encodings include the length of the data encoded,
-// there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
-// B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
-// each triplet results in the same string of bytes but where each part in A is
-// not equal to its counterpart in B. This is important because as a result it
-// is computationally infeasible to find collisions that would subvert this
-// cache (given that SHA384 is a cryptographically-secure hash function).
+// Let serialNumberLen be the number of bytes of serialNumber.
+// The first party domain is only non-empty when "privacy.firstParty.isolate"
+// is enabled, in order to isolate OCSP cache by first party.
+// Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
+// The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
+// || serialNumber || firstPartyDomainLen || firstPartyDomain).
+// Because the DER encodings include the length of the data encoded, and we also
+// include the length of serialNumber and firstPartyDomain, there do not exist
+// A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
+// firstPartyDomainLenA, firstPartyDomainA) and B(derIssuerB, derPublicKeyB,
+// serialNumberLenB, serialNumberB, firstPartyDomainLenB, firstPartyDomainB)
+// such that the concatenation of each tuple results in the same string of
+// bytes but where each part in A is not equal to its counterpart in B. This is
+// important because as a result it is computationally infeasible to find
+// collisions that would subvert this cache (given that SHA384 is a
+// cryptographically-secure hash function).
 static SECStatus
-CertIDHash(SHA384Buffer& buf, const CertID& certID)
+CertIDHash(SHA384Buffer& buf, const CertID& certID,
+           const char* firstPartyDomain)
 {
   UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
   if (!context) {
     return SECFailure;
   }
   SECStatus rv = PK11_DigestBegin(context.get());
   if (rv != SECSuccess) {
     return rv;
@@ -69,33 +94,50 @@ CertIDHash(SHA384Buffer& buf, const Cert
     UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
   rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
                      certIDIssuerSubjectPublicKeyInfo.len);
   if (rv != SECSuccess) {
     return rv;
   }
   SECItem certIDSerialNumber =
     UnsafeMapInputToSECItem(certID.serialNumber);
+  rv = DigestLength(context, certIDSerialNumber.len);
+  if (rv != SECSuccess) {
+    return rv;
+  }
   rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
                      certIDSerialNumber.len);
   if (rv != SECSuccess) {
     return rv;
   }
+  if (firstPartyDomain) {
+    uint32_t firstPartyDomainLen = strlen(firstPartyDomain);
+    rv = DigestLength(context, firstPartyDomainLen);
+    if (rv != SECSuccess) {
+      return rv;
+    }
+    rv = PK11_DigestOp(context.get(),
+                       BitwiseCast<const unsigned char*>(firstPartyDomain),
+                       firstPartyDomainLen);
+    if (rv != SECSuccess) {
+      return rv;
+    }
+  }
   uint32_t outLen = 0;
   rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
   if (outLen != SHA384_LENGTH) {
     return SECFailure;
   }
   return rv;
 }
 
 Result
-OCSPCache::Entry::Init(const CertID& aCertID)
+OCSPCache::Entry::Init(const CertID& aCertID, const char* aFirstPartyDomain)
 {
-  SECStatus srv = CertIDHash(mIDHash, aCertID);
+  SECStatus srv = CertIDHash(mIDHash, aCertID, aFirstPartyDomain);
   if (srv != SECSuccess) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
   return Success;
 }
 
 OCSPCache::OCSPCache()
   : mMutex("OCSPCache-mutex")
@@ -105,25 +147,26 @@ OCSPCache::OCSPCache()
 OCSPCache::~OCSPCache()
 {
   Clear();
 }
 
 // Returns false with index in an undefined state if no matching entry was
 // found.
 bool
-OCSPCache::FindInternal(const CertID& aCertID, /*out*/ size_t& index,
+OCSPCache::FindInternal(const CertID& aCertID, const char* aFirstPartyDomain,
+                        /*out*/ size_t& index,
                         const MutexAutoLock& /* aProofOfLock */)
 {
   if (mEntries.length() == 0) {
     return false;
   }
 
   SHA384Buffer idHash;
-  SECStatus rv = CertIDHash(idHash, aCertID);
+  SECStatus rv = CertIDHash(idHash, aCertID, aFirstPartyDomain);
   if (rv != SECSuccess) {
     return false;
   }
 
   // mEntries is sorted with the most-recently-used entry at the end.
   // Thus, searching from the end will often be fastest.
   index = mEntries.length();
   while (index > 0) {
@@ -131,98 +174,107 @@ OCSPCache::FindInternal(const CertID& aC
     if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
       return true;
     }
   }
   return false;
 }
 
 static inline void
-LogWithCertID(const char* aMessage, const CertID& aCertID)
+LogWithCertID(const char* aMessage, const CertID& aCertID,
+              const char* aFirstPartyDomain)
 {
-  MOZ_LOG(gCertVerifierLog, LogLevel::Debug, (aMessage, &aCertID));
+  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+          (aMessage, &aCertID, aFirstPartyDomain));
 }
 
 void
 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
                                 const MutexAutoLock& /* aProofOfLock */)
 {
   Entry* entry = mEntries[aIndex];
   // Since mEntries is sorted with the most-recently-used entry at the end,
   // aIndex is likely to be near the end, so this is likely to be fast.
   mEntries.erase(mEntries.begin() + aIndex);
   // erase() does not shrink or realloc memory, so the append below should
   // always succeed.
   MOZ_RELEASE_ASSERT(mEntries.append(entry));
 }
 
 bool
-OCSPCache::Get(const CertID& aCertID, Result& aResult, Time& aValidThrough)
+OCSPCache::Get(const CertID& aCertID, const char* aFirstPartyDomain,
+               Result& aResult, Time& aValidThrough)
 {
   MutexAutoLock lock(mMutex);
 
   size_t index;
-  if (!FindInternal(aCertID, index, lock)) {
-    LogWithCertID("OCSPCache::Get(%p) not in cache", aCertID);
+  if (!FindInternal(aCertID, aFirstPartyDomain, index, lock)) {
+    LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
+                  aFirstPartyDomain);
     return false;
   }
-  LogWithCertID("OCSPCache::Get(%p) in cache", aCertID);
+  LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
+                aFirstPartyDomain);
   aResult = mEntries[index]->mResult;
   aValidThrough = mEntries[index]->mValidThrough;
   MakeMostRecentlyUsed(index, lock);
   return true;
 }
 
 Result
-OCSPCache::Put(const CertID& aCertID, Result aResult,
-               Time aThisUpdate, Time aValidThrough)
+OCSPCache::Put(const CertID& aCertID, const char* aFirstPartyDomain,
+               Result aResult, Time aThisUpdate, Time aValidThrough)
 {
   MutexAutoLock lock(mMutex);
 
   size_t index;
-  if (FindInternal(aCertID, index, lock)) {
+  if (FindInternal(aCertID, aFirstPartyDomain, index, lock)) {
     // Never replace an entry indicating a revoked certificate.
     if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
-      LogWithCertID("OCSPCache::Put(%p) already in cache as revoked - "
-                    "not replacing", aCertID);
+      LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
+                    "not replacing", aCertID, aFirstPartyDomain);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
     // Never replace a newer entry with an older one unless the older entry
     // indicates a revoked certificate, which we want to remember.
     if (mEntries[index]->mThisUpdate > aThisUpdate &&
         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
-      LogWithCertID("OCSPCache::Put(%p) already in cache with more recent "
-                    "validity - not replacing", aCertID);
+      LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache with more "
+                    "recent validity - not replacing", aCertID,
+                    aFirstPartyDomain);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
     // Only known good responses or responses indicating an unknown
     // or revoked certificate should replace previously known responses.
     if (aResult != Success &&
         aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
         aResult != Result::ERROR_REVOKED_CERTIFICATE) {
-      LogWithCertID("OCSPCache::Put(%p) already in cache - not replacing "
-                    "with less important status", aCertID);
+      LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - not "
+                    "replacing with less important status", aCertID,
+                    aFirstPartyDomain);
       MakeMostRecentlyUsed(index, lock);
       return Success;
     }
 
-    LogWithCertID("OCSPCache::Put(%p) already in cache - replacing", aCertID);
+    LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
+                  aCertID, aFirstPartyDomain);
     mEntries[index]->mResult = aResult;
     mEntries[index]->mThisUpdate = aThisUpdate;
     mEntries[index]->mValidThrough = aValidThrough;
     MakeMostRecentlyUsed(index, lock);
     return Success;
   }
 
   if (mEntries.length() == MaxEntries) {
-    LogWithCertID("OCSPCache::Put(%p) too full - evicting an entry", aCertID);
+    LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
+                  aCertID, aFirstPartyDomain);
     for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
          toEvict++) {
       // Never evict an entry that indicates a revoked or unknokwn certificate,
       // because revoked responses are more security-critical to remember.
       if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
           (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
         delete *toEvict;
         mEntries.erase(toEvict);
@@ -244,26 +296,27 @@ OCSPCache::Put(const CertID& aCertID, Re
   Entry* newEntry = new (std::nothrow) Entry(aResult, aThisUpdate,
                                              aValidThrough);
   // Normally we don't have to do this in Gecko, because OOM is fatal.
   // However, if we want to embed this in another project, OOM might not
   // be fatal, so handle this case.
   if (!newEntry) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
-  Result rv = newEntry->Init(aCertID);
+  Result rv = newEntry->Init(aCertID, aFirstPartyDomain);
   if (rv != Success) {
     delete newEntry;
     return rv;
   }
   if (!mEntries.append(newEntry)) {
     delete newEntry;
     return Result::FATAL_ERROR_NO_MEMORY;
   }
-  LogWithCertID("OCSPCache::Put(%p) added to cache", aCertID);
+  LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
+                aFirstPartyDomain);
   return Success;
 }
 
 void
 OCSPCache::Clear()
 {
   MutexAutoLock lock(mMutex);
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("OCSPCache::Clear: clearing cache"));
--- a/security/certverifier/OCSPCache.h
+++ b/security/certverifier/OCSPCache.h
@@ -51,32 +51,38 @@ typedef uint8_t SHA384Buffer[SHA384_LENG
 class OCSPCache
 {
 public:
   OCSPCache();
   ~OCSPCache();
 
   // Returns true if the status of the given certificate (issued by the given
   // issuer) is in the cache, and false otherwise.
+  // The first party domain is only non-empty when "privacy.firstParty.isolate"
+  // is enabled, in order to isolate OCSP cache by first party.
   // If it is in the cache, returns by reference the error code of the cached
   // status and the time through which the status is considered trustworthy.
   bool Get(const mozilla::pkix::CertID& aCertID,
+           const char* aFirstPartyDomain,
            /*out*/ mozilla::pkix::Result& aResult,
            /*out*/ mozilla::pkix::Time& aValidThrough);
 
   // Caches the status of the given certificate (issued by the given issuer).
+  // The first party domain is only non-empty when "privacy.firstParty.isolate"
+  // is enabled, in order to isolate OCSP cache by first party.
   // The status is considered trustworthy through the given time.
   // A status with an error code of SEC_ERROR_REVOKED_CERTIFICATE will not
   // be replaced or evicted.
   // A status with an error code of SEC_ERROR_OCSP_UNKNOWN_CERT will not
   // be evicted when the cache is full.
   // A status with a more recent thisUpdate will not be replaced with a
   // status with a less recent thisUpdate unless the less recent status
   // indicates the certificate is revoked.
   mozilla::pkix::Result Put(const mozilla::pkix::CertID& aCertID,
+                            const char* aFirstPartyDomain,
                             mozilla::pkix::Result aResult,
                             mozilla::pkix::Time aThisUpdate,
                             mozilla::pkix::Time aValidThrough);
 
   // Removes everything from the cache.
   void Clear();
 
 private:
@@ -86,28 +92,33 @@ private:
     Entry(mozilla::pkix::Result aResult,
           mozilla::pkix::Time aThisUpdate,
           mozilla::pkix::Time aValidThrough)
       : mResult(aResult)
       , mThisUpdate(aThisUpdate)
       , mValidThrough(aValidThrough)
     {
     }
-    mozilla::pkix::Result Init(const mozilla::pkix::CertID& aCertID);
+    mozilla::pkix::Result Init(const mozilla::pkix::CertID& aCertID,
+                               const char* aFirstPartyDomain);
 
     mozilla::pkix::Result mResult;
     mozilla::pkix::Time mThisUpdate;
     mozilla::pkix::Time mValidThrough;
     // The SHA-384 hash of the concatenation of the DER encodings of the
-    // issuer name and issuer key, followed by the serial number.
+    // issuer name and issuer key, followed by the length of the serial number,
+    // the serial number, the length of the first party domain, and the first
+    // party domain (if "privacy.firstparty.isolate" is enabled).
     // See the documentation for CertIDHash in OCSPCache.cpp.
     SHA384Buffer mIDHash;
   };
 
-  bool FindInternal(const mozilla::pkix::CertID& aCertID, /*out*/ size_t& index,
+  bool FindInternal(const mozilla::pkix::CertID& aCertID,
+                    const char* aFirstPartyDomain,
+                    /*out*/ size_t& index,
                     const MutexAutoLock& aProofOfLock);
   void MakeMostRecentlyUsed(size_t aIndex, const MutexAutoLock& aProofOfLock);
 
   Mutex mMutex;
   static const size_t MaxEntries = 1024;
   // Sorted with the most-recently-used entry at the end.
   // Using 256 here reserves as much possible inline storage as the vector
   // implementation will give us. 1024 bytes is the maximum it allows,
--- a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
+++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
@@ -42,29 +42,29 @@ protected:
   }
 
   const Time now;
   mozilla::psm::OCSPCache cache;
 };
 
 static void
 PutAndGet(OCSPCache& cache, const CertID& certID, Result result,
-          Time time)
+          Time time, const char* firstPartyDomain = nullptr)
 {
   // The first time is thisUpdate. The second is validUntil.
   // The caller is expecting the validUntil returned with Get
   // to be equal to the passed-in time. Since these values will
   // be different in practice, make thisUpdate less than validUntil.
   Time thisUpdate(time);
   ASSERT_EQ(Success, thisUpdate.SubtractSeconds(10));
-  Result rv = cache.Put(certID, result, thisUpdate, time);
+  Result rv = cache.Put(certID, firstPartyDomain, result, thisUpdate, time);
   ASSERT_TRUE(rv == Success);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, firstPartyDomain, resultOut, timeOut));
   ASSERT_EQ(result, resultOut);
   ASSERT_EQ(time, timeOut);
 }
 
 Input fakeIssuer1(LiteralInput("CN=issuer1"));
 Input fakeKey000(LiteralInput("key000"));
 Input fakeKey001(LiteralInput("key001"));
 Input fakeSerial0000(LiteralInput("0000"));
@@ -75,17 +75,17 @@ TEST_F(psm_OCSPCacheTest, TestPutAndGet)
   Input fakeSerial001(LiteralInput("001"));
 
   SCOPED_TRACE("");
   PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial001),
             Success, now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial000),
-                         resultOut, timeOut));
+                         nullptr, resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestVariousGets)
 {
   SCOPED_TRACE("");
   for (int i = 0; i < MaxCacheEntries; i++) {
     uint8_t serialBuf[8];
     snprintf(mozilla::BitwiseCast<char*, uint8_t*>(serialBuf), sizeof(serialBuf),
@@ -99,41 +99,41 @@ TEST_F(psm_OCSPCacheTest, TestVariousGet
   }
 
   Time timeIn(now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
 
   // This will be at the end of the list in the cache
   CertID cert0000(fakeIssuer1, fakeKey000, fakeSerial0000);
-  ASSERT_TRUE(cache.Get(cert0000, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0000, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
   // Once we access it, it goes to the front
-  ASSERT_TRUE(cache.Get(cert0000, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0000, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
 
   // This will be in the middle
   Time timeInPlus512(now);
   ASSERT_EQ(Success, timeInPlus512.AddSeconds(512));
 
   static const Input fakeSerial0512(LiteralInput("0512"));
   CertID cert0512(fakeIssuer1, fakeKey000, fakeSerial0512);
-  ASSERT_TRUE(cache.Get(cert0512, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0512, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeInPlus512, timeOut);
-  ASSERT_TRUE(cache.Get(cert0512, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(cert0512, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeInPlus512, timeOut);
 
   // We've never seen this certificate
   static const Input fakeSerial1111(LiteralInput("1111"));
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey000, fakeSerial1111),
-                         resultOut, timeOut));
+                         nullptr, resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestEviction)
 {
   SCOPED_TRACE("");
   // By putting more distinct entries in the cache than it can hold,
   // we cause the least recently used entry to be evicted.
   for (int i = 0; i < MaxCacheEntries + 1; i++) {
@@ -146,17 +146,17 @@ TEST_F(psm_OCSPCacheTest, TestEviction)
     ASSERT_EQ(Success, timeIn.AddSeconds(i));
     PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial),
               Success, timeIn);
   }
 
   Result resultOut;
   Time timeOut(Time::uninitialized);
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial0000),
-                         resultOut, timeOut));
+                         nullptr, resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestNoEvictionForRevokedResponses)
 {
   SCOPED_TRACE("");
   CertID notEvicted(fakeIssuer1, fakeKey000, fakeSerial0000);
   Time timeIn(now);
   PutAndGet(cache, notEvicted, Result::ERROR_REVOKED_CERTIFICATE, timeIn);
@@ -170,23 +170,23 @@ TEST_F(psm_OCSPCacheTest, TestNoEviction
     ASSERT_EQ(Success, fakeSerial.Init(serialBuf, 4));
     Time timeIn(now);
     ASSERT_EQ(Success, timeIn.AddSeconds(i));
     PutAndGet(cache, CertID(fakeIssuer1, fakeKey000, fakeSerial),
               Success, timeIn);
   }
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(notEvicted, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(notEvicted, nullptr, resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
   ASSERT_EQ(timeIn, timeOut);
 
   Input fakeSerial0001(LiteralInput("0001"));
   CertID evicted(fakeIssuer1, fakeKey000, fakeSerial0001);
-  ASSERT_FALSE(cache.Get(evicted, resultOut, timeOut));
+  ASSERT_FALSE(cache.Get(evicted, nullptr, resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, TestEverythingIsRevoked)
 {
   SCOPED_TRACE("");
   Time timeIn(now);
   // Fill up the cache with revoked responses.
   for (int i = 0; i < MaxCacheEntries; i++) {
@@ -203,71 +203,71 @@ TEST_F(psm_OCSPCacheTest, TestEverything
   static const Input fakeSerial1025(LiteralInput("1025"));
   CertID good(fakeIssuer1, fakeKey000, fakeSerial1025);
   // This will "succeed", allowing verification to continue. However,
   // nothing was actually put in the cache.
   Time timeInPlus1025(timeIn);
   ASSERT_EQ(Success, timeInPlus1025.AddSeconds(1025));
   Time timeInPlus1025Minus50(timeInPlus1025);
   ASSERT_EQ(Success, timeInPlus1025Minus50.SubtractSeconds(50));
-  Result result = cache.Put(good, Success, timeInPlus1025Minus50,
+  Result result = cache.Put(good, nullptr, Success, timeInPlus1025Minus50,
                             timeInPlus1025);
   ASSERT_EQ(Success, result);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_FALSE(cache.Get(good, resultOut, timeOut));
+  ASSERT_FALSE(cache.Get(good, nullptr, resultOut, timeOut));
 
   static const Input fakeSerial1026(LiteralInput("1026"));
   CertID revoked(fakeIssuer1, fakeKey000, fakeSerial1026);
   // This will fail, causing verification to fail.
   Time timeInPlus1026(timeIn);
   ASSERT_EQ(Success, timeInPlus1026.AddSeconds(1026));
   Time timeInPlus1026Minus50(timeInPlus1026);
   ASSERT_EQ(Success, timeInPlus1026Minus50.SubtractSeconds(50));
-  result = cache.Put(revoked, Result::ERROR_REVOKED_CERTIFICATE,
+  result = cache.Put(revoked, nullptr, Result::ERROR_REVOKED_CERTIFICATE,
                      timeInPlus1026Minus50, timeInPlus1026);
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, result);
 }
 
 TEST_F(psm_OCSPCacheTest, VariousIssuers)
 {
   SCOPED_TRACE("");
   Time timeIn(now);
   static const Input fakeIssuer2(LiteralInput("CN=issuer2"));
   static const Input fakeSerial001(LiteralInput("001"));
   CertID subject(fakeIssuer1, fakeKey000, fakeSerial001);
   PutAndGet(cache, subject, Success, now);
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(subject, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(subject, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(timeIn, timeOut);
   // Test that we don't match a different issuer DN
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer2, fakeKey000, fakeSerial001),
-                         resultOut, timeOut));
+                         nullptr, resultOut, timeOut));
   // Test that we don't match a different issuer key
   ASSERT_FALSE(cache.Get(CertID(fakeIssuer1, fakeKey001, fakeSerial001),
-                         resultOut, timeOut));
+                         nullptr, resultOut, timeOut));
 }
 
 TEST_F(psm_OCSPCacheTest, Times)
 {
   SCOPED_TRACE("");
   CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
   PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
             TimeFromElapsedSecondsAD(100));
   PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
   // This should not override the more recent entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, Result::ERROR_OCSP_UNKNOWN_CERT,
+            cache.Put(certID, nullptr, Result::ERROR_OCSP_UNKNOWN_CERT,
                       TimeFromElapsedSecondsAD(100),
                       TimeFromElapsedSecondsAD(100)));
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
   // Here we see the more recent time.
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
 
   // Result::ERROR_REVOKED_CERTIFICATE overrides everything
   PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
             TimeFromElapsedSecondsAD(50));
 }
@@ -276,39 +276,51 @@ TEST_F(psm_OCSPCacheTest, NetworkFailure
 {
   SCOPED_TRACE("");
   CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
   PutAndGet(cache, certID, Result::ERROR_CONNECT_REFUSED,
             TimeFromElapsedSecondsAD(100));
   PutAndGet(cache, certID, Success, TimeFromElapsedSecondsAD(200));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(300),
                       TimeFromElapsedSecondsAD(350)));
   Result resultOut;
   Time timeOut(Time::uninitialized);
-  ASSERT_TRUE(cache.Get(certID, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
   ASSERT_EQ(Success, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(200), timeOut);
 
   PutAndGet(cache, certID, Result::ERROR_OCSP_UNKNOWN_CERT,
             TimeFromElapsedSecondsAD(400));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(500),
                       TimeFromElapsedSecondsAD(550)));
-  ASSERT_TRUE(cache.Get(certID, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(400), timeOut);
 
   PutAndGet(cache, certID, Result::ERROR_REVOKED_CERTIFICATE,
             TimeFromElapsedSecondsAD(600));
   // This should not override the already present entry.
   ASSERT_EQ(Success,
-            cache.Put(certID, Result::ERROR_CONNECT_REFUSED,
+            cache.Put(certID, nullptr, Result::ERROR_CONNECT_REFUSED,
                       TimeFromElapsedSecondsAD(700),
                       TimeFromElapsedSecondsAD(750)));
-  ASSERT_TRUE(cache.Get(certID, resultOut, timeOut));
+  ASSERT_TRUE(cache.Get(certID, nullptr, resultOut, timeOut));
   ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, resultOut);
   ASSERT_EQ(TimeFromElapsedSecondsAD(600), timeOut);
 }
+
+TEST_F(psm_OCSPCacheTest, TestFirstPartyDomain)
+{
+  CertID certID(fakeIssuer1, fakeKey000, fakeSerial0000);
+
+  SCOPED_TRACE("");
+  PutAndGet(cache, certID, Success, now, "foo.com");
+
+  Result resultOut;
+  Time timeOut(Time::uninitialized);
+  ASSERT_FALSE(cache.Get(certID, "bar.com", resultOut, timeOut));
+}