Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Unified Diff: net/cert/cert_verify_proc_mac.cc

Issue 2101303005: CertVerifyProcMac: Add Keychain re-ordering hack, check CRLsets in path pruning loop. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/BUILD.gn ('k') | net/cert/cert_verify_proc_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..9d827fdd55779c59fbabd1cfdf0d805dcc2b96f2 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,163 @@ 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}) {
+ ScopedCFTypeRef<CFArrayRef> scoped_alternate_keychain_search_list;
+ if (TestKeychainSearchList::HasInstance()) {
+ // Unit tests need to be able to hermetically simulate situations where a
+ // user has an undesirable certificate in a per-user keychain.
+ // Adding/Removing a Keychain using SecKeychainCreate/SecKeychainDelete
+ // has global side effects, which would break other tests and processes
+ // running on the same machine, so instead tests may load pre-created
+ // keychains using SecKeychainOpen and then inject them through
+ // TestKeychainSearchList.
+ 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);
}
- // 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 a TestKeychainSearchList is present, it will have already set
+ // |scoped_alternate_keychain_search_list|, which will be used as the
+ // basis for reordering the keychain. Otherwise, get the current keychain
+ // search list and use that.
+ 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. The System Roots
+ // keychain is not normally present in the keychain search list, but is
+ // implicitly checked after the keychains in the search list. By
+ // including it directly, force it to be checked first. 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);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
+
+ CFArrayInsertValueAtIndex(mutable_keychain_search_list, 0, keychain);
+ }
+
+ ScopedCFTypeRef<CFMutableArrayRef> cert_array(
+ cert->CreateOSCertChainForCert());
+
+ // Beginning with the certificate chain as supplied by the server, attempt
+ // to verify the chain. If a failure is encountered, trim a certificate
+ // from the end (so long as one remains) and retry, in the hope of forcing
+ // OS X to find a better path.
+ 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,
+ scoped_alternate_keychain_search_list.get(), &temp_ref,
+ &temp_trust_result, &temp_chain, &temp_chain_info);
+ if (rv != OK)
+ return rv;
+
+ // Check to see if the path |temp_chain| has been revoked. This is less
+ // than ideal to perform after path building, rather than during, because
+ // there may be multiple paths to trust anchors, and only some of them
+ // are revoked. Ideally, CRLSets would be part of path building, which
+ // they are when using NSS (Linux) or CryptoAPI (Windows).
+ //
+ // The CRLSet checking is performed inside the loop in the hope that if a
+ // path is revoked, it's an older path, and the only reason it was built
+ // is because the server forced it (by supplying an older or less
+ // desirable intermediate) or because the user had installed a
+ // certificate in their Keychain forcing this path. However, this means
+ // its still possible for a CRLSet block of an intermediate to prevent
+ // access, even when there is a 'good' chain. To fully remedy this, a
+ // solution might be to have CRLSets contain enough knowledge about what
+ // the 'desired' path might be, but for the time being, the
+ // implementation is kept as 'simple' as it can be.
+ bool revoked =
+ (crl_set && !CheckRevocationWithCRLSet(temp_chain, crl_set));
+ 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) {
« no previous file with comments | « net/BUILD.gn ('k') | net/cert/cert_verify_proc_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698