• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
CopyCertChain(SecCertificateRef cert_handle,CFArrayRef * 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
IsIssuedByInKeychain(const std::vector<std::string> & valid_issuers,scoped_refptr<X509Certificate> * cert)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.
GetClientCertsImpl(const scoped_refptr<X509Certificate> & preferred_cert,const CertificateList & regular_certs,const SSLCertRequestInfo & request,bool query_keychain,CertificateList * selected_certs)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 
ClientCertStoreMac()175 ClientCertStoreMac::ClientCertStoreMac() {}
176 
~ClientCertStoreMac()177 ClientCertStoreMac::~ClientCertStoreMac() {}
178 
GetClientCerts(const SSLCertRequestInfo & request,CertificateList * selected_certs,const base::Closure & callback)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 
SelectClientCertsForTesting(const CertificateList & input_certs,const SSLCertRequestInfo & request,CertificateList * selected_certs)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 
SelectClientCertsGivenPreferredForTesting(const scoped_refptr<X509Certificate> & preferred_cert,const CertificateList & regular_certs,const SSLCertRequestInfo & request,CertificateList * selected_certs)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