Home | History | Annotate | Download | only in ssl
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      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/ssl/client_cert_store_mac.h"
      6 
      7 #include <CommonCrypto/CommonDigest.h>
      8 #include <CoreFoundation/CFArray.h>
      9 #include <CoreServices/CoreServices.h>
     10 #include <Security/SecBase.h>
     11 #include <Security/Security.h>
     12 
     13 #include <algorithm>
     14 #include <string>
     15 
     16 #include "base/callback.h"
     17 #include "base/logging.h"
     18 #include "base/mac/mac_logging.h"
     19 #include "base/mac/scoped_cftyperef.h"
     20 #include "base/strings/sys_string_conversions.h"
     21 #include "base/synchronization/lock.h"
     22 #include "crypto/mac_security_services_lock.h"
     23 #include "net/base/host_port_pair.h"
     24 #include "net/cert/x509_util.h"
     25 #include "net/cert/x509_util_mac.h"
     26 
     27 using base::ScopedCFTypeRef;
     28 
     29 namespace net {
     30 
     31 namespace {
     32 
     33 // Gets the issuer for a given cert, starting with the cert itself and
     34 // including the intermediate and finally root certificates (if any).
     35 // This function calls SecTrust but doesn't actually pay attention to the trust
     36 // result: it shouldn't be used to determine trust, just to traverse the chain.
     37 // Caller is responsible for releasing the value stored into *out_cert_chain.
     38 OSStatus CopyCertChain(SecCertificateRef cert_handle,
     39                        CFArrayRef* out_cert_chain) {
     40   DCHECK(cert_handle);
     41   DCHECK(out_cert_chain);
     42 
     43   // Create an SSL policy ref configured for client cert evaluation.
     44   SecPolicyRef ssl_policy;
     45   OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy);
     46   if (result)
     47     return result;
     48   ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
     49 
     50   // Create a SecTrustRef.
     51   ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate(
     52       NULL, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)),
     53       1, &kCFTypeArrayCallBacks));
     54   SecTrustRef trust_ref = NULL;
     55   {
     56     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
     57     result = SecTrustCreateWithCertificates(input_certs, ssl_policy,
     58                                             &trust_ref);
     59   }
     60   if (result)
     61     return result;
     62   ScopedCFTypeRef<SecTrustRef> trust(trust_ref);
     63 
     64   // Evaluate trust, which creates the cert chain.
     65   SecTrustResultType status;
     66   CSSM_TP_APPLE_EVIDENCE_INFO* status_chain;
     67   {
     68     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
     69     result = SecTrustEvaluate(trust, &status);
     70   }
     71   if (result)
     72     return result;
     73   {
     74     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
     75     result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain);
     76   }
     77   return result;
     78 }
     79 
     80 // Returns true if |*cert| is issued by an authority in |valid_issuers|
     81 // according to Keychain Services, rather than using |cert|'s intermediate
     82 // certificates. If it is, |*cert| is updated to point to the completed
     83 // certificate
     84 bool IsIssuedByInKeychain(const std::vector<std::string>& valid_issuers,
     85                           scoped_refptr<X509Certificate>* cert) {
     86   DCHECK(cert);
     87   DCHECK(cert->get());
     88 
     89   X509Certificate::OSCertHandle cert_handle = (*cert)->os_cert_handle();
     90   CFArrayRef cert_chain = NULL;
     91   OSStatus result = CopyCertChain(cert_handle, &cert_chain);
     92   if (result) {
     93     OSSTATUS_LOG(ERROR, result) << "CopyCertChain error";
     94     return false;
     95   }
     96 
     97   if (!cert_chain)
     98     return false;
     99 
    100   X509Certificate::OSCertHandles intermediates;
    101   for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain);
    102        i < chain_count; ++i) {
    103     SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
    104         const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
    105     intermediates.push_back(cert);
    106   }
    107 
    108   scoped_refptr<X509Certificate> new_cert(X509Certificate::CreateFromHandle(
    109       cert_handle, intermediates));
    110   CFRelease(cert_chain);  // Also frees |intermediates|.
    111 
    112   if (!new_cert->IsIssuedByEncoded(valid_issuers))
    113     return false;
    114 
    115   cert->swap(new_cert);
    116   return true;
    117 }
    118 
    119 // Examines the certificates in |preferred_cert| and |regular_certs| to find
    120 // all certificates that match the client certificate request in |request|,
    121 // storing the matching certificates in |selected_certs|.
    122 // If |query_keychain| is true, Keychain Services will be queried to construct
    123 // full certificate chains. If it is false, only the the certificates and their
    124 // intermediates (available via X509Certificate::GetIntermediateCertificates())
    125 // will be considered.
    126 void GetClientCertsImpl(const scoped_refptr<X509Certificate>& preferred_cert,
    127                         const CertificateList& regular_certs,
    128                         const SSLCertRequestInfo& request,
    129                         bool query_keychain,
    130                         CertificateList* selected_certs) {
    131   CertificateList preliminary_list;
    132   if (preferred_cert.get())
    133     preliminary_list.push_back(preferred_cert);
    134   preliminary_list.insert(preliminary_list.end(), regular_certs.begin(),
    135                           regular_certs.end());
    136 
    137   selected_certs->clear();
    138   for (size_t i = 0; i < preliminary_list.size(); ++i) {
    139     scoped_refptr<X509Certificate>& cert = preliminary_list[i];
    140     if (cert->HasExpired() || !cert->SupportsSSLClientAuth())
    141       continue;
    142 
    143     // Skip duplicates (a cert may be in multiple keychains).
    144     const SHA1HashValue& fingerprint = cert->fingerprint();
    145     size_t pos;
    146     for (pos = 0; pos < selected_certs->size(); ++pos) {
    147       if ((*selected_certs)[pos]->fingerprint().Equals(fingerprint))
    148         break;
    149     }
    150     if (pos < selected_certs->size())
    151       continue;
    152 
    153     // Check if the certificate issuer is allowed by the server.
    154     if (request.cert_authorities.empty() ||
    155         cert->IsIssuedByEncoded(request.cert_authorities) ||
    156         (query_keychain &&
    157          IsIssuedByInKeychain(request.cert_authorities, &cert))) {
    158       selected_certs->push_back(cert);
    159     }
    160   }
    161 
    162   // Preferred cert should appear first in the ui, so exclude it from the
    163   // sorting.
    164   CertificateList::iterator sort_begin = selected_certs->begin();
    165   CertificateList::iterator sort_end = selected_certs->end();
    166   if (preferred_cert.get() && sort_begin != sort_end &&
    167       sort_begin->get() == preferred_cert.get()) {
    168     ++sort_begin;
    169   }
    170   sort(sort_begin, sort_end, x509_util::ClientCertSorter());
    171 }
    172 
    173 }  // namespace
    174 
    175 ClientCertStoreMac::ClientCertStoreMac() {}
    176 
    177 ClientCertStoreMac::~ClientCertStoreMac() {}
    178 
    179 void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request,
    180                                          CertificateList* selected_certs,
    181                                          const base::Closure& callback) {
    182   std::string server_domain = request.host_and_port.host();
    183 
    184   ScopedCFTypeRef<SecIdentityRef> preferred_identity;
    185   if (!server_domain.empty()) {
    186     // See if there's an identity preference for this domain:
    187     ScopedCFTypeRef<CFStringRef> domain_str(
    188         base::SysUTF8ToCFStringRef("https://" + server_domain));
    189     SecIdentityRef identity = NULL;
    190     // While SecIdentityCopyPreferences appears to take a list of CA issuers
    191     // to restrict the identity search to, within Security.framework the
    192     // argument is ignored and filtering unimplemented. See
    193     // SecIdentity.cpp in libsecurity_keychain, specifically
    194     // _SecIdentityCopyPreferenceMatchingName().
    195     {
    196       base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    197       if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr)
    198         preferred_identity.reset(identity);
    199     }
    200   }
    201 
    202   // Now enumerate the identities in the available keychains.
    203   scoped_refptr<X509Certificate> preferred_cert = NULL;
    204   CertificateList regular_certs;
    205 
    206   SecIdentitySearchRef search = NULL;
    207   OSStatus err;
    208   {
    209     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    210     err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
    211   }
    212   if (err) {
    213     selected_certs->clear();
    214     callback.Run();
    215     return;
    216   }
    217   ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
    218   while (!err) {
    219     SecIdentityRef identity = NULL;
    220     {
    221       base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    222       err = SecIdentitySearchCopyNext(search, &identity);
    223     }
    224     if (err)
    225       break;
    226     ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity);
    227 
    228     SecCertificateRef cert_handle;
    229     err = SecIdentityCopyCertificate(identity, &cert_handle);
    230     if (err != noErr)
    231       continue;
    232     ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle);
    233 
    234     scoped_refptr<X509Certificate> cert(
    235         X509Certificate::CreateFromHandle(cert_handle,
    236                                           X509Certificate::OSCertHandles()));
    237 
    238     if (preferred_identity && CFEqual(preferred_identity, identity)) {
    239       // Only one certificate should match.
    240       DCHECK(!preferred_cert.get());
    241       preferred_cert = cert;
    242     } else {
    243       regular_certs.push_back(cert);
    244     }
    245   }
    246 
    247   if (err != errSecItemNotFound) {
    248     OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
    249     selected_certs->clear();
    250     callback.Run();
    251     return;
    252   }
    253 
    254   GetClientCertsImpl(preferred_cert, regular_certs, request, true,
    255                      selected_certs);
    256   callback.Run();
    257 }
    258 
    259 bool ClientCertStoreMac::SelectClientCertsForTesting(
    260     const CertificateList& input_certs,
    261     const SSLCertRequestInfo& request,
    262     CertificateList* selected_certs) {
    263   GetClientCertsImpl(NULL, input_certs, request, false, selected_certs);
    264   return true;
    265 }
    266 
    267 bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
    268     const scoped_refptr<X509Certificate>& preferred_cert,
    269     const CertificateList& regular_certs,
    270     const SSLCertRequestInfo& request,
    271     CertificateList* selected_certs) {
    272   GetClientCertsImpl(
    273       preferred_cert, regular_certs, request, false, selected_certs);
    274   return true;
    275 }
    276 
    277 }  // namespace net
    278