|
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 Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "NSSCertDBTrustDomain.h" |
|
8 |
|
9 #include <stdint.h> |
|
10 |
|
11 #include "ExtendedValidation.h" |
|
12 #include "OCSPRequestor.h" |
|
13 #include "certdb.h" |
|
14 #include "mozilla/Telemetry.h" |
|
15 #include "nss.h" |
|
16 #include "ocsp.h" |
|
17 #include "pk11pub.h" |
|
18 #include "pkix/pkix.h" |
|
19 #include "prerror.h" |
|
20 #include "prmem.h" |
|
21 #include "prprf.h" |
|
22 #include "secerr.h" |
|
23 #include "secmod.h" |
|
24 |
|
25 using namespace mozilla::pkix; |
|
26 |
|
27 #ifdef PR_LOGGING |
|
28 extern PRLogModuleInfo* gCertVerifierLog; |
|
29 #endif |
|
30 |
|
31 namespace mozilla { namespace psm { |
|
32 |
|
33 const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module"; |
|
34 |
|
35 void PORT_Free_string(char* str) { PORT_Free(str); } |
|
36 |
|
37 namespace { |
|
38 |
|
39 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule; |
|
40 |
|
41 } // unnamed namespace |
|
42 |
|
43 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType, |
|
44 OCSPFetching ocspFetching, |
|
45 OCSPCache& ocspCache, |
|
46 void* pinArg, |
|
47 CERTChainVerifyCallback* checkChainCallback) |
|
48 : mCertDBTrustType(certDBTrustType) |
|
49 , mOCSPFetching(ocspFetching) |
|
50 , mOCSPCache(ocspCache) |
|
51 , mPinArg(pinArg) |
|
52 , mCheckChainCallback(checkChainCallback) |
|
53 { |
|
54 } |
|
55 |
|
56 SECStatus |
|
57 NSSCertDBTrustDomain::FindPotentialIssuers( |
|
58 const SECItem* encodedIssuerName, PRTime time, |
|
59 /*out*/ mozilla::pkix::ScopedCERTCertList& results) |
|
60 { |
|
61 // TODO: normalize encodedIssuerName |
|
62 // TODO: NSS seems to be ambiguous between "no potential issuers found" and |
|
63 // "there was an error trying to retrieve the potential issuers." |
|
64 results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(), |
|
65 encodedIssuerName, time, true); |
|
66 return SECSuccess; |
|
67 } |
|
68 |
|
69 SECStatus |
|
70 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, |
|
71 SECOidTag policy, |
|
72 const CERTCertificate* candidateCert, |
|
73 /*out*/ TrustLevel* trustLevel) |
|
74 { |
|
75 PR_ASSERT(candidateCert); |
|
76 PR_ASSERT(trustLevel); |
|
77 |
|
78 if (!candidateCert || !trustLevel) { |
|
79 PR_SetError(SEC_ERROR_INVALID_ARGS, 0); |
|
80 return SECFailure; |
|
81 } |
|
82 |
|
83 #ifdef MOZ_NO_EV_CERTS |
|
84 if (policy != SEC_OID_X509_ANY_POLICY) { |
|
85 PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); |
|
86 return SECFailure; |
|
87 } |
|
88 #endif |
|
89 |
|
90 // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where |
|
91 // SECSuccess means that there is a trust record and SECFailure means there |
|
92 // is not a trust record. I looked at NSS's internal uses of |
|
93 // CERT_GetCertTrust, and all that code uses the result as a boolean meaning |
|
94 // "We have a trust record." |
|
95 CERTCertTrust trust; |
|
96 if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) { |
|
97 PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType); |
|
98 |
|
99 // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit, |
|
100 // because we can have active distrust for either type of cert. Note that |
|
101 // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the |
|
102 // relevant trust bit isn't set then that means the cert must be considered |
|
103 // distrusted. |
|
104 PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA ? CERTDB_TRUSTED_CA |
|
105 : CERTDB_TRUSTED; |
|
106 if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD))) |
|
107 == CERTDB_TERMINAL_RECORD) { |
|
108 *trustLevel = ActivelyDistrusted; |
|
109 return SECSuccess; |
|
110 } |
|
111 |
|
112 // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't |
|
113 // needed to consider end-entity certs to be their own trust anchors since |
|
114 // Gecko implemented nsICertOverrideService. |
|
115 if (flags & CERTDB_TRUSTED_CA) { |
|
116 if (policy == SEC_OID_X509_ANY_POLICY) { |
|
117 *trustLevel = TrustAnchor; |
|
118 return SECSuccess; |
|
119 } |
|
120 #ifndef MOZ_NO_EV_CERTS |
|
121 if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) { |
|
122 *trustLevel = TrustAnchor; |
|
123 return SECSuccess; |
|
124 } |
|
125 #endif |
|
126 } |
|
127 } |
|
128 |
|
129 *trustLevel = InheritsTrust; |
|
130 return SECSuccess; |
|
131 } |
|
132 |
|
133 SECStatus |
|
134 NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData, |
|
135 const CERTCertificate* cert) |
|
136 { |
|
137 return ::mozilla::pkix::VerifySignedData(signedData, cert, mPinArg); |
|
138 } |
|
139 |
|
140 static PRIntervalTime |
|
141 OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching) |
|
142 { |
|
143 switch (ocspFetching) { |
|
144 case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail: |
|
145 return PR_SecondsToInterval(2); |
|
146 case NSSCertDBTrustDomain::FetchOCSPForEV: |
|
147 case NSSCertDBTrustDomain::FetchOCSPForDVHardFail: |
|
148 return PR_SecondsToInterval(10); |
|
149 // The rest of these are error cases. Assert in debug builds, but return |
|
150 // the default value corresponding to 2 seconds in release builds. |
|
151 case NSSCertDBTrustDomain::NeverFetchOCSP: |
|
152 case NSSCertDBTrustDomain::LocalOnlyOCSPForEV: |
|
153 PR_NOT_REACHED("we should never see this OCSPFetching type here"); |
|
154 default: |
|
155 PR_NOT_REACHED("we're not handling every OCSPFetching type"); |
|
156 } |
|
157 return PR_SecondsToInterval(2); |
|
158 } |
|
159 |
|
160 SECStatus |
|
161 NSSCertDBTrustDomain::CheckRevocation( |
|
162 mozilla::pkix::EndEntityOrCA endEntityOrCA, |
|
163 const CERTCertificate* cert, |
|
164 /*const*/ CERTCertificate* issuerCert, |
|
165 PRTime time, |
|
166 /*optional*/ const SECItem* stapledOCSPResponse) |
|
167 { |
|
168 // Actively distrusted certificates will have already been blocked by |
|
169 // GetCertTrust. |
|
170 |
|
171 // TODO: need to verify that IsRevoked isn't called for trust anchors AND |
|
172 // that that fact is documented in mozillapkix. |
|
173 |
|
174 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
175 ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); |
|
176 |
|
177 PORT_Assert(cert); |
|
178 PORT_Assert(issuerCert); |
|
179 if (!cert || !issuerCert) { |
|
180 PORT_SetError(SEC_ERROR_INVALID_ARGS); |
|
181 return SECFailure; |
|
182 } |
|
183 |
|
184 // Bug 991815: The BR allow OCSP for intermediates to be up to one year old. |
|
185 // Since this affects EV there is no reason why DV should be more strict |
|
186 // so all intermediatates are allowed to have OCSP responses up to one year |
|
187 // old. |
|
188 uint16_t maxOCSPLifetimeInDays = 10; |
|
189 if (endEntityOrCA == EndEntityOrCA::MustBeCA) { |
|
190 maxOCSPLifetimeInDays = 365; |
|
191 } |
|
192 |
|
193 // If we have a stapled OCSP response then the verification of that response |
|
194 // determines the result unless the OCSP response is expired. We make an |
|
195 // exception for expired responses because some servers, nginx in particular, |
|
196 // are known to serve expired responses due to bugs. |
|
197 // We keep track of the result of verifying the stapled response but don't |
|
198 // immediately return failure if the response has expired. |
|
199 PRErrorCode stapledOCSPResponseErrorCode = 0; |
|
200 if (stapledOCSPResponse) { |
|
201 PR_ASSERT(endEntityOrCA == MustBeEndEntity); |
|
202 bool expired; |
|
203 SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, |
|
204 time, |
|
205 maxOCSPLifetimeInDays, |
|
206 stapledOCSPResponse, |
|
207 ResponseWasStapled, |
|
208 expired); |
|
209 if (rv == SECSuccess) { |
|
210 // stapled OCSP response present and good |
|
211 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1); |
|
212 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
213 ("NSSCertDBTrustDomain: stapled OCSP response: good")); |
|
214 return rv; |
|
215 } |
|
216 stapledOCSPResponseErrorCode = PR_GetError(); |
|
217 if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE || |
|
218 expired) { |
|
219 // stapled OCSP response present but expired |
|
220 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3); |
|
221 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
222 ("NSSCertDBTrustDomain: expired stapled OCSP response")); |
|
223 } else { |
|
224 // stapled OCSP response present but invalid for some reason |
|
225 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4); |
|
226 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
227 ("NSSCertDBTrustDomain: stapled OCSP response: failure")); |
|
228 return rv; |
|
229 } |
|
230 } else { |
|
231 // no stapled OCSP response |
|
232 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2); |
|
233 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
234 ("NSSCertDBTrustDomain: no stapled OCSP response")); |
|
235 } |
|
236 |
|
237 PRErrorCode cachedResponseErrorCode = 0; |
|
238 PRTime cachedResponseValidThrough = 0; |
|
239 bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert, |
|
240 cachedResponseErrorCode, |
|
241 cachedResponseValidThrough); |
|
242 if (cachedResponsePresent) { |
|
243 if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) { |
|
244 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
245 ("NSSCertDBTrustDomain: cached OCSP response: good")); |
|
246 return SECSuccess; |
|
247 } |
|
248 // If we have a cached revoked response, use it. |
|
249 if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) { |
|
250 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
251 ("NSSCertDBTrustDomain: cached OCSP response: revoked")); |
|
252 PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); |
|
253 return SECFailure; |
|
254 } |
|
255 // The cached response may indicate an unknown certificate or it may be |
|
256 // expired. Don't return with either of these statuses yet - we may be |
|
257 // able to fetch a more recent one. |
|
258 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
259 ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid " |
|
260 "until %lld", cachedResponseErrorCode, cachedResponseValidThrough)); |
|
261 // When a good cached response has expired, it is more convenient |
|
262 // to convert that to an error code and just deal with |
|
263 // cachedResponseErrorCode from here on out. |
|
264 if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) { |
|
265 cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE; |
|
266 } |
|
267 // We may have a cached indication of server failure. Ignore it if |
|
268 // it has expired. |
|
269 if (cachedResponseErrorCode != 0 && |
|
270 cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && |
|
271 cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE && |
|
272 cachedResponseValidThrough < time) { |
|
273 cachedResponseErrorCode = 0; |
|
274 cachedResponsePresent = false; |
|
275 } |
|
276 } else { |
|
277 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
278 ("NSSCertDBTrustDomain: no cached OCSP response")); |
|
279 } |
|
280 // At this point, if and only if cachedErrorResponseCode is 0, there was no |
|
281 // cached response. |
|
282 PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) || |
|
283 (cachedResponsePresent && cachedResponseErrorCode != 0)); |
|
284 |
|
285 // TODO: We still need to handle the fallback for expired responses. But, |
|
286 // if/when we disable OCSP fetching by default, it would be ambiguous whether |
|
287 // security.OCSP.enable==0 means "I want the default" or "I really never want |
|
288 // you to ever fetch OCSP." |
|
289 |
|
290 if ((mOCSPFetching == NeverFetchOCSP) || |
|
291 (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail || |
|
292 mOCSPFetching == FetchOCSPForDVSoftFail))) { |
|
293 // We're not going to be doing any fetching, so if there was a cached |
|
294 // "unknown" response, say so. |
|
295 if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { |
|
296 PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); |
|
297 return SECFailure; |
|
298 } |
|
299 // If we're doing hard-fail, we want to know if we have a cached response |
|
300 // that has expired. |
|
301 if (mOCSPFetching == FetchOCSPForDVHardFail && |
|
302 cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { |
|
303 PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); |
|
304 return SECFailure; |
|
305 } |
|
306 |
|
307 return SECSuccess; |
|
308 } |
|
309 |
|
310 if (mOCSPFetching == LocalOnlyOCSPForEV) { |
|
311 PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode |
|
312 : SEC_ERROR_OCSP_UNKNOWN_CERT, 0); |
|
313 return SECFailure; |
|
314 } |
|
315 |
|
316 ScopedPtr<char, PORT_Free_string> |
|
317 url(CERT_GetOCSPAuthorityInfoAccessLocation(cert)); |
|
318 |
|
319 if (!url) { |
|
320 if (mOCSPFetching == FetchOCSPForEV || |
|
321 cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { |
|
322 PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); |
|
323 return SECFailure; |
|
324 } |
|
325 if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { |
|
326 PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); |
|
327 return SECFailure; |
|
328 } |
|
329 if (stapledOCSPResponseErrorCode != 0) { |
|
330 PR_SetError(stapledOCSPResponseErrorCode, 0); |
|
331 return SECFailure; |
|
332 } |
|
333 |
|
334 // Nothing to do if we don't have an OCSP responder URI for the cert; just |
|
335 // assume it is good. Note that this is the confusing, but intended, |
|
336 // interpretation of "strict" revocation checking in the face of a |
|
337 // certificate that lacks an OCSP responder URI. |
|
338 return SECSuccess; |
|
339 } |
|
340 |
|
341 ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
|
342 if (!arena) { |
|
343 return SECFailure; |
|
344 } |
|
345 |
|
346 // Only request a response if we didn't have a cached indication of failure |
|
347 // (don't keep requesting responses from a failing server). |
|
348 const SECItem* response = nullptr; |
|
349 if (cachedResponseErrorCode == 0 || |
|
350 cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT || |
|
351 cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { |
|
352 const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert, |
|
353 issuerCert)); |
|
354 if (!request) { |
|
355 return SECFailure; |
|
356 } |
|
357 |
|
358 response = DoOCSPRequest(arena.get(), url.get(), request, |
|
359 OCSPFetchingTypeToTimeoutTime(mOCSPFetching)); |
|
360 } |
|
361 |
|
362 if (!response) { |
|
363 PRErrorCode error = PR_GetError(); |
|
364 if (error == 0) { |
|
365 error = cachedResponseErrorCode; |
|
366 } |
|
367 PRTime timeout = time + ServerFailureDelay; |
|
368 if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) { |
|
369 return SECFailure; |
|
370 } |
|
371 PR_SetError(error, 0); |
|
372 if (mOCSPFetching != FetchOCSPForDVSoftFail) { |
|
373 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
374 ("NSSCertDBTrustDomain: returning SECFailure after " |
|
375 "OCSP request failure")); |
|
376 return SECFailure; |
|
377 } |
|
378 if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { |
|
379 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
380 ("NSSCertDBTrustDomain: returning SECFailure from cached " |
|
381 "response after OCSP request failure")); |
|
382 PR_SetError(cachedResponseErrorCode, 0); |
|
383 return SECFailure; |
|
384 } |
|
385 if (stapledOCSPResponseErrorCode != 0) { |
|
386 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
387 ("NSSCertDBTrustDomain: returning SECFailure from expired " |
|
388 "stapled response after OCSP request failure")); |
|
389 PR_SetError(stapledOCSPResponseErrorCode, 0); |
|
390 return SECFailure; |
|
391 } |
|
392 |
|
393 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
394 ("NSSCertDBTrustDomain: returning SECSuccess after " |
|
395 "OCSP request failure")); |
|
396 return SECSuccess; // Soft fail -> success :( |
|
397 } |
|
398 |
|
399 // If the response from the network has expired but indicates a revoked |
|
400 // or unknown certificate, PR_GetError() will return the appropriate error. |
|
401 // We actually ignore expired here. |
|
402 bool expired; |
|
403 SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, |
|
404 maxOCSPLifetimeInDays, |
|
405 response, |
|
406 ResponseIsFromNetwork, |
|
407 expired); |
|
408 if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) { |
|
409 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
410 ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse")); |
|
411 return rv; |
|
412 } |
|
413 |
|
414 PRErrorCode error = PR_GetError(); |
|
415 if (error == SEC_ERROR_OCSP_UNKNOWN_CERT || |
|
416 error == SEC_ERROR_REVOKED_CERTIFICATE) { |
|
417 return rv; |
|
418 } |
|
419 |
|
420 if (stapledOCSPResponseErrorCode != 0) { |
|
421 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
422 ("NSSCertDBTrustDomain: returning SECFailure from expired stapled " |
|
423 "response after OCSP request verification failure")); |
|
424 PR_SetError(stapledOCSPResponseErrorCode, 0); |
|
425 return SECFailure; |
|
426 } |
|
427 |
|
428 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
429 ("NSSCertDBTrustDomain: end of CheckRevocation")); |
|
430 |
|
431 return SECSuccess; // Soft fail -> success :( |
|
432 } |
|
433 |
|
434 SECStatus |
|
435 NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( |
|
436 const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time, |
|
437 uint16_t maxLifetimeInDays, const SECItem* encodedResponse, |
|
438 EncodedResponseSource responseSource, /*out*/ bool& expired) |
|
439 { |
|
440 PRTime thisUpdate = 0; |
|
441 PRTime validThrough = 0; |
|
442 SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time, |
|
443 maxLifetimeInDays, encodedResponse, |
|
444 expired, &thisUpdate, &validThrough); |
|
445 PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError()); |
|
446 // If a response was stapled and expired, we don't want to cache it. Return |
|
447 // early to simplify the logic here. |
|
448 if (responseSource == ResponseWasStapled && expired) { |
|
449 PR_ASSERT(rv != SECSuccess); |
|
450 return rv; |
|
451 } |
|
452 // validThrough is only trustworthy if the response successfully verifies |
|
453 // or it indicates a revoked or unknown certificate. |
|
454 // If this isn't the case, store an indication of failure (to prevent |
|
455 // repeatedly requesting a response from a failing server). |
|
456 if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE && |
|
457 error != SEC_ERROR_OCSP_UNKNOWN_CERT) { |
|
458 validThrough = time + ServerFailureDelay; |
|
459 } |
|
460 if (responseSource == ResponseIsFromNetwork || |
|
461 rv == SECSuccess || |
|
462 error == SEC_ERROR_REVOKED_CERTIFICATE || |
|
463 error == SEC_ERROR_OCSP_UNKNOWN_CERT) { |
|
464 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
465 ("NSSCertDBTrustDomain: caching OCSP response")); |
|
466 if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough) |
|
467 != SECSuccess) { |
|
468 return SECFailure; |
|
469 } |
|
470 } |
|
471 |
|
472 // If the verification failed, re-set to that original error |
|
473 // (the call to Put may have un-set it). |
|
474 if (rv != SECSuccess) { |
|
475 PR_SetError(error, 0); |
|
476 } |
|
477 return rv; |
|
478 } |
|
479 |
|
480 SECStatus |
|
481 NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) { |
|
482 SECStatus rv = SECFailure; |
|
483 |
|
484 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, |
|
485 ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p", |
|
486 mCheckChainCallback)); |
|
487 |
|
488 if (!mCheckChainCallback) { |
|
489 return SECSuccess; |
|
490 } |
|
491 if (!mCheckChainCallback->isChainValid) { |
|
492 PR_SetError(SEC_ERROR_INVALID_ARGS, 0); |
|
493 return SECFailure; |
|
494 } |
|
495 PRBool chainOK; |
|
496 rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg, |
|
497 certChain, &chainOK); |
|
498 if (rv != SECSuccess) { |
|
499 return rv; |
|
500 } |
|
501 // rv = SECSuccess only implies successful call, now is time |
|
502 // to check the chain check status |
|
503 // we should only return success if the chain is valid |
|
504 if (chainOK) { |
|
505 return SECSuccess; |
|
506 } |
|
507 PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); |
|
508 return SECFailure; |
|
509 } |
|
510 |
|
511 namespace { |
|
512 |
|
513 static char* |
|
514 nss_addEscape(const char* string, char quote) |
|
515 { |
|
516 char* newString = 0; |
|
517 int escapes = 0, size = 0; |
|
518 const char* src; |
|
519 char* dest; |
|
520 |
|
521 for (src = string; *src; src++) { |
|
522 if ((*src == quote) || (*src == '\\')) { |
|
523 escapes++; |
|
524 } |
|
525 size++; |
|
526 } |
|
527 |
|
528 newString = (char*) PORT_ZAlloc(escapes + size + 1); |
|
529 if (!newString) { |
|
530 return nullptr; |
|
531 } |
|
532 |
|
533 for (src = string, dest = newString; *src; src++, dest++) { |
|
534 if ((*src == quote) || (*src == '\\')) { |
|
535 *dest++ = '\\'; |
|
536 } |
|
537 *dest = *src; |
|
538 } |
|
539 |
|
540 return newString; |
|
541 } |
|
542 |
|
543 } // unnamed namespace |
|
544 |
|
545 SECStatus |
|
546 InitializeNSS(const char* dir, bool readOnly) |
|
547 { |
|
548 // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs |
|
549 // module by NSS_Initialize because we will load it in InstallLoadableRoots |
|
550 // later. It also allows us to work around a bug in the system NSS in |
|
551 // Ubuntu 8.04, which loads any nonexistent "<configdir>/libnssckbi.so" as |
|
552 // "/usr/lib/nss/libnssckbi.so". |
|
553 uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE; |
|
554 if (readOnly) { |
|
555 flags |= NSS_INIT_READONLY; |
|
556 } |
|
557 return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags); |
|
558 } |
|
559 |
|
560 void |
|
561 DisableMD5() |
|
562 { |
|
563 NSS_SetAlgorithmPolicy(SEC_OID_MD5, |
|
564 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
|
565 NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, |
|
566 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
|
567 NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, |
|
568 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
|
569 } |
|
570 |
|
571 SECStatus |
|
572 LoadLoadableRoots(/*optional*/ const char* dir, const char* modNameUTF8) |
|
573 { |
|
574 PR_ASSERT(modNameUTF8); |
|
575 |
|
576 if (!modNameUTF8) { |
|
577 PR_SetError(SEC_ERROR_INVALID_ARGS, 0); |
|
578 return SECFailure; |
|
579 } |
|
580 |
|
581 ScopedPtr<char, PR_FreeLibraryName> fullLibraryPath( |
|
582 PR_GetLibraryName(dir, "nssckbi")); |
|
583 if (!fullLibraryPath) { |
|
584 return SECFailure; |
|
585 } |
|
586 |
|
587 ScopedPtr<char, PORT_Free_string> escaped_fullLibraryPath( |
|
588 nss_addEscape(fullLibraryPath.get(), '\"')); |
|
589 if (!escaped_fullLibraryPath) { |
|
590 return SECFailure; |
|
591 } |
|
592 |
|
593 // If a module exists with the same name, delete it. |
|
594 int modType; |
|
595 SECMOD_DeleteModule(modNameUTF8, &modType); |
|
596 |
|
597 ScopedPtr<char, PR_smprintf_free> pkcs11ModuleSpec( |
|
598 PR_smprintf("name=\"%s\" library=\"%s\"", modNameUTF8, |
|
599 escaped_fullLibraryPath.get())); |
|
600 if (!pkcs11ModuleSpec) { |
|
601 return SECFailure; |
|
602 } |
|
603 |
|
604 ScopedSECMODModule rootsModule(SECMOD_LoadUserModule(pkcs11ModuleSpec.get(), |
|
605 nullptr, false)); |
|
606 if (!rootsModule) { |
|
607 return SECFailure; |
|
608 } |
|
609 |
|
610 if (!rootsModule->loaded) { |
|
611 PR_SetError(PR_INVALID_STATE_ERROR, 0); |
|
612 return SECFailure; |
|
613 } |
|
614 |
|
615 return SECSuccess; |
|
616 } |
|
617 |
|
618 void |
|
619 UnloadLoadableRoots(const char* modNameUTF8) |
|
620 { |
|
621 PR_ASSERT(modNameUTF8); |
|
622 ScopedSECMODModule rootsModule(SECMOD_FindModule(modNameUTF8)); |
|
623 |
|
624 if (rootsModule) { |
|
625 SECMOD_UnloadUserModule(rootsModule.get()); |
|
626 } |
|
627 } |
|
628 |
|
629 void |
|
630 SetClassicOCSPBehavior(CertVerifier::ocsp_download_config enabled, |
|
631 CertVerifier::ocsp_strict_config strict, |
|
632 CertVerifier::ocsp_get_config get) |
|
633 { |
|
634 CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB()); |
|
635 if (enabled == CertVerifier::ocsp_off) { |
|
636 CERT_DisableOCSPChecking(CERT_GetDefaultCertDB()); |
|
637 } else { |
|
638 CERT_EnableOCSPChecking(CERT_GetDefaultCertDB()); |
|
639 } |
|
640 |
|
641 SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict |
|
642 ? ocspMode_FailureIsVerificationFailure |
|
643 : ocspMode_FailureIsNotAVerificationFailure; |
|
644 (void) CERT_SetOCSPFailureMode(failureMode); |
|
645 |
|
646 CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled); |
|
647 |
|
648 int OCSPTimeoutSeconds = 3; |
|
649 if (strict == CertVerifier::ocsp_strict) { |
|
650 OCSPTimeoutSeconds = 10; |
|
651 } |
|
652 CERT_SetOCSPTimeout(OCSPTimeoutSeconds); |
|
653 } |
|
654 |
|
655 char* |
|
656 DefaultServerNicknameForCert(CERTCertificate* cert) |
|
657 { |
|
658 char* nickname = nullptr; |
|
659 int count; |
|
660 bool conflict; |
|
661 char* servername = nullptr; |
|
662 |
|
663 servername = CERT_GetCommonName(&cert->subject); |
|
664 if (!servername) { |
|
665 // Certs without common names are strange, but they do exist... |
|
666 // Let's try to use another string for the nickname |
|
667 servername = CERT_GetOrgUnitName(&cert->subject); |
|
668 if (!servername) { |
|
669 servername = CERT_GetOrgName(&cert->subject); |
|
670 if (!servername) { |
|
671 servername = CERT_GetLocalityName(&cert->subject); |
|
672 if (!servername) { |
|
673 servername = CERT_GetStateName(&cert->subject); |
|
674 if (!servername) { |
|
675 servername = CERT_GetCountryName(&cert->subject); |
|
676 if (!servername) { |
|
677 // We tried hard, there is nothing more we can do. |
|
678 // A cert without any names doesn't really make sense. |
|
679 return nullptr; |
|
680 } |
|
681 } |
|
682 } |
|
683 } |
|
684 } |
|
685 } |
|
686 |
|
687 count = 1; |
|
688 while (1) { |
|
689 if (count == 1) { |
|
690 nickname = PR_smprintf("%s", servername); |
|
691 } |
|
692 else { |
|
693 nickname = PR_smprintf("%s #%d", servername, count); |
|
694 } |
|
695 if (!nickname) { |
|
696 break; |
|
697 } |
|
698 |
|
699 conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject, |
|
700 cert->dbhandle); |
|
701 if (!conflict) { |
|
702 break; |
|
703 } |
|
704 PR_Free(nickname); |
|
705 count++; |
|
706 } |
|
707 PR_FREEIF(servername); |
|
708 return nickname; |
|
709 } |
|
710 |
|
711 void |
|
712 SaveIntermediateCerts(const ScopedCERTCertList& certList) |
|
713 { |
|
714 if (!certList) { |
|
715 return; |
|
716 } |
|
717 |
|
718 bool isEndEntity = true; |
|
719 for (CERTCertListNode* node = CERT_LIST_HEAD(certList); |
|
720 !CERT_LIST_END(node, certList); |
|
721 node = CERT_LIST_NEXT(node)) { |
|
722 if (isEndEntity) { |
|
723 // Skip the end-entity; we only want to store intermediates |
|
724 isEndEntity = false; |
|
725 continue; |
|
726 } |
|
727 |
|
728 if (node->cert->slot) { |
|
729 // This cert was found on a token, no need to remember it in the temp db. |
|
730 continue; |
|
731 } |
|
732 |
|
733 if (node->cert->isperm) { |
|
734 // We don't need to remember certs already stored in perm db. |
|
735 continue; |
|
736 } |
|
737 |
|
738 // We have found a signer cert that we want to remember. |
|
739 char* nickname = DefaultServerNicknameForCert(node->cert); |
|
740 if (nickname && *nickname) { |
|
741 ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalKeySlot()); |
|
742 if (slot) { |
|
743 PK11_ImportCert(slot.get(), node->cert, CK_INVALID_HANDLE, |
|
744 nickname, false); |
|
745 } |
|
746 } |
|
747 PR_FREEIF(nickname); |
|
748 } |
|
749 } |
|
750 |
|
751 } } // namespace mozilla::psm |