Chromium Code Reviews| Index: net/cert/cert_verify_proc_mac.cc |
| diff --git a/net/cert/cert_verify_proc_mac.cc b/net/cert/cert_verify_proc_mac.cc |
| index 07a49a234b0103594aa4673eb3a7506c0e9eaa53..abbae63d07716ad549191464f695b9128eeba64d 100644 |
| --- a/net/cert/cert_verify_proc_mac.cc |
| +++ b/net/cert/cert_verify_proc_mac.cc |
| @@ -28,6 +28,7 @@ |
| #include "net/cert/cert_verifier.h" |
| #include "net/cert/cert_verify_result.h" |
| #include "net/cert/crl_set.h" |
| +#include "net/cert/test_keychain_search_list_mac.h" |
| #include "net/cert/test_root_certs.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/cert/x509_util_mac.h" |
| @@ -148,21 +149,19 @@ CertStatus CertStatusFromOSStatus(OSStatus status) { |
| } |
| // Creates a series of SecPolicyRefs to be added to a SecTrustRef used to |
| -// validate a certificate for an SSL server. |hostname| contains the name of |
| -// the SSL server that the certificate should be verified against. |flags| is |
| -// a bitwise-OR of VerifyFlags that can further alter how trust is validated, |
| -// such as how revocation is checked. If successful, returns noErr, and |
| -// stores the resultant array of SecPolicyRefs in |policies|. |
| -OSStatus CreateTrustPolicies(const std::string& hostname, |
| - int flags, |
| - ScopedCFTypeRef<CFArrayRef>* policies) { |
| +// validate a certificate for an SSL server. |flags| is a bitwise-OR of |
| +// VerifyFlags that can further alter how trust is validated, such as how |
| +// revocation is checked. If successful, returns noErr, and stores the |
| +// resultant array of SecPolicyRefs in |policies|. |
| +OSStatus CreateTrustPolicies(int flags, ScopedCFTypeRef<CFArrayRef>* policies) { |
| ScopedCFTypeRef<CFMutableArrayRef> local_policies( |
| CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); |
| if (!local_policies) |
| return memFullErr; |
| SecPolicyRef ssl_policy; |
| - OSStatus status = x509_util::CreateSSLServerPolicy(hostname, &ssl_policy); |
| + OSStatus status = |
| + x509_util::CreateSSLServerPolicy(std::string(), &ssl_policy); |
| if (status) |
| return status; |
| CFArrayAppendValue(local_policies, ssl_policy); |
| @@ -377,6 +376,7 @@ bool CheckRevocationWithCRLSet(CFArrayRef chain, CRLSet* crl_set) { |
| int BuildAndEvaluateSecTrustRef(CFArrayRef cert_array, |
| CFArrayRef trust_policies, |
| int flags, |
| + CFArrayRef keychain_search_list, |
| ScopedCFTypeRef<SecTrustRef>* trust_ref, |
| SecTrustResultType* trust_result, |
| ScopedCFTypeRef<CFArrayRef>* verified_chain, |
| @@ -394,6 +394,12 @@ int BuildAndEvaluateSecTrustRef(CFArrayRef cert_array, |
| return NetErrorFromOSStatus(status); |
| } |
| + if (keychain_search_list) { |
| + status = SecTrustSetKeychains(tmp_trust, keychain_search_list); |
| + if (status) |
| + return NetErrorFromOSStatus(status); |
| + } |
| + |
| CSSM_APPLE_TP_ACTION_DATA tp_action_data; |
| memset(&tp_action_data, 0, sizeof(tp_action_data)); |
| tp_action_data.Version = CSSM_APPLE_TP_ACTION_VERSION; |
| @@ -540,18 +546,10 @@ int CertVerifyProcMac::VerifyInternal( |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result) { |
| ScopedCFTypeRef<CFArrayRef> trust_policies; |
| - OSStatus status = CreateTrustPolicies(hostname, flags, &trust_policies); |
| + OSStatus status = CreateTrustPolicies(flags, &trust_policies); |
| if (status) |
| return NetErrorFromOSStatus(status); |
| - // Create and configure a SecTrustRef, which takes our certificate(s) |
| - // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an |
| - // array of certificates, the first of which is the certificate we're |
| - // verifying, and the subsequent (optional) certificates are used for |
| - // chain building. |
| - ScopedCFTypeRef<CFMutableArrayRef> cert_array( |
| - cert->CreateOSCertChainForCert()); |
| - |
| // Serialize all calls that may use the Keychain, to work around various |
| // issues in OS X 10.6+ with multi-threaded access to Security.framework. |
| base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| @@ -562,6 +560,7 @@ int CertVerifyProcMac::VerifyInternal( |
| CSSM_TP_APPLE_EVIDENCE_INFO* chain_info = NULL; |
| bool candidate_untrusted = true; |
| bool candidate_weak = false; |
| + bool completed_chain_revoked = false; |
| // OS X lacks proper path discovery; it will take the input certs and never |
| // backtrack the graph attempting to discover valid paths. |
| @@ -597,6 +596,10 @@ int CertVerifyProcMac::VerifyInternal( |
| // present in the AIA of A is signed using a strong algorithm. Since a |
| // 'strong' chain exists, it would be desirable to prefer this chain. |
| // |
| + // - A user keychain may contain a less desirable intermediate or root. |
| + // OS X gives the user keychains higher priority than the system keychain, |
| + // so it may build a weak chain. |
| + // |
| // Because of this, the code below first attempts to validate the peer's |
| // identity using the supplied chain. If it is not trusted (e.g. the OS only |
| // trusts C, but the version of C signed by D was sent, and D is not trusted), |
| @@ -605,73 +608,129 @@ int CertVerifyProcMac::VerifyInternal( |
| // chain is found, it is used, otherwise, the algorithm continues until only |
| // the peer's certificate remains. |
| // |
| + // If the loop does not find a trusted chain, the loop will be repeated with |
| + // the keychain search order altered to give priority to the System Roots |
| + // keychain. |
| + // |
| // This does cause a performance hit for these users, but only in cases where |
| // OS X is building weaker chains than desired, or when it would otherwise |
| // fail the connection. |
| - while (CFArrayGetCount(cert_array) > 0) { |
| - ScopedCFTypeRef<SecTrustRef> temp_ref; |
| - SecTrustResultType temp_trust_result = kSecTrustResultDeny; |
| - ScopedCFTypeRef<CFArrayRef> temp_chain; |
| - CSSM_TP_APPLE_EVIDENCE_INFO* temp_chain_info = NULL; |
| - |
| - int rv = BuildAndEvaluateSecTrustRef(cert_array, trust_policies, flags, |
| - &temp_ref, &temp_trust_result, |
| - &temp_chain, &temp_chain_info); |
| - if (rv != OK) |
| - return rv; |
| - |
| - bool untrusted = (temp_trust_result != kSecTrustResultUnspecified && |
| - temp_trust_result != kSecTrustResultProceed); |
| - bool weak_chain = false; |
| - if (CFArrayGetCount(temp_chain) == 0) { |
| - // If the chain is empty, it cannot be trusted or have recoverable |
| - // errors. |
| - DCHECK(untrusted); |
| - DCHECK_NE(kSecTrustResultRecoverableTrustFailure, temp_trust_result); |
| - } else { |
| - CertVerifyResult temp_verify_result; |
| - bool leaf_is_weak = false; |
| - GetCertChainInfo(temp_chain, temp_chain_info, &temp_verify_result, |
| - &leaf_is_weak); |
| - weak_chain = !leaf_is_weak && |
| - (temp_verify_result.has_md2 || temp_verify_result.has_md4 || |
| - temp_verify_result.has_md5 || temp_verify_result.has_sha1); |
| + for (bool try_reordered_keychain : {false, true}) { |
|
Ryan Sleevi
2016/08/12 19:50:17
NICE! :)
|
| + ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list; |
| + if (TestKeychainSearchList::HasInstance()) { |
| + CFArrayRef keychain_search_list; |
| + status = TestKeychainSearchList::GetInstance()->CopySearchList( |
| + &keychain_search_list); |
| + if (status) |
| + return NetErrorFromOSStatus(status); |
| + scoped_alternate_keychain_search_list.reset(keychain_search_list); |
| } |
|
Ryan Sleevi
2016/08/12 19:50:17
Could you add some additional documentation here e
mattm
2016/08/16 01:41:34
Done.
|
| - // Set the result to the current chain if: |
| - // - This is the first verification attempt. This ensures that if |
| - // everything is awful (e.g. it may just be an untrusted cert), that |
| - // what is reported is exactly what was sent by the server |
| - // - If the current chain is trusted, and the old chain was not trusted, |
| - // then prefer this chain. This ensures that if there is at least a |
| - // valid path to a trust anchor, it's preferred over reporting an error. |
| - // - If the current chain is trusted, and the old chain is trusted, but |
| - // the old chain contained weak algorithms while the current chain only |
| - // contains strong algorithms, then prefer the current chain over the |
| - // old chain. |
| - // |
| - // Note: If the leaf certificate itself is weak, then the only |
| - // consideration is whether or not there is a trusted chain. That's |
| - // because no amount of path discovery will fix a weak leaf. |
| - if (!trust_ref || (!untrusted && (candidate_untrusted || |
| - (candidate_weak && !weak_chain)))) { |
| - trust_ref = temp_ref; |
| - trust_result = temp_trust_result; |
| - completed_chain = temp_chain; |
| - chain_info = temp_chain_info; |
| - |
| - candidate_untrusted = untrusted; |
| - candidate_weak = weak_chain; |
| + if (try_reordered_keychain) { |
| + if (!scoped_alternate_keychain_search_list) { |
| + CFArrayRef keychain_search_list; |
| + status = SecKeychainCopySearchList(&keychain_search_list); |
| + if (status) |
| + return NetErrorFromOSStatus(status); |
| + scoped_alternate_keychain_search_list.reset(keychain_search_list); |
| + } |
| + CFMutableArrayRef mutable_keychain_search_list = CFArrayCreateMutableCopy( |
| + kCFAllocatorDefault, |
| + CFArrayGetCount(scoped_alternate_keychain_search_list.get()) + 1, |
| + scoped_alternate_keychain_search_list.get()); |
| + if (!mutable_keychain_search_list) |
| + return ERR_OUT_OF_MEMORY; |
| + scoped_alternate_keychain_search_list.reset(mutable_keychain_search_list); |
| + |
| + SecKeychainRef keychain; |
| + // Get a reference to the System Roots keychain. This is a gross hack, |
| + // but the path is known to be valid on OS X 10.9-10.11. |
| + status = SecKeychainOpen( |
| + "/System/Library/Keychains/SystemRootCertificates.keychain", |
| + &keychain); |
|
Ryan Sleevi
2016/08/12 19:50:17
DESIGN: Is it worth adding a unit test for this ca
mattm
2016/08/16 01:41:34
Done.
|
| + if (status) |
| + return NetErrorFromOSStatus(status); |
| + ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain); |
| + |
| + CFArrayInsertValueAtIndex(mutable_keychain_search_list, 0, keychain); |
| + } |
| + |
| + ScopedCFTypeRef<CFMutableArrayRef> cert_array( |
| + cert->CreateOSCertChainForCert()); |
| + |
| + while (CFArrayGetCount(cert_array) > 0) { |
|
Ryan Sleevi
2016/08/12 19:50:17
I'm a little nervous that this is moved far away f
mattm
2016/08/16 01:41:34
Done.
|
| + ScopedCFTypeRef<SecTrustRef> temp_ref; |
| + SecTrustResultType temp_trust_result = kSecTrustResultDeny; |
| + ScopedCFTypeRef<CFArrayRef> temp_chain; |
| + CSSM_TP_APPLE_EVIDENCE_INFO* temp_chain_info = NULL; |
| + |
| + int rv = BuildAndEvaluateSecTrustRef( |
| + cert_array, trust_policies, flags, |
| + scoped_alternate_keychain_search_list.get(), &temp_ref, |
| + &temp_trust_result, &temp_chain, &temp_chain_info); |
| + if (rv != OK) |
| + return rv; |
| + |
| + bool revoked = |
| + (crl_set && !CheckRevocationWithCRLSet(temp_chain, crl_set)); |
|
Ryan Sleevi
2016/08/12 19:50:17
I suspect we should do with some documentation her
mattm
2016/08/16 01:41:34
That would be great since I'm not sure what the nu
Ryan Sleevi
2016/08/17 02:28:12
// Check to see if the path |temp_chain| has been
mattm
2016/08/17 03:50:57
Ah, that. Done.
|
| + bool untrusted = (temp_trust_result != kSecTrustResultUnspecified && |
| + temp_trust_result != kSecTrustResultProceed) || |
| + revoked; |
| + bool weak_chain = false; |
| + if (CFArrayGetCount(temp_chain) == 0) { |
| + // If the chain is empty, it cannot be trusted or have recoverable |
| + // errors. |
| + DCHECK(untrusted); |
| + DCHECK_NE(kSecTrustResultRecoverableTrustFailure, temp_trust_result); |
| + } else { |
| + CertVerifyResult temp_verify_result; |
| + bool leaf_is_weak = false; |
| + GetCertChainInfo(temp_chain, temp_chain_info, &temp_verify_result, |
| + &leaf_is_weak); |
| + weak_chain = |
| + !leaf_is_weak && |
| + (temp_verify_result.has_md2 || temp_verify_result.has_md4 || |
| + temp_verify_result.has_md5 || temp_verify_result.has_sha1); |
| + } |
| + // Set the result to the current chain if: |
| + // - This is the first verification attempt. This ensures that if |
| + // everything is awful (e.g. it may just be an untrusted cert), that |
| + // what is reported is exactly what was sent by the server |
| + // - If the current chain is trusted, and the old chain was not trusted, |
| + // then prefer this chain. This ensures that if there is at least a |
| + // valid path to a trust anchor, it's preferred over reporting an error. |
| + // - If the current chain is trusted, and the old chain is trusted, but |
| + // the old chain contained weak algorithms while the current chain only |
| + // contains strong algorithms, then prefer the current chain over the |
| + // old chain. |
| + // |
| + // Note: If the leaf certificate itself is weak, then the only |
| + // consideration is whether or not there is a trusted chain. That's |
| + // because no amount of path discovery will fix a weak leaf. |
| + if (!trust_ref || (!untrusted && (candidate_untrusted || |
| + (candidate_weak && !weak_chain)))) { |
| + trust_ref = temp_ref; |
| + trust_result = temp_trust_result; |
| + completed_chain = temp_chain; |
| + completed_chain_revoked = revoked; |
| + chain_info = temp_chain_info; |
| + |
| + candidate_untrusted = untrusted; |
| + candidate_weak = weak_chain; |
| + } |
| + // Short-circuit when a current, trusted chain is found. |
| + if (!untrusted && !weak_chain) |
| + break; |
| + CFArrayRemoveValueAtIndex(cert_array, CFArrayGetCount(cert_array) - 1); |
| } |
| // Short-circuit when a current, trusted chain is found. |
| - if (!untrusted && !weak_chain) |
| + if (!candidate_untrusted && !candidate_weak) |
| break; |
| - CFArrayRemoveValueAtIndex(cert_array, CFArrayGetCount(cert_array) - 1); |
| } |
| if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) |
| verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; |
| - if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set)) |
| + if (completed_chain_revoked) |
| verify_result->cert_status |= CERT_STATUS_REVOKED; |
| if (CFArrayGetCount(completed_chain) > 0) { |