// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/ssl/client_cert_store_mac.h" #include #include #include #include #include #include #include #include "base/callback.h" #include "base/logging.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_cftyperef.h" #include "base/strings/sys_string_conversions.h" #include "base/synchronization/lock.h" #include "crypto/mac_security_services_lock.h" #include "net/base/host_port_pair.h" #include "net/cert/x509_util.h" #include "net/cert/x509_util_mac.h" using base::ScopedCFTypeRef; namespace net { namespace { // Gets the issuer for a given cert, starting with the cert itself and // including the intermediate and finally root certificates (if any). // This function calls SecTrust but doesn't actually pay attention to the trust // result: it shouldn't be used to determine trust, just to traverse the chain. // Caller is responsible for releasing the value stored into *out_cert_chain. OSStatus CopyCertChain(SecCertificateRef cert_handle, CFArrayRef* out_cert_chain) { DCHECK(cert_handle); DCHECK(out_cert_chain); // Create an SSL policy ref configured for client cert evaluation. SecPolicyRef ssl_policy; OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy); if (result) return result; ScopedCFTypeRef scoped_ssl_policy(ssl_policy); // Create a SecTrustRef. ScopedCFTypeRef input_certs(CFArrayCreate( NULL, const_cast(reinterpret_cast(&cert_handle)), 1, &kCFTypeArrayCallBacks)); SecTrustRef trust_ref = NULL; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustCreateWithCertificates(input_certs, ssl_policy, &trust_ref); } if (result) return result; ScopedCFTypeRef trust(trust_ref); // Evaluate trust, which creates the cert chain. SecTrustResultType status; CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustEvaluate(trust, &status); } if (result) return result; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain); } return result; } // Returns true if |*cert| is issued by an authority in |valid_issuers| // according to Keychain Services, rather than using |cert|'s intermediate // certificates. If it is, |*cert| is updated to point to the completed // certificate bool IsIssuedByInKeychain(const std::vector& valid_issuers, scoped_refptr* cert) { DCHECK(cert); DCHECK(cert->get()); X509Certificate::OSCertHandle cert_handle = (*cert)->os_cert_handle(); CFArrayRef cert_chain = NULL; OSStatus result = CopyCertChain(cert_handle, &cert_chain); if (result) { OSSTATUS_LOG(ERROR, result) << "CopyCertChain error"; return false; } if (!cert_chain) return false; X509Certificate::OSCertHandles intermediates; for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain); i < chain_count; ++i) { SecCertificateRef cert = reinterpret_cast( const_cast(CFArrayGetValueAtIndex(cert_chain, i))); intermediates.push_back(cert); } scoped_refptr new_cert(X509Certificate::CreateFromHandle( cert_handle, intermediates)); CFRelease(cert_chain); // Also frees |intermediates|. if (!new_cert->IsIssuedByEncoded(valid_issuers)) return false; cert->swap(new_cert); return true; } // Examines the certificates in |preferred_cert| and |regular_certs| to find // all certificates that match the client certificate request in |request|, // storing the matching certificates in |selected_certs|. // If |query_keychain| is true, Keychain Services will be queried to construct // full certificate chains. If it is false, only the the certificates and their // intermediates (available via X509Certificate::GetIntermediateCertificates()) // will be considered. void GetClientCertsImpl(const scoped_refptr& preferred_cert, const CertificateList& regular_certs, const SSLCertRequestInfo& request, bool query_keychain, CertificateList* selected_certs) { CertificateList preliminary_list; if (preferred_cert.get()) preliminary_list.push_back(preferred_cert); preliminary_list.insert(preliminary_list.end(), regular_certs.begin(), regular_certs.end()); selected_certs->clear(); for (size_t i = 0; i < preliminary_list.size(); ++i) { scoped_refptr& cert = preliminary_list[i]; if (cert->HasExpired() || !cert->SupportsSSLClientAuth()) continue; // Skip duplicates (a cert may be in multiple keychains). const SHA1HashValue& fingerprint = cert->fingerprint(); size_t pos; for (pos = 0; pos < selected_certs->size(); ++pos) { if ((*selected_certs)[pos]->fingerprint().Equals(fingerprint)) break; } if (pos < selected_certs->size()) continue; // Check if the certificate issuer is allowed by the server. if (request.cert_authorities.empty() || cert->IsIssuedByEncoded(request.cert_authorities) || (query_keychain && IsIssuedByInKeychain(request.cert_authorities, &cert))) { selected_certs->push_back(cert); } } // Preferred cert should appear first in the ui, so exclude it from the // sorting. CertificateList::iterator sort_begin = selected_certs->begin(); CertificateList::iterator sort_end = selected_certs->end(); if (preferred_cert.get() && sort_begin != sort_end && sort_begin->get() == preferred_cert.get()) { ++sort_begin; } sort(sort_begin, sort_end, x509_util::ClientCertSorter()); } } // namespace ClientCertStoreMac::ClientCertStoreMac() {} ClientCertStoreMac::~ClientCertStoreMac() {} void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request, CertificateList* selected_certs, const base::Closure& callback) { std::string server_domain = request.host_and_port.host(); ScopedCFTypeRef preferred_identity; if (!server_domain.empty()) { // See if there's an identity preference for this domain: ScopedCFTypeRef domain_str( base::SysUTF8ToCFStringRef("https://" + server_domain)); SecIdentityRef identity = NULL; // While SecIdentityCopyPreferences appears to take a list of CA issuers // to restrict the identity search to, within Security.framework the // argument is ignored and filtering unimplemented. See // SecIdentity.cpp in libsecurity_keychain, specifically // _SecIdentityCopyPreferenceMatchingName(). { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr) preferred_identity.reset(identity); } } // Now enumerate the identities in the available keychains. scoped_refptr preferred_cert = NULL; CertificateList regular_certs; SecIdentitySearchRef search = NULL; OSStatus err; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search); } if (err) { selected_certs->clear(); callback.Run(); return; } ScopedCFTypeRef scoped_search(search); while (!err) { SecIdentityRef identity = NULL; { base::AutoLock lock(crypto::GetMacSecurityServicesLock()); err = SecIdentitySearchCopyNext(search, &identity); } if (err) break; ScopedCFTypeRef scoped_identity(identity); SecCertificateRef cert_handle; err = SecIdentityCopyCertificate(identity, &cert_handle); if (err != noErr) continue; ScopedCFTypeRef scoped_cert_handle(cert_handle); scoped_refptr cert( X509Certificate::CreateFromHandle(cert_handle, X509Certificate::OSCertHandles())); if (preferred_identity && CFEqual(preferred_identity, identity)) { // Only one certificate should match. DCHECK(!preferred_cert.get()); preferred_cert = cert; } else { regular_certs.push_back(cert); } } if (err != errSecItemNotFound) { OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error"; selected_certs->clear(); callback.Run(); return; } GetClientCertsImpl(preferred_cert, regular_certs, request, true, selected_certs); callback.Run(); } bool ClientCertStoreMac::SelectClientCertsForTesting( const CertificateList& input_certs, const SSLCertRequestInfo& request, CertificateList* selected_certs) { GetClientCertsImpl(NULL, input_certs, request, false, selected_certs); return true; } bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting( const scoped_refptr& preferred_cert, const CertificateList& regular_certs, const SSLCertRequestInfo& request, CertificateList* selected_certs) { GetClientCertsImpl( preferred_cert, regular_certs, request, false, selected_certs); return true; } } // namespace net