|
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 */ |
|
17 |
|
18 #include "OCSPCache.h" |
|
19 |
|
20 #include "NSSCertDBTrustDomain.h" |
|
21 #include "pk11pub.h" |
|
22 #include "secerr.h" |
|
23 |
|
24 #ifdef PR_LOGGING |
|
25 extern PRLogModuleInfo* gCertVerifierLog; |
|
26 #endif |
|
27 |
|
28 namespace mozilla { namespace psm { |
|
29 |
|
30 void |
|
31 MozillaPKIX_PK11_DestroyContext_true(PK11Context* context) |
|
32 { |
|
33 PK11_DestroyContext(context, true); |
|
34 } |
|
35 |
|
36 typedef mozilla::pkix::ScopedPtr<PK11Context, |
|
37 MozillaPKIX_PK11_DestroyContext_true> |
|
38 ScopedPK11Context; |
|
39 |
|
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 } |
|
85 |
|
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 } |
|
98 |
|
99 OCSPCache::OCSPCache() |
|
100 : mMutex("OCSPCache-mutex") |
|
101 { |
|
102 } |
|
103 |
|
104 OCSPCache::~OCSPCache() |
|
105 { |
|
106 Clear(); |
|
107 } |
|
108 |
|
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 } |
|
118 |
|
119 SHA384Buffer idHash; |
|
120 SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert); |
|
121 if (rv != SECSuccess) { |
|
122 return -1; |
|
123 } |
|
124 |
|
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 } |
|
134 |
|
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 } |
|
149 |
|
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 } |
|
160 |
|
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); |
|
169 |
|
170 MutexAutoLock lock(mMutex); |
|
171 |
|
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 } |
|
183 |
|
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); |
|
193 |
|
194 MutexAutoLock lock(mMutex); |
|
195 |
|
196 int32_t index = FindInternal(aCert, aIssuerCert, lock); |
|
197 |
|
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 } |
|
206 |
|
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 } |
|
216 |
|
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 } |
|
226 |
|
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 } |
|
235 |
|
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 } |
|
265 |
|
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 } |
|
283 |
|
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 } |
|
298 |
|
299 } } // namespace mozilla::psm |