security/certverifier/OCSPCache.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
     3 /* Copyright 2013 Mozilla Foundation
     4  *
     5  * Licensed under the Apache License, Version 2.0 (the "License");
     6  * you may not use this file except in compliance with the License.
     7  * You may obtain a copy of the License at
     8  *
     9  *     http://www.apache.org/licenses/LICENSE-2.0
    10  *
    11  * Unless required by applicable law or agreed to in writing, software
    12  * distributed under the License is distributed on an "AS IS" BASIS,
    13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  * See the License for the specific language governing permissions and
    15  * limitations under the License.
    16  */
    18 #include "OCSPCache.h"
    20 #include "NSSCertDBTrustDomain.h"
    21 #include "pk11pub.h"
    22 #include "secerr.h"
    24 #ifdef PR_LOGGING
    25 extern PRLogModuleInfo* gCertVerifierLog;
    26 #endif
    28 namespace mozilla { namespace psm {
    30 void
    31 MozillaPKIX_PK11_DestroyContext_true(PK11Context* context)
    32 {
    33   PK11_DestroyContext(context, true);
    34 }
    36 typedef mozilla::pkix::ScopedPtr<PK11Context,
    37                                  MozillaPKIX_PK11_DestroyContext_true>
    38                                  ScopedPK11Context;
    40 // Let derIssuer be the DER encoding of the issuer of aCert.
    41 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
    42 // Let serialNumber be the bytes of the serial number of aCert.
    43 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
    44 // Because the DER encodings include the length of the data encoded,
    45 // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
    46 // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
    47 // each triplet results in the same string of bytes but where each part in A is
    48 // not equal to its counterpart in B. This is important because as a result it
    49 // is computationally infeasible to find collisions that would subvert this
    50 // cache (given that SHA384 is a cryptographically-secure hash function).
    51 static SECStatus
    52 CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert,
    53        const CERTCertificate* aIssuerCert)
    54 {
    55   ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
    56   if (!context) {
    57     return SECFailure;
    58   }
    59   SECStatus rv = PK11_DigestBegin(context.get());
    60   if (rv != SECSuccess) {
    61     return rv;
    62   }
    63   rv = PK11_DigestOp(context.get(), aCert->derIssuer.data,
    64                      aCert->derIssuer.len);
    65   if (rv != SECSuccess) {
    66     return rv;
    67   }
    68   rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data,
    69                      aIssuerCert->derPublicKey.len);
    70   if (rv != SECSuccess) {
    71     return rv;
    72   }
    73   rv = PK11_DigestOp(context.get(), aCert->serialNumber.data,
    74                      aCert->serialNumber.len);
    75   if (rv != SECSuccess) {
    76     return rv;
    77   }
    78   uint32_t outLen = 0;
    79   rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
    80   if (outLen != SHA384_LENGTH) {
    81     return SECFailure;
    82   }
    83   return rv;
    84 }
    86 SECStatus
    87 OCSPCache::Entry::Init(const CERTCertificate* aCert,
    88                        const CERTCertificate* aIssuerCert,
    89                        PRErrorCode aErrorCode,
    90                        PRTime aThisUpdate,
    91                        PRTime aValidThrough)
    92 {
    93   mErrorCode = aErrorCode;
    94   mThisUpdate = aThisUpdate;
    95   mValidThrough = aValidThrough;
    96   return CertIDHash(mIDHash, aCert, aIssuerCert);
    97 }
    99 OCSPCache::OCSPCache()
   100   : mMutex("OCSPCache-mutex")
   101 {
   102 }
   104 OCSPCache::~OCSPCache()
   105 {
   106   Clear();
   107 }
   109 // Returns -1 if no entry is found for the given (cert, issuer) pair.
   110 int32_t
   111 OCSPCache::FindInternal(const CERTCertificate* aCert,
   112                         const CERTCertificate* aIssuerCert,
   113                         const MutexAutoLock& /* aProofOfLock */)
   114 {
   115   if (mEntries.length() == 0) {
   116     return -1;
   117   }
   119   SHA384Buffer idHash;
   120   SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
   121   if (rv != SECSuccess) {
   122     return -1;
   123   }
   125   // mEntries is sorted with the most-recently-used entry at the end.
   126   // Thus, searching from the end will often be fastest.
   127   for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
   128     if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
   129       return i;
   130     }
   131   }
   132   return -1;
   133 }
   135 void
   136 OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
   137                         const CERTCertificate* aIssuerCert)
   138 {
   139 #ifdef PR_LOGGING
   140   if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
   141     mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
   142       cn(CERT_GetCommonName(&aCert->subject));
   143     mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
   144       cnIssuer(CERT_GetCommonName(&aIssuerCert->subject));
   145     PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get()));
   146   }
   147 #endif
   148 }
   150 void
   151 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
   152                                 const MutexAutoLock& /* aProofOfLock */)
   153 {
   154   Entry* entry = mEntries[aIndex];
   155   // Since mEntries is sorted with the most-recently-used entry at the end,
   156   // aIndex is likely to be near the end, so this is likely to be fast.
   157   mEntries.erase(mEntries.begin() + aIndex);
   158   mEntries.append(entry);
   159 }
   161 bool
   162 OCSPCache::Get(const CERTCertificate* aCert,
   163                const CERTCertificate* aIssuerCert,
   164                PRErrorCode& aErrorCode,
   165                PRTime& aValidThrough)
   166 {
   167   PR_ASSERT(aCert);
   168   PR_ASSERT(aIssuerCert);
   170   MutexAutoLock lock(mMutex);
   172   int32_t index = FindInternal(aCert, aIssuerCert, lock);
   173   if (index < 0) {
   174     LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
   175     return false;
   176   }
   177   LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
   178   aErrorCode = mEntries[index]->mErrorCode;
   179   aValidThrough = mEntries[index]->mValidThrough;
   180   MakeMostRecentlyUsed(index, lock);
   181   return true;
   182 }
   184 SECStatus
   185 OCSPCache::Put(const CERTCertificate* aCert,
   186                const CERTCertificate* aIssuerCert,
   187                PRErrorCode aErrorCode,
   188                PRTime aThisUpdate,
   189                PRTime aValidThrough)
   190 {
   191   PR_ASSERT(aCert);
   192   PR_ASSERT(aIssuerCert);
   194   MutexAutoLock lock(mMutex);
   196   int32_t index = FindInternal(aCert, aIssuerCert, lock);
   198   if (index >= 0) {
   199     // Never replace an entry indicating a revoked certificate.
   200     if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
   201       LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
   202                    "not replacing", aCert, aIssuerCert);
   203       MakeMostRecentlyUsed(index, lock);
   204       return SECSuccess;
   205     }
   207     // Never replace a newer entry with an older one unless the older entry
   208     // indicates a revoked certificate, which we want to remember.
   209     if (mEntries[index]->mThisUpdate > aThisUpdate &&
   210         aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
   211       LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent "
   212                    "validity - not replacing", aCert, aIssuerCert);
   213       MakeMostRecentlyUsed(index, lock);
   214       return SECSuccess;
   215     }
   217     // Only known good responses or responses indicating an unknown
   218     // or revoked certificate should replace previously known responses.
   219     if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
   220         aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
   221       LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing "
   222                    "with less important status", aCert, aIssuerCert);
   223       MakeMostRecentlyUsed(index, lock);
   224       return SECSuccess;
   225     }
   227     LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing",
   228                  aCert, aIssuerCert);
   229     mEntries[index]->mErrorCode = aErrorCode;
   230     mEntries[index]->mThisUpdate = aThisUpdate;
   231     mEntries[index]->mValidThrough = aValidThrough;
   232     MakeMostRecentlyUsed(index, lock);
   233     return SECSuccess;
   234   }
   236   if (mEntries.length() == MaxEntries) {
   237     LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert,
   238                  aIssuerCert);
   239     for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
   240          toEvict++) {
   241       // Never evict an entry that indicates a revoked or unknokwn certificate,
   242       // because revoked responses are more security-critical to remember.
   243       if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE &&
   244           (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) {
   245         delete *toEvict;
   246         mEntries.erase(toEvict);
   247         break;
   248       }
   249     }
   250     // Well, we tried, but apparently everything is revoked or unknown.
   251     // We don't want to remove a cached revoked or unknown response. If we're
   252     // trying to insert a good response, we can just return "successfully"
   253     // without doing so. This means we'll lose some speed, but it's not a
   254     // security issue. If we're trying to insert a revoked or unknown response,
   255     // we can't. We should return with an error that causes the current
   256     // verification to fail.
   257     if (mEntries.length() == MaxEntries) {
   258       if (aErrorCode != 0) {
   259         PR_SetError(aErrorCode, 0);
   260         return SECFailure;
   261       }
   262       return SECSuccess;
   263     }
   264   }
   266   Entry* newEntry = new Entry();
   267   // Normally we don't have to do this in Gecko, because OOM is fatal.
   268   // However, if we want to embed this in another project, OOM might not
   269   // be fatal, so handle this case.
   270   if (!newEntry) {
   271     PR_SetError(SEC_ERROR_NO_MEMORY, 0);
   272     return SECFailure;
   273   }
   274   SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate,
   275                                 aValidThrough);
   276   if (rv != SECSuccess) {
   277     return rv;
   278   }
   279   mEntries.append(newEntry);
   280   LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert);
   281   return SECSuccess;
   282 }
   284 void
   285 OCSPCache::Clear()
   286 {
   287   MutexAutoLock lock(mMutex);
   288   PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
   289   // First go through and delete the memory being pointed to by the pointers
   290   // in the vector.
   291   for (Entry** entry = mEntries.begin(); entry < mEntries.end();
   292        entry++) {
   293     delete *entry;
   294   }
   295   // Then remove the pointers themselves.
   296   mEntries.clearAndFree();
   297 }
   299 } } // namespace mozilla::psm

mercurial