Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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 /* Copyright 2013 Mozilla Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
18 #include "OCSPCache.h"
20 #include "NSSCertDBTrustDomain.h"
21 #include "pk11pub.h"
22 #include "secerr.h"
24 #ifdef PR_LOGGING
25 extern PRLogModuleInfo* gCertVerifierLog;
26 #endif
28 namespace mozilla { namespace psm {
30 void
31 MozillaPKIX_PK11_DestroyContext_true(PK11Context* context)
32 {
33 PK11_DestroyContext(context, true);
34 }
36 typedef mozilla::pkix::ScopedPtr<PK11Context,
37 MozillaPKIX_PK11_DestroyContext_true>
38 ScopedPK11Context;
40 // Let derIssuer be the DER encoding of the issuer of aCert.
41 // Let derPublicKey be the DER encoding of the public key of aIssuerCert.
42 // Let serialNumber be the bytes of the serial number of aCert.
43 // The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
44 // Because the DER encodings include the length of the data encoded,
45 // there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
46 // B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
47 // each triplet results in the same string of bytes but where each part in A is
48 // not equal to its counterpart in B. This is important because as a result it
49 // is computationally infeasible to find collisions that would subvert this
50 // cache (given that SHA384 is a cryptographically-secure hash function).
51 static SECStatus
52 CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert,
53 const CERTCertificate* aIssuerCert)
54 {
55 ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
56 if (!context) {
57 return SECFailure;
58 }
59 SECStatus rv = PK11_DigestBegin(context.get());
60 if (rv != SECSuccess) {
61 return rv;
62 }
63 rv = PK11_DigestOp(context.get(), aCert->derIssuer.data,
64 aCert->derIssuer.len);
65 if (rv != SECSuccess) {
66 return rv;
67 }
68 rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data,
69 aIssuerCert->derPublicKey.len);
70 if (rv != SECSuccess) {
71 return rv;
72 }
73 rv = PK11_DigestOp(context.get(), aCert->serialNumber.data,
74 aCert->serialNumber.len);
75 if (rv != SECSuccess) {
76 return rv;
77 }
78 uint32_t outLen = 0;
79 rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
80 if (outLen != SHA384_LENGTH) {
81 return SECFailure;
82 }
83 return rv;
84 }
86 SECStatus
87 OCSPCache::Entry::Init(const CERTCertificate* aCert,
88 const CERTCertificate* aIssuerCert,
89 PRErrorCode aErrorCode,
90 PRTime aThisUpdate,
91 PRTime aValidThrough)
92 {
93 mErrorCode = aErrorCode;
94 mThisUpdate = aThisUpdate;
95 mValidThrough = aValidThrough;
96 return CertIDHash(mIDHash, aCert, aIssuerCert);
97 }
99 OCSPCache::OCSPCache()
100 : mMutex("OCSPCache-mutex")
101 {
102 }
104 OCSPCache::~OCSPCache()
105 {
106 Clear();
107 }
109 // Returns -1 if no entry is found for the given (cert, issuer) pair.
110 int32_t
111 OCSPCache::FindInternal(const CERTCertificate* aCert,
112 const CERTCertificate* aIssuerCert,
113 const MutexAutoLock& /* aProofOfLock */)
114 {
115 if (mEntries.length() == 0) {
116 return -1;
117 }
119 SHA384Buffer idHash;
120 SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
121 if (rv != SECSuccess) {
122 return -1;
123 }
125 // mEntries is sorted with the most-recently-used entry at the end.
126 // Thus, searching from the end will often be fastest.
127 for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
128 if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
129 return i;
130 }
131 }
132 return -1;
133 }
135 void
136 OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
137 const CERTCertificate* aIssuerCert)
138 {
139 #ifdef PR_LOGGING
140 if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
141 mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
142 cn(CERT_GetCommonName(&aCert->subject));
143 mozilla::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
144 cnIssuer(CERT_GetCommonName(&aIssuerCert->subject));
145 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get()));
146 }
147 #endif
148 }
150 void
151 OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
152 const MutexAutoLock& /* aProofOfLock */)
153 {
154 Entry* entry = mEntries[aIndex];
155 // Since mEntries is sorted with the most-recently-used entry at the end,
156 // aIndex is likely to be near the end, so this is likely to be fast.
157 mEntries.erase(mEntries.begin() + aIndex);
158 mEntries.append(entry);
159 }
161 bool
162 OCSPCache::Get(const CERTCertificate* aCert,
163 const CERTCertificate* aIssuerCert,
164 PRErrorCode& aErrorCode,
165 PRTime& aValidThrough)
166 {
167 PR_ASSERT(aCert);
168 PR_ASSERT(aIssuerCert);
170 MutexAutoLock lock(mMutex);
172 int32_t index = FindInternal(aCert, aIssuerCert, lock);
173 if (index < 0) {
174 LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
175 return false;
176 }
177 LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
178 aErrorCode = mEntries[index]->mErrorCode;
179 aValidThrough = mEntries[index]->mValidThrough;
180 MakeMostRecentlyUsed(index, lock);
181 return true;
182 }
184 SECStatus
185 OCSPCache::Put(const CERTCertificate* aCert,
186 const CERTCertificate* aIssuerCert,
187 PRErrorCode aErrorCode,
188 PRTime aThisUpdate,
189 PRTime aValidThrough)
190 {
191 PR_ASSERT(aCert);
192 PR_ASSERT(aIssuerCert);
194 MutexAutoLock lock(mMutex);
196 int32_t index = FindInternal(aCert, aIssuerCert, lock);
198 if (index >= 0) {
199 // Never replace an entry indicating a revoked certificate.
200 if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
201 LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
202 "not replacing", aCert, aIssuerCert);
203 MakeMostRecentlyUsed(index, lock);
204 return SECSuccess;
205 }
207 // Never replace a newer entry with an older one unless the older entry
208 // indicates a revoked certificate, which we want to remember.
209 if (mEntries[index]->mThisUpdate > aThisUpdate &&
210 aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
211 LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent "
212 "validity - not replacing", aCert, aIssuerCert);
213 MakeMostRecentlyUsed(index, lock);
214 return SECSuccess;
215 }
217 // Only known good responses or responses indicating an unknown
218 // or revoked certificate should replace previously known responses.
219 if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
220 aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
221 LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing "
222 "with less important status", aCert, aIssuerCert);
223 MakeMostRecentlyUsed(index, lock);
224 return SECSuccess;
225 }
227 LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing",
228 aCert, aIssuerCert);
229 mEntries[index]->mErrorCode = aErrorCode;
230 mEntries[index]->mThisUpdate = aThisUpdate;
231 mEntries[index]->mValidThrough = aValidThrough;
232 MakeMostRecentlyUsed(index, lock);
233 return SECSuccess;
234 }
236 if (mEntries.length() == MaxEntries) {
237 LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert,
238 aIssuerCert);
239 for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
240 toEvict++) {
241 // Never evict an entry that indicates a revoked or unknokwn certificate,
242 // because revoked responses are more security-critical to remember.
243 if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE &&
244 (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) {
245 delete *toEvict;
246 mEntries.erase(toEvict);
247 break;
248 }
249 }
250 // Well, we tried, but apparently everything is revoked or unknown.
251 // We don't want to remove a cached revoked or unknown response. If we're
252 // trying to insert a good response, we can just return "successfully"
253 // without doing so. This means we'll lose some speed, but it's not a
254 // security issue. If we're trying to insert a revoked or unknown response,
255 // we can't. We should return with an error that causes the current
256 // verification to fail.
257 if (mEntries.length() == MaxEntries) {
258 if (aErrorCode != 0) {
259 PR_SetError(aErrorCode, 0);
260 return SECFailure;
261 }
262 return SECSuccess;
263 }
264 }
266 Entry* newEntry = new Entry();
267 // Normally we don't have to do this in Gecko, because OOM is fatal.
268 // However, if we want to embed this in another project, OOM might not
269 // be fatal, so handle this case.
270 if (!newEntry) {
271 PR_SetError(SEC_ERROR_NO_MEMORY, 0);
272 return SECFailure;
273 }
274 SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate,
275 aValidThrough);
276 if (rv != SECSuccess) {
277 return rv;
278 }
279 mEntries.append(newEntry);
280 LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert);
281 return SECSuccess;
282 }
284 void
285 OCSPCache::Clear()
286 {
287 MutexAutoLock lock(mMutex);
288 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
289 // First go through and delete the memory being pointed to by the pointers
290 // in the vector.
291 for (Entry** entry = mEntries.begin(); entry < mEntries.end();
292 entry++) {
293 delete *entry;
294 }
295 // Then remove the pointers themselves.
296 mEntries.clearAndFree();
297 }
299 } } // namespace mozilla::psm