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