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
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/.
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"
29 #include "NSSCertDBTrustDomain.h"
31 #include "mozpkix/pkixnss.h"
32 #include "ScopedNSSTypes.h"
35 extern mozilla::LazyLogModule gCertVerifierLog
;
37 using namespace mozilla::pkix
;
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) {
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
));
84 SECStatus rv
= PK11_DigestBegin(context
.get());
85 if (rv
!= SECSuccess
) {
88 SECItem certIDIssuer
= UnsafeMapInputToSECItem(certID
.issuer
);
89 rv
= PK11_DigestOp(context
.get(), certIDIssuer
.data
, certIDIssuer
.len
);
90 if (rv
!= SECSuccess
) {
93 SECItem certIDIssuerSubjectPublicKeyInfo
=
94 UnsafeMapInputToSECItem(certID
.issuerSubjectPublicKeyInfo
);
95 rv
= PK11_DigestOp(context
.get(), certIDIssuerSubjectPublicKeyInfo
.data
,
96 certIDIssuerSubjectPublicKeyInfo
.len
);
97 if (rv
!= SECSuccess
) {
100 SECItem certIDSerialNumber
= UnsafeMapInputToSECItem(certID
.serialNumber
);
101 rv
= DigestLength(context
, certIDSerialNumber
.len
);
102 if (rv
!= SECSuccess
) {
105 rv
= PK11_DigestOp(context
.get(), certIDSerialNumber
.data
,
106 certIDSerialNumber
.len
);
107 if (rv
!= SECSuccess
) {
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
) {
119 PK11_DigestOp(context
.get(),
120 BitwiseCast
<const unsigned char*>(firstPartyDomain
.get()),
121 firstPartyDomain
.Length());
122 if (rv
!= SECSuccess
) {
127 rv
= PK11_DigestFinal(context
.get(), buf
, &outLen
, SHA384_LENGTH
);
128 if (outLen
!= SHA384_LENGTH
) {
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());
143 OCSPCache::OCSPCache() : mMutex("OCSPCache-mutex") {}
145 OCSPCache::~OCSPCache() { Clear(); }
147 // Returns false with index in an undefined state if no matching entry was
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) {
158 SECStatus rv
= CertIDHash(idHash
, aCertID
, aOriginAttributes
);
159 if (rv
!= SECSuccess
) {
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();
168 if (memcmp(mEntries
[index
]->mIDHash
, idHash
, SHA384_LENGTH
) == 0) {
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
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
);
199 if (!FindInternal(aCertID
, aOriginAttributes
, index
, lock
)) {
200 LogWithCertID("OCSPCache::Get(%p,\"%s\") not in cache", aCertID
,
204 LogWithCertID("OCSPCache::Get(%p,\"%s\") in cache", aCertID
,
206 aResult
= mEntries
[index
]->mResult
;
207 aValidThrough
= mEntries
[index
]->mValidThrough
;
208 MakeMostRecentlyUsed(index
, lock
);
212 Result
OCSPCache::Put(const CertID
& aCertID
,
213 const OriginAttributes
& aOriginAttributes
, Result aResult
,
214 Time aThisUpdate
, Time aValidThrough
) {
215 MutexAutoLock
lock(mMutex
);
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
) {
222 "OCSPCache::Put(%p, \"%s\") already in cache as revoked - "
224 aCertID
, aOriginAttributes
);
225 MakeMostRecentlyUsed(index
, lock
);
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
) {
234 "OCSPCache::Put(%p, \"%s\") already in cache with more "
235 "recent validity - not replacing",
236 aCertID
, aOriginAttributes
);
237 MakeMostRecentlyUsed(index
, lock
);
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
) {
246 "OCSPCache::Put(%p, \"%s\") already in cache - not "
247 "replacing with less important status",
248 aCertID
, aOriginAttributes
);
249 MakeMostRecentlyUsed(index
, lock
);
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
);
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();
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
) {
272 mEntries
.erase(toEvict
);
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
) {
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.
294 return Result::FATAL_ERROR_NO_MEMORY
;
296 Result rv
= newEntry
->Init(aCertID
, aOriginAttributes
);
301 if (!mEntries
.append(newEntry
)) {
303 return Result::FATAL_ERROR_NO_MEMORY
;
305 LogWithCertID("OCSPCache::Put(%p, \"%s\") added to cache", aCertID
,
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
316 for (Entry
** entry
= mEntries
.begin(); entry
< mEntries
.end(); entry
++) {
319 // Then remove the pointers themselves.
320 mEntries
.clearAndFree();
324 } // namespace mozilla