| // 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 <CommonCrypto/CommonDigest.h> |
| #include <CoreFoundation/CFArray.h> |
| #include <CoreServices/CoreServices.h> |
| #include <Security/SecBase.h> |
| #include <Security/Security.h> |
| |
| #include <algorithm> |
| #include <string> |
| |
| #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<SecPolicyRef> scoped_ssl_policy(ssl_policy); |
| |
| // Create a SecTrustRef. |
| ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate( |
| NULL, const_cast<const void**>(reinterpret_cast<void**>(&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<SecTrustRef> 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<std::string>& valid_issuers, |
| scoped_refptr<X509Certificate>* 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<SecCertificateRef>( |
| const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i))); |
| intermediates.push_back(cert); |
| } |
| |
| scoped_refptr<X509Certificate> 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<X509Certificate>& 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<X509Certificate>& 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<SecIdentityRef> preferred_identity; |
| if (!server_domain.empty()) { |
| // See if there's an identity preference for this domain: |
| ScopedCFTypeRef<CFStringRef> 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<X509Certificate> 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<SecIdentitySearchRef> scoped_search(search); |
| while (!err) { |
| SecIdentityRef identity = NULL; |
| { |
| base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| err = SecIdentitySearchCopyNext(search, &identity); |
| } |
| if (err) |
| break; |
| ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity); |
| |
| SecCertificateRef cert_handle; |
| err = SecIdentityCopyCertificate(identity, &cert_handle); |
| if (err != noErr) |
| continue; |
| ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle); |
| |
| scoped_refptr<X509Certificate> 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<X509Certificate>& 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 |