Bug 1839316: part 5) Guard the "fetchpriority" attribute behind a pref. r=kershaw...
[gecko.git] / security / certverifier / OCSPCache.cpp
blob557b501ad53e4e5e4bcf551c5f6f5030fbf3b657
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
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
16 * http://www.apache.org/licenses/LICENSE-2.0
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.
25 #include "OCSPCache.h"
27 #include <limits>
29 #include "NSSCertDBTrustDomain.h"
30 #include "pk11pub.h"
31 #include "mozilla/Logging.h"
32 #include "mozilla/StaticPrefs_privacy.h"
33 #include "mozpkix/pkixnss.h"
34 #include "ScopedNSSTypes.h"
35 #include "secerr.h"
37 extern mozilla::LazyLogModule gCertVerifierLog;
39 using namespace mozilla::pkix;
41 namespace mozilla {
42 namespace psm {
44 typedef mozilla::pkix::Result Result;
46 static SECStatus DigestLength(UniquePK11Context& context, uint32_t length) {
47 // Restrict length to 2 bytes because it should be big enough for all
48 // inputs this code will actually see and that it is well-defined and
49 // type-size-independent.
50 if (length >= 65536) {
51 return SECFailure;
53 unsigned char array[2];
54 array[0] = length & 255;
55 array[1] = (length >> 8) & 255;
57 return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
60 // Let derIssuer be the DER encoding of the issuer of certID.
61 // Let derPublicKey be the DER encoding of the public key of certID.
62 // Let serialNumber be the bytes of the serial number of certID.
63 // Let serialNumberLen be the number of bytes of serialNumber.
64 // Let firstPartyDomain be the first party domain of originAttributes.
65 // It is only non-empty when "privacy.firstParty.isolate" is enabled, in order
66 // to isolate OCSP cache by first party.
67 // Let firstPartyDomainLen be the number of bytes of firstPartyDomain.
68 // Let partitionKey be the partition key of originAttributes.
69 // Let partitionKeyLen be the number of bytes of partitionKey.
70 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
71 // || serialNumber || firstPartyDomainLen || firstPartyDomain || partitionKeyLen
72 // || partitionKey).
73 // Because the DER encodings include the length of the data encoded, and we also
74 // include the length of serialNumber and originAttributes, there do not exist
75 // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
76 // originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
77 // serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
78 // such that the concatenation of each tuple results in the same string of
79 // bytes but where each part in A is not equal to its counterpart in B. This is
80 // important because as a result it is computationally infeasible to find
81 // collisions that would subvert this cache (given that SHA384 is a
82 // cryptographically-secure hash function).
83 static SECStatus CertIDHash(SHA384Buffer& buf, const CertID& certID,
84 const OriginAttributes& originAttributes) {
85 UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
86 if (!context) {
87 return SECFailure;
89 SECStatus rv = PK11_DigestBegin(context.get());
90 if (rv != SECSuccess) {
91 return rv;
93 SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
94 rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
95 if (rv != SECSuccess) {
96 return rv;
98 SECItem certIDIssuerSubjectPublicKeyInfo =
99 UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
100 rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
101 certIDIssuerSubjectPublicKeyInfo.len);
102 if (rv != SECSuccess) {
103 return rv;
105 SECItem certIDSerialNumber = UnsafeMapInputToSECItem(certID.serialNumber);
106 rv = DigestLength(context, certIDSerialNumber.len);
107 if (rv != SECSuccess) {
108 return rv;
110 rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
111 certIDSerialNumber.len);
112 if (rv != SECSuccess) {
113 return rv;
116 auto populateOriginAttributesKey = [&context](const nsString& aKey) {
117 NS_ConvertUTF16toUTF8 key(aKey);
119 if (key.IsEmpty()) {
120 return SECSuccess;
123 SECStatus rv = DigestLength(context, key.Length());
124 if (rv != SECSuccess) {
125 return rv;
128 return PK11_DigestOp(context.get(),
129 BitwiseCast<const unsigned char*>(key.get()),
130 key.Length());
133 // OCSP should be isolated by firstPartyDomain and partitionKey, but not
134 // by containers.
135 rv = populateOriginAttributesKey(originAttributes.mFirstPartyDomain);
136 if (rv != SECSuccess) {
137 return rv;
140 bool isolateByPartitionKey =
141 originAttributes.mPrivateBrowsingId > 0
142 ? StaticPrefs::privacy_partition_network_state_ocsp_cache_pbmode()
143 : StaticPrefs::privacy_partition_network_state_ocsp_cache();
144 if (isolateByPartitionKey) {
145 rv = populateOriginAttributesKey(originAttributes.mPartitionKey);
146 if (rv != SECSuccess) {
147 return rv;
150 uint32_t outLen = 0;
151 rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
152 if (outLen != SHA384_LENGTH) {
153 return SECFailure;
155 return rv;
158 Result OCSPCache::Entry::Init(const CertID& aCertID,
159 const OriginAttributes& aOriginAttributes) {
160 SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
161 if (srv != SECSuccess) {
162 return MapPRErrorCodeToResult(PR_GetError());
164 return Success;
167 OCSPCache::OCSPCache() : mMutex("OCSPCache-mutex") {}
169 OCSPCache::~OCSPCache() { Clear(); }
171 // Returns false with index in an undefined state if no matching entry was
172 // found.
173 bool OCSPCache::FindInternal(const CertID& aCertID,
174 const OriginAttributes& aOriginAttributes,
175 /*out*/ size_t& index,
176 const MutexAutoLock& /* aProofOfLock */) {
177 mMutex.AssertCurrentThreadOwns();
178 if (mEntries.length() == 0) {
179 return false;
182 SHA384Buffer idHash;
183 SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
184 if (rv != SECSuccess) {
185 return false;
188 // mEntries is sorted with the most-recently-used entry at the end.
189 // Thus, searching from the end will often be fastest.
190 index = mEntries.length();
191 while (index > 0) {
192 --index;
193 if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
194 return true;
197 return false;
200 static inline void LogWithCertID(const char* aMessage, const CertID& aCertID,
201 const OriginAttributes& aOriginAttributes) {
202 nsAutoString info = u"firstPartyDomain: "_ns +
203 aOriginAttributes.mFirstPartyDomain +
204 u", partitionKey: "_ns + aOriginAttributes.mPartitionKey;
205 MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
206 (aMessage, &aCertID, NS_ConvertUTF16toUTF8(info).get()));
209 void OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
210 const MutexAutoLock& /* aProofOfLock */) {
211 mMutex.AssertCurrentThreadOwns();
212 Entry* entry = mEntries[aIndex];
213 // Since mEntries is sorted with the most-recently-used entry at the end,
214 // aIndex is likely to be near the end, so this is likely to be fast.
215 mEntries.erase(mEntries.begin() + aIndex);
216 // erase() does not shrink or realloc memory, so the append below should
217 // always succeed.
218 MOZ_RELEASE_ASSERT(mEntries.append(entry));
221 bool OCSPCache::Get(const CertID& aCertID,
222 const OriginAttributes& aOriginAttributes, Result& aResult,
223 Time& aValidThrough) {
224 MutexAutoLock lock(mMutex);
226 size_t index;
227 if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
228 LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
229 aOriginAttributes);
230 return false;
232 LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
233 aOriginAttributes);
234 aResult = mEntries[index]->mResult;
235 aValidThrough = mEntries[index]->mValidThrough;
236 MakeMostRecentlyUsed(index, lock);
237 return true;
240 Result OCSPCache::Put(const CertID& aCertID,
241 const OriginAttributes& aOriginAttributes, Result aResult,
242 Time aThisUpdate, Time aValidThrough) {
243 MutexAutoLock lock(mMutex);
245 size_t index;
246 if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
247 // Never replace an entry indicating a revoked certificate.
248 if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
249 LogWithCertID(
250 "OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
251 "not replacing",
252 aCertID, aOriginAttributes);
253 MakeMostRecentlyUsed(index, lock);
254 return Success;
257 // Never replace a newer entry with an older one unless the older entry
258 // indicates a revoked certificate, which we want to remember.
259 if (mEntries[index]->mThisUpdate > aThisUpdate &&
260 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
261 LogWithCertID(
262 "OCSPCache::Put(%p, \"%s\") already in cache with more "
263 "recent validity - not replacing",
264 aCertID, aOriginAttributes);
265 MakeMostRecentlyUsed(index, lock);
266 return Success;
269 // Only known good responses or responses indicating an unknown
270 // or revoked certificate should replace previously known responses.
271 if (aResult != Success && aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
272 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
273 LogWithCertID(
274 "OCSPCache::Put(%p, \"%s\") already in cache - not "
275 "replacing with less important status",
276 aCertID, aOriginAttributes);
277 MakeMostRecentlyUsed(index, lock);
278 return Success;
281 LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
282 aCertID, aOriginAttributes);
283 mEntries[index]->mResult = aResult;
284 mEntries[index]->mThisUpdate = aThisUpdate;
285 mEntries[index]->mValidThrough = aValidThrough;
286 MakeMostRecentlyUsed(index, lock);
287 return Success;
290 if (mEntries.length() == MaxEntries) {
291 LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
292 aCertID, aOriginAttributes);
293 for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
294 toEvict++) {
295 // Never evict an entry that indicates a revoked or unknokwn certificate,
296 // because revoked responses are more security-critical to remember.
297 if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
298 (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
299 delete *toEvict;
300 mEntries.erase(toEvict);
301 break;
304 // Well, we tried, but apparently everything is revoked or unknown.
305 // We don't want to remove a cached revoked or unknown response. If we're
306 // trying to insert a good response, we can just return "successfully"
307 // without doing so. This means we'll lose some speed, but it's not a
308 // security issue. If we're trying to insert a revoked or unknown response,
309 // we can't. We should return with an error that causes the current
310 // verification to fail.
311 if (mEntries.length() == MaxEntries) {
312 return aResult;
316 Entry* newEntry =
317 new (std::nothrow) Entry(aResult, aThisUpdate, aValidThrough);
318 // Normally we don't have to do this in Gecko, because OOM is fatal.
319 // However, if we want to embed this in another project, OOM might not
320 // be fatal, so handle this case.
321 if (!newEntry) {
322 return Result::FATAL_ERROR_NO_MEMORY;
324 Result rv = newEntry->Init(aCertID, aOriginAttributes);
325 if (rv != Success) {
326 delete newEntry;
327 return rv;
329 if (!mEntries.append(newEntry)) {
330 delete newEntry;
331 return Result::FATAL_ERROR_NO_MEMORY;
333 LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
334 aOriginAttributes);
335 return Success;
338 void OCSPCache::Clear() {
339 MutexAutoLock lock(mMutex);
340 MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
341 ("OCSPCache::Clear: clearing cache"));
342 // First go through and delete the memory being pointed to by the pointers
343 // in the vector.
344 for (Entry** entry = mEntries.begin(); entry < mEntries.end(); entry++) {
345 delete *entry;
347 // Then remove the pointers themselves.
348 mEntries.clearAndFree();
351 } // namespace psm
352 } // namespace mozilla