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