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