/src/mozilla-central/security/certverifier/CertVerifier.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 "CertVerifier.h" |
8 | | |
9 | | #include <stdint.h> |
10 | | |
11 | | #include "CTDiversityPolicy.h" |
12 | | #include "CTKnownLogs.h" |
13 | | #include "CTLogVerifier.h" |
14 | | #include "ExtendedValidation.h" |
15 | | #include "MultiLogCTVerifier.h" |
16 | | #include "NSSCertDBTrustDomain.h" |
17 | | #include "NSSErrorsService.h" |
18 | | #include "cert.h" |
19 | | #include "mozilla/Assertions.h" |
20 | | #include "mozilla/Casting.h" |
21 | | #include "mozilla/IntegerPrintfMacros.h" |
22 | | #include "nsNSSComponent.h" |
23 | | #include "nsPromiseFlatString.h" |
24 | | #include "nsServiceManagerUtils.h" |
25 | | #include "pk11pub.h" |
26 | | #include "pkix/pkix.h" |
27 | | #include "pkix/pkixnss.h" |
28 | | #include "secmod.h" |
29 | | |
30 | | using namespace mozilla::ct; |
31 | | using namespace mozilla::pkix; |
32 | | using namespace mozilla::psm; |
33 | | |
34 | | mozilla::LazyLogModule gCertVerifierLog("certverifier"); |
35 | | |
36 | | // Returns the certificate validity period in calendar months (rounded down). |
37 | | // "extern" to allow unit tests in CTPolicyEnforcerTest.cpp. |
38 | | extern mozilla::pkix::Result |
39 | | GetCertLifetimeInFullMonths(PRTime certNotBefore, |
40 | | PRTime certNotAfter, |
41 | | size_t& months) |
42 | 0 | { |
43 | 0 | if (certNotBefore >= certNotAfter) { |
44 | 0 | MOZ_ASSERT_UNREACHABLE("Expected notBefore < notAfter"); |
45 | 0 | return mozilla::pkix::Result::FATAL_ERROR_INVALID_ARGS; |
46 | 0 | } |
47 | 0 |
|
48 | 0 | PRExplodedTime explodedNotBefore; |
49 | 0 | PRExplodedTime explodedNotAfter; |
50 | 0 |
|
51 | 0 | PR_ExplodeTime(certNotBefore, PR_LocalTimeParameters, &explodedNotBefore); |
52 | 0 | PR_ExplodeTime(certNotAfter, PR_LocalTimeParameters, &explodedNotAfter); |
53 | 0 |
|
54 | 0 | PRInt32 signedMonths = |
55 | 0 | (explodedNotAfter.tm_year - explodedNotBefore.tm_year) * 12 + |
56 | 0 | (explodedNotAfter.tm_month - explodedNotBefore.tm_month); |
57 | 0 | if (explodedNotAfter.tm_mday < explodedNotBefore.tm_mday) { |
58 | 0 | --signedMonths; |
59 | 0 | } |
60 | 0 |
|
61 | 0 | // Can't use `mozilla::AssertedCast<size_t>(signedMonths)` below |
62 | 0 | // since it currently generates a warning on Win x64 debug. |
63 | 0 | if (signedMonths < 0) { |
64 | 0 | MOZ_ASSERT_UNREACHABLE("Expected explodedNotBefore < explodedNotAfter"); |
65 | 0 | return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; |
66 | 0 | } |
67 | 0 | months = static_cast<size_t>(signedMonths); |
68 | 0 |
|
69 | 0 | return Success; |
70 | 0 | } |
71 | | |
72 | | namespace mozilla { namespace psm { |
73 | | |
74 | | const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1; |
75 | | const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2; |
76 | | const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4; |
77 | | |
78 | | void |
79 | | CertificateTransparencyInfo::Reset() |
80 | 0 | { |
81 | 0 | enabled = false; |
82 | 0 | verifyResult.Reset(); |
83 | 0 | policyCompliance = CTPolicyCompliance::Unknown; |
84 | 0 | } |
85 | | |
86 | | CertVerifier::CertVerifier(OcspDownloadConfig odc, |
87 | | OcspStrictConfig osc, |
88 | | mozilla::TimeDuration ocspTimeoutSoft, |
89 | | mozilla::TimeDuration ocspTimeoutHard, |
90 | | uint32_t certShortLifetimeInDays, |
91 | | PinningMode pinningMode, |
92 | | SHA1Mode sha1Mode, |
93 | | BRNameMatchingPolicy::Mode nameMatchingMode, |
94 | | NetscapeStepUpPolicy netscapeStepUpPolicy, |
95 | | CertificateTransparencyMode ctMode, |
96 | | DistrustedCAPolicy distrustedCAPolicy) |
97 | | : mOCSPDownloadConfig(odc) |
98 | | , mOCSPStrict(osc == ocspStrict) |
99 | | , mOCSPTimeoutSoft(ocspTimeoutSoft) |
100 | | , mOCSPTimeoutHard(ocspTimeoutHard) |
101 | | , mCertShortLifetimeInDays(certShortLifetimeInDays) |
102 | | , mPinningMode(pinningMode) |
103 | | , mSHA1Mode(sha1Mode) |
104 | | , mNameMatchingMode(nameMatchingMode) |
105 | | , mNetscapeStepUpPolicy(netscapeStepUpPolicy) |
106 | | , mCTMode(ctMode) |
107 | | , mDistrustedCAPolicy(distrustedCAPolicy) |
108 | 0 | { |
109 | 0 | LoadKnownCTLogs(); |
110 | 0 | } |
111 | | |
112 | | CertVerifier::~CertVerifier() |
113 | 0 | { |
114 | 0 | } |
115 | | |
116 | | Result |
117 | | IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain, bool& result) |
118 | 0 | { |
119 | 0 | if (!chain || CERT_LIST_EMPTY(chain)) { |
120 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
121 | 0 | } |
122 | 0 | CERTCertListNode* rootNode = CERT_LIST_TAIL(chain); |
123 | 0 | if (!rootNode) { |
124 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
125 | 0 | } |
126 | 0 | CERTCertificate* root = rootNode->cert; |
127 | 0 | if (!root) { |
128 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
129 | 0 | } |
130 | 0 | return IsCertBuiltInRoot(root, result); |
131 | 0 | } |
132 | | |
133 | | // The term "builtin root" traditionally refers to a root CA certificate that |
134 | | // has been added to the NSS trust store, because it has been approved |
135 | | // for inclusion according to the Mozilla CA policy, and might be accepted |
136 | | // by Mozilla applications as an issuer for certificates seen on the public web. |
137 | | Result |
138 | | IsCertBuiltInRoot(CERTCertificate* cert, bool& result) |
139 | 0 | { |
140 | 0 | if (NS_FAILED(BlockUntilLoadableRootsLoaded())) { |
141 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
142 | 0 | } |
143 | 0 | |
144 | 0 | result = false; |
145 | | #ifdef DEBUG |
146 | | nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID)); |
147 | | if (!component) { |
148 | | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
149 | | } |
150 | | nsresult rv = component->IsCertTestBuiltInRoot(cert, &result); |
151 | | if (NS_FAILED(rv)) { |
152 | | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
153 | | } |
154 | | if (result) { |
155 | | return Success; |
156 | | } |
157 | | #endif // DEBUG |
158 | | AutoSECMODListReadLock lock; |
159 | 0 | for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list; |
160 | 0 | list = list->next) { |
161 | 0 | for (int i = 0; i < list->module->slotCount; i++) { |
162 | 0 | PK11SlotInfo* slot = list->module->slots[i]; |
163 | 0 | // We're searching for the "builtin root module", which is a module that |
164 | 0 | // contains an object with a CKA_CLASS of CKO_NETSCAPE_BUILTIN_ROOT_LIST. |
165 | 0 | // We use PK11_HasRootCerts() to identify a module with that property. |
166 | 0 | // In the past, we exclusively used the PKCS#11 module named nssckbi, |
167 | 0 | // which is provided by the NSS library. |
168 | 0 | // Nowadays, some distributions use a replacement module, which contains |
169 | 0 | // the builtin roots, but which also contains additional CA certificates, |
170 | 0 | // such as CAs trusted in a local deployment. |
171 | 0 | // We want to be able to distinguish between these two categories, |
172 | 0 | // because a CA, which may issue certificates for the public web, |
173 | 0 | // is expected to comply with additional requirements. |
174 | 0 | // If the certificate has attribute CKA_NSS_MOZILLA_CA_POLICY set to true, |
175 | 0 | // then we treat it as a "builtin root". |
176 | 0 | if (PK11_IsPresent(slot) && PK11_HasRootCerts(slot)) { |
177 | 0 | CK_OBJECT_HANDLE handle = PK11_FindCertInSlot(slot, cert, nullptr); |
178 | 0 | if (handle != CK_INVALID_HANDLE && |
179 | 0 | PK11_HasAttributeSet(slot, handle, CKA_NSS_MOZILLA_CA_POLICY, |
180 | 0 | false)) { |
181 | 0 | // Attribute was found, and is set to true |
182 | 0 | result = true; |
183 | 0 | break; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | 0 | return Success; |
189 | 0 | } |
190 | | |
191 | | static Result |
192 | | BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER, |
193 | | Time time, KeyUsage ku1, KeyUsage ku2, |
194 | | KeyUsage ku3, KeyPurposeId eku, |
195 | | const CertPolicyId& requiredPolicy, |
196 | | const Input* stapledOCSPResponse, |
197 | | /*optional out*/ CertVerifier::OCSPStaplingStatus* |
198 | | ocspStaplingStatus) |
199 | 0 | { |
200 | 0 | trustDomain.ResetAccumulatedState(); |
201 | 0 | Result rv = BuildCertChain(trustDomain, certDER, time, |
202 | 0 | EndEntityOrCA::MustBeEndEntity, ku1, |
203 | 0 | eku, requiredPolicy, stapledOCSPResponse); |
204 | 0 | if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { |
205 | 0 | trustDomain.ResetAccumulatedState(); |
206 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
207 | 0 | EndEntityOrCA::MustBeEndEntity, ku2, |
208 | 0 | eku, requiredPolicy, stapledOCSPResponse); |
209 | 0 | if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { |
210 | 0 | trustDomain.ResetAccumulatedState(); |
211 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
212 | 0 | EndEntityOrCA::MustBeEndEntity, ku3, |
213 | 0 | eku, requiredPolicy, stapledOCSPResponse); |
214 | 0 | if (rv != Success) { |
215 | 0 | rv = Result::ERROR_INADEQUATE_KEY_USAGE; |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | 0 | if (ocspStaplingStatus) { |
220 | 0 | *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus(); |
221 | 0 | } |
222 | 0 | return rv; |
223 | 0 | } |
224 | | |
225 | | void |
226 | | CertVerifier::LoadKnownCTLogs() |
227 | 0 | { |
228 | 0 | mCTVerifier = MakeUnique<MultiLogCTVerifier>(); |
229 | 0 | for (const CTLogInfo& log : kCTLogList) { |
230 | 0 | Input publicKey; |
231 | 0 | Result rv = publicKey.Init( |
232 | 0 | BitwiseCast<const uint8_t*, const char*>(log.key), log.keyLength); |
233 | 0 | if (rv != Success) { |
234 | 0 | MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log"); |
235 | 0 | continue; |
236 | 0 | } |
237 | 0 |
|
238 | 0 | CTLogVerifier logVerifier; |
239 | 0 | const CTLogOperatorInfo& logOperator = |
240 | 0 | kCTLogOperatorList[log.operatorIndex]; |
241 | 0 | rv = logVerifier.Init(publicKey, logOperator.id, log.status, |
242 | 0 | log.disqualificationTime); |
243 | 0 | if (rv != Success) { |
244 | 0 | MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log"); |
245 | 0 | continue; |
246 | 0 | } |
247 | 0 |
|
248 | 0 | rv = mCTVerifier->AddLog(std::move(logVerifier)); |
249 | 0 | if (rv != Success) { |
250 | 0 | MOZ_ASSERT_UNREACHABLE("Failed activating a known CT Log"); |
251 | 0 | continue; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | // TBD: Initialize mCTDiversityPolicy with the CA dependency map |
255 | 0 | // of the known CT logs operators. |
256 | 0 | mCTDiversityPolicy = MakeUnique<CTDiversityPolicy>(); |
257 | 0 | } |
258 | | |
259 | | Result |
260 | | CertVerifier::VerifyCertificateTransparencyPolicy( |
261 | | NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain, |
262 | | Input sctsFromTLS, Time time, |
263 | | /*optional out*/ CertificateTransparencyInfo* ctInfo) |
264 | 0 | { |
265 | 0 | if (ctInfo) { |
266 | 0 | ctInfo->Reset(); |
267 | 0 | } |
268 | 0 | if (mCTMode == CertificateTransparencyMode::Disabled) { |
269 | 0 | return Success; |
270 | 0 | } |
271 | 0 | if (ctInfo) { |
272 | 0 | ctInfo->enabled = true; |
273 | 0 | } |
274 | 0 |
|
275 | 0 | if (!builtChain || CERT_LIST_EMPTY(builtChain)) { |
276 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
277 | 0 | } |
278 | 0 | |
279 | 0 | Input embeddedSCTs = trustDomain.GetSCTListFromCertificate(); |
280 | 0 | if (embeddedSCTs.GetLength() > 0) { |
281 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
282 | 0 | ("Got embedded SCT data of length %zu\n", |
283 | 0 | static_cast<size_t>(embeddedSCTs.GetLength()))); |
284 | 0 | } |
285 | 0 | Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling(); |
286 | 0 | if (sctsFromOCSP.GetLength() > 0) { |
287 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
288 | 0 | ("Got OCSP SCT data of length %zu\n", |
289 | 0 | static_cast<size_t>(sctsFromOCSP.GetLength()))); |
290 | 0 | } |
291 | 0 | if (sctsFromTLS.GetLength() > 0) { |
292 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
293 | 0 | ("Got TLS SCT data of length %zu\n", |
294 | 0 | static_cast<size_t>(sctsFromTLS.GetLength()))); |
295 | 0 | } |
296 | 0 |
|
297 | 0 | CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain); |
298 | 0 | if (!endEntityNode || CERT_LIST_END(endEntityNode, builtChain)) { |
299 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
300 | 0 | } |
301 | 0 | CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode); |
302 | 0 | if (!issuerNode || CERT_LIST_END(issuerNode, builtChain)) { |
303 | 0 | // Issuer certificate is required for SCT verification. |
304 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
305 | 0 | } |
306 | 0 | |
307 | 0 | CERTCertificate* endEntity = endEntityNode->cert; |
308 | 0 | CERTCertificate* issuer = issuerNode->cert; |
309 | 0 | if (!endEntity || !issuer) { |
310 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
311 | 0 | } |
312 | 0 | |
313 | 0 | if (endEntity->subjectName) { |
314 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
315 | 0 | ("Verifying CT Policy compliance of subject %s\n", |
316 | 0 | endEntity->subjectName)); |
317 | 0 | } |
318 | 0 |
|
319 | 0 | Input endEntityDER; |
320 | 0 | Result rv = endEntityDER.Init(endEntity->derCert.data, |
321 | 0 | endEntity->derCert.len); |
322 | 0 | if (rv != Success) { |
323 | 0 | return rv; |
324 | 0 | } |
325 | 0 | |
326 | 0 | Input issuerPublicKeyDER; |
327 | 0 | rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data, |
328 | 0 | issuer->derPublicKey.len); |
329 | 0 | if (rv != Success) { |
330 | 0 | return rv; |
331 | 0 | } |
332 | 0 | |
333 | 0 | CTVerifyResult result; |
334 | 0 | rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER, |
335 | 0 | embeddedSCTs, sctsFromOCSP, sctsFromTLS, time, |
336 | 0 | result); |
337 | 0 | if (rv != Success) { |
338 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
339 | 0 | ("SCT verification failed with fatal error %" PRId32 "\n", |
340 | 0 | static_cast<uint32_t>(rv))); |
341 | 0 | return rv; |
342 | 0 | } |
343 | 0 |
|
344 | 0 | if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) { |
345 | 0 | size_t validCount = 0; |
346 | 0 | size_t unknownLogCount = 0; |
347 | 0 | size_t disqualifiedLogCount = 0; |
348 | 0 | size_t invalidSignatureCount = 0; |
349 | 0 | size_t invalidTimestampCount = 0; |
350 | 0 | for (const VerifiedSCT& verifiedSct : result.verifiedScts) { |
351 | 0 | switch (verifiedSct.status) { |
352 | 0 | case VerifiedSCT::Status::Valid: |
353 | 0 | validCount++; |
354 | 0 | break; |
355 | 0 | case VerifiedSCT::Status::ValidFromDisqualifiedLog: |
356 | 0 | disqualifiedLogCount++; |
357 | 0 | break; |
358 | 0 | case VerifiedSCT::Status::UnknownLog: |
359 | 0 | unknownLogCount++; |
360 | 0 | break; |
361 | 0 | case VerifiedSCT::Status::InvalidSignature: |
362 | 0 | invalidSignatureCount++; |
363 | 0 | break; |
364 | 0 | case VerifiedSCT::Status::InvalidTimestamp: |
365 | 0 | invalidTimestampCount++; |
366 | 0 | break; |
367 | 0 | case VerifiedSCT::Status::None: |
368 | 0 | default: |
369 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected SCT verification status"); |
370 | 0 | } |
371 | 0 | } |
372 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
373 | 0 | ("SCT verification result: " |
374 | 0 | "valid=%zu unknownLog=%zu disqualifiedLog=%zu " |
375 | 0 | "invalidSignature=%zu invalidTimestamp=%zu " |
376 | 0 | "decodingErrors=%zu\n", |
377 | 0 | validCount, unknownLogCount, disqualifiedLogCount, |
378 | 0 | invalidSignatureCount, invalidTimestampCount, |
379 | 0 | result.decodingErrors)); |
380 | 0 | } |
381 | 0 |
|
382 | 0 | PRTime notBefore; |
383 | 0 | PRTime notAfter; |
384 | 0 | if (CERT_GetCertTimes(endEntity, ¬Before, ¬After) != SECSuccess) { |
385 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
386 | 0 | } |
387 | 0 | size_t lifetimeInMonths; |
388 | 0 | rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths); |
389 | 0 | if (rv != Success) { |
390 | 0 | return rv; |
391 | 0 | } |
392 | 0 | |
393 | 0 | CTLogOperatorList allOperators; |
394 | 0 | rv = GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts, |
395 | 0 | allOperators); |
396 | 0 | if (rv != Success) { |
397 | 0 | return rv; |
398 | 0 | } |
399 | 0 | |
400 | 0 | CTLogOperatorList dependentOperators; |
401 | 0 | rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators, |
402 | 0 | dependentOperators); |
403 | 0 | if (rv != Success) { |
404 | 0 | return rv; |
405 | 0 | } |
406 | 0 | |
407 | 0 | CTPolicyEnforcer ctPolicyEnforcer; |
408 | 0 | CTPolicyCompliance ctPolicyCompliance; |
409 | 0 | rv = ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths, |
410 | 0 | dependentOperators, ctPolicyCompliance); |
411 | 0 | if (rv != Success) { |
412 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
413 | 0 | ("CT policy check failed with fatal error %" PRIu32 "\n", |
414 | 0 | static_cast<uint32_t>(rv))); |
415 | 0 | return rv; |
416 | 0 | } |
417 | 0 |
|
418 | 0 | if (ctInfo) { |
419 | 0 | ctInfo->verifyResult = std::move(result); |
420 | 0 | ctInfo->policyCompliance = ctPolicyCompliance; |
421 | 0 | } |
422 | 0 | return Success; |
423 | 0 | } |
424 | | |
425 | | bool |
426 | | CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode) |
427 | 0 | { |
428 | 0 | switch (mSHA1Mode) { |
429 | 0 | case SHA1Mode::Forbidden: |
430 | 0 | return mode != SHA1Mode::Forbidden; |
431 | 0 | case SHA1Mode::ImportedRoot: |
432 | 0 | return mode != SHA1Mode::Forbidden && mode != SHA1Mode::ImportedRoot; |
433 | 0 | case SHA1Mode::ImportedRootOrBefore2016: |
434 | 0 | return mode == SHA1Mode::Allowed; |
435 | 0 | case SHA1Mode::Allowed: |
436 | 0 | return false; |
437 | 0 | // MSVC warns unless we explicitly handle this now-unused option. |
438 | 0 | case SHA1Mode::UsedToBeBefore2016ButNowIsForbidden: |
439 | 0 | default: |
440 | 0 | MOZ_ASSERT(false, "unexpected SHA1Mode type"); |
441 | 0 | return true; |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | static const unsigned int MIN_RSA_BITS = 2048; |
446 | | static const unsigned int MIN_RSA_BITS_WEAK = 1024; |
447 | | |
448 | | Result |
449 | | CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage, |
450 | | Time time, void* pinArg, const char* hostname, |
451 | | /*out*/ UniqueCERTCertList& builtChain, |
452 | | /*optional*/ const Flags flags, |
453 | | /*optional*/ const SECItem* stapledOCSPResponseSECItem, |
454 | | /*optional*/ const SECItem* sctsFromTLSSECItem, |
455 | | /*optional*/ const OriginAttributes& originAttributes, |
456 | | /*optional out*/ SECOidTag* evOidPolicy, |
457 | | /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, |
458 | | /*optional out*/ KeySizeStatus* keySizeStatus, |
459 | | /*optional out*/ SHA1ModeResult* sha1ModeResult, |
460 | | /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, |
461 | | /*optional out*/ CertificateTransparencyInfo* ctInfo) |
462 | 0 | { |
463 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n")); |
464 | 0 |
|
465 | 0 | MOZ_ASSERT(cert); |
466 | 0 | MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV)); |
467 | 0 | MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus); |
468 | 0 | MOZ_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult); |
469 | 0 |
|
470 | 0 | if (NS_FAILED(BlockUntilLoadableRootsLoaded())) { |
471 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
472 | 0 | } |
473 | 0 | if (NS_FAILED(CheckForSmartCardChanges())) { |
474 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
475 | 0 | } |
476 | 0 | |
477 | 0 | if (evOidPolicy) { |
478 | 0 | *evOidPolicy = SEC_OID_UNKNOWN; |
479 | 0 | } |
480 | 0 | if (ocspStaplingStatus) { |
481 | 0 | if (usage != certificateUsageSSLServer) { |
482 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
483 | 0 | } |
484 | 0 | *ocspStaplingStatus = OCSP_STAPLING_NEVER_CHECKED; |
485 | 0 | } |
486 | 0 |
|
487 | 0 | if (keySizeStatus) { |
488 | 0 | if (usage != certificateUsageSSLServer) { |
489 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
490 | 0 | } |
491 | 0 | *keySizeStatus = KeySizeStatus::NeverChecked; |
492 | 0 | } |
493 | 0 |
|
494 | 0 | if (sha1ModeResult) { |
495 | 0 | if (usage != certificateUsageSSLServer) { |
496 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
497 | 0 | } |
498 | 0 | *sha1ModeResult = SHA1ModeResult::NeverChecked; |
499 | 0 | } |
500 | 0 |
|
501 | 0 | if (!cert || |
502 | 0 | (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) { |
503 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
504 | 0 | } |
505 | 0 | |
506 | 0 | Input certDER; |
507 | 0 | Result rv = certDER.Init(cert->derCert.data, cert->derCert.len); |
508 | 0 | if (rv != Success) { |
509 | 0 | return rv; |
510 | 0 | } |
511 | 0 | |
512 | 0 | // We configure the OCSP fetching modes separately for EV and non-EV |
513 | 0 | // verifications. |
514 | 0 | NSSCertDBTrustDomain::OCSPFetching defaultOCSPFetching |
515 | 0 | = (mOCSPDownloadConfig == ocspOff) || |
516 | 0 | (mOCSPDownloadConfig == ocspEVOnly) || |
517 | 0 | (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP |
518 | 0 | : !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail |
519 | 0 | : NSSCertDBTrustDomain::FetchOCSPForDVHardFail; |
520 | 0 |
|
521 | 0 | Input stapledOCSPResponseInput; |
522 | 0 | const Input* stapledOCSPResponse = nullptr; |
523 | 0 | if (stapledOCSPResponseSECItem) { |
524 | 0 | rv = stapledOCSPResponseInput.Init(stapledOCSPResponseSECItem->data, |
525 | 0 | stapledOCSPResponseSECItem->len); |
526 | 0 | if (rv != Success) { |
527 | 0 | // The stapled OCSP response was too big. |
528 | 0 | return Result::ERROR_OCSP_MALFORMED_RESPONSE; |
529 | 0 | } |
530 | 0 | stapledOCSPResponse = &stapledOCSPResponseInput; |
531 | 0 | } |
532 | 0 |
|
533 | 0 | Input sctsFromTLSInput; |
534 | 0 | if (sctsFromTLSSECItem) { |
535 | 0 | rv = sctsFromTLSInput.Init(sctsFromTLSSECItem->data, |
536 | 0 | sctsFromTLSSECItem->len); |
537 | 0 | // Silently discard the error of the extension being too big, |
538 | 0 | // do not fail the verification. |
539 | 0 | MOZ_ASSERT(rv == Success); |
540 | 0 | } |
541 | 0 |
|
542 | 0 | switch (usage) { |
543 | 0 | case certificateUsageSSLClient: { |
544 | 0 | // XXX: We don't really have a trust bit for SSL client authentication so |
545 | 0 | // just use trustEmail as it is the closest alternative. |
546 | 0 | NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching, |
547 | 0 | mOCSPCache, pinArg, |
548 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
549 | 0 | mCertShortLifetimeInDays, |
550 | 0 | pinningDisabled, MIN_RSA_BITS_WEAK, |
551 | 0 | ValidityCheckingMode::CheckingOff, |
552 | 0 | SHA1Mode::Allowed, |
553 | 0 | NetscapeStepUpPolicy::NeverMatch, |
554 | 0 | mDistrustedCAPolicy, originAttributes, |
555 | 0 | builtChain, nullptr, nullptr); |
556 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
557 | 0 | EndEntityOrCA::MustBeEndEntity, |
558 | 0 | KeyUsage::digitalSignature, |
559 | 0 | KeyPurposeId::id_kp_clientAuth, |
560 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
561 | 0 | break; |
562 | 0 | } |
563 | 0 |
|
564 | 0 | case certificateUsageSSLServer: { |
565 | 0 | // TODO: When verifying a certificate in an SSL handshake, we should |
566 | 0 | // restrict the acceptable key usage based on the key exchange method |
567 | 0 | // chosen by the server. |
568 | 0 |
|
569 | 0 | // These configurations are in order of most restrictive to least |
570 | 0 | // restrictive. This enables us to gather telemetry on the expected |
571 | 0 | // results of setting the default policy to a particular configuration. |
572 | 0 | SHA1Mode sha1ModeConfigurations[] = { |
573 | 0 | SHA1Mode::Forbidden, |
574 | 0 | SHA1Mode::ImportedRoot, |
575 | 0 | SHA1Mode::ImportedRootOrBefore2016, |
576 | 0 | SHA1Mode::Allowed, |
577 | 0 | }; |
578 | 0 |
|
579 | 0 | SHA1ModeResult sha1ModeResults[] = { |
580 | 0 | SHA1ModeResult::SucceededWithoutSHA1, |
581 | 0 | SHA1ModeResult::SucceededWithImportedRoot, |
582 | 0 | SHA1ModeResult::SucceededWithImportedRootOrSHA1Before2016, |
583 | 0 | SHA1ModeResult::SucceededWithSHA1, |
584 | 0 | }; |
585 | 0 |
|
586 | 0 | size_t sha1ModeConfigurationsCount = MOZ_ARRAY_LENGTH(sha1ModeConfigurations); |
587 | 0 |
|
588 | 0 | static_assert(MOZ_ARRAY_LENGTH(sha1ModeConfigurations) == |
589 | 0 | MOZ_ARRAY_LENGTH(sha1ModeResults), |
590 | 0 | "digestAlgorithm array lengths differ"); |
591 | 0 |
|
592 | 0 | rv = Result::ERROR_UNKNOWN_ERROR; |
593 | 0 |
|
594 | 0 | // Try to validate for EV first. |
595 | 0 | NSSCertDBTrustDomain::OCSPFetching evOCSPFetching |
596 | 0 | = (mOCSPDownloadConfig == ocspOff) || |
597 | 0 | (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV |
598 | 0 | : NSSCertDBTrustDomain::FetchOCSPForEV; |
599 | 0 |
|
600 | 0 | CertPolicyId evPolicy; |
601 | 0 | SECOidTag evPolicyOidTag; |
602 | 0 | bool foundEVPolicy = GetFirstEVPolicy(*cert, evPolicy, evPolicyOidTag); |
603 | 0 | for (size_t i = 0; |
604 | 0 | i < sha1ModeConfigurationsCount && rv != Success && foundEVPolicy; |
605 | 0 | i++) { |
606 | 0 | // Don't attempt verification if the SHA1 mode set by preferences |
607 | 0 | // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on. |
608 | 0 | // (To put it another way, only attempt verification if the SHA1 mode |
609 | 0 | // option we're on is as restrictive or more restrictive than |
610 | 0 | // mSHA1Mode.) This allows us to gather telemetry information while |
611 | 0 | // still enforcing the mode set by preferences. |
612 | 0 | if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[i])) { |
613 | 0 | continue; |
614 | 0 | } |
615 | 0 | |
616 | 0 | // Because of the try-strict and fallback approach, we have to clear any |
617 | 0 | // previously noted telemetry information |
618 | 0 | if (pinningTelemetryInfo) { |
619 | 0 | pinningTelemetryInfo->Reset(); |
620 | 0 | } |
621 | 0 |
|
622 | 0 | NSSCertDBTrustDomain |
623 | 0 | trustDomain(trustSSL, evOCSPFetching, |
624 | 0 | mOCSPCache, pinArg, |
625 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
626 | 0 | mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS, |
627 | 0 | ValidityCheckingMode::CheckForEV, |
628 | 0 | sha1ModeConfigurations[i], mNetscapeStepUpPolicy, |
629 | 0 | mDistrustedCAPolicy, originAttributes, builtChain, |
630 | 0 | pinningTelemetryInfo, hostname); |
631 | 0 | rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time, |
632 | 0 | KeyUsage::digitalSignature,// (EC)DHE |
633 | 0 | KeyUsage::keyEncipherment, // RSA |
634 | 0 | KeyUsage::keyAgreement, // (EC)DH |
635 | 0 | KeyPurposeId::id_kp_serverAuth, |
636 | 0 | evPolicy, stapledOCSPResponse, |
637 | 0 | ocspStaplingStatus); |
638 | 0 | if (rv == Success && |
639 | 0 | sha1ModeConfigurations[i] == SHA1Mode::ImportedRoot) { |
640 | 0 | bool isBuiltInRoot = false; |
641 | 0 | rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); |
642 | 0 | if (rv != Success) { |
643 | 0 | break; |
644 | 0 | } |
645 | 0 | if (isBuiltInRoot) { |
646 | 0 | rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; |
647 | 0 | } |
648 | 0 | } |
649 | 0 | if (rv == Success) { |
650 | 0 | MOZ_LOG(gCertVerifierLog, LogLevel::Debug, |
651 | 0 | ("cert is EV with status %i\n", static_cast<int>(sha1ModeResults[i]))); |
652 | 0 | if (evOidPolicy) { |
653 | 0 | *evOidPolicy = evPolicyOidTag; |
654 | 0 | } |
655 | 0 | if (sha1ModeResult) { |
656 | 0 | *sha1ModeResult = sha1ModeResults[i]; |
657 | 0 | } |
658 | 0 | rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain, |
659 | 0 | sctsFromTLSInput, time, |
660 | 0 | ctInfo); |
661 | 0 | if (rv != Success) { |
662 | 0 | break; |
663 | 0 | } |
664 | 0 | } |
665 | 0 | } |
666 | 0 | if (rv == Success) { |
667 | 0 | break; |
668 | 0 | } |
669 | 0 | |
670 | 0 | if (flags & FLAG_MUST_BE_EV) { |
671 | 0 | rv = Result::ERROR_POLICY_VALIDATION_FAILED; |
672 | 0 | break; |
673 | 0 | } |
674 | 0 | |
675 | 0 | // Now try non-EV. |
676 | 0 | unsigned int keySizeOptions[] = { |
677 | 0 | MIN_RSA_BITS, |
678 | 0 | MIN_RSA_BITS_WEAK |
679 | 0 | }; |
680 | 0 |
|
681 | 0 | KeySizeStatus keySizeStatuses[] = { |
682 | 0 | KeySizeStatus::LargeMinimumSucceeded, |
683 | 0 | KeySizeStatus::CompatibilityRisk |
684 | 0 | }; |
685 | 0 |
|
686 | 0 | static_assert(MOZ_ARRAY_LENGTH(keySizeOptions) == |
687 | 0 | MOZ_ARRAY_LENGTH(keySizeStatuses), |
688 | 0 | "keySize array lengths differ"); |
689 | 0 |
|
690 | 0 | size_t keySizeOptionsCount = MOZ_ARRAY_LENGTH(keySizeStatuses); |
691 | 0 |
|
692 | 0 | for (size_t i = 0; i < keySizeOptionsCount && rv != Success; i++) { |
693 | 0 | for (size_t j = 0; j < sha1ModeConfigurationsCount && rv != Success; |
694 | 0 | j++) { |
695 | 0 | // Don't attempt verification if the SHA1 mode set by preferences |
696 | 0 | // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on. |
697 | 0 | // (To put it another way, only attempt verification if the SHA1 mode |
698 | 0 | // option we're on is as restrictive or more restrictive than |
699 | 0 | // mSHA1Mode.) This allows us to gather telemetry information while |
700 | 0 | // still enforcing the mode set by preferences. |
701 | 0 | if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[j])) { |
702 | 0 | continue; |
703 | 0 | } |
704 | 0 | |
705 | 0 | // invalidate any telemetry info relating to failed chains |
706 | 0 | if (pinningTelemetryInfo) { |
707 | 0 | pinningTelemetryInfo->Reset(); |
708 | 0 | } |
709 | 0 |
|
710 | 0 | NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching, |
711 | 0 | mOCSPCache, pinArg, |
712 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
713 | 0 | mCertShortLifetimeInDays, |
714 | 0 | mPinningMode, keySizeOptions[i], |
715 | 0 | ValidityCheckingMode::CheckingOff, |
716 | 0 | sha1ModeConfigurations[j], |
717 | 0 | mNetscapeStepUpPolicy, |
718 | 0 | mDistrustedCAPolicy, originAttributes, |
719 | 0 | builtChain, pinningTelemetryInfo, |
720 | 0 | hostname); |
721 | 0 | rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time, |
722 | 0 | KeyUsage::digitalSignature,//(EC)DHE |
723 | 0 | KeyUsage::keyEncipherment,//RSA |
724 | 0 | KeyUsage::keyAgreement,//(EC)DH |
725 | 0 | KeyPurposeId::id_kp_serverAuth, |
726 | 0 | CertPolicyId::anyPolicy, |
727 | 0 | stapledOCSPResponse, |
728 | 0 | ocspStaplingStatus); |
729 | 0 | if (rv != Success && !IsFatalError(rv) && |
730 | 0 | rv != Result::ERROR_REVOKED_CERTIFICATE && |
731 | 0 | trustDomain.GetIsErrorDueToDistrustedCAPolicy()) { |
732 | 0 | // Bug 1444440 - If there are multiple paths, at least one to a CA |
733 | 0 | // distrusted-by-policy, and none of them ending in a trusted root, |
734 | 0 | // then we might show a different error (UNKNOWN_ISSUER) than we |
735 | 0 | // intend, confusing users. |
736 | 0 | rv = Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; |
737 | 0 | } |
738 | 0 | if (rv == Success && |
739 | 0 | sha1ModeConfigurations[j] == SHA1Mode::ImportedRoot) { |
740 | 0 | bool isBuiltInRoot = false; |
741 | 0 | rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); |
742 | 0 | if (rv != Success) { |
743 | 0 | break; |
744 | 0 | } |
745 | 0 | if (isBuiltInRoot) { |
746 | 0 | rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; |
747 | 0 | } |
748 | 0 | } |
749 | 0 | if (rv == Success) { |
750 | 0 | if (keySizeStatus) { |
751 | 0 | *keySizeStatus = keySizeStatuses[i]; |
752 | 0 | } |
753 | 0 | if (sha1ModeResult) { |
754 | 0 | *sha1ModeResult = sha1ModeResults[j]; |
755 | 0 | } |
756 | 0 | rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain, |
757 | 0 | sctsFromTLSInput, time, |
758 | 0 | ctInfo); |
759 | 0 | if (rv != Success) { |
760 | 0 | break; |
761 | 0 | } |
762 | 0 | } |
763 | 0 | } |
764 | 0 | } |
765 | 0 |
|
766 | 0 | if (rv == Success) { |
767 | 0 | break; |
768 | 0 | } |
769 | 0 | |
770 | 0 | if (keySizeStatus) { |
771 | 0 | *keySizeStatus = KeySizeStatus::AlreadyBad; |
772 | 0 | } |
773 | 0 | // The telemetry probe CERT_CHAIN_SHA1_POLICY_STATUS gives us feedback on |
774 | 0 | // the result of setting a specific policy. However, we don't want noise |
775 | 0 | // from users who have manually set the policy to something other than the |
776 | 0 | // default, so we only collect for ImportedRoot (which is the default). |
777 | 0 | if (sha1ModeResult && mSHA1Mode == SHA1Mode::ImportedRoot) { |
778 | 0 | *sha1ModeResult = SHA1ModeResult::Failed; |
779 | 0 | } |
780 | 0 |
|
781 | 0 | break; |
782 | 0 | } |
783 | 0 |
|
784 | 0 | case certificateUsageSSLCA: { |
785 | 0 | NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching, |
786 | 0 | mOCSPCache, pinArg, |
787 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
788 | 0 | mCertShortLifetimeInDays, |
789 | 0 | pinningDisabled, MIN_RSA_BITS_WEAK, |
790 | 0 | ValidityCheckingMode::CheckingOff, |
791 | 0 | SHA1Mode::Allowed, mNetscapeStepUpPolicy, |
792 | 0 | mDistrustedCAPolicy, originAttributes, |
793 | 0 | builtChain, nullptr, nullptr); |
794 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
795 | 0 | EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign, |
796 | 0 | KeyPurposeId::id_kp_serverAuth, |
797 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
798 | 0 | break; |
799 | 0 | } |
800 | 0 |
|
801 | 0 | case certificateUsageEmailSigner: { |
802 | 0 | NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching, |
803 | 0 | mOCSPCache, pinArg, |
804 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
805 | 0 | mCertShortLifetimeInDays, |
806 | 0 | pinningDisabled, MIN_RSA_BITS_WEAK, |
807 | 0 | ValidityCheckingMode::CheckingOff, |
808 | 0 | SHA1Mode::Allowed, |
809 | 0 | NetscapeStepUpPolicy::NeverMatch, |
810 | 0 | mDistrustedCAPolicy, originAttributes, |
811 | 0 | builtChain, nullptr, nullptr); |
812 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
813 | 0 | EndEntityOrCA::MustBeEndEntity, |
814 | 0 | KeyUsage::digitalSignature, |
815 | 0 | KeyPurposeId::id_kp_emailProtection, |
816 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
817 | 0 | if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { |
818 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
819 | 0 | EndEntityOrCA::MustBeEndEntity, |
820 | 0 | KeyUsage::nonRepudiation, |
821 | 0 | KeyPurposeId::id_kp_emailProtection, |
822 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
823 | 0 | } |
824 | 0 | break; |
825 | 0 | } |
826 | 0 |
|
827 | 0 | case certificateUsageEmailRecipient: { |
828 | 0 | // TODO: The higher level S/MIME processing should pass in which key |
829 | 0 | // usage it is trying to verify for, and base its algorithm choices |
830 | 0 | // based on the result of the verification(s). |
831 | 0 | NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching, |
832 | 0 | mOCSPCache, pinArg, |
833 | 0 | mOCSPTimeoutSoft, mOCSPTimeoutHard, |
834 | 0 | mCertShortLifetimeInDays, |
835 | 0 | pinningDisabled, MIN_RSA_BITS_WEAK, |
836 | 0 | ValidityCheckingMode::CheckingOff, |
837 | 0 | SHA1Mode::Allowed, |
838 | 0 | NetscapeStepUpPolicy::NeverMatch, |
839 | 0 | mDistrustedCAPolicy, originAttributes, |
840 | 0 | builtChain, nullptr, nullptr); |
841 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
842 | 0 | EndEntityOrCA::MustBeEndEntity, |
843 | 0 | KeyUsage::keyEncipherment, // RSA |
844 | 0 | KeyPurposeId::id_kp_emailProtection, |
845 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
846 | 0 | if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { |
847 | 0 | rv = BuildCertChain(trustDomain, certDER, time, |
848 | 0 | EndEntityOrCA::MustBeEndEntity, |
849 | 0 | KeyUsage::keyAgreement, // ECDH/DH |
850 | 0 | KeyPurposeId::id_kp_emailProtection, |
851 | 0 | CertPolicyId::anyPolicy, stapledOCSPResponse); |
852 | 0 | } |
853 | 0 | break; |
854 | 0 | } |
855 | 0 |
|
856 | 0 | default: |
857 | 0 | rv = Result::FATAL_ERROR_INVALID_ARGS; |
858 | 0 | } |
859 | 0 |
|
860 | 0 | if (rv != Success) { |
861 | 0 | return rv; |
862 | 0 | } |
863 | 0 | |
864 | 0 | return Success; |
865 | 0 | } |
866 | | |
867 | | |
868 | | static bool |
869 | | CertIsSelfSigned(const UniqueCERTCertificate& cert, void* pinarg) |
870 | 0 | { |
871 | 0 | if (!SECITEM_ItemsAreEqual(&cert->derIssuer, &cert->derSubject)) { |
872 | 0 | return false; |
873 | 0 | } |
874 | 0 | |
875 | 0 | // Check that the certificate is signed with the cert's spki. |
876 | 0 | SECStatus rv = CERT_VerifySignedDataWithPublicKeyInfo( |
877 | 0 | const_cast<CERTSignedData*>(&cert->signatureWrap), |
878 | 0 | const_cast<CERTSubjectPublicKeyInfo*>(&cert->subjectPublicKeyInfo), |
879 | 0 | pinarg); |
880 | 0 | if (rv != SECSuccess) { |
881 | 0 | return false; |
882 | 0 | } |
883 | 0 | |
884 | 0 | return true; |
885 | 0 | } |
886 | | |
887 | | Result |
888 | | CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert, |
889 | | /*optional*/ const SECItem* stapledOCSPResponse, |
890 | | /*optional*/ const SECItem* sctsFromTLS, |
891 | | Time time, |
892 | | /*optional*/ void* pinarg, |
893 | | const nsACString& hostname, |
894 | | /*out*/ UniqueCERTCertList& builtChain, |
895 | | /*optional*/ bool saveIntermediatesInPermanentDatabase, |
896 | | /*optional*/ Flags flags, |
897 | | /*optional*/ const OriginAttributes& originAttributes, |
898 | | /*optional out*/ SECOidTag* evOidPolicy, |
899 | | /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, |
900 | | /*optional out*/ KeySizeStatus* keySizeStatus, |
901 | | /*optional out*/ SHA1ModeResult* sha1ModeResult, |
902 | | /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, |
903 | | /*optional out*/ CertificateTransparencyInfo* ctInfo) |
904 | 0 | { |
905 | 0 | MOZ_ASSERT(peerCert); |
906 | 0 | // XXX: MOZ_ASSERT(pinarg); |
907 | 0 | MOZ_ASSERT(!hostname.IsEmpty()); |
908 | 0 |
|
909 | 0 | if (evOidPolicy) { |
910 | 0 | *evOidPolicy = SEC_OID_UNKNOWN; |
911 | 0 | } |
912 | 0 |
|
913 | 0 | if (hostname.IsEmpty()) { |
914 | 0 | return Result::ERROR_BAD_CERT_DOMAIN; |
915 | 0 | } |
916 | 0 | |
917 | 0 | // CreateCertErrorRunnable assumes that CheckCertHostname is only called |
918 | 0 | // if VerifyCert succeeded. |
919 | 0 | Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time, |
920 | 0 | pinarg, PromiseFlatCString(hostname).get(), builtChain, |
921 | 0 | flags, stapledOCSPResponse, sctsFromTLS, |
922 | 0 | originAttributes, evOidPolicy, ocspStaplingStatus, |
923 | 0 | keySizeStatus, sha1ModeResult, pinningTelemetryInfo, |
924 | 0 | ctInfo); |
925 | 0 | if (rv != Success) { |
926 | 0 | if (rv == Result::ERROR_UNKNOWN_ISSUER && |
927 | 0 | CertIsSelfSigned(peerCert, pinarg)) { |
928 | 0 | // In this case we didn't find any issuer for the certificate and the |
929 | 0 | // certificate is self-signed. |
930 | 0 | return Result::ERROR_SELF_SIGNED_CERT; |
931 | 0 | } |
932 | 0 | if (rv == Result::ERROR_UNKNOWN_ISSUER) { |
933 | 0 | // In this case we didn't get any valid path for the cert. Let's see if |
934 | 0 | // the issuer is the same as the issuer for our canary probe. If yes, this |
935 | 0 | // connection is connecting via a misconfigured proxy. |
936 | 0 | // Note: The MitM canary might not be set. In this case we consider this |
937 | 0 | // an unknown issuer error. |
938 | 0 | nsCOMPtr<nsINSSComponent> component( |
939 | 0 | do_GetService(PSM_COMPONENT_CONTRACTID)); |
940 | 0 | if (!component) { |
941 | 0 | return Result::FATAL_ERROR_LIBRARY_FAILURE; |
942 | 0 | } |
943 | 0 | // IssuerMatchesMitmCanary succeeds if the issuer matches the canary and |
944 | 0 | // the feature is enabled. |
945 | 0 | nsresult rv = component->IssuerMatchesMitmCanary(peerCert->issuerName); |
946 | 0 | if (NS_SUCCEEDED(rv)) { |
947 | 0 | return Result::ERROR_MITM_DETECTED; |
948 | 0 | } |
949 | 0 | } |
950 | 0 | return rv; |
951 | 0 | } |
952 | 0 | |
953 | 0 | Input peerCertInput; |
954 | 0 | rv = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len); |
955 | 0 | if (rv != Success) { |
956 | 0 | return rv; |
957 | 0 | } |
958 | 0 | |
959 | 0 | Input stapledOCSPResponseInput; |
960 | 0 | Input* responseInputPtr = nullptr; |
961 | 0 | if (stapledOCSPResponse) { |
962 | 0 | rv = stapledOCSPResponseInput.Init(stapledOCSPResponse->data, |
963 | 0 | stapledOCSPResponse->len); |
964 | 0 | if (rv != Success) { |
965 | 0 | // The stapled OCSP response was too big. |
966 | 0 | return Result::ERROR_OCSP_MALFORMED_RESPONSE; |
967 | 0 | } |
968 | 0 | responseInputPtr = &stapledOCSPResponseInput; |
969 | 0 | } |
970 | 0 |
|
971 | 0 | if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) { |
972 | 0 | rv = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr); |
973 | 0 | if (rv != Success) { |
974 | 0 | return rv; |
975 | 0 | } |
976 | 0 | } |
977 | 0 | |
978 | 0 | Input hostnameInput; |
979 | 0 | rv = hostnameInput.Init( |
980 | 0 | BitwiseCast<const uint8_t*, const char*>(hostname.BeginReading()), |
981 | 0 | hostname.Length()); |
982 | 0 | if (rv != Success) { |
983 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
984 | 0 | } |
985 | 0 | bool isBuiltInRoot; |
986 | 0 | rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); |
987 | 0 | if (rv != Success) { |
988 | 0 | return rv; |
989 | 0 | } |
990 | 0 | BRNameMatchingPolicy nameMatchingPolicy( |
991 | 0 | isBuiltInRoot ? mNameMatchingMode |
992 | 0 | : BRNameMatchingPolicy::Mode::DoNotEnforce); |
993 | 0 | rv = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy); |
994 | 0 | if (rv != Success) { |
995 | 0 | // Treat malformed name information as a domain mismatch. |
996 | 0 | if (rv == Result::ERROR_BAD_DER) { |
997 | 0 | return Result::ERROR_BAD_CERT_DOMAIN; |
998 | 0 | } |
999 | 0 | |
1000 | 0 | return rv; |
1001 | 0 | } |
1002 | 0 | |
1003 | 0 | if (saveIntermediatesInPermanentDatabase) { |
1004 | 0 | SaveIntermediateCerts(builtChain); |
1005 | 0 | } |
1006 | 0 |
|
1007 | 0 | return Success; |
1008 | 0 | } |
1009 | | |
1010 | | } } // namespace mozilla::psm |