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