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
27using base::ScopedCFTypeRef;
28
29namespace net {
30
31namespace {
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.
38OSStatus 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
84bool 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.
126void 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
175ClientCertStoreMac::ClientCertStoreMac() {}
176
177ClientCertStoreMac::~ClientCertStoreMac() {}
178
179void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request,
180                                         CertificateList* selected_certs,
181                                         const base::Closure& callback) {
182  std::string server_domain =
183      HostPortPair::FromString(request.host_and_port).host();
184
185  ScopedCFTypeRef<SecIdentityRef> preferred_identity;
186  if (!server_domain.empty()) {
187    // See if there's an identity preference for this domain:
188    ScopedCFTypeRef<CFStringRef> domain_str(
189        base::SysUTF8ToCFStringRef("https://" + server_domain));
190    SecIdentityRef identity = NULL;
191    // While SecIdentityCopyPreferences appears to take a list of CA issuers
192    // to restrict the identity search to, within Security.framework the
193    // argument is ignored and filtering unimplemented. See
194    // SecIdentity.cpp in libsecurity_keychain, specifically
195    // _SecIdentityCopyPreferenceMatchingName().
196    {
197      base::AutoLock lock(crypto::GetMacSecurityServicesLock());
198      if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr)
199        preferred_identity.reset(identity);
200    }
201  }
202
203  // Now enumerate the identities in the available keychains.
204  scoped_refptr<X509Certificate> preferred_cert = NULL;
205  CertificateList regular_certs;
206
207  SecIdentitySearchRef search = NULL;
208  OSStatus err;
209  {
210    base::AutoLock lock(crypto::GetMacSecurityServicesLock());
211    err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
212  }
213  if (err) {
214    selected_certs->clear();
215    callback.Run();
216    return;
217  }
218  ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
219  while (!err) {
220    SecIdentityRef identity = NULL;
221    {
222      base::AutoLock lock(crypto::GetMacSecurityServicesLock());
223      err = SecIdentitySearchCopyNext(search, &identity);
224    }
225    if (err)
226      break;
227    ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity);
228
229    SecCertificateRef cert_handle;
230    err = SecIdentityCopyCertificate(identity, &cert_handle);
231    if (err != noErr)
232      continue;
233    ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle);
234
235    scoped_refptr<X509Certificate> cert(
236        X509Certificate::CreateFromHandle(cert_handle,
237                                          X509Certificate::OSCertHandles()));
238
239    if (preferred_identity && CFEqual(preferred_identity, identity)) {
240      // Only one certificate should match.
241      DCHECK(!preferred_cert.get());
242      preferred_cert = cert;
243    } else {
244      regular_certs.push_back(cert);
245    }
246  }
247
248  if (err != errSecItemNotFound) {
249    OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
250    selected_certs->clear();
251    callback.Run();
252    return;
253  }
254
255  GetClientCertsImpl(preferred_cert, regular_certs, request, true,
256                     selected_certs);
257  callback.Run();
258}
259
260bool ClientCertStoreMac::SelectClientCertsForTesting(
261    const CertificateList& input_certs,
262    const SSLCertRequestInfo& request,
263    CertificateList* selected_certs) {
264  GetClientCertsImpl(NULL, input_certs, request, false, selected_certs);
265  return true;
266}
267
268bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
269    const scoped_refptr<X509Certificate>& preferred_cert,
270    const CertificateList& regular_certs,
271    const SSLCertRequestInfo& request,
272    CertificateList* selected_certs) {
273  GetClientCertsImpl(
274      preferred_cert, regular_certs, request, false, selected_certs);
275  return true;
276}
277
278}  // namespace net
279