/src/mozilla-central/security/certverifier/NSSCertDBTrustDomain.cpp
Line | Count | Source (jump to first uncovered line) |
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 "NSSErrorsService.h" |
13 | | #include "OCSPVerificationTrustDomain.h" |
14 | | #include "PublicKeyPinningService.h" |
15 | | #include "cert.h" |
16 | | #include "certdb.h" |
17 | | #include "mozilla/Assertions.h" |
18 | | #include "mozilla/Casting.h" |
19 | | #include "mozilla/Move.h" |
20 | | #include "mozilla/PodOperations.h" |
21 | | #include "mozilla/TimeStamp.h" |
22 | | #include "mozilla/Unused.h" |
23 | | #include "nsCRTGlue.h" |
24 | | #include "nsNSSCertHelper.h" |
25 | | #include "nsNSSCertValidity.h" |
26 | | #include "nsNSSCertificate.h" |
27 | | #include "nsServiceManagerUtils.h" |
28 | | #include "nsThreadUtils.h" |
29 | | #include "nss.h" |
30 | | #include "pk11pub.h" |
31 | | #include "pkix/Result.h" |
32 | | #include "pkix/pkix.h" |
33 | | #include "pkix/pkixnss.h" |
34 | | #include "prerror.h" |
35 | | #include "secerr.h" |
36 | | |
37 | | #include "TrustOverrideUtils.h" |
38 | | #include "TrustOverride-StartComAndWoSignData.inc" |
39 | | #include "TrustOverride-GlobalSignData.inc" |
40 | | #include "TrustOverride-SymantecData.inc" |
41 | | #include "TrustOverride-AppleGoogleDigiCertData.inc" |
42 | | |
43 | | using namespace mozilla; |
44 | | using namespace mozilla::pkix; |
45 | | |
46 | | extern LazyLogModule gCertVerifierLog; |
47 | | |
48 | | static const uint64_t ServerFailureDelaySeconds = 5 * 60; |
49 | | |
50 | | namespace mozilla { namespace psm { |
51 | | |
52 | | NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType, |
53 | | OCSPFetching ocspFetching, |
54 | | OCSPCache& ocspCache, |
55 | | /*optional but shouldn't be*/ void* pinArg, |
56 | | TimeDuration ocspTimeoutSoft, |
57 | | TimeDuration ocspTimeoutHard, |
58 | | uint32_t certShortLifetimeInDays, |
59 | | CertVerifier::PinningMode pinningMode, |
60 | | unsigned int minRSABits, |
61 | | ValidityCheckingMode validityCheckingMode, |
62 | | CertVerifier::SHA1Mode sha1Mode, |
63 | | NetscapeStepUpPolicy netscapeStepUpPolicy, |
64 | | DistrustedCAPolicy distrustedCAPolicy, |
65 | | const OriginAttributes& originAttributes, |
66 | | UniqueCERTCertList& builtChain, |
67 | | /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo, |
68 | | /*optional*/ const char* hostname) |
69 | | : mCertDBTrustType(certDBTrustType) |
70 | | , mOCSPFetching(ocspFetching) |
71 | | , mOCSPCache(ocspCache) |
72 | | , mPinArg(pinArg) |
73 | | , mOCSPTimeoutSoft(ocspTimeoutSoft) |
74 | | , mOCSPTimeoutHard(ocspTimeoutHard) |
75 | | , mCertShortLifetimeInDays(certShortLifetimeInDays) |
76 | | , mPinningMode(pinningMode) |
77 | | , mMinRSABits(minRSABits) |
78 | | , mValidityCheckingMode(validityCheckingMode) |
79 | | , mSHA1Mode(sha1Mode) |
80 | | , mNetscapeStepUpPolicy(netscapeStepUpPolicy) |
81 | | , mDistrustedCAPolicy(distrustedCAPolicy) |
82 | | , mSawDistrustedCAByPolicyError(false) |
83 | | , mOriginAttributes(originAttributes) |
84 | | , mBuiltChain(builtChain) |
85 | | , mPinningTelemetryInfo(pinningTelemetryInfo) |
86 | | , mHostname(hostname) |
87 | | , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) |
88 | | , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED) |
89 | | , mSCTListFromCertificate() |
90 | | , mSCTListFromOCSPStapling() |
91 | 0 | { |
92 | 0 | } |
93 | | |
94 | | // If useRoots is true, we only use root certificates in the candidate list. |
95 | | // If useRoots is false, we only use non-root certificates in the list. |
96 | | static Result |
97 | | FindIssuerInner(const UniqueCERTCertList& candidates, bool useRoots, |
98 | | Input encodedIssuerName, TrustDomain::IssuerChecker& checker, |
99 | | /*out*/ bool& keepGoing) |
100 | 0 | { |
101 | 0 | keepGoing = true; |
102 | 0 | for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); |
103 | 0 | !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { |
104 | 0 | bool candidateIsRoot = !!n->cert->isRoot; |
105 | 0 | if (candidateIsRoot != useRoots) { |
106 | 0 | continue; |
107 | 0 | } |
108 | 0 | Input certDER; |
109 | 0 | Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len); |
110 | 0 | if (rv != Success) { |
111 | 0 | continue; // probably too big |
112 | 0 | } |
113 | 0 | |
114 | 0 | const SECItem encodedIssuerNameItem = { |
115 | 0 | siBuffer, |
116 | 0 | const_cast<unsigned char*>(encodedIssuerName.UnsafeGetData()), |
117 | 0 | encodedIssuerName.GetLength() |
118 | 0 | }; |
119 | 0 | ScopedAutoSECItem nameConstraints; |
120 | 0 | SECStatus srv = CERT_GetImposedNameConstraints(&encodedIssuerNameItem, |
121 | 0 | &nameConstraints); |
122 | 0 | if (srv != SECSuccess) { |
123 | 0 | if (PR_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { |
124 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
125 | 0 | } |
126 | 0 | |
127 | 0 | // If no imposed name constraints were found, continue without them |
128 | 0 | rv = checker.Check(certDER, nullptr, keepGoing); |
129 | 0 | } else { |
130 | 0 | // Otherwise apply the constraints |
131 | 0 | Input nameConstraintsInput; |
132 | 0 | if (nameConstraintsInput.Init(nameConstraints.data, nameConstraints.len) |
133 | 0 | != Success) { |
134 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
135 | 0 | } |
136 | 0 | rv = checker.Check(certDER, &nameConstraintsInput, keepGoing); |
137 | 0 | } |
138 | 0 | if (rv != Success) { |
139 | 0 | return rv; |
140 | 0 | } |
141 | 0 | if (!keepGoing) { |
142 | 0 | break; |
143 | 0 | } |
144 | 0 | } |
145 | 0 |
|
146 | 0 | return Success; |
147 | 0 | } |
148 | | |
149 | | Result |
150 | | NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName, |
151 | | IssuerChecker& checker, Time) |
152 | 0 | { |
153 | 0 | // TODO: NSS seems to be ambiguous between "no potential issuers found" and |
154 | 0 | // "there was an error trying to retrieve the potential issuers." |
155 | 0 | SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName); |
156 | 0 | UniqueCERTCertList |
157 | 0 | candidates(CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(), |
158 | 0 | &encodedIssuerNameItem, 0, |
159 | 0 | false)); |
160 | 0 | if (candidates) { |
161 | 0 | // First, try all the root certs; then try all the non-root certs. |
162 | 0 | bool keepGoing; |
163 | 0 | Result rv = FindIssuerInner(candidates, true, encodedIssuerName, checker, |
164 | 0 | keepGoing); |
165 | 0 | if (rv != Success) { |
166 | 0 | return rv; |
167 | 0 | } |
168 | 0 | if (keepGoing) { |
169 | 0 | rv = FindIssuerInner(candidates, false, encodedIssuerName, checker, |
170 | 0 | keepGoing); |
171 | 0 | if (rv != Success) { |
172 | 0 | return rv; |
173 | 0 | } |
174 | 0 | } |
175 | 0 | } |
176 | 0 | |
177 | 0 | return Success; |
178 | 0 | } |
179 | | |
180 | | Result |
181 | | NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, |
182 | | const CertPolicyId& policy, |
183 | | Input candidateCertDER, |
184 | | /*out*/ TrustLevel& trustLevel) |
185 | 0 | { |
186 | 0 | // XXX: This would be cleaner and more efficient if we could get the trust |
187 | 0 | // information without constructing a CERTCertificate here, but NSS doesn't |
188 | 0 | // expose it in any other easy-to-use fashion. The use of |
189 | 0 | // CERT_NewTempCertificate to get a CERTCertificate shouldn't be a |
190 | 0 | // performance problem because NSS will just find the existing |
191 | 0 | // CERTCertificate in its in-memory cache and return it. |
192 | 0 | SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER); |
193 | 0 | UniqueCERTCertificate candidateCert( |
194 | 0 | CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &candidateCertDERSECItem, |
195 | 0 | nullptr, false, true)); |
196 | 0 | if (!candidateCert) { |
197 | 0 | return MapPRErrorCodeToResult(PR_GetError()); |
198 | 0 | } |
199 | 0 | |
200 | 0 | // Check the certificate against the OneCRL cert blocklist |
201 | 0 | if (!mCertBlocklist) { |
202 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
203 | 0 | } |
204 | 0 | |
205 | 0 | // The certificate blocklist currently only applies to TLS server |
206 | 0 | // certificates. |
207 | 0 | if (mCertDBTrustType == trustSSL) { |
208 | 0 | bool isCertRevoked; |
209 | 0 | nsresult nsrv = mCertBlocklist->IsCertRevoked( |
210 | 0 | candidateCert->derIssuer.data, |
211 | 0 | candidateCert->derIssuer.len, |
212 | 0 | candidateCert->serialNumber.data, |
213 | 0 | candidateCert->serialNumber.len, |
214 | 0 | candidateCert->derSubject.data, |
215 | 0 | candidateCert->derSubject.len, |
216 | 0 | candidateCert->derPublicKey.data, |
217 | 0 | candidateCert->derPublicKey.len, |
218 | 0 | &isCertRevoked); |
219 | 0 | if (NS_FAILED(nsrv)) { |
220 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
221 | 0 | } |
222 | 0 | |
223 | 0 | if (isCertRevoked) { |
224 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
225 | 0 | ("NSSCertDBTrustDomain: certificate is in blocklist")); |
226 | 0 | return Result::ERROR_REVOKED_CERTIFICATE; |
227 | 0 | } |
228 | 0 | } |
229 | 0 |
|
230 | 0 | // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where |
231 | 0 | // SECSuccess means that there is a trust record and SECFailure means there |
232 | 0 | // is not a trust record. I looked at NSS's internal uses of |
233 | 0 | // CERT_GetCertTrust, and all that code uses the result as a boolean meaning |
234 | 0 | // "We have a trust record." |
235 | 0 | CERTCertTrust trust; |
236 | 0 | if (CERT_GetCertTrust(candidateCert.get(), &trust) == SECSuccess) { |
237 | 0 | uint32_t flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType); |
238 | 0 |
|
239 | 0 | // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit, |
240 | 0 | // because we can have active distrust for either type of cert. Note that |
241 | 0 | // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the |
242 | 0 | // relevant trust bit isn't set then that means the cert must be considered |
243 | 0 | // distrusted. |
244 | 0 | uint32_t relevantTrustBit = |
245 | 0 | endEntityOrCA == EndEntityOrCA::MustBeCA ? CERTDB_TRUSTED_CA |
246 | 0 | : CERTDB_TRUSTED; |
247 | 0 | if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD))) |
248 | 0 | == CERTDB_TERMINAL_RECORD) { |
249 | 0 | trustLevel = TrustLevel::ActivelyDistrusted; |
250 | 0 | return Success; |
251 | 0 | } |
252 | 0 | |
253 | 0 | // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't |
254 | 0 | // needed to consider end-entity certs to be their own trust anchors since |
255 | 0 | // Gecko implemented nsICertOverrideService. |
256 | 0 | // Of course, for this to work as expected, we need to make sure we're |
257 | 0 | // inquiring about the trust of a CA and not an end-entity. If an end-entity |
258 | 0 | // has the CERTDB_TRUSTED_CA bit set, Gecko does not consider it to be a |
259 | 0 | // trust anchor; it must inherit its trust. |
260 | 0 | if (flags & CERTDB_TRUSTED_CA && endEntityOrCA == EndEntityOrCA::MustBeCA) { |
261 | 0 | if (policy.IsAnyPolicy()) { |
262 | 0 | trustLevel = TrustLevel::TrustAnchor; |
263 | 0 | return Success; |
264 | 0 | } |
265 | 0 | if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) { |
266 | 0 | trustLevel = TrustLevel::TrustAnchor; |
267 | 0 | return Success; |
268 | 0 | } |
269 | 0 | } |
270 | 0 | } |
271 | 0 | |
272 | 0 | trustLevel = TrustLevel::InheritsTrust; |
273 | 0 | return Success; |
274 | 0 | } |
275 | | |
276 | | Result |
277 | | NSSCertDBTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg, |
278 | | /*out*/ uint8_t* digestBuf, size_t digestBufLen) |
279 | 0 | { |
280 | 0 | return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); |
281 | 0 | } |
282 | | |
283 | | TimeDuration |
284 | | NSSCertDBTrustDomain::GetOCSPTimeout() const |
285 | 0 | { |
286 | 0 | switch (mOCSPFetching) { |
287 | 0 | case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail: |
288 | 0 | return mOCSPTimeoutSoft; |
289 | 0 | case NSSCertDBTrustDomain::FetchOCSPForEV: |
290 | 0 | case NSSCertDBTrustDomain::FetchOCSPForDVHardFail: |
291 | 0 | return mOCSPTimeoutHard; |
292 | 0 | // The rest of these are error cases. Assert in debug builds, but return |
293 | 0 | // the soft timeout value in release builds. |
294 | 0 | case NSSCertDBTrustDomain::NeverFetchOCSP: |
295 | 0 | case NSSCertDBTrustDomain::LocalOnlyOCSPForEV: |
296 | 0 | MOZ_ASSERT_UNREACHABLE("we should never see this OCSPFetching type here"); |
297 | 0 | break; |
298 | 0 | } |
299 | 0 |
|
300 | 0 | MOZ_ASSERT_UNREACHABLE("we're not handling every OCSPFetching type"); |
301 | 0 | return mOCSPTimeoutSoft; |
302 | 0 | } |
303 | | |
304 | | // Copied and modified from CERT_GetOCSPAuthorityInfoAccessLocation and |
305 | | // CERT_GetGeneralNameByType. Returns a non-Result::Success result on error, |
306 | | // Success with result.IsVoid() == true when an OCSP URI was not found, and |
307 | | // Success with result.IsVoid() == false when an OCSP URI was found. |
308 | | static Result |
309 | | GetOCSPAuthorityInfoAccessLocation(const UniquePLArenaPool& arena, |
310 | | Input aiaExtension, |
311 | | /*out*/ nsCString& result) |
312 | 0 | { |
313 | 0 | MOZ_ASSERT(arena.get()); |
314 | 0 | if (!arena.get()) { |
315 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
316 | 0 | } |
317 | 0 | |
318 | 0 | result.Assign(VoidCString()); |
319 | 0 | SECItem aiaExtensionSECItem = UnsafeMapInputToSECItem(aiaExtension); |
320 | 0 | CERTAuthInfoAccess** aia = |
321 | 0 | CERT_DecodeAuthInfoAccessExtension(arena.get(), &aiaExtensionSECItem); |
322 | 0 | if (!aia) { |
323 | 0 | return Result::ERROR_CERT_BAD_ACCESS_LOCATION; |
324 | 0 | } |
325 | 0 | for (size_t i = 0; aia[i]; ++i) { |
326 | 0 | if (SECOID_FindOIDTag(&aia[i]->method) == SEC_OID_PKIX_OCSP) { |
327 | 0 | // NSS chooses the **last** OCSP URL; we choose the **first** |
328 | 0 | CERTGeneralName* current = aia[i]->location; |
329 | 0 | if (!current) { |
330 | 0 | continue; |
331 | 0 | } |
332 | 0 | do { |
333 | 0 | if (current->type == certURI) { |
334 | 0 | const SECItem& location = current->name.other; |
335 | 0 | // (location.len + 1) must be small enough to fit into a uint32_t, |
336 | 0 | // but we limit it to a smaller bound to reduce OOM risk. |
337 | 0 | if (location.len > 1024 || memchr(location.data, 0, location.len)) { |
338 | 0 | // Reject embedded nulls. (NSS doesn't do this) |
339 | 0 | return Result::ERROR_CERT_BAD_ACCESS_LOCATION; |
340 | 0 | } |
341 | 0 | result.Assign(nsDependentCSubstring( |
342 | 0 | reinterpret_cast<const char*>(location.data), |
343 | 0 | location.len)); |
344 | 0 | return Success; |
345 | 0 | } |
346 | 0 | current = CERT_GetNextGeneralName(current); |
347 | 0 | } while (current != aia[i]->location); |
348 | 0 | } |
349 | 0 | } |
350 | 0 |
|
351 | 0 | return Success; |
352 | 0 | } |
353 | | |
354 | | Result |
355 | | NSSCertDBTrustDomain::CheckRevocation(EndEntityOrCA endEntityOrCA, |
356 | | const CertID& certID, Time time, |
357 | | Duration validityDuration, |
358 | | /*optional*/ const Input* stapledOCSPResponse, |
359 | | /*optional*/ const Input* aiaExtension) |
360 | 0 | { |
361 | 0 | // Actively distrusted certificates will have already been blocked by |
362 | 0 | // GetCertTrust. |
363 | 0 |
|
364 | 0 | // TODO: need to verify that IsRevoked isn't called for trust anchors AND |
365 | 0 | // that that fact is documented in mozillapkix. |
366 | 0 |
|
367 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
368 | 0 | ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); |
369 | 0 |
|
370 | 0 | // Bug 991815: The BR allow OCSP for intermediates to be up to one year old. |
371 | 0 | // Since this affects EV there is no reason why DV should be more strict |
372 | 0 | // so all intermediatates are allowed to have OCSP responses up to one year |
373 | 0 | // old. |
374 | 0 | uint16_t maxOCSPLifetimeInDays = 10; |
375 | 0 | if (endEntityOrCA == EndEntityOrCA::MustBeCA) { |
376 | 0 | maxOCSPLifetimeInDays = 365; |
377 | 0 | } |
378 | 0 |
|
379 | 0 | // If we have a stapled OCSP response then the verification of that response |
380 | 0 | // determines the result unless the OCSP response is expired. We make an |
381 | 0 | // exception for expired responses because some servers, nginx in particular, |
382 | 0 | // are known to serve expired responses due to bugs. |
383 | 0 | // We keep track of the result of verifying the stapled response but don't |
384 | 0 | // immediately return failure if the response has expired. |
385 | 0 | // |
386 | 0 | // We only set the OCSP stapling status if we're validating the end-entity |
387 | 0 | // certificate. Non-end-entity certificates would always be |
388 | 0 | // OCSP_STAPLING_NONE unless/until we implement multi-stapling. |
389 | 0 | Result stapledOCSPResponseResult = Success; |
390 | 0 | if (stapledOCSPResponse) { |
391 | 0 | MOZ_ASSERT(endEntityOrCA == EndEntityOrCA::MustBeEndEntity); |
392 | 0 | bool expired; |
393 | 0 | stapledOCSPResponseResult = |
394 | 0 | VerifyAndMaybeCacheEncodedOCSPResponse(certID, time, |
395 | 0 | maxOCSPLifetimeInDays, |
396 | 0 | *stapledOCSPResponse, |
397 | 0 | ResponseWasStapled, expired); |
398 | 0 | if (stapledOCSPResponseResult == Success) { |
399 | 0 | // stapled OCSP response present and good |
400 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_GOOD; |
401 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
402 | 0 | ("NSSCertDBTrustDomain: stapled OCSP response: good")); |
403 | 0 | return Success; |
404 | 0 | } |
405 | 0 | if (stapledOCSPResponseResult == Result::ERROR_OCSP_OLD_RESPONSE || |
406 | 0 | expired) { |
407 | 0 | // stapled OCSP response present but expired |
408 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_EXPIRED; |
409 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
410 | 0 | ("NSSCertDBTrustDomain: expired stapled OCSP response")); |
411 | 0 | } else if (stapledOCSPResponseResult == |
412 | 0 | Result::ERROR_OCSP_TRY_SERVER_LATER || |
413 | 0 | stapledOCSPResponseResult == |
414 | 0 | Result::ERROR_OCSP_INVALID_SIGNING_CERT) { |
415 | 0 | // Stapled OCSP response present but invalid for a small number of reasons |
416 | 0 | // CAs/servers commonly get wrong. This will be treated similarly to an |
417 | 0 | // expired stapled response. |
418 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_INVALID; |
419 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
420 | 0 | ("NSSCertDBTrustDomain: stapled OCSP response: " |
421 | 0 | "failure (whitelisted for compatibility)")); |
422 | 0 | } else { |
423 | 0 | // stapled OCSP response present but invalid for some reason |
424 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_INVALID; |
425 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
426 | 0 | ("NSSCertDBTrustDomain: stapled OCSP response: failure")); |
427 | 0 | return stapledOCSPResponseResult; |
428 | 0 | } |
429 | 0 | } else if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) { |
430 | 0 | // no stapled OCSP response |
431 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NONE; |
432 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
433 | 0 | ("NSSCertDBTrustDomain: no stapled OCSP response")); |
434 | 0 | } |
435 | 0 |
|
436 | 0 | Result cachedResponseResult = Success; |
437 | 0 | Time cachedResponseValidThrough(Time::uninitialized); |
438 | 0 | bool cachedResponsePresent = mOCSPCache.Get(certID, mOriginAttributes, |
439 | 0 | cachedResponseResult, |
440 | 0 | cachedResponseValidThrough); |
441 | 0 | if (cachedResponsePresent) { |
442 | 0 | if (cachedResponseResult == Success && cachedResponseValidThrough >= time) { |
443 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
444 | 0 | ("NSSCertDBTrustDomain: cached OCSP response: good")); |
445 | 0 | return Success; |
446 | 0 | } |
447 | 0 | // If we have a cached revoked response, use it. |
448 | 0 | if (cachedResponseResult == Result::ERROR_REVOKED_CERTIFICATE) { |
449 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
450 | 0 | ("NSSCertDBTrustDomain: cached OCSP response: revoked")); |
451 | 0 | return Result::ERROR_REVOKED_CERTIFICATE; |
452 | 0 | } |
453 | 0 | // The cached response may indicate an unknown certificate or it may be |
454 | 0 | // expired. Don't return with either of these statuses yet - we may be |
455 | 0 | // able to fetch a more recent one. |
456 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
457 | 0 | ("NSSCertDBTrustDomain: cached OCSP response: error %d", |
458 | 0 | static_cast<int>(cachedResponseResult))); |
459 | 0 | // When a good cached response has expired, it is more convenient |
460 | 0 | // to convert that to an error code and just deal with |
461 | 0 | // cachedResponseResult from here on out. |
462 | 0 | if (cachedResponseResult == Success && cachedResponseValidThrough < time) { |
463 | 0 | cachedResponseResult = Result::ERROR_OCSP_OLD_RESPONSE; |
464 | 0 | } |
465 | 0 | // We may have a cached indication of server failure. Ignore it if |
466 | 0 | // it has expired. |
467 | 0 | if (cachedResponseResult != Success && |
468 | 0 | cachedResponseResult != Result::ERROR_OCSP_UNKNOWN_CERT && |
469 | 0 | cachedResponseResult != Result::ERROR_OCSP_OLD_RESPONSE && |
470 | 0 | cachedResponseValidThrough < time) { |
471 | 0 | cachedResponseResult = Success; |
472 | 0 | cachedResponsePresent = false; |
473 | 0 | } |
474 | 0 | } else { |
475 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
476 | 0 | ("NSSCertDBTrustDomain: no cached OCSP response")); |
477 | 0 | } |
478 | 0 | // At this point, if and only if cachedErrorResult is Success, there was no |
479 | 0 | // cached response. |
480 | 0 | MOZ_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) || |
481 | 0 | (cachedResponsePresent && cachedResponseResult != Success)); |
482 | 0 |
|
483 | 0 | // If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs |
484 | 0 | bool blocklistIsFresh; |
485 | 0 | nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh); |
486 | 0 | if (NS_FAILED(nsrv)) { |
487 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
488 | 0 | } |
489 | 0 | |
490 | 0 | // TODO: We still need to handle the fallback for invalid stapled responses. |
491 | 0 | // But, if/when we disable OCSP fetching by default, it would be ambiguous |
492 | 0 | // whether security.OCSP.enable==0 means "I want the default" or "I really |
493 | 0 | // never want you to ever fetch OCSP." |
494 | 0 | // Additionally, this doesn't properly handle OCSP-must-staple when OCSP |
495 | 0 | // fetching is disabled. |
496 | 0 | Duration shortLifetime(mCertShortLifetimeInDays * Time::ONE_DAY_IN_SECONDS); |
497 | 0 | if ((mOCSPFetching == NeverFetchOCSP) || |
498 | 0 | (validityDuration < shortLifetime) || |
499 | 0 | (endEntityOrCA == EndEntityOrCA::MustBeCA && |
500 | 0 | (mOCSPFetching == FetchOCSPForDVHardFail || |
501 | 0 | mOCSPFetching == FetchOCSPForDVSoftFail || |
502 | 0 | blocklistIsFresh))) { |
503 | 0 | // We're not going to be doing any fetching, so if there was a cached |
504 | 0 | // "unknown" response, say so. |
505 | 0 | if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { |
506 | 0 | return Result::ERROR_OCSP_UNKNOWN_CERT; |
507 | 0 | } |
508 | 0 | // If we're doing hard-fail, we want to know if we have a cached response |
509 | 0 | // that has expired. |
510 | 0 | if (mOCSPFetching == FetchOCSPForDVHardFail && |
511 | 0 | cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { |
512 | 0 | return Result::ERROR_OCSP_OLD_RESPONSE; |
513 | 0 | } |
514 | 0 | |
515 | 0 | return Success; |
516 | 0 | } |
517 | 0 | |
518 | 0 | if (mOCSPFetching == LocalOnlyOCSPForEV) { |
519 | 0 | if (cachedResponseResult != Success) { |
520 | 0 | return cachedResponseResult; |
521 | 0 | } |
522 | 0 | return Result::ERROR_OCSP_UNKNOWN_CERT; |
523 | 0 | } |
524 | 0 | |
525 | 0 | UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); |
526 | 0 | if (!arena) { |
527 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
528 | 0 | } |
529 | 0 | |
530 | 0 | Result rv; |
531 | 0 | nsCString aiaLocation(VoidCString()); |
532 | 0 |
|
533 | 0 | if (aiaExtension) { |
534 | 0 | rv = GetOCSPAuthorityInfoAccessLocation(arena, *aiaExtension, aiaLocation); |
535 | 0 | if (rv != Success) { |
536 | 0 | return rv; |
537 | 0 | } |
538 | 0 | } |
539 | 0 | |
540 | 0 | if (aiaLocation.IsVoid()) { |
541 | 0 | if (mOCSPFetching == FetchOCSPForEV || |
542 | 0 | cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { |
543 | 0 | return Result::ERROR_OCSP_UNKNOWN_CERT; |
544 | 0 | } |
545 | 0 | if (cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { |
546 | 0 | return Result::ERROR_OCSP_OLD_RESPONSE; |
547 | 0 | } |
548 | 0 | if (stapledOCSPResponseResult != Success) { |
549 | 0 | return stapledOCSPResponseResult; |
550 | 0 | } |
551 | 0 | |
552 | 0 | // Nothing to do if we don't have an OCSP responder URI for the cert; just |
553 | 0 | // assume it is good. Note that this is the confusing, but intended, |
554 | 0 | // interpretation of "strict" revocation checking in the face of a |
555 | 0 | // certificate that lacks an OCSP responder URI. |
556 | 0 | return Success; |
557 | 0 | } |
558 | 0 | |
559 | 0 | // Only request a response if we didn't have a cached indication of failure |
560 | 0 | // (don't keep requesting responses from a failing server). |
561 | 0 | bool attemptedRequest; |
562 | 0 | Vector<uint8_t> ocspResponse; |
563 | 0 | Input response; |
564 | 0 | if (cachedResponseResult == Success || |
565 | 0 | cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT || |
566 | 0 | cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { |
567 | 0 | uint8_t ocspRequestBytes[OCSP_REQUEST_MAX_LENGTH]; |
568 | 0 | size_t ocspRequestLength; |
569 | 0 | rv = CreateEncodedOCSPRequest(*this, certID, ocspRequestBytes, |
570 | 0 | ocspRequestLength); |
571 | 0 | if (rv != Success) { |
572 | 0 | return rv; |
573 | 0 | } |
574 | 0 | Vector<uint8_t> ocspRequest; |
575 | 0 | if (!ocspRequest.append(ocspRequestBytes, ocspRequestLength)) { |
576 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
577 | 0 | } |
578 | 0 | Result tempRV = DoOCSPRequest(aiaLocation, mOriginAttributes, |
579 | 0 | std::move(ocspRequest), GetOCSPTimeout(), |
580 | 0 | ocspResponse); |
581 | 0 | MOZ_ASSERT((tempRV != Success) || ocspResponse.length() > 0); |
582 | 0 | if (tempRV != Success) { |
583 | 0 | rv = tempRV; |
584 | 0 | } else if (response.Init(ocspResponse.begin(), ocspResponse.length()) |
585 | 0 | != Success) { |
586 | 0 | rv = Result::ERROR_OCSP_MALFORMED_RESPONSE; // too big |
587 | 0 | } |
588 | 0 | attemptedRequest = true; |
589 | 0 | } else { |
590 | 0 | rv = cachedResponseResult; |
591 | 0 | attemptedRequest = false; |
592 | 0 | } |
593 | 0 |
|
594 | 0 | if (response.GetLength() == 0) { |
595 | 0 | Result error = rv; |
596 | 0 | if (attemptedRequest) { |
597 | 0 | Time timeout(time); |
598 | 0 | if (timeout.AddSeconds(ServerFailureDelaySeconds) != Success) { |
599 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow |
600 | 0 | } |
601 | 0 | rv = mOCSPCache.Put(certID, mOriginAttributes, error, time, timeout); |
602 | 0 | if (rv != Success) { |
603 | 0 | return rv; |
604 | 0 | } |
605 | 0 | } |
606 | 0 | if (mOCSPFetching != FetchOCSPForDVSoftFail) { |
607 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
608 | 0 | ("NSSCertDBTrustDomain: returning SECFailure after " |
609 | 0 | "OCSP request failure")); |
610 | 0 | return error; |
611 | 0 | } |
612 | 0 | if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { |
613 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
614 | 0 | ("NSSCertDBTrustDomain: returning SECFailure from cached " |
615 | 0 | "response after OCSP request failure")); |
616 | 0 | return cachedResponseResult; |
617 | 0 | } |
618 | 0 | if (stapledOCSPResponseResult != Success) { |
619 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
620 | 0 | ("NSSCertDBTrustDomain: returning SECFailure from expired/invalid " |
621 | 0 | "stapled response after OCSP request failure")); |
622 | 0 | return stapledOCSPResponseResult; |
623 | 0 | } |
624 | 0 |
|
625 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
626 | 0 | ("NSSCertDBTrustDomain: returning SECSuccess after " |
627 | 0 | "OCSP request failure")); |
628 | 0 | return Success; // Soft fail -> success :( |
629 | 0 | } |
630 | 0 | |
631 | 0 | // If the response from the network has expired but indicates a revoked |
632 | 0 | // or unknown certificate, PR_GetError() will return the appropriate error. |
633 | 0 | // We actually ignore expired here. |
634 | 0 | bool expired; |
635 | 0 | rv = VerifyAndMaybeCacheEncodedOCSPResponse(certID, time, |
636 | 0 | maxOCSPLifetimeInDays, |
637 | 0 | response, ResponseIsFromNetwork, |
638 | 0 | expired); |
639 | 0 | if (rv == Success || mOCSPFetching != FetchOCSPForDVSoftFail) { |
640 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
641 | 0 | ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse")); |
642 | 0 | return rv; |
643 | 0 | } |
644 | 0 |
|
645 | 0 | if (rv == Result::ERROR_OCSP_UNKNOWN_CERT || |
646 | 0 | rv == Result::ERROR_REVOKED_CERTIFICATE) { |
647 | 0 | return rv; |
648 | 0 | } |
649 | 0 | if (stapledOCSPResponseResult != Success) { |
650 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
651 | 0 | ("NSSCertDBTrustDomain: returning SECFailure from expired/invalid " |
652 | 0 | "stapled response after OCSP request verification failure")); |
653 | 0 | return stapledOCSPResponseResult; |
654 | 0 | } |
655 | 0 |
|
656 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
657 | 0 | ("NSSCertDBTrustDomain: end of CheckRevocation")); |
658 | 0 |
|
659 | 0 | return Success; // Soft fail -> success :( |
660 | 0 | } |
661 | | |
662 | | Result |
663 | | NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( |
664 | | const CertID& certID, Time time, uint16_t maxLifetimeInDays, |
665 | | Input encodedResponse, EncodedResponseSource responseSource, |
666 | | /*out*/ bool& expired) |
667 | 0 | { |
668 | 0 | Time thisUpdate(Time::uninitialized); |
669 | 0 | Time validThrough(Time::uninitialized); |
670 | 0 |
|
671 | 0 | // We use a try and fallback approach which first mandates good signature |
672 | 0 | // digest algorithms, then falls back to SHA-1 if this fails. If a delegated |
673 | 0 | // OCSP response signing certificate was issued with a SHA-1 signature, |
674 | 0 | // verification initially fails. We cache the failure and then re-use that |
675 | 0 | // result even when doing fallback (i.e. when weak signature digest algorithms |
676 | 0 | // should succeed). To address this we use an OCSPVerificationTrustDomain |
677 | 0 | // here, rather than using *this, to ensure verification succeeds for all |
678 | 0 | // allowed signature digest algorithms. |
679 | 0 | OCSPVerificationTrustDomain trustDomain(*this); |
680 | 0 | Result rv = VerifyEncodedOCSPResponse(trustDomain, certID, time, |
681 | 0 | maxLifetimeInDays, encodedResponse, |
682 | 0 | expired, &thisUpdate, &validThrough); |
683 | 0 | // If a response was stapled and expired, we don't want to cache it. Return |
684 | 0 | // early to simplify the logic here. |
685 | 0 | if (responseSource == ResponseWasStapled && expired) { |
686 | 0 | MOZ_ASSERT(rv != Success); |
687 | 0 | return rv; |
688 | 0 | } |
689 | 0 | // validThrough is only trustworthy if the response successfully verifies |
690 | 0 | // or it indicates a revoked or unknown certificate. |
691 | 0 | // If this isn't the case, store an indication of failure (to prevent |
692 | 0 | // repeatedly requesting a response from a failing server). |
693 | 0 | if (rv != Success && rv != Result::ERROR_REVOKED_CERTIFICATE && |
694 | 0 | rv != Result::ERROR_OCSP_UNKNOWN_CERT) { |
695 | 0 | validThrough = time; |
696 | 0 | if (validThrough.AddSeconds(ServerFailureDelaySeconds) != Success) { |
697 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow |
698 | 0 | } |
699 | 0 | } |
700 | 0 | if (responseSource == ResponseIsFromNetwork || |
701 | 0 | rv == Success || |
702 | 0 | rv == Result::ERROR_REVOKED_CERTIFICATE || |
703 | 0 | rv == Result::ERROR_OCSP_UNKNOWN_CERT) { |
704 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
705 | 0 | ("NSSCertDBTrustDomain: caching OCSP response")); |
706 | 0 | Result putRV = mOCSPCache.Put(certID, mOriginAttributes, rv, thisUpdate, |
707 | 0 | validThrough); |
708 | 0 | if (putRV != Success) { |
709 | 0 | return putRV; |
710 | 0 | } |
711 | 0 | } |
712 | 0 | |
713 | 0 | return rv; |
714 | 0 | } |
715 | | |
716 | | // If a certificate in the given chain appears to have been issued by one of |
717 | | // seven roots operated by StartCom and WoSign that are not trusted to issue new |
718 | | // certificates, verify that the end-entity has a notBefore date before 21 |
719 | | // October 2016. If the value of notBefore is after this time, the chain is not |
720 | | // valid. |
721 | | // (NB: While there are seven distinct roots being checked for, two of them |
722 | | // share distinguished names, resulting in six distinct distinguished names to |
723 | | // actually look for.) |
724 | | static Result |
725 | | CheckForStartComOrWoSign(const UniqueCERTCertList& certChain) |
726 | 0 | { |
727 | 0 | if (CERT_LIST_EMPTY(certChain)) { |
728 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
729 | 0 | } |
730 | 0 | const CERTCertListNode* endEntityNode = CERT_LIST_HEAD(certChain); |
731 | 0 | if (!endEntityNode || !endEntityNode->cert) { |
732 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
733 | 0 | } |
734 | 0 | PRTime notBefore; |
735 | 0 | PRTime notAfter; |
736 | 0 | if (CERT_GetCertTimes(endEntityNode->cert, ¬Before, ¬After) |
737 | 0 | != SECSuccess) { |
738 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
739 | 0 | } |
740 | 0 | // PRTime is microseconds since the epoch, whereas JS time is milliseconds. |
741 | 0 | // (new Date("2016-10-21T00:00:00Z")).getTime() * 1000 |
742 | 0 | static const PRTime OCTOBER_21_2016 = 1477008000000000; |
743 | 0 | if (notBefore <= OCTOBER_21_2016) { |
744 | 0 | return Success; |
745 | 0 | } |
746 | 0 | |
747 | 0 | for (const CERTCertListNode* node = CERT_LIST_HEAD(certChain); |
748 | 0 | !CERT_LIST_END(node, certChain); node = CERT_LIST_NEXT(node)) { |
749 | 0 | if (!node || !node->cert) { |
750 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
751 | 0 | } |
752 | 0 | if (CertDNIsInList(node->cert, StartComAndWoSignDNs)) { |
753 | 0 | return Result::ERROR_REVOKED_CERTIFICATE; |
754 | 0 | } |
755 | 0 | } |
756 | 0 | return Success; |
757 | 0 | } |
758 | | |
759 | | Result |
760 | | NSSCertDBTrustDomain::IsChainValid(const DERArray& certArray, Time time, |
761 | | const CertPolicyId& requiredPolicy) |
762 | 0 | { |
763 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
764 | 0 | ("NSSCertDBTrustDomain: IsChainValid")); |
765 | 0 |
|
766 | 0 | UniqueCERTCertList certList; |
767 | 0 | SECStatus srv = ConstructCERTCertListFromReversedDERArray(certArray, |
768 | 0 | certList); |
769 | 0 | if (srv != SECSuccess) { |
770 | 0 | return MapPRErrorCodeToResult(PR_GetError()); |
771 | 0 | } |
772 | 0 | if (CERT_LIST_EMPTY(certList)) { |
773 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
774 | 0 | } |
775 | 0 | |
776 | 0 | Result rv = CheckForStartComOrWoSign(certList); |
777 | 0 | if (rv != Success) { |
778 | 0 | return rv; |
779 | 0 | } |
780 | 0 | |
781 | 0 | // Modernization in-progress: Keep certList as a CERTCertList for storage into |
782 | 0 | // the mBuiltChain variable at the end, but let's use nsNSSCertList for the |
783 | 0 | // validity calculations. |
784 | 0 | UniqueCERTCertList certListCopy = nsNSSCertList::DupCertList(certList); |
785 | 0 |
|
786 | 0 | // This adopts the list |
787 | 0 | RefPtr<nsNSSCertList> nssCertList = new nsNSSCertList(std::move(certListCopy)); |
788 | 0 | if (!nssCertList) { |
789 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
790 | 0 | } |
791 | 0 | |
792 | 0 | nsCOMPtr<nsIX509Cert> rootCert; |
793 | 0 | nsresult nsrv = nssCertList->GetRootCertificate(rootCert); |
794 | 0 | if (NS_FAILED(nsrv)) { |
795 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
796 | 0 | } |
797 | 0 | UniqueCERTCertificate root(rootCert->GetCert()); |
798 | 0 | if (!root) { |
799 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
800 | 0 | } |
801 | 0 | bool isBuiltInRoot = false; |
802 | 0 | nsrv = rootCert->GetIsBuiltInRoot(&isBuiltInRoot); |
803 | 0 | if (NS_FAILED(nsrv)) { |
804 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
805 | 0 | } |
806 | 0 | bool skipPinningChecksBecauseOfMITMMode = |
807 | 0 | (!isBuiltInRoot && mPinningMode == CertVerifier::pinningAllowUserCAMITM); |
808 | 0 | // If mHostname isn't set, we're not verifying in the context of a TLS |
809 | 0 | // handshake, so don't verify HPKP in those cases. |
810 | 0 | if (mHostname && (mPinningMode != CertVerifier::pinningDisabled) && |
811 | 0 | !skipPinningChecksBecauseOfMITMMode) { |
812 | 0 | bool enforceTestMode = |
813 | 0 | (mPinningMode == CertVerifier::pinningEnforceTestMode); |
814 | 0 | bool chainHasValidPins; |
815 | 0 | nsrv = PublicKeyPinningService::ChainHasValidPins( |
816 | 0 | nssCertList, mHostname, time, enforceTestMode, mOriginAttributes, |
817 | 0 | chainHasValidPins, mPinningTelemetryInfo); |
818 | 0 | if (NS_FAILED(nsrv)) { |
819 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
820 | 0 | } |
821 | 0 | if (!chainHasValidPins) { |
822 | 0 | return Result::ERROR_KEY_PINNING_FAILURE; |
823 | 0 | } |
824 | 0 | } |
825 | 0 | |
826 | 0 | // See bug 1349762. If the root is "GlobalSign Root CA - R2", don't consider |
827 | 0 | // the end-entity valid for EV unless the |
828 | 0 | // "GlobalSign Extended Validation CA - SHA256 - G2" intermediate is in the |
829 | 0 | // chain as well. It should be possible to remove this workaround after |
830 | 0 | // January 2019 as per bug 1349727 comment 17. |
831 | 0 | if (requiredPolicy == sGlobalSignEVPolicy && |
832 | 0 | CertMatchesStaticData(root.get(), sGlobalSignRootCAR2SubjectBytes, |
833 | 0 | sGlobalSignRootCAR2SPKIBytes)) { |
834 | 0 |
|
835 | 0 | rootCert = nullptr; // Clear the state for Segment... |
836 | 0 | nsCOMPtr<nsIX509CertList> intCerts; |
837 | 0 | nsCOMPtr<nsIX509Cert> eeCert; |
838 | 0 |
|
839 | 0 | nsrv = nssCertList->SegmentCertificateChain(rootCert, intCerts, eeCert); |
840 | 0 | if (NS_FAILED(nsrv)) { |
841 | 0 | // This chain is supposed to be complete, so this is an error. There |
842 | 0 | // are no intermediates, so return before searching just as if the |
843 | 0 | // search failed. |
844 | 0 | return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; |
845 | 0 | } |
846 | 0 | |
847 | 0 | bool foundRequiredIntermediate = false; |
848 | 0 | RefPtr<nsNSSCertList> intCertList = intCerts->GetCertList(); |
849 | 0 | nsrv = intCertList->ForEachCertificateInChain( |
850 | 0 | [&foundRequiredIntermediate] (nsCOMPtr<nsIX509Cert> aCert, bool aHasMore, |
851 | 0 | /* out */ bool& aContinue) { |
852 | 0 | // We need an owning handle when calling nsIX509Cert::GetCert(). |
853 | 0 | UniqueCERTCertificate nssCert(aCert->GetCert()); |
854 | 0 | if (CertMatchesStaticData( |
855 | 0 | nssCert.get(), |
856 | 0 | sGlobalSignExtendedValidationCASHA256G2SubjectBytes, |
857 | 0 | sGlobalSignExtendedValidationCASHA256G2SPKIBytes)) { |
858 | 0 | foundRequiredIntermediate = true; |
859 | 0 | aContinue = false; |
860 | 0 | } |
861 | 0 | return NS_OK; |
862 | 0 | }); |
863 | 0 |
|
864 | 0 | if (NS_FAILED(nsrv)) { |
865 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
866 | 0 | } |
867 | 0 | |
868 | 0 | if (!foundRequiredIntermediate) { |
869 | 0 | return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; |
870 | 0 | } |
871 | 0 | } |
872 | 0 | |
873 | 0 | // See bug 1434300. If the root is a Symantec root, see if we distrust this |
874 | 0 | // path. Since we already have the root available, we can check that cheaply |
875 | 0 | // here before proceeding with the rest of the algorithm. |
876 | 0 | |
877 | 0 | // This algorithm only applies if we are verifying in the context of a TLS |
878 | 0 | // handshake. To determine this, we check mHostname: If it isn't set, this is |
879 | 0 | // not TLS, so don't run the algorithm. |
880 | 0 | if (mHostname && CertDNIsInList(root.get(), RootSymantecDNs) && |
881 | 0 | ((mDistrustedCAPolicy & DistrustedCAPolicy::DistrustSymantecRoots) || |
882 | 0 | (mDistrustedCAPolicy & DistrustedCAPolicy::DistrustSymantecRootsRegardlessOfDate))) { |
883 | 0 |
|
884 | 0 | rootCert = nullptr; // Clear the state for Segment... |
885 | 0 | nsCOMPtr<nsIX509CertList> intCerts; |
886 | 0 | nsCOMPtr<nsIX509Cert> eeCert; |
887 | 0 |
|
888 | 0 | nsrv = nssCertList->SegmentCertificateChain(rootCert, intCerts, eeCert); |
889 | 0 | if (NS_FAILED(nsrv)) { |
890 | 0 | // This chain is supposed to be complete, so this is an error. |
891 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
892 | 0 | } |
893 | 0 | |
894 | 0 | // PRTime is microseconds since the epoch, whereas JS time is milliseconds. |
895 | 0 | // (new Date("2016-06-01T00:00:00Z")).getTime() * 1000 |
896 | 0 | static const PRTime JUNE_1_2016 = 1464739200000000; |
897 | 0 |
|
898 | 0 | PRTime permitAfterDate = JUNE_1_2016; |
899 | 0 | if (mDistrustedCAPolicy & DistrustedCAPolicy::DistrustSymantecRootsRegardlessOfDate) { |
900 | 0 | permitAfterDate = 0; // 0 indicates there is no permitAfterDate |
901 | 0 | } |
902 | 0 |
|
903 | 0 | bool isDistrusted = false; |
904 | 0 | nsrv = CheckForSymantecDistrust(intCerts, eeCert, permitAfterDate, |
905 | 0 | RootAppleAndGoogleSPKIs, isDistrusted); |
906 | 0 | if (NS_FAILED(nsrv)) { |
907 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
908 | 0 | } |
909 | 0 | if (isDistrusted) { |
910 | 0 | mSawDistrustedCAByPolicyError = true; |
911 | 0 | return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; |
912 | 0 | } |
913 | 0 | } |
914 | 0 | |
915 | 0 | mBuiltChain = std::move(certList); |
916 | 0 |
|
917 | 0 | return Success; |
918 | 0 | } |
919 | | |
920 | | Result |
921 | | NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(DigestAlgorithm aAlg, |
922 | | EndEntityOrCA endEntityOrCA, |
923 | | Time notBefore) |
924 | 0 | { |
925 | 0 | // (new Date("2016-01-01T00:00:00Z")).getTime() / 1000 |
926 | 0 | static const Time JANUARY_FIRST_2016 = TimeFromEpochInSeconds(1451606400); |
927 | 0 |
|
928 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
929 | 0 | ("NSSCertDBTrustDomain: CheckSignatureDigestAlgorithm")); |
930 | 0 | if (aAlg == DigestAlgorithm::sha1) { |
931 | 0 | switch (mSHA1Mode) { |
932 | 0 | case CertVerifier::SHA1Mode::Forbidden: |
933 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("SHA-1 certificate rejected")); |
934 | 0 | return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; |
935 | 0 | case CertVerifier::SHA1Mode::ImportedRootOrBefore2016: |
936 | 0 | if (JANUARY_FIRST_2016 <= notBefore) { |
937 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Post-2015 SHA-1 certificate rejected")); |
938 | 0 | return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; |
939 | 0 | } |
940 | 0 | break; |
941 | 0 | case CertVerifier::SHA1Mode::Allowed: |
942 | 0 | // Enforcing that the resulting chain uses an imported root is only |
943 | 0 | // possible at a higher level. This is done in CertVerifier::VerifyCert. |
944 | 0 | case CertVerifier::SHA1Mode::ImportedRoot: |
945 | 0 | default: |
946 | 0 | break; |
947 | 0 | // MSVC warns unless we explicitly handle this now-unused option. |
948 | 0 | case CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden: |
949 | 0 | MOZ_ASSERT_UNREACHABLE("unexpected SHA1Mode type"); |
950 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
951 | 0 | } |
952 | 0 | } |
953 | 0 |
|
954 | 0 | return Success; |
955 | 0 | } |
956 | | |
957 | | Result |
958 | | NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits( |
959 | | EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits) |
960 | 0 | { |
961 | 0 | if (modulusSizeInBits < mMinRSABits) { |
962 | 0 | return Result::ERROR_INADEQUATE_KEY_SIZE; |
963 | 0 | } |
964 | 0 | return Success; |
965 | 0 | } |
966 | | |
967 | | Result |
968 | | NSSCertDBTrustDomain::VerifyRSAPKCS1SignedDigest( |
969 | | const SignedDigest& signedDigest, |
970 | | Input subjectPublicKeyInfo) |
971 | 0 | { |
972 | 0 | return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo, |
973 | 0 | mPinArg); |
974 | 0 | } |
975 | | |
976 | | Result |
977 | | NSSCertDBTrustDomain::CheckECDSACurveIsAcceptable( |
978 | | EndEntityOrCA /*endEntityOrCA*/, NamedCurve curve) |
979 | 0 | { |
980 | 0 | switch (curve) { |
981 | 0 | case NamedCurve::secp256r1: // fall through |
982 | 0 | case NamedCurve::secp384r1: // fall through |
983 | 0 | case NamedCurve::secp521r1: |
984 | 0 | return Success; |
985 | 0 | } |
986 | 0 | |
987 | 0 | return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE; |
988 | 0 | } |
989 | | |
990 | | Result |
991 | | NSSCertDBTrustDomain::VerifyECDSASignedDigest(const SignedDigest& signedDigest, |
992 | | Input subjectPublicKeyInfo) |
993 | 0 | { |
994 | 0 | return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo, |
995 | 0 | mPinArg); |
996 | 0 | } |
997 | | |
998 | | Result |
999 | | NSSCertDBTrustDomain::CheckValidityIsAcceptable(Time notBefore, Time notAfter, |
1000 | | EndEntityOrCA endEntityOrCA, |
1001 | | KeyPurposeId keyPurpose) |
1002 | 0 | { |
1003 | 0 | if (endEntityOrCA != EndEntityOrCA::MustBeEndEntity) { |
1004 | 0 | return Success; |
1005 | 0 | } |
1006 | 0 | if (keyPurpose == KeyPurposeId::id_kp_OCSPSigning) { |
1007 | 0 | return Success; |
1008 | 0 | } |
1009 | 0 | |
1010 | 0 | Duration DURATION_27_MONTHS_PLUS_SLOP((2 * 365 + 3 * 31 + 7) * |
1011 | 0 | Time::ONE_DAY_IN_SECONDS); |
1012 | 0 | Duration maxValidityDuration(UINT64_MAX); |
1013 | 0 | Duration validityDuration(notBefore, notAfter); |
1014 | 0 |
|
1015 | 0 | switch (mValidityCheckingMode) { |
1016 | 0 | case ValidityCheckingMode::CheckingOff: |
1017 | 0 | return Success; |
1018 | 0 | case ValidityCheckingMode::CheckForEV: |
1019 | 0 | // The EV Guidelines say the maximum is 27 months, but we use a slightly |
1020 | 0 | // higher limit here to (hopefully) minimize compatibility breakage. |
1021 | 0 | maxValidityDuration = DURATION_27_MONTHS_PLUS_SLOP; |
1022 | 0 | break; |
1023 | 0 | default: |
1024 | 0 | MOZ_ASSERT_UNREACHABLE("We're not handling every ValidityCheckingMode type"); |
1025 | 0 | } |
1026 | 0 |
|
1027 | 0 | if (validityDuration > maxValidityDuration) { |
1028 | 0 | return Result::ERROR_VALIDITY_TOO_LONG; |
1029 | 0 | } |
1030 | 0 | |
1031 | 0 | return Success; |
1032 | 0 | } |
1033 | | |
1034 | | Result |
1035 | | NSSCertDBTrustDomain::NetscapeStepUpMatchesServerAuth(Time notBefore, |
1036 | | /*out*/ bool& matches) |
1037 | 0 | { |
1038 | 0 | // (new Date("2015-08-23T00:00:00Z")).getTime() / 1000 |
1039 | 0 | static const Time AUGUST_23_2015 = TimeFromEpochInSeconds(1440288000); |
1040 | 0 | // (new Date("2016-08-23T00:00:00Z")).getTime() / 1000 |
1041 | 0 | static const Time AUGUST_23_2016 = TimeFromEpochInSeconds(1471910400); |
1042 | 0 |
|
1043 | 0 | switch (mNetscapeStepUpPolicy) { |
1044 | 0 | case NetscapeStepUpPolicy::AlwaysMatch: |
1045 | 0 | matches = true; |
1046 | 0 | return Success; |
1047 | 0 | case NetscapeStepUpPolicy::MatchBefore23August2016: |
1048 | 0 | matches = notBefore < AUGUST_23_2016; |
1049 | 0 | return Success; |
1050 | 0 | case NetscapeStepUpPolicy::MatchBefore23August2015: |
1051 | 0 | matches = notBefore < AUGUST_23_2015; |
1052 | 0 | return Success; |
1053 | 0 | case NetscapeStepUpPolicy::NeverMatch: |
1054 | 0 | matches = false; |
1055 | 0 | return Success; |
1056 | 0 | default: |
1057 | 0 | MOZ_ASSERT_UNREACHABLE("unhandled NetscapeStepUpPolicy type"); |
1058 | 0 | } |
1059 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
1060 | 0 | } |
1061 | | |
1062 | | void |
1063 | | NSSCertDBTrustDomain::ResetAccumulatedState() |
1064 | 0 | { |
1065 | 0 | mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED; |
1066 | 0 | mSCTListFromOCSPStapling = nullptr; |
1067 | 0 | mSCTListFromCertificate = nullptr; |
1068 | 0 | mSawDistrustedCAByPolicyError = false; |
1069 | 0 | } |
1070 | | |
1071 | | static Input |
1072 | | SECItemToInput(const UniqueSECItem& item) |
1073 | 0 | { |
1074 | 0 | Input result; |
1075 | 0 | if (item) { |
1076 | 0 | MOZ_ASSERT(item->type == siBuffer); |
1077 | 0 | Result rv = result.Init(item->data, item->len); |
1078 | 0 | // As used here, |item| originally comes from an Input, |
1079 | 0 | // so there should be no issues converting it back. |
1080 | 0 | MOZ_ASSERT(rv == Success); |
1081 | 0 | Unused << rv; // suppresses warnings in release builds |
1082 | 0 | } |
1083 | 0 | return result; |
1084 | 0 | } |
1085 | | |
1086 | | Input |
1087 | | NSSCertDBTrustDomain::GetSCTListFromCertificate() const |
1088 | 0 | { |
1089 | 0 | return SECItemToInput(mSCTListFromCertificate); |
1090 | 0 | } |
1091 | | |
1092 | | Input |
1093 | | NSSCertDBTrustDomain::GetSCTListFromOCSPStapling() const |
1094 | 0 | { |
1095 | 0 | return SECItemToInput(mSCTListFromOCSPStapling); |
1096 | 0 | } |
1097 | | |
1098 | | bool |
1099 | | NSSCertDBTrustDomain::GetIsErrorDueToDistrustedCAPolicy() const |
1100 | 0 | { |
1101 | 0 | return mSawDistrustedCAByPolicyError; |
1102 | 0 | } |
1103 | | |
1104 | | void |
1105 | | NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension extension, |
1106 | | Input extensionData) |
1107 | 0 | { |
1108 | 0 | UniqueSECItem* out = nullptr; |
1109 | 0 | switch (extension) { |
1110 | 0 | case AuxiliaryExtension::EmbeddedSCTList: |
1111 | 0 | out = &mSCTListFromCertificate; |
1112 | 0 | break; |
1113 | 0 | case AuxiliaryExtension::SCTListFromOCSPResponse: |
1114 | 0 | out = &mSCTListFromOCSPStapling; |
1115 | 0 | break; |
1116 | 0 | default: |
1117 | 0 | MOZ_ASSERT_UNREACHABLE("unhandled AuxiliaryExtension"); |
1118 | 0 | } |
1119 | 0 | if (out) { |
1120 | 0 | SECItem extensionDataItem = UnsafeMapInputToSECItem(extensionData); |
1121 | 0 | out->reset(SECITEM_DupItem(&extensionDataItem)); |
1122 | 0 | } |
1123 | 0 | } |
1124 | | |
1125 | | SECStatus |
1126 | | InitializeNSS(const nsACString& dir, bool readOnly, bool loadPKCS11Modules) |
1127 | 0 | { |
1128 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1129 | 0 |
|
1130 | 0 | // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs |
1131 | 0 | // module by NSS_Initialize because we will load it in InstallLoadableRoots |
1132 | 0 | // later. It also allows us to work around a bug in the system NSS in |
1133 | 0 | // Ubuntu 8.04, which loads any nonexistent "<configdir>/libnssckbi.so" as |
1134 | 0 | // "/usr/lib/nss/libnssckbi.so". |
1135 | 0 | uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE; |
1136 | 0 | if (readOnly) { |
1137 | 0 | flags |= NSS_INIT_READONLY; |
1138 | 0 | } |
1139 | 0 | if (!loadPKCS11Modules) { |
1140 | 0 | flags |= NSS_INIT_NOMODDB; |
1141 | 0 | } |
1142 | 0 | nsAutoCString dbTypeAndDirectory("sql:"); |
1143 | 0 | dbTypeAndDirectory.Append(dir); |
1144 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
1145 | 0 | ("InitializeNSS(%s, %d, %d)", dbTypeAndDirectory.get(), readOnly, |
1146 | 0 | loadPKCS11Modules)); |
1147 | 0 | SECStatus srv = NSS_Initialize(dbTypeAndDirectory.get(), "", "", |
1148 | 0 | SECMOD_DB, flags); |
1149 | 0 | if (srv != SECSuccess) { |
1150 | 0 | return srv; |
1151 | 0 | } |
1152 | 0 | |
1153 | 0 | if (!readOnly) { |
1154 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); |
1155 | 0 | if (!slot) { |
1156 | 0 | return SECFailure; |
1157 | 0 | } |
1158 | 0 | // If the key DB doesn't have a password set, PK11_NeedUserInit will return |
1159 | 0 | // true. For the SQL DB, we need to set a password or we won't be able to |
1160 | 0 | // import any certificates or change trust settings. |
1161 | 0 | if (PK11_NeedUserInit(slot.get())) { |
1162 | 0 | srv = PK11_InitPin(slot.get(), nullptr, nullptr); |
1163 | 0 | MOZ_ASSERT(srv == SECSuccess); |
1164 | 0 | Unused << srv; |
1165 | 0 | } |
1166 | 0 | } |
1167 | 0 |
|
1168 | 0 | return SECSuccess; |
1169 | 0 | } |
1170 | | |
1171 | | void |
1172 | | DisableMD5() |
1173 | 0 | { |
1174 | 0 | NSS_SetAlgorithmPolicy(SEC_OID_MD5, |
1175 | 0 | 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
1176 | 0 | NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, |
1177 | 0 | 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
1178 | 0 | NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, |
1179 | 0 | 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); |
1180 | 0 | } |
1181 | | |
1182 | | bool |
1183 | | LoadLoadableRoots(const nsCString& dir) |
1184 | 0 | { |
1185 | 0 | // If a module exists with the same name, make a best effort attempt to delete |
1186 | 0 | // it. Note that it isn't possible to delete the internal module, so checking |
1187 | 0 | // the return value would be detrimental in that case. |
1188 | 0 | int unusedModType; |
1189 | 0 | Unused << SECMOD_DeleteModule(kRootModuleName, &unusedModType); |
1190 | 0 | // Some NSS command-line utilities will load a roots module under the name |
1191 | 0 | // "Root Certs" if there happens to be a `MOZ_DLL_PREFIX "nssckbi" MOZ_DLL_SUFFIX` |
1192 | 0 | // file in the directory being operated on. In some cases this can cause us to |
1193 | 0 | // fail to load our roots module. In these cases, deleting the "Root Certs" |
1194 | 0 | // module allows us to load the correct one. See bug 1406396. |
1195 | 0 | Unused << SECMOD_DeleteModule("Root Certs", &unusedModType); |
1196 | 0 |
|
1197 | 0 | nsAutoCString fullLibraryPath; |
1198 | 0 | if (!dir.IsEmpty()) { |
1199 | 0 | fullLibraryPath.Assign(dir); |
1200 | 0 | fullLibraryPath.AppendLiteral(FILE_PATH_SEPARATOR); |
1201 | 0 | } |
1202 | 0 | fullLibraryPath.Append(MOZ_DLL_PREFIX "nssckbi" MOZ_DLL_SUFFIX); |
1203 | 0 | // Escape the \ and " characters. |
1204 | 0 | fullLibraryPath.ReplaceSubstring("\\", "\\\\"); |
1205 | 0 | fullLibraryPath.ReplaceSubstring("\"", "\\\""); |
1206 | 0 |
|
1207 | 0 | nsAutoCString pkcs11ModuleSpec("name=\""); |
1208 | 0 | pkcs11ModuleSpec.Append(kRootModuleName); |
1209 | 0 | pkcs11ModuleSpec.AppendLiteral("\" library=\""); |
1210 | 0 | pkcs11ModuleSpec.Append(fullLibraryPath); |
1211 | 0 | pkcs11ModuleSpec.AppendLiteral("\""); |
1212 | 0 |
|
1213 | 0 | UniqueSECMODModule rootsModule( |
1214 | 0 | SECMOD_LoadUserModule(const_cast<char*>(pkcs11ModuleSpec.get()), nullptr, |
1215 | 0 | false)); |
1216 | 0 | if (!rootsModule) { |
1217 | 0 | return false; |
1218 | 0 | } |
1219 | 0 | |
1220 | 0 | if (!rootsModule->loaded) { |
1221 | 0 | return false; |
1222 | 0 | } |
1223 | 0 | |
1224 | 0 | return true; |
1225 | 0 | } |
1226 | | |
1227 | | void |
1228 | | UnloadLoadableRoots() |
1229 | 0 | { |
1230 | 0 | UniqueSECMODModule rootsModule(SECMOD_FindModule(kRootModuleName)); |
1231 | 0 |
|
1232 | 0 | if (rootsModule) { |
1233 | 0 | SECMOD_UnloadUserModule(rootsModule.get()); |
1234 | 0 | } |
1235 | 0 | } |
1236 | | |
1237 | | nsresult |
1238 | | DefaultServerNicknameForCert(const CERTCertificate* cert, |
1239 | | /*out*/ nsCString& nickname) |
1240 | 0 | { |
1241 | 0 | MOZ_ASSERT(cert); |
1242 | 0 | NS_ENSURE_ARG_POINTER(cert); |
1243 | 0 |
|
1244 | 0 | UniquePORTString baseName(CERT_GetCommonName(&cert->subject)); |
1245 | 0 | if (!baseName) { |
1246 | 0 | baseName = UniquePORTString(CERT_GetOrgUnitName(&cert->subject)); |
1247 | 0 | } |
1248 | 0 | if (!baseName) { |
1249 | 0 | baseName = UniquePORTString(CERT_GetOrgName(&cert->subject)); |
1250 | 0 | } |
1251 | 0 | if (!baseName) { |
1252 | 0 | baseName = UniquePORTString(CERT_GetLocalityName(&cert->subject)); |
1253 | 0 | } |
1254 | 0 | if (!baseName) { |
1255 | 0 | baseName = UniquePORTString(CERT_GetStateName(&cert->subject)); |
1256 | 0 | } |
1257 | 0 | if (!baseName) { |
1258 | 0 | baseName = UniquePORTString(CERT_GetCountryName(&cert->subject)); |
1259 | 0 | } |
1260 | 0 | if (!baseName) { |
1261 | 0 | return NS_ERROR_FAILURE; |
1262 | 0 | } |
1263 | 0 | |
1264 | 0 | // This function is only used in contexts where a failure to find a suitable |
1265 | 0 | // nickname does not block the overall task from succeeding. |
1266 | 0 | // As such, we use an arbitrary limit to prevent this nickname searching |
1267 | 0 | // process from taking forever. |
1268 | 0 | static const uint32_t ARBITRARY_LIMIT = 500; |
1269 | 0 | for (uint32_t count = 1; count < ARBITRARY_LIMIT; count++) { |
1270 | 0 | nickname = baseName.get(); |
1271 | 0 | if (count != 1) { |
1272 | 0 | nickname.AppendPrintf(" #%u", count); |
1273 | 0 | } |
1274 | 0 | if (nickname.IsEmpty()) { |
1275 | 0 | return NS_ERROR_FAILURE; |
1276 | 0 | } |
1277 | 0 | |
1278 | 0 | bool conflict = SEC_CertNicknameConflict(nickname.get(), &cert->derSubject, |
1279 | 0 | cert->dbhandle); |
1280 | 0 | if (!conflict) { |
1281 | 0 | return NS_OK; |
1282 | 0 | } |
1283 | 0 | } |
1284 | 0 |
|
1285 | 0 | return NS_ERROR_FAILURE; |
1286 | 0 | } |
1287 | | |
1288 | | /** |
1289 | | * Given a list of certificates representing a verified certificate path from an |
1290 | | * end-entity certificate to a trust anchor, imports the intermediate |
1291 | | * certificates into the permanent certificate database. This is an attempt to |
1292 | | * cope with misconfigured servers that don't include the appropriate |
1293 | | * intermediate certificates in the TLS handshake. |
1294 | | * |
1295 | | * @param certList the verified certificate list |
1296 | | */ |
1297 | | void |
1298 | | SaveIntermediateCerts(const UniqueCERTCertList& certList) |
1299 | 0 | { |
1300 | 0 | if (!certList) { |
1301 | 0 | return; |
1302 | 0 | } |
1303 | 0 | |
1304 | 0 | UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); |
1305 | 0 | if (!slot) { |
1306 | 0 | return; |
1307 | 0 | } |
1308 | 0 | |
1309 | 0 | bool isEndEntity = true; |
1310 | 0 | for (CERTCertListNode* node = CERT_LIST_HEAD(certList); |
1311 | 0 | !CERT_LIST_END(node, certList); |
1312 | 0 | node = CERT_LIST_NEXT(node)) { |
1313 | 0 | if (isEndEntity) { |
1314 | 0 | // Skip the end-entity; we only want to store intermediates |
1315 | 0 | isEndEntity = false; |
1316 | 0 | continue; |
1317 | 0 | } |
1318 | 0 | |
1319 | 0 | if (node->cert->slot) { |
1320 | 0 | // This cert was found on a token; no need to remember it in the permanent |
1321 | 0 | // database. |
1322 | 0 | continue; |
1323 | 0 | } |
1324 | 0 | |
1325 | 0 | if (node->cert->isperm) { |
1326 | 0 | // We don't need to remember certs already stored in perm db. |
1327 | 0 | continue; |
1328 | 0 | } |
1329 | 0 | |
1330 | 0 | // No need to save the trust anchor - it's either already a permanent |
1331 | 0 | // certificate or it's the Microsoft Family Safety root or an enterprise |
1332 | 0 | // root temporarily imported via the child mode or enterprise root features. |
1333 | 0 | // We don't want to import these because they're intended to be temporary |
1334 | 0 | // (and because importing them happens to reset their trust settings, which |
1335 | 0 | // breaks these features). |
1336 | 0 | if (node == CERT_LIST_TAIL(certList)) { |
1337 | 0 | continue; |
1338 | 0 | } |
1339 | 0 | |
1340 | 0 | nsAutoCString nickname; |
1341 | 0 | nsresult rv = DefaultServerNicknameForCert(node->cert, nickname); |
1342 | 0 | if (NS_FAILED(rv)) { |
1343 | 0 | continue; |
1344 | 0 | } |
1345 | 0 | |
1346 | 0 | // As mentioned in the documentation of this function, we're importing only |
1347 | 0 | // to cope with misconfigured servers. As such, we ignore the return value |
1348 | 0 | // below, since it doesn't really matter if the import fails. |
1349 | 0 | Unused << PK11_ImportCert(slot.get(), node->cert, CK_INVALID_HANDLE, |
1350 | 0 | nickname.get(), false); |
1351 | 0 | } |
1352 | 0 | } |
1353 | | |
1354 | | } } // namespace mozilla::psm |