1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/cert/cert_verify_proc_ios.h"
6
7 #include <CommonCrypto/CommonDigest.h>
8
9 #include <string_view>
10
11 #include "base/apple/foundation_util.h"
12 #include "base/apple/osstatus_logging.h"
13 #include "base/apple/scoped_cftyperef.h"
14 #include "base/logging.h"
15 #include "base/notreached.h"
16 #include "crypto/sha2.h"
17 #include "net/base/net_errors.h"
18 #include "net/cert/asn1_util.h"
19 #include "net/cert/cert_verify_result.h"
20 #include "net/cert/crl_set.h"
21 #include "net/cert/ct_serialization.h"
22 #include "net/cert/known_roots.h"
23 #include "net/cert/test_root_certs.h"
24 #include "net/cert/x509_certificate.h"
25 #include "net/cert/x509_util.h"
26 #include "net/cert/x509_util_apple.h"
27
28 using base::apple::ScopedCFTypeRef;
29
30 namespace net {
31
32 namespace {
33
NetErrorFromOSStatus(OSStatus status)34 int NetErrorFromOSStatus(OSStatus status) {
35 switch (status) {
36 case noErr:
37 return OK;
38 case errSecNotAvailable:
39 return ERR_NOT_IMPLEMENTED;
40 case errSecAuthFailed:
41 return ERR_ACCESS_DENIED;
42 default:
43 return ERR_FAILED;
44 }
45 }
46
47 // Maps errors from OSStatus codes to CertStatus flags.
48 //
49 // The selection of errors is based off of Apple's SecPolicyChecks.list, and
50 // any unknown errors are mapped to CERT_STATUS_INVALID for safety.
CertStatusFromOSStatus(OSStatus status)51 CertStatus CertStatusFromOSStatus(OSStatus status) {
52 switch (status) {
53 case errSecHostNameMismatch:
54 return CERT_STATUS_COMMON_NAME_INVALID;
55
56 case errSecCertificateExpired:
57 case errSecCertificateNotValidYet:
58 return CERT_STATUS_DATE_INVALID;
59
60 case errSecCreateChainFailed:
61 case errSecNotTrusted:
62 // errSecVerifyActionFailed is used when CT is required
63 // and not present. The OS rejected this chain, and so mapping
64 // to CERT_STATUS_CT_COMPLIANCE_FAILED (which is informational,
65 // as policy enforcement is not handled in the CertVerifier)
66 // would cause this error to be ignored and mapped to
67 // CERT_STATUS_INVALID. Rather than do that, mark it simply as
68 // "untrusted". The CT_COMPLIANCE_FAILED bit is not set, since
69 // it's not necessarily a compliance failure with the embedder's
70 // CT policy. It's a bit of a hack, but hopefully temporary.
71 // errSecNotTrusted is somewhat similar. It applies for
72 // situations where a root isn't trusted or an intermediate
73 // isn't trusted, when a key is restricted, or when the calling
74 // application requested CT enforcement (which CertVerifier
75 // should never being doing).
76 case errSecVerifyActionFailed:
77 return CERT_STATUS_AUTHORITY_INVALID;
78
79 case errSecInvalidIDLinkage:
80 case errSecNoBasicConstraintsCA:
81 case errSecInvalidSubjectName:
82 case errSecInvalidExtendedKeyUsage:
83 case errSecInvalidKeyUsageForPolicy:
84 case errSecMissingRequiredExtension:
85 case errSecNoBasicConstraints:
86 case errSecPathLengthConstraintExceeded:
87 case errSecUnknownCertExtension:
88 case errSecUnknownCriticalExtensionFlag:
89 // errSecCertificatePolicyNotAllowed and errSecCertificateNameNotAllowed
90 // are used for certificates that violate the constraints imposed upon the
91 // issuer. Nominally this could be mapped to CERT_STATUS_AUTHORITY_INVALID,
92 // except the trustd behaviour is to treat this as a fatal
93 // (non-recoverable) error. That behavior is preserved here for consistency
94 // with Safari.
95 case errSecCertificatePolicyNotAllowed:
96 case errSecCertificateNameNotAllowed:
97 return CERT_STATUS_INVALID;
98
99 // Unfortunately, iOS's handling of weak digest algorithms and key sizes
100 // doesn't map exactly to Chrome's. errSecInvalidDigestAlgorithm and
101 // errSecUnsupportedKeySize may indicate errors that iOS considers fatal
102 // (too weak to process at all) or recoverable (too weak according to
103 // compliance policies).
104 // Further, because SecTrustEvaluateWithError only returns a single error
105 // code, a fatal error may have occurred elsewhere in the chain, so the
106 // overall result can't be used to distinguish individual certificate
107 // errors. For this complicated reason, the weak key and weak digest cases
108 // also map to CERT_STATUS_INVALID for safety.
109 case errSecInvalidDigestAlgorithm:
110 return CERT_STATUS_WEAK_SIGNATURE_ALGORITHM | CERT_STATUS_INVALID;
111 case errSecUnsupportedKeySize:
112 return CERT_STATUS_WEAK_KEY | CERT_STATUS_INVALID;
113
114 case errSecCertificateRevoked:
115 return CERT_STATUS_REVOKED;
116
117 case errSecIncompleteCertRevocationCheck:
118 return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
119
120 case errSecCertificateValidityPeriodTooLong:
121 return CERT_STATUS_VALIDITY_TOO_LONG;
122
123 case errSecInvalidCertificateRef:
124 case errSecInvalidName:
125 case errSecInvalidPolicyIdentifiers:
126 return CERT_STATUS_INVALID;
127
128 // This function should only be called on errors, so should always return a
129 // CertStatus code that is considered an error. If the input is unexpectedly
130 // errSecSuccess, return CERT_STATUS_INVALID for safety.
131 case errSecSuccess:
132 default:
133 OSSTATUS_LOG(WARNING, status)
134 << "Unknown error mapped to CERT_STATUS_INVALID";
135 return CERT_STATUS_INVALID;
136 }
137 }
138
139 // Creates a series of SecPolicyRefs to be added to a SecTrustRef used to
140 // validate a certificate for an SSL server. |hostname| contains the name of
141 // the SSL server that the certificate should be verified against. If
142 // successful, returns noErr, and stores the resultant array of SecPolicyRefs
143 // in |policies|.
CreateTrustPolicies(ScopedCFTypeRef<CFArrayRef> * policies)144 OSStatus CreateTrustPolicies(ScopedCFTypeRef<CFArrayRef>* policies) {
145 ScopedCFTypeRef<CFMutableArrayRef> local_policies(
146 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
147 if (!local_policies)
148 return errSecAllocate;
149
150 base::apple::ScopedCFTypeRef<SecPolicyRef> ssl_policy(
151 SecPolicyCreateBasicX509());
152 CFArrayAppendValue(local_policies.get(), ssl_policy.get());
153 ssl_policy.reset(SecPolicyCreateSSL(/*server=*/true, /*hostname=*/nullptr));
154 CFArrayAppendValue(local_policies.get(), ssl_policy.get());
155
156 *policies = std::move(local_policies);
157 return noErr;
158 }
159
160 // Builds and evaluates a SecTrustRef for the certificate chain contained
161 // in |cert_array|, using the verification policies in |trust_policies|. On
162 // success, returns OK, and updates |trust_ref|, |is_trusted|, and
163 // |trust_error|. On failure, no output parameters are modified.
164 //
165 // Note: An OK return does not mean that |cert_array| is trusted, merely that
166 // verification was performed successfully.
BuildAndEvaluateSecTrustRef(CFArrayRef cert_array,CFArrayRef trust_policies,CFDataRef ocsp_response_ref,CFArrayRef sct_array_ref,ScopedCFTypeRef<SecTrustRef> * trust_ref,ScopedCFTypeRef<CFArrayRef> * verified_chain,bool * is_trusted,ScopedCFTypeRef<CFErrorRef> * trust_error)167 int BuildAndEvaluateSecTrustRef(CFArrayRef cert_array,
168 CFArrayRef trust_policies,
169 CFDataRef ocsp_response_ref,
170 CFArrayRef sct_array_ref,
171 ScopedCFTypeRef<SecTrustRef>* trust_ref,
172 ScopedCFTypeRef<CFArrayRef>* verified_chain,
173 bool* is_trusted,
174 ScopedCFTypeRef<CFErrorRef>* trust_error) {
175 ScopedCFTypeRef<SecTrustRef> tmp_trust;
176 OSStatus status = SecTrustCreateWithCertificates(cert_array, trust_policies,
177 tmp_trust.InitializeInto());
178 if (status)
179 return NetErrorFromOSStatus(status);
180
181 if (TestRootCerts::HasInstance()) {
182 status = TestRootCerts::GetInstance()->FixupSecTrustRef(tmp_trust.get());
183 if (status)
184 return NetErrorFromOSStatus(status);
185 }
186
187 if (ocsp_response_ref) {
188 status = SecTrustSetOCSPResponse(tmp_trust.get(), ocsp_response_ref);
189 if (status)
190 return NetErrorFromOSStatus(status);
191 }
192
193 if (sct_array_ref) {
194 if (__builtin_available(iOS 12.1.1, *)) {
195 status = SecTrustSetSignedCertificateTimestamps(tmp_trust.get(),
196 sct_array_ref);
197 if (status)
198 return NetErrorFromOSStatus(status);
199 }
200 }
201
202 ScopedCFTypeRef<CFErrorRef> tmp_error;
203 bool tmp_is_trusted = false;
204 if (__builtin_available(iOS 12.0, *)) {
205 tmp_is_trusted =
206 SecTrustEvaluateWithError(tmp_trust.get(), tmp_error.InitializeInto());
207 } else {
208 #if !defined(__IPHONE_12_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0
209 SecTrustResultType tmp_trust_result;
210 status = SecTrustEvaluate(tmp_trust.get(), &tmp_trust_result);
211 if (status)
212 return NetErrorFromOSStatus(status);
213 switch (tmp_trust_result) {
214 case kSecTrustResultUnspecified:
215 case kSecTrustResultProceed:
216 tmp_is_trusted = true;
217 break;
218 case kSecTrustResultInvalid:
219 return ERR_FAILED;
220 default:
221 tmp_is_trusted = false;
222 }
223 #endif
224 }
225
226 trust_ref->swap(tmp_trust);
227 trust_error->swap(tmp_error);
228 *verified_chain = x509_util::CertificateChainFromSecTrust(trust_ref->get());
229 *is_trusted = tmp_is_trusted;
230 return OK;
231 }
232
GetCertChainInfo(CFArrayRef cert_chain,CertVerifyResult * verify_result)233 void GetCertChainInfo(CFArrayRef cert_chain, CertVerifyResult* verify_result) {
234 DCHECK_LT(0, CFArrayGetCount(cert_chain));
235
236 base::apple::ScopedCFTypeRef<SecCertificateRef> verified_cert;
237 std::vector<base::apple::ScopedCFTypeRef<SecCertificateRef>> verified_chain;
238 for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) {
239 SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>(
240 const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
241 if (i == 0) {
242 verified_cert.reset(chain_cert, base::scoped_policy::RETAIN);
243 } else {
244 verified_chain.emplace_back(chain_cert, base::scoped_policy::RETAIN);
245 }
246
247 base::apple::ScopedCFTypeRef<CFDataRef> der_data(
248 SecCertificateCopyData(chain_cert));
249 if (!der_data) {
250 verify_result->cert_status |= CERT_STATUS_INVALID;
251 return;
252 }
253
254 std::string_view spki_bytes;
255 if (!asn1::ExtractSPKIFromDERCert(
256 std::string_view(
257 reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
258 CFDataGetLength(der_data.get())),
259 &spki_bytes)) {
260 verify_result->cert_status |= CERT_STATUS_INVALID;
261 return;
262 }
263
264 HashValue sha256(HASH_VALUE_SHA256);
265 CC_SHA256(spki_bytes.data(), spki_bytes.size(), sha256.data());
266 verify_result->public_key_hashes.push_back(sha256);
267 }
268 if (!verified_cert.get()) {
269 NOTREACHED();
270 verify_result->cert_status |= CERT_STATUS_INVALID;
271 return;
272 }
273
274 scoped_refptr<X509Certificate> verified_cert_with_chain =
275 x509_util::CreateX509CertificateFromSecCertificate(verified_cert,
276 verified_chain);
277 if (verified_cert_with_chain)
278 verify_result->verified_cert = std::move(verified_cert_with_chain);
279 else
280 verify_result->cert_status |= CERT_STATUS_INVALID;
281 }
282
283 } // namespace
284
CertVerifyProcIOS(scoped_refptr<CRLSet> crl_set)285 CertVerifyProcIOS::CertVerifyProcIOS(scoped_refptr<CRLSet> crl_set)
286 : CertVerifyProc(std::move(crl_set)) {}
287
288 // static
GetCertFailureStatusFromError(CFErrorRef error)289 CertStatus CertVerifyProcIOS::GetCertFailureStatusFromError(CFErrorRef error) {
290 if (!error)
291 return CERT_STATUS_INVALID;
292
293 base::apple::ScopedCFTypeRef<CFStringRef> error_domain(
294 CFErrorGetDomain(error));
295 CFIndex error_code = CFErrorGetCode(error);
296
297 if (error_domain.get() != kCFErrorDomainOSStatus) {
298 LOG(WARNING) << "Unhandled error domain: " << error;
299 return CERT_STATUS_INVALID;
300 }
301
302 return CertStatusFromOSStatus(error_code);
303 }
304
305 #if !defined(__IPHONE_12_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0
306 // The iOS APIs don't expose an API-stable set of reasons for certificate
307 // validation failures. However, internally, the reason is tracked, and it's
308 // converted to user-facing localized strings.
309 //
310 // In the absence of a consistent API, convert the English strings to their
311 // localized counterpart, and then compare that with the error properties. If
312 // they're equal, it's a strong sign that this was the cause for the error.
313 // While this will break if/when iOS changes the contents of these strings,
314 // it's sufficient enough for now.
315 //
316 // TODO(rsleevi): https://crbug.com/601915 - Use a less brittle solution when
317 // possible.
318 // static
GetCertFailureStatusFromTrust(SecTrustRef trust)319 CertStatus CertVerifyProcIOS::GetCertFailureStatusFromTrust(SecTrustRef trust) {
320 CertStatus reason = 0;
321
322 base::apple::ScopedCFTypeRef<CFArrayRef> properties(
323 SecTrustCopyProperties(trust));
324 if (!properties)
325 return CERT_STATUS_INVALID;
326
327 const CFIndex properties_length = CFArrayGetCount(properties.get());
328 if (properties_length == 0)
329 return CERT_STATUS_INVALID;
330
331 CFBundleRef bundle =
332 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Security"));
333 CFStringRef date_string =
334 CFSTR("One or more certificates have expired or are not valid yet.");
335 ScopedCFTypeRef<CFStringRef> date_error(CFBundleCopyLocalizedString(
336 bundle, date_string, date_string, CFSTR("SecCertificate")));
337 CFStringRef trust_string = CFSTR("Root certificate is not trusted.");
338 ScopedCFTypeRef<CFStringRef> trust_error(CFBundleCopyLocalizedString(
339 bundle, trust_string, trust_string, CFSTR("SecCertificate")));
340 CFStringRef weak_string =
341 CFSTR("One or more certificates is using a weak key size.");
342 ScopedCFTypeRef<CFStringRef> weak_error(CFBundleCopyLocalizedString(
343 bundle, weak_string, weak_string, CFSTR("SecCertificate")));
344 CFStringRef hostname_mismatch_string = CFSTR("Hostname mismatch.");
345 ScopedCFTypeRef<CFStringRef> hostname_mismatch_error(
346 CFBundleCopyLocalizedString(bundle, hostname_mismatch_string,
347 hostname_mismatch_string,
348 CFSTR("SecCertificate")));
349 CFStringRef root_certificate_string =
350 CFSTR("Unable to build chain to root certificate.");
351 ScopedCFTypeRef<CFStringRef> root_certificate_error(
352 CFBundleCopyLocalizedString(bundle, root_certificate_string,
353 root_certificate_string,
354 CFSTR("SecCertificate")));
355 CFStringRef policy_requirements_not_met_string =
356 CFSTR("Policy requirements not met.");
357 ScopedCFTypeRef<CFStringRef> policy_requirements_not_met_error(
358 CFBundleCopyLocalizedString(bundle, policy_requirements_not_met_string,
359 policy_requirements_not_met_string,
360 CFSTR("SecCertificate")));
361
362 for (CFIndex i = 0; i < properties_length; ++i) {
363 CFDictionaryRef dict = reinterpret_cast<CFDictionaryRef>(
364 const_cast<void*>(CFArrayGetValueAtIndex(properties.get(), i)));
365 CFStringRef error = reinterpret_cast<CFStringRef>(
366 const_cast<void*>(CFDictionaryGetValue(dict, CFSTR("value"))));
367
368 if (CFEqual(error, date_error.get())) {
369 reason |= CERT_STATUS_DATE_INVALID;
370 } else if (CFEqual(error, trust_error.get())) {
371 reason |= CERT_STATUS_AUTHORITY_INVALID;
372 } else if (CFEqual(error, weak_error.get())) {
373 reason |= CERT_STATUS_WEAK_KEY;
374 } else if (CFEqual(error, hostname_mismatch_error.get())) {
375 reason |= CERT_STATUS_COMMON_NAME_INVALID;
376 } else if (CFEqual(error, policy_requirements_not_met_error.get())) {
377 reason |= CERT_STATUS_INVALID | CERT_STATUS_AUTHORITY_INVALID;
378 } else if (CFEqual(error, root_certificate_error.get())) {
379 reason |= CERT_STATUS_AUTHORITY_INVALID;
380 } else {
381 LOG(ERROR) << "Unrecognized error: " << error;
382 reason |= CERT_STATUS_INVALID;
383 }
384 }
385
386 return reason;
387 }
388 #endif // !defined(__IPHONE_12_0) || __IPHONE_OS_VERSION_MIN_REQUIRED <
389 // __IPHONE_12_0
390
391 CertVerifyProcIOS::~CertVerifyProcIOS() = default;
392
VerifyInternal(X509Certificate * cert,const std::string & hostname,const std::string & ocsp_response,const std::string & sct_list,int flags,CertVerifyResult * verify_result,const NetLogWithSource & net_log,std::optional<base::Time> time_now)393 int CertVerifyProcIOS::VerifyInternal(X509Certificate* cert,
394 const std::string& hostname,
395 const std::string& ocsp_response,
396 const std::string& sct_list,
397 int flags,
398 CertVerifyResult* verify_result,
399 const NetLogWithSource& net_log,
400 std::optional<base::Time> time_now) {
401 ScopedCFTypeRef<CFArrayRef> trust_policies;
402 OSStatus status = CreateTrustPolicies(&trust_policies);
403 if (status)
404 return NetErrorFromOSStatus(status);
405
406 ScopedCFTypeRef<CFMutableArrayRef> cert_array(
407 x509_util::CreateSecCertificateArrayForX509Certificate(
408 cert, x509_util::InvalidIntermediateBehavior::kIgnore));
409 if (!cert_array) {
410 verify_result->cert_status |= CERT_STATUS_INVALID;
411 return ERR_CERT_INVALID;
412 }
413
414 ScopedCFTypeRef<CFDataRef> ocsp_response_ref;
415 if (!ocsp_response.empty()) {
416 ocsp_response_ref.reset(
417 CFDataCreate(kCFAllocatorDefault,
418 reinterpret_cast<const UInt8*>(ocsp_response.data()),
419 base::checked_cast<CFIndex>(ocsp_response.size())));
420 if (!ocsp_response_ref)
421 return ERR_OUT_OF_MEMORY;
422 }
423
424 ScopedCFTypeRef<CFMutableArrayRef> sct_array_ref;
425 if (!sct_list.empty()) {
426 if (__builtin_available(iOS 12.1.1, *)) {
427 std::vector<std::string_view> decoded_sct_list;
428 if (ct::DecodeSCTList(sct_list, &decoded_sct_list)) {
429 sct_array_ref.reset(CFArrayCreateMutable(kCFAllocatorDefault,
430 decoded_sct_list.size(),
431 &kCFTypeArrayCallBacks));
432 if (!sct_array_ref)
433 return ERR_OUT_OF_MEMORY;
434 for (const auto& sct : decoded_sct_list) {
435 ScopedCFTypeRef<CFDataRef> sct_ref(CFDataCreate(
436 kCFAllocatorDefault, reinterpret_cast<const UInt8*>(sct.data()),
437 base::checked_cast<CFIndex>(sct.size())));
438 if (!sct_ref)
439 return ERR_OUT_OF_MEMORY;
440 CFArrayAppendValue(sct_array_ref.get(), sct_ref.get());
441 }
442 }
443 }
444 }
445
446 ScopedCFTypeRef<SecTrustRef> trust_ref;
447 bool is_trusted = false;
448 ScopedCFTypeRef<CFArrayRef> final_chain;
449 ScopedCFTypeRef<CFErrorRef> trust_error;
450
451 int err = BuildAndEvaluateSecTrustRef(
452 cert_array.get(), trust_policies.get(), ocsp_response_ref.get(),
453 sct_array_ref.get(), &trust_ref, &final_chain, &is_trusted, &trust_error);
454 if (err)
455 return err;
456
457 if (CFArrayGetCount(final_chain.get()) == 0) {
458 return ERR_FAILED;
459 }
460
461 // TODO(rsleevi): Support CRLSet revocation.
462 if (!is_trusted) {
463 if (__builtin_available(iOS 12.0, *)) {
464 verify_result->cert_status |=
465 GetCertFailureStatusFromError(trust_error.get());
466 } else {
467 #if !defined(__IPHONE_12_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0
468 SecTrustResultType trust_result = kSecTrustResultInvalid;
469 status = SecTrustGetTrustResult(trust_ref.get(), &trust_result);
470 if (status)
471 return NetErrorFromOSStatus(status);
472 switch (trust_result) {
473 case kSecTrustResultUnspecified:
474 case kSecTrustResultProceed:
475 NOTREACHED();
476 break;
477 case kSecTrustResultDeny:
478 verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
479 break;
480 default:
481 verify_result->cert_status |=
482 GetCertFailureStatusFromTrust(trust_ref.get());
483 }
484 #else
485 // It should be impossible to reach this code, but if somehow it is
486 // reached it would allow any certificate as valid since no errors would
487 // be added to cert_status. Therefore, add a CHECK as a fail safe.
488 CHECK(false);
489 #endif
490 }
491 }
492 GetCertChainInfo(final_chain.get(), verify_result);
493
494 // While iOS lacks the ability to distinguish system-trusted versus
495 // user-installed roots, the set of roots that are expected to comply with
496 // the Baseline Requirements can be determined by
497 // GetNetTrustAnchorHistogramForSPKI() - a non-zero value means that it is
498 // known as a publicly trusted, and therefore subject to the BRs, cert.
499 for (auto it = verify_result->public_key_hashes.rbegin();
500 it != verify_result->public_key_hashes.rend() &&
501 !verify_result->is_issued_by_known_root;
502 ++it) {
503 verify_result->is_issued_by_known_root =
504 GetNetTrustAnchorHistogramIdForSPKI(*it) != 0;
505 }
506
507 if (IsCertStatusError(verify_result->cert_status))
508 return MapCertStatusToNetError(verify_result->cert_status);
509
510 if (TestRootCerts::HasInstance() &&
511 !verify_result->verified_cert->intermediate_buffers().empty() &&
512 TestRootCerts::GetInstance()->IsKnownRoot(x509_util::CryptoBufferAsSpan(
513 verify_result->verified_cert->intermediate_buffers().back().get()))) {
514 verify_result->is_issued_by_known_root = true;
515 }
516
517 LogNameNormalizationMetrics(".IOS", verify_result->verified_cert.get(),
518 verify_result->is_issued_by_known_root);
519
520 return OK;
521 }
522
523 } // namespace net
524