Bumping manifests a=b2g-bump
[gecko.git] / security / certverifier / OCSPCache.cpp
blob0bf7e0878a0a74df442a7a40f264556eafab3563
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 "pkix/pkixnss.h"
32 #include "ScopedNSSTypes.h"
33 #include "secerr.h"
35 #ifdef PR_LOGGING
36 extern PRLogModuleInfo* gCertVerifierLog;
37 #endif
39 using namespace mozilla::pkix;
41 namespace mozilla { namespace psm {
43 // Let derIssuer be the DER encoding of the issuer of aCert.
44 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
45 // Let serialNumber be the bytes of the serial number of aCert.
46 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
47 // Because the DER encodings include the length of the data encoded,
48 // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
49 // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
50 // each triplet results in the same string of bytes but where each part in A is
51 // not equal to its counterpart in B. This is important because as a result it
52 // is computationally infeasible to find collisions that would subvert this
53 // cache (given that SHA384 is a cryptographically-secure hash function).
54 static SECStatus
55 CertIDHash(SHA384Buffer& buf, const CertID& certID)
57 ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
58 if (!context) {
59 return SECFailure;
61 SECStatus rv = PK11_DigestBegin(context.get());
62 if (rv != SECSuccess) {
63 return rv;
65 SECItem certIDIssuer = UnsafeMapInputToSECItem(certID.issuer);
66 rv = PK11_DigestOp(context.get(), certIDIssuer.data, certIDIssuer.len);
67 if (rv != SECSuccess) {
68 return rv;
70 SECItem certIDIssuerSubjectPublicKeyInfo =
71 UnsafeMapInputToSECItem(certID.issuerSubjectPublicKeyInfo);
72 rv = PK11_DigestOp(context.get(), certIDIssuerSubjectPublicKeyInfo.data,
73 certIDIssuerSubjectPublicKeyInfo.len);
74 if (rv != SECSuccess) {
75 return rv;
77 SECItem certIDSerialNumber =
78 UnsafeMapInputToSECItem(certID.serialNumber);
79 rv = PK11_DigestOp(context.get(), certIDSerialNumber.data,
80 certIDSerialNumber.len);
81 if (rv != SECSuccess) {
82 return rv;
84 uint32_t outLen = 0;
85 rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
86 if (outLen != SHA384_LENGTH) {
87 return SECFailure;
89 return rv;
92 Result
93 OCSPCache::Entry::Init(const CertID& aCertID)
95 SECStatus srv = CertIDHash(mIDHash, aCertID);
96 if (srv != SECSuccess) {
97 return MapPRErrorCodeToResult(PR_GetError());
99 return Success;
102 OCSPCache::OCSPCache()
103 : mMutex("OCSPCache-mutex")
107 OCSPCache::~OCSPCache()
109 Clear();
112 // Returns false with index in an undefined state if no matching entry was
113 // found.
114 bool
115 OCSPCache::FindInternal(const CertID& aCertID, /*out*/ size_t& index,
116 const MutexAutoLock& /* aProofOfLock */)
118 if (mEntries.length() == 0) {
119 return false;
122 SHA384Buffer idHash;
123 SECStatus rv = CertIDHash(idHash, aCertID);
124 if (rv != SECSuccess) {
125 return false;
128 // mEntries is sorted with the most-recently-used entry at the end.
129 // Thus, searching from the end will often be fastest.
130 index = mEntries.length();
131 while (index > 0) {
132 --index;
133 if (memcmp(mEntries[index]->mIDHash, idHash, SHA384_LENGTH) == 0) {
134 return true;
137 return false;
140 static inline void
141 LogWithCertID(const char* aMessage, const CertID& aCertID)
143 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, &aCertID));
146 void
147 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
148 const MutexAutoLock& /* aProofOfLock */)
150 Entry* entry = mEntries[aIndex];
151 // Since mEntries is sorted with the most-recently-used entry at the end,
152 // aIndex is likely to be near the end, so this is likely to be fast.
153 mEntries.erase(mEntries.begin() + aIndex);
154 mEntries.append(entry);
157 bool
158 OCSPCache::Get(const CertID& aCertID, Result& aResult, Time& aValidThrough)
160 MutexAutoLock lock(mMutex);
162 size_t index;
163 if (!FindInternal(aCertID, index, lock)) {
164 LogWithCertID("OCSPCache::Get(%p) not in cache", aCertID);
165 return false;
167 LogWithCertID("OCSPCache::Get(%p) in cache", aCertID);
168 aResult = mEntries[index]->mResult;
169 aValidThrough = mEntries[index]->mValidThrough;
170 MakeMostRecentlyUsed(index, lock);
171 return true;
174 Result
175 OCSPCache::Put(const CertID& aCertID, Result aResult,
176 Time aThisUpdate, Time aValidThrough)
178 MutexAutoLock lock(mMutex);
180 size_t index;
181 if (FindInternal(aCertID, index, lock)) {
182 // Never replace an entry indicating a revoked certificate.
183 if (mEntries[index]->mResult == Result::ERROR_REVOKED_CERTIFICATE) {
184 LogWithCertID("OCSPCache::Put(%p) already in cache as revoked - "
185 "not replacing", aCertID);
186 MakeMostRecentlyUsed(index, lock);
187 return Success;
190 // Never replace a newer entry with an older one unless the older entry
191 // indicates a revoked certificate, which we want to remember.
192 if (mEntries[index]->mThisUpdate > aThisUpdate &&
193 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
194 LogWithCertID("OCSPCache::Put(%p) already in cache with more recent "
195 "validity - not replacing", aCertID);
196 MakeMostRecentlyUsed(index, lock);
197 return Success;
200 // Only known good responses or responses indicating an unknown
201 // or revoked certificate should replace previously known responses.
202 if (aResult != Success &&
203 aResult != Result::ERROR_OCSP_UNKNOWN_CERT &&
204 aResult != Result::ERROR_REVOKED_CERTIFICATE) {
205 LogWithCertID("OCSPCache::Put(%p) already in cache - not replacing "
206 "with less important status", aCertID);
207 MakeMostRecentlyUsed(index, lock);
208 return Success;
211 LogWithCertID("OCSPCache::Put(%p) already in cache - replacing", aCertID);
212 mEntries[index]->mResult = aResult;
213 mEntries[index]->mThisUpdate = aThisUpdate;
214 mEntries[index]->mValidThrough = aValidThrough;
215 MakeMostRecentlyUsed(index, lock);
216 return Success;
219 if (mEntries.length() == MaxEntries) {
220 LogWithCertID("OCSPCache::Put(%p) too full - evicting an entry", aCertID);
221 for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
222 toEvict++) {
223 // Never evict an entry that indicates a revoked or unknokwn certificate,
224 // because revoked responses are more security-critical to remember.
225 if ((*toEvict)->mResult != Result::ERROR_REVOKED_CERTIFICATE &&
226 (*toEvict)->mResult != Result::ERROR_OCSP_UNKNOWN_CERT) {
227 delete *toEvict;
228 mEntries.erase(toEvict);
229 break;
232 // Well, we tried, but apparently everything is revoked or unknown.
233 // We don't want to remove a cached revoked or unknown response. If we're
234 // trying to insert a good response, we can just return "successfully"
235 // without doing so. This means we'll lose some speed, but it's not a
236 // security issue. If we're trying to insert a revoked or unknown response,
237 // we can't. We should return with an error that causes the current
238 // verification to fail.
239 if (mEntries.length() == MaxEntries) {
240 return aResult;
244 Entry* newEntry = new (std::nothrow) Entry(aResult, aThisUpdate,
245 aValidThrough);
246 // Normally we don't have to do this in Gecko, because OOM is fatal.
247 // However, if we want to embed this in another project, OOM might not
248 // be fatal, so handle this case.
249 if (!newEntry) {
250 return Result::FATAL_ERROR_NO_MEMORY;
252 Result rv = newEntry->Init(aCertID);
253 if (rv != Success) {
254 delete newEntry;
255 return rv;
257 mEntries.append(newEntry);
258 LogWithCertID("OCSPCache::Put(%p) added to cache", aCertID);
259 return Success;
262 void
263 OCSPCache::Clear()
265 MutexAutoLock lock(mMutex);
266 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
267 // First go through and delete the memory being pointed to by the pointers
268 // in the vector.
269 for (Entry** entry = mEntries.begin(); entry < mEntries.end();
270 entry++) {
271 delete *entry;
273 // Then remove the pointers themselves.
274 mEntries.clearAndFree();
277 } } // namespace mozilla::psm