tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

OCSPCache.cpp (13023B)


      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 /* This code is made available to you under your choice of the following sets
      4 * of licensing terms:
      5 */
      6 /* This Source Code Form is subject to the terms of the Mozilla Public
      7 * License, v. 2.0. If a copy of the MPL was not distributed with this
      8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
      9 */
     10 /* Copyright 2013 Mozilla Contributors
     11 *
     12 * Licensed under the Apache License, Version 2.0 (the "License");
     13 * you may not use this file except in compliance with the License.
     14 * You may obtain a copy of the License at
     15 *
     16 *     http://www.apache.org/licenses/LICENSE-2.0
     17 *
     18 * Unless required by applicable law or agreed to in writing, software
     19 * distributed under the License is distributed on an "AS IS" BASIS,
     20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     21 * See the License for the specific language governing permissions and
     22 * limitations under the License.
     23 */
     24 
     25 #include "OCSPCache.h"
     26 
     27 #include "NSSCertDBTrustDomain.h"
     28 #include "pk11pub.h"
     29 #include "mozilla/Logging.h"
     30 #include "mozilla/StaticPrefs_privacy.h"
     31 #include "mozpkix/pkixnss.h"
     32 #include "ScopedNSSTypes.h"
     33 #include "secerr.h"
     34 
     35 extern mozilla::LazyLogModule gCertVerifierLog;
     36 
     37 using namespace mozilla::pkix;
     38 
     39 namespace mozilla {
     40 namespace psm {
     41 
     42 typedef mozilla::pkix::Result Result;
     43 
     44 static SECStatus DigestLength(UniquePK11Context& context, uint32_t length) {
     45  // Restrict length to 2 bytes because it should be big enough for all
     46  // inputs this code will actually see and that it is well-defined and
     47  // type-size-independent.
     48  if (length >= 65536) {
     49    return SECFailure;
     50  }
     51  unsigned char array[2];
     52  array[0] = length & 255;
     53  array[1] = (length >> 8) & 255;
     54 
     55  return PK11_DigestOp(context.get(), array, std::size(array));
     56 }
     57 
     58 // Let derIssuer be the DER encoding of the issuer of certID.
     59 // Let derPublicKey be the DER encoding of the public key of certID.
     60 // Let serialNumber be the bytes of the serial number of certID.
     61 // Let serialNumberLen be the number of bytes of serialNumber.
     62 // Let firstPartyDomain be the first party domain of originAttributes.
     63 // It is only non-empty when "privacy.firstParty.isolate" is enabled, in order
     64 // to isolate OCSP cache by first party.
     65 // Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
     66 // Let partitionKey be the partition key of originAttributes.
     67 // Let partitionKeyLen be the number of bytes of partitionKey.
     68 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
     69 // || serialNumber || firstPartyDomainLen || firstPartyDomain || partitionKeyLen
     70 // || partitionKey).
     71 // Because the DER encodings include the length of the data encoded, and we also
     72 // include the length of serialNumber and originAttributes, there do not exist
     73 // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
     74 // originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
     75 // serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
     76 // such that the concatenation of each tuple results in the same string of
     77 // bytes but where each part in A is not equal to its counterpart in B. This is
     78 // important because as a result it is computationally infeasible to find
     79 // collisions that would subvert this cache (given that SHA384 is a
     80 // cryptographically-secure hash function).
     81 static SECStatus CertIDHash(SHA384Buffer& buf, const CertID& certID,
     82                            const OriginAttributes& originAttributes) {
     83  UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
     84  if (!context) {
     85    return SECFailure;
     86  }
     87  SECStatus rv = PK11_DigestBegin(context.get());
     88  if (rv != SECSuccess) {
     89    return rv;
     90  }
     91  SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
     92  rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
     93  if (rv != SECSuccess) {
     94    return rv;
     95  }
     96  SECItem certIDIssuerSubjectPublicKeyInfo =
     97      UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
     98  rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
     99                     certIDIssuerSubjectPublicKeyInfo.len);
    100  if (rv != SECSuccess) {
    101    return rv;
    102  }
    103  SECItem certIDSerialNumber = UnsafeMapInputToSECItem(certID.serialNumber);
    104  rv = DigestLength(context, certIDSerialNumber.len);
    105  if (rv != SECSuccess) {
    106    return rv;
    107  }
    108  rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
    109                     certIDSerialNumber.len);
    110  if (rv != SECSuccess) {
    111    return rv;
    112  }
    113 
    114  auto populateOriginAttributesKey = [&context](const nsString& aKey) {
    115    NS_ConvertUTF16toUTF8 key(aKey);
    116 
    117    if (key.IsEmpty()) {
    118      return SECSuccess;
    119    }
    120 
    121    SECStatus rv = DigestLength(context, key.Length());
    122    if (rv != SECSuccess) {
    123      return rv;
    124    }
    125 
    126    return PK11_DigestOp(context.get(),
    127                         BitwiseCast<const unsigned char*>(key.get()),
    128                         key.Length());
    129  };
    130 
    131  // OCSP should be isolated by firstPartyDomain and partitionKey, but not
    132  // by containers.
    133  rv = populateOriginAttributesKey(originAttributes.mFirstPartyDomain);
    134  if (rv != SECSuccess) {
    135    return rv;
    136  }
    137 
    138  bool isolateByPartitionKey =
    139      originAttributes.IsPrivateBrowsing()
    140          ? StaticPrefs::privacy_partition_network_state_ocsp_cache_pbmode()
    141          : StaticPrefs::privacy_partition_network_state_ocsp_cache();
    142  if (isolateByPartitionKey) {
    143    rv = populateOriginAttributesKey(originAttributes.mPartitionKey);
    144    if (rv != SECSuccess) {
    145      return rv;
    146    }
    147  }
    148  uint32_t outLen = 0;
    149  rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
    150  if (outLen != SHA384_LENGTH) {
    151    return SECFailure;
    152  }
    153  return rv;
    154 }
    155 
    156 Result OCSPCache::Entry::Init(const CertID& aCertID,
    157                              const OriginAttributes& aOriginAttributes) {
    158  SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
    159  if (srv != SECSuccess) {
    160    return MapPRErrorCodeToResult(PR_GetError());
    161  }
    162  return Success;
    163 }
    164 
    165 OCSPCache::OCSPCache() : mMutex("OCSPCache-mutex") {}
    166 
    167 OCSPCache::~OCSPCache() { Clear(); }
    168 
    169 // Returns false with index in an undefined state if no matching entry was
    170 // found.
    171 bool OCSPCache::FindInternal(const CertID& aCertID,
    172                             const OriginAttributes& aOriginAttributes,
    173                             /*out*/ size_t& index,
    174                             const MutexAutoLock& /* aProofOfLock */) {
    175  mMutex.AssertCurrentThreadOwns();
    176  if (mEntries.length() == 0) {
    177    return false;
    178  }
    179 
    180  SHA384Buffer idHash;
    181  SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
    182  if (rv != SECSuccess) {
    183    return false;
    184  }
    185 
    186  // mEntries is sorted with the most-recently-used entry at the end.
    187  // Thus, searching from the end will often be fastest.
    188  index = mEntries.length();
    189  while (index > 0) {
    190    --index;
    191    if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
    192      return true;
    193    }
    194  }
    195  return false;
    196 }
    197 
    198 static inline void LogWithCertID(const char* aMessage, const CertID& aCertID,
    199                                 const OriginAttributes& aOriginAttributes) {
    200  nsAutoString info = u"firstPartyDomain: "_ns +
    201                      aOriginAttributes.mFirstPartyDomain +
    202                      u", partitionKey: "_ns + aOriginAttributes.mPartitionKey;
    203  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
    204          (aMessage, &aCertID, NS_ConvertUTF16toUTF8(info).get()));
    205 }
    206 
    207 void OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
    208                                     const MutexAutoLock& /* aProofOfLock */) {
    209  mMutex.AssertCurrentThreadOwns();
    210  Entry* entry = mEntries[aIndex];
    211  // Since mEntries is sorted with the most-recently-used entry at the end,
    212  // aIndex is likely to be near the end, so this is likely to be fast.
    213  mEntries.erase(mEntries.begin() + aIndex);
    214  // erase() does not shrink or realloc memory, so the append below should
    215  // always succeed.
    216  MOZ_RELEASE_ASSERT(mEntries.append(entry));
    217 }
    218 
    219 bool OCSPCache::Get(const CertID& aCertID,
    220                    const OriginAttributes& aOriginAttributes, Result& aResult,
    221                    Time& aValidThrough) {
    222  MutexAutoLock lock(mMutex);
    223 
    224  size_t index;
    225  if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
    226    LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
    227                  aOriginAttributes);
    228    return false;
    229  }
    230  LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
    231                aOriginAttributes);
    232  aResult = mEntries[index]->mResult;
    233  aValidThrough = mEntries[index]->mValidThrough;
    234  MakeMostRecentlyUsed(index, lock);
    235  return true;
    236 }
    237 
    238 Result OCSPCache::Put(const CertID& aCertID,
    239                      const OriginAttributes& aOriginAttributes, Result aResult,
    240                      Time aThisUpdate, Time aValidThrough) {
    241  MutexAutoLock lock(mMutex);
    242 
    243  size_t index;
    244  if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
    245    // Never replace an entry indicating a revoked certificate.
    246    if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
    247      LogWithCertID(
    248          "OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
    249          "not replacing",
    250          aCertID, aOriginAttributes);
    251      MakeMostRecentlyUsed(index, lock);
    252      return Success;
    253    }
    254 
    255    // Never replace a newer entry with an older one unless the older entry
    256    // indicates a revoked certificate, which we want to remember.
    257    if (mEntries[index]->mThisUpdate > aThisUpdate &&
    258        aResult != Result::ERROR_REVOKED_CERTIFICATE) {
    259      LogWithCertID(
    260          "OCSPCache::Put(%p, \"%s\") already in cache with more "
    261          "recent validity - not replacing",
    262          aCertID, aOriginAttributes);
    263      MakeMostRecentlyUsed(index, lock);
    264      return Success;
    265    }
    266 
    267    // Only known good responses or responses indicating an unknown
    268    // or revoked certificate should replace previously known responses.
    269    if (aResult != Success && aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
    270        aResult != Result::ERROR_REVOKED_CERTIFICATE) {
    271      LogWithCertID(
    272          "OCSPCache::Put(%p, \"%s\") already in cache - not "
    273          "replacing with less important status",
    274          aCertID, aOriginAttributes);
    275      MakeMostRecentlyUsed(index, lock);
    276      return Success;
    277    }
    278 
    279    LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
    280                  aCertID, aOriginAttributes);
    281    mEntries[index]->mResult = aResult;
    282    mEntries[index]->mThisUpdate = aThisUpdate;
    283    mEntries[index]->mValidThrough = aValidThrough;
    284    MakeMostRecentlyUsed(index, lock);
    285    return Success;
    286  }
    287 
    288  if (mEntries.length() == MaxEntries) {
    289    LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
    290                  aCertID, aOriginAttributes);
    291    for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
    292         toEvict++) {
    293      // Never evict an entry that indicates a revoked or unknokwn certificate,
    294      // because revoked responses are more security-critical to remember.
    295      if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
    296          (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
    297        delete *toEvict;
    298        mEntries.erase(toEvict);
    299        break;
    300      }
    301    }
    302    // Well, we tried, but apparently everything is revoked or unknown.
    303    // We don't want to remove a cached revoked or unknown response. If we're
    304    // trying to insert a good response, we can just return "successfully"
    305    // without doing so. This means we'll lose some speed, but it's not a
    306    // security issue. If we're trying to insert a revoked or unknown response,
    307    // we can't. We should return with an error that causes the current
    308    // verification to fail.
    309    if (mEntries.length() == MaxEntries) {
    310      return aResult;
    311    }
    312  }
    313 
    314  Entry* newEntry =
    315      new (std::nothrow) Entry(aResult, aThisUpdate, aValidThrough);
    316  // Normally we don't have to do this in Gecko, because OOM is fatal.
    317  // However, if we want to embed this in another project, OOM might not
    318  // be fatal, so handle this case.
    319  if (!newEntry) {
    320    return Result::FATAL_ERROR_NO_MEMORY;
    321  }
    322  Result rv = newEntry->Init(aCertID, aOriginAttributes);
    323  if (rv != Success) {
    324    delete newEntry;
    325    return rv;
    326  }
    327  if (!mEntries.append(newEntry)) {
    328    delete newEntry;
    329    return Result::FATAL_ERROR_NO_MEMORY;
    330  }
    331  LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
    332                aOriginAttributes);
    333  return Success;
    334 }
    335 
    336 void OCSPCache::Clear() {
    337  MutexAutoLock lock(mMutex);
    338  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
    339          ("OCSPCache::Clear: clearing cache"));
    340  // First go through and delete the memory being pointed to by the pointers
    341  // in the vector.
    342  for (Entry** entry = mEntries.begin(); entry < mEntries.end(); entry++) {
    343    delete *entry;
    344  }
    345  // Then remove the pointers themselves.
    346  mEntries.clearAndFree();
    347 }
    348 
    349 }  // namespace psm
    350 }  // namespace mozilla