Bug 1641886 [wpt PR 23851] - Support interpolating contain-intrinsic-size, a=testonly
[gecko.git] / security / certverifier / OCSPCache.cpp
blob2036c0e20238f390a48563aa51e1ec3316d0f495
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 "mozpkix/pkixnss.h"
32 #include "ScopedNSSTypes.h"
33 #include "secerr.h"
35 extern mozilla::LazyLogModule gCertVerifierLog;
37 using namespace mozilla::pkix;
39 namespace mozilla {
40 namespace psm {
42 typedef mozilla::pkix::Result Result;
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;
51 unsigned char array[2];
52 array[0] = length & 255;
53 array[1] = (length >> 8) & 255;
55 return PK11_DigestOp(context.get(), array, MOZ_ARRAY_LENGTH(array));
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 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumberLen
67 // || serialNumber || firstPartyDomainLen || firstPartyDomain).
68 // Because the DER encodings include the length of the data encoded, and we also
69 // include the length of serialNumber and originAttributes, there do not exist
70 // A(derIssuerA, derPublicKeyA, serialNumberLenA, serialNumberA,
71 // originAttributesLenA, originAttributesA) and B(derIssuerB, derPublicKeyB,
72 // serialNumberLenB, serialNumberB, originAttributesLenB, originAttributesB)
73 // such that the concatenation of each tuple results in the same string of
74 // bytes but where each part in A is not equal to its counterpart in B. This is
75 // important because as a result it is computationally infeasible to find
76 // collisions that would subvert this cache (given that SHA384 is a
77 // cryptographically-secure hash function).
78 static SECStatus CertIDHash(SHA384Buffer& buf, const CertID& certID,
79 const OriginAttributes& originAttributes) {
80 UniquePK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
81 if (!context) {
82 return SECFailure;
84 SECStatus rv = PK11_DigestBegin(context.get());
85 if (rv != SECSuccess) {
86 return rv;
88 SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
89 rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
90 if (rv != SECSuccess) {
91 return rv;
93 SECItem certIDIssuerSubjectPublicKeyInfo =
94 UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
95 rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
96 certIDIssuerSubjectPublicKeyInfo.len);
97 if (rv != SECSuccess) {
98 return rv;
100 SECItem certIDSerialNumber = UnsafeMapInputToSECItem(certID.serialNumber);
101 rv = DigestLength(context, certIDSerialNumber.len);
102 if (rv != SECSuccess) {
103 return rv;
105 rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
106 certIDSerialNumber.len);
107 if (rv != SECSuccess) {
108 return rv;
111 // OCSP should not be isolated by containers.
112 NS_ConvertUTF16toUTF8 firstPartyDomain(originAttributes.mFirstPartyDomain);
113 if (!firstPartyDomain.IsEmpty()) {
114 rv = DigestLength(context, firstPartyDomain.Length());
115 if (rv != SECSuccess) {
116 return rv;
118 rv =
119 PK11_DigestOp(context.get(),
120 BitwiseCast<const unsigned char*>(firstPartyDomain.get()),
121 firstPartyDomain.Length());
122 if (rv != SECSuccess) {
123 return rv;
126 uint32_t outLen = 0;
127 rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
128 if (outLen != SHA384_LENGTH) {
129 return SECFailure;
131 return rv;
134 Result OCSPCache::Entry::Init(const CertID& aCertID,
135 const OriginAttributes& aOriginAttributes) {
136 SECStatus srv = CertIDHash(mIDHash, aCertID, aOriginAttributes);
137 if (srv != SECSuccess) {
138 return MapPRErrorCodeToResult(PR_GetError());
140 return Success;
143 OCSPCache::OCSPCache() : mMutex("OCSPCache-mutex") {}
145 OCSPCache::~OCSPCache() { Clear(); }
147 // Returns false with index in an undefined state if no matching entry was
148 // found.
149 bool OCSPCache::FindInternal(const CertID& aCertID,
150 const OriginAttributes& aOriginAttributes,
151 /*out*/ size_t& index,
152 const MutexAutoLock& /* aProofOfLock */) {
153 if (mEntries.length() == 0) {
154 return false;
157 SHA384Buffer idHash;
158 SECStatus rv = CertIDHash(idHash, aCertID, aOriginAttributes);
159 if (rv != SECSuccess) {
160 return false;
163 // mEntries is sorted with the most-recently-used entry at the end.
164 // Thus, searching from the end will often be fastest.
165 index = mEntries.length();
166 while (index > 0) {
167 --index;
168 if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
169 return true;
172 return false;
175 static inline void LogWithCertID(const char* aMessage, const CertID& aCertID,
176 const OriginAttributes& aOriginAttributes) {
177 NS_ConvertUTF16toUTF8 firstPartyDomain(aOriginAttributes.mFirstPartyDomain);
178 MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
179 (aMessage, &aCertID, firstPartyDomain.get()));
182 void OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
183 const MutexAutoLock& /* aProofOfLock */) {
184 Entry* entry = mEntries[aIndex];
185 // Since mEntries is sorted with the most-recently-used entry at the end,
186 // aIndex is likely to be near the end, so this is likely to be fast.
187 mEntries.erase(mEntries.begin() + aIndex);
188 // erase() does not shrink or realloc memory, so the append below should
189 // always succeed.
190 MOZ_RELEASE_ASSERT(mEntries.append(entry));
193 bool OCSPCache::Get(const CertID& aCertID,
194 const OriginAttributes& aOriginAttributes, Result& aResult,
195 Time& aValidThrough) {
196 MutexAutoLock lock(mMutex);
198 size_t index;
199 if (!FindInternal(aCertID, aOriginAttributes, index, lock)) {
200 LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID,
201 aOriginAttributes);
202 return false;
204 LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID,
205 aOriginAttributes);
206 aResult = mEntries[index]->mResult;
207 aValidThrough = mEntries[index]->mValidThrough;
208 MakeMostRecentlyUsed(index, lock);
209 return true;
212 Result OCSPCache::Put(const CertID& aCertID,
213 const OriginAttributes& aOriginAttributes, Result aResult,
214 Time aThisUpdate, Time aValidThrough) {
215 MutexAutoLock lock(mMutex);
217 size_t index;
218 if (FindInternal(aCertID, aOriginAttributes, index, lock)) {
219 // Never replace an entry indicating a revoked certificate.
220 if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
221 LogWithCertID(
222 "OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
223 "not replacing",
224 aCertID, aOriginAttributes);
225 MakeMostRecentlyUsed(index, lock);
226 return Success;
229 // Never replace a newer entry with an older one unless the older entry
230 // indicates a revoked certificate, which we want to remember.
231 if (mEntries[index]->mThisUpdate > aThisUpdate &&
232 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
233 LogWithCertID(
234 "OCSPCache::Put(%p, \"%s\") already in cache with more "
235 "recent validity - not replacing",
236 aCertID, aOriginAttributes);
237 MakeMostRecentlyUsed(index, lock);
238 return Success;
241 // Only known good responses or responses indicating an unknown
242 // or revoked certificate should replace previously known responses.
243 if (aResult != Success && aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
244 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
245 LogWithCertID(
246 "OCSPCache::Put(%p, \"%s\") already in cache - not "
247 "replacing with less important status",
248 aCertID, aOriginAttributes);
249 MakeMostRecentlyUsed(index, lock);
250 return Success;
253 LogWithCertID("OCSPCache::Put(%p, \"%s\") already in cache - replacing",
254 aCertID, aOriginAttributes);
255 mEntries[index]->mResult = aResult;
256 mEntries[index]->mThisUpdate = aThisUpdate;
257 mEntries[index]->mValidThrough = aValidThrough;
258 MakeMostRecentlyUsed(index, lock);
259 return Success;
262 if (mEntries.length() == MaxEntries) {
263 LogWithCertID("OCSPCache::Put(%p, \"%s\") too full - evicting an entry",
264 aCertID, aOriginAttributes);
265 for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
266 toEvict++) {
267 // Never evict an entry that indicates a revoked or unknokwn certificate,
268 // because revoked responses are more security-critical to remember.
269 if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
270 (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
271 delete *toEvict;
272 mEntries.erase(toEvict);
273 break;
276 // Well, we tried, but apparently everything is revoked or unknown.
277 // We don't want to remove a cached revoked or unknown response. If we're
278 // trying to insert a good response, we can just return "successfully"
279 // without doing so. This means we'll lose some speed, but it's not a
280 // security issue. If we're trying to insert a revoked or unknown response,
281 // we can't. We should return with an error that causes the current
282 // verification to fail.
283 if (mEntries.length() == MaxEntries) {
284 return aResult;
288 Entry* newEntry =
289 new (std::nothrow) Entry(aResult, aThisUpdate, aValidThrough);
290 // Normally we don't have to do this in Gecko, because OOM is fatal.
291 // However, if we want to embed this in another project, OOM might not
292 // be fatal, so handle this case.
293 if (!newEntry) {
294 return Result::FATAL_ERROR_NO_MEMORY;
296 Result rv = newEntry->Init(aCertID, aOriginAttributes);
297 if (rv != Success) {
298 delete newEntry;
299 return rv;
301 if (!mEntries.append(newEntry)) {
302 delete newEntry;
303 return Result::FATAL_ERROR_NO_MEMORY;
305 LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID,
306 aOriginAttributes);
307 return Success;
310 void OCSPCache::Clear() {
311 MutexAutoLock lock(mMutex);
312 MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
313 ("OCSPCache::Clear: clearing cache"));
314 // First go through and delete the memory being pointed to by the pointers
315 // in the vector.
316 for (Entry** entry = mEntries.begin(); entry < mEntries.end(); entry++) {
317 delete *entry;
319 // Then remove the pointers themselves.
320 mEntries.clearAndFree();
323 } // namespace psm
324 } // namespace mozilla