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 <functional>
15 #include <memory>
16 #include <string>
17 #include <utility>
18 #include <vector>
19 
20 #include "base/bind.h"
21 #include "base/callback.h"
22 #include "base/callback_helpers.h"
23 #include "base/logging.h"
24 #include "base/mac/mac_logging.h"
25 #include "base/mac/scoped_cftyperef.h"
26 #include "base/stl_util.h"
27 #include "base/strings/sys_string_conversions.h"
28 #include "base/synchronization/lock.h"
29 #include "base/task_runner_util.h"
30 #include "crypto/mac_security_services_lock.h"
31 #include "net/base/host_port_pair.h"
32 #include "net/cert/x509_util.h"
33 #include "net/cert/x509_util_ios_and_mac.h"
34 #include "net/cert/x509_util_mac.h"
35 #include "net/ssl/client_cert_identity_mac.h"
36 #include "net/ssl/ssl_platform_key_util.h"
37 
38 using base::ScopedCFTypeRef;
39 
40 namespace net {
41 
42 // CSSM functions are deprecated as of OSX 10.7, but have no replacement.
43 // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1
44 #pragma clang diagnostic push
45 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
46 
47 namespace {
48 
49 // Gets the issuer for a given cert, starting with the cert itself and
50 // including the intermediate and finally root certificates (if any).
51 // This function calls SecTrust but doesn't actually pay attention to the trust
52 // result: it shouldn't be used to determine trust, just to traverse the chain.
53 // Caller is responsible for releasing the value stored into *out_cert_chain.
CopyCertChain(SecCertificateRef cert_handle,CFArrayRef * out_cert_chain)54 OSStatus CopyCertChain(SecCertificateRef cert_handle,
55                        CFArrayRef* out_cert_chain) {
56   DCHECK(cert_handle);
57   DCHECK(out_cert_chain);
58 
59   // Create an SSL policy ref configured for client cert evaluation.
60   SecPolicyRef ssl_policy;
61   OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy);
62   if (result)
63     return result;
64   ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
65 
66   // Create a SecTrustRef.
67   ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate(
68       NULL, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)),
69       1, &kCFTypeArrayCallBacks));
70   SecTrustRef trust_ref = NULL;
71   {
72     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
73     result = SecTrustCreateWithCertificates(input_certs, ssl_policy,
74                                             &trust_ref);
75   }
76   if (result)
77     return result;
78   ScopedCFTypeRef<SecTrustRef> trust(trust_ref);
79 
80   // Evaluate trust, which creates the cert chain.
81   SecTrustResultType status;
82   CSSM_TP_APPLE_EVIDENCE_INFO* status_chain;
83   {
84     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
85     result = SecTrustEvaluate(trust, &status);
86   }
87   if (result)
88     return result;
89   {
90     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
91     result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain);
92   }
93   return result;
94 }
95 
96 // Returns true if |*identity| is issued by an authority in |valid_issuers|
97 // according to Keychain Services, rather than using |identity|'s intermediate
98 // certificates. If it is, |*identity| is updated to include the intermediates.
IsIssuedByInKeychain(const std::vector<std::string> & valid_issuers,ClientCertIdentity * identity)99 bool IsIssuedByInKeychain(const std::vector<std::string>& valid_issuers,
100                           ClientCertIdentity* identity) {
101   DCHECK(identity);
102   DCHECK(identity->sec_identity_ref());
103 
104   ScopedCFTypeRef<SecCertificateRef> os_cert;
105   int err = SecIdentityCopyCertificate(identity->sec_identity_ref(),
106                                        os_cert.InitializeInto());
107   if (err != noErr)
108     return false;
109   CFArrayRef cert_chain = NULL;
110   OSStatus result = CopyCertChain(os_cert.get(), &cert_chain);
111   if (result) {
112     OSSTATUS_LOG(ERROR, result) << "CopyCertChain error";
113     return false;
114   }
115 
116   if (!cert_chain)
117     return false;
118 
119   std::vector<SecCertificateRef> intermediates;
120   for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain);
121        i < chain_count; ++i) {
122     SecCertificateRef sec_cert = reinterpret_cast<SecCertificateRef>(
123         const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
124     intermediates.push_back(sec_cert);
125   }
126 
127   // Allow UTF-8 inside PrintableStrings in client certificates. See
128   // crbug.com/770323.
129   X509Certificate::UnsafeCreateOptions options;
130   options.printable_string_is_utf8 = true;
131   scoped_refptr<X509Certificate> new_cert(
132       x509_util::CreateX509CertificateFromSecCertificate(
133           os_cert.get(), intermediates, options));
134   CFRelease(cert_chain);  // Also frees |intermediates|.
135 
136   if (!new_cert || !new_cert->IsIssuedByEncoded(valid_issuers))
137     return false;
138 
139   std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediate_buffers;
140   intermediate_buffers.reserve(new_cert->intermediate_buffers().size());
141   for (const auto& intermediate : new_cert->intermediate_buffers()) {
142     intermediate_buffers.push_back(bssl::UpRef(intermediate.get()));
143   }
144   identity->SetIntermediates(std::move(intermediate_buffers));
145   return true;
146 }
147 
148 // Returns true if |purpose| is listed as allowed in |usage|. This
149 // function also considers the "Any" purpose. If the attribute is
150 // present and empty, we return false.
ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage * usage,const CSSM_OID * purpose)151 bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage,
152                             const CSSM_OID* purpose) {
153   for (unsigned p = 0; p < usage->numPurposes; ++p) {
154     if (x509_util::CSSMOIDEqual(&usage->purposes[p], purpose))
155       return true;
156     if (x509_util::CSSMOIDEqual(&usage->purposes[p],
157                                 &CSSMOID_ExtendedKeyUsageAny))
158       return true;
159   }
160   return false;
161 }
162 
163 // Does |cert|'s usage allow SSL client authentication?
SupportsSSLClientAuth(SecCertificateRef cert)164 bool SupportsSSLClientAuth(SecCertificateRef cert) {
165   x509_util::CSSMCachedCertificate cached_cert;
166   OSStatus status = cached_cert.Init(cert);
167   if (status)
168     return false;
169 
170   // RFC5280 says to take the intersection of the two extensions.
171   //
172   // Our underlying crypto libraries don't expose
173   // ClientCertificateType, so for now we will not support fixed
174   // Diffie-Hellman mechanisms. For rsa_sign, we need the
175   // digitalSignature bit.
176   //
177   // In particular, if a key has the nonRepudiation bit and not the
178   // digitalSignature one, we will not offer it to the user.
179   x509_util::CSSMFieldValue key_usage;
180   status = cached_cert.GetField(&CSSMOID_KeyUsage, &key_usage);
181   if (status == CSSM_OK && key_usage.field()) {
182     const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>();
183     const CE_KeyUsage* key_usage_value =
184         reinterpret_cast<const CE_KeyUsage*>(ext->value.parsedValue);
185     if (!((*key_usage_value) & CE_KU_DigitalSignature))
186       return false;
187   }
188 
189   status = cached_cert.GetField(&CSSMOID_ExtendedKeyUsage, &key_usage);
190   if (status == CSSM_OK && key_usage.field()) {
191     const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>();
192     const CE_ExtendedKeyUsage* ext_key_usage =
193         reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue);
194     if (!ExtendedKeyUsageAllows(ext_key_usage, &CSSMOID_ClientAuth))
195       return false;
196   }
197   return true;
198 }
199 
200 // Examines the certificates in |preferred_identity| and |regular_identities| to
201 // find all certificates that match the client certificate request in |request|,
202 // storing the matching certificates in |selected_identities|.
203 // If |query_keychain| is true, Keychain Services will be queried to construct
204 // full certificate chains. If it is false, only the the certificates and their
205 // intermediates (available via X509Certificate::intermediate_buffers())
206 // will be considered.
GetClientCertsImpl(std::unique_ptr<ClientCertIdentity> preferred_identity,ClientCertIdentityList regular_identities,const SSLCertRequestInfo & request,bool query_keychain,ClientCertIdentityList * selected_identities)207 void GetClientCertsImpl(std::unique_ptr<ClientCertIdentity> preferred_identity,
208                         ClientCertIdentityList regular_identities,
209                         const SSLCertRequestInfo& request,
210                         bool query_keychain,
211                         ClientCertIdentityList* selected_identities) {
212   scoped_refptr<X509Certificate> preferred_cert_orig;
213   ClientCertIdentityList preliminary_list(std::move(regular_identities));
214   if (preferred_identity) {
215     preferred_cert_orig = preferred_identity->certificate();
216     preliminary_list.insert(preliminary_list.begin(),
217                             std::move(preferred_identity));
218   }
219 
220   selected_identities->clear();
221   for (size_t i = 0; i < preliminary_list.size(); ++i) {
222     std::unique_ptr<ClientCertIdentity>& cert = preliminary_list[i];
223     if (cert->certificate()->HasExpired())
224       continue;
225 
226     // Skip duplicates (a cert may be in multiple keychains).
227     auto cert_iter = std::find_if(
228         selected_identities->begin(), selected_identities->end(),
229         [&cert](
230             const std::unique_ptr<ClientCertIdentity>& other_cert_identity) {
231           return x509_util::CryptoBufferEqual(
232               cert->certificate()->cert_buffer(),
233               other_cert_identity->certificate()->cert_buffer());
234         });
235     if (cert_iter != selected_identities->end())
236       continue;
237 
238     // Check if the certificate issuer is allowed by the server.
239     if (request.cert_authorities.empty() ||
240         cert->certificate()->IsIssuedByEncoded(request.cert_authorities) ||
241         (query_keychain &&
242          IsIssuedByInKeychain(request.cert_authorities, cert.get()))) {
243       selected_identities->push_back(std::move(cert));
244     }
245   }
246 
247   // Preferred cert should appear first in the ui, so exclude it from the
248   // sorting.  Compare the cert_buffer since the X509Certificate object may
249   // have changed if intermediates were added.
250   ClientCertIdentityList::iterator sort_begin = selected_identities->begin();
251   ClientCertIdentityList::iterator sort_end = selected_identities->end();
252   if (preferred_cert_orig && sort_begin != sort_end &&
253       x509_util::CryptoBufferEqual(
254           sort_begin->get()->certificate()->cert_buffer(),
255           preferred_cert_orig->cert_buffer())) {
256     ++sort_begin;
257   }
258   sort(sort_begin, sort_end, ClientCertIdentitySorter());
259 }
260 
261 // Given a |sec_identity|, identifies its corresponding certificate, and either
262 // adds it to |regular_identities| or assigns it to |preferred_identity|, if the
263 // |sec_identity| matches the |preferred_sec_identity|.
AddIdentity(ScopedCFTypeRef<SecIdentityRef> sec_identity,SecIdentityRef preferred_sec_identity,ClientCertIdentityList * regular_identities,std::unique_ptr<ClientCertIdentity> * preferred_identity)264 void AddIdentity(ScopedCFTypeRef<SecIdentityRef> sec_identity,
265                  SecIdentityRef preferred_sec_identity,
266                  ClientCertIdentityList* regular_identities,
267                  std::unique_ptr<ClientCertIdentity>* preferred_identity) {
268   OSStatus err;
269   ScopedCFTypeRef<SecCertificateRef> cert_handle;
270   err = SecIdentityCopyCertificate(sec_identity.get(),
271                                    cert_handle.InitializeInto());
272   if (err != noErr)
273     return;
274 
275   if (!SupportsSSLClientAuth(cert_handle.get()))
276     return;
277 
278   // Allow UTF-8 inside PrintableStrings in client certificates. See
279   // crbug.com/770323.
280   X509Certificate::UnsafeCreateOptions options;
281   options.printable_string_is_utf8 = true;
282   scoped_refptr<X509Certificate> cert(
283       x509_util::CreateX509CertificateFromSecCertificate(cert_handle.get(), {},
284                                                          options));
285   if (!cert)
286     return;
287 
288   if (preferred_sec_identity &&
289       CFEqual(preferred_sec_identity, sec_identity.get())) {
290     *preferred_identity = std::make_unique<ClientCertIdentityMac>(
291         std::move(cert), std::move(sec_identity));
292   } else {
293     regular_identities->push_back(std::make_unique<ClientCertIdentityMac>(
294         std::move(cert), std::move(sec_identity)));
295   }
296 }
297 
GetClientCertsOnBackgroundThread(const SSLCertRequestInfo & request)298 ClientCertIdentityList GetClientCertsOnBackgroundThread(
299     const SSLCertRequestInfo& request) {
300   std::string server_domain = request.host_and_port.host();
301 
302   ScopedCFTypeRef<SecIdentityRef> preferred_sec_identity;
303   if (!server_domain.empty()) {
304     // See if there's an identity preference for this domain:
305     ScopedCFTypeRef<CFStringRef> domain_str(
306         base::SysUTF8ToCFStringRef("https://" + server_domain));
307     SecIdentityRef sec_identity = NULL;
308     // While SecIdentityCopyPreferences appears to take a list of CA issuers
309     // to restrict the identity search to, within Security.framework the
310     // argument is ignored and filtering unimplemented. See
311     // SecIdentity.cpp in libsecurity_keychain, specifically
312     // _SecIdentityCopyPreferenceMatchingName().
313     {
314       base::AutoLock lock(crypto::GetMacSecurityServicesLock());
315       if (SecIdentityCopyPreference(domain_str, 0, NULL, &sec_identity) ==
316           noErr)
317         preferred_sec_identity.reset(sec_identity);
318     }
319   }
320 
321   // Now enumerate the identities in the available keychains.
322   std::unique_ptr<ClientCertIdentity> preferred_identity;
323   ClientCertIdentityList regular_identities;
324 
325   SecIdentitySearchRef search = NULL;
326   OSStatus err;
327   {
328     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
329     err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
330   }
331   if (err)
332     return ClientCertIdentityList();
333   ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
334   while (!err) {
335     ScopedCFTypeRef<SecIdentityRef> sec_identity;
336     {
337       base::AutoLock lock(crypto::GetMacSecurityServicesLock());
338       err = SecIdentitySearchCopyNext(search, sec_identity.InitializeInto());
339     }
340     if (err)
341       break;
342     AddIdentity(std::move(sec_identity), preferred_sec_identity.get(),
343                 &regular_identities, &preferred_identity);
344   }
345 
346   if (err != errSecItemNotFound) {
347     OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
348     return ClientCertIdentityList();
349   }
350 
351   // macOS provides two ways to search for identities. SecIdentitySearchCreate()
352   // is deprecated, as it relies on CSSM_KEYUSE_SIGN (part of the deprecated
353   // CDSM/CSSA implementation), but is necessary to return some certificates
354   // that would otherwise not be returned by SecItemCopyMatching(), which is the
355   // non-deprecated way. However, SecIdentitySearchCreate() will not return all
356   // items, particularly smart-card based identities, so it's necessary to call
357   // both functions.
358   static const void* kKeys[] = {
359       kSecClass, kSecMatchLimit, kSecReturnRef, kSecAttrCanSign,
360   };
361   static const void* kValues[] = {
362       kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue, kCFBooleanTrue,
363   };
364   ScopedCFTypeRef<CFDictionaryRef> query(CFDictionaryCreate(
365       kCFAllocatorDefault, kKeys, kValues, base::size(kValues),
366       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
367   ScopedCFTypeRef<CFArrayRef> result;
368   {
369     base::AutoLock lock(crypto::GetMacSecurityServicesLock());
370     err = SecItemCopyMatching(
371         query, reinterpret_cast<CFTypeRef*>(result.InitializeInto()));
372   }
373   if (!err) {
374     for (CFIndex i = 0; i < CFArrayGetCount(result); i++) {
375       SecIdentityRef item = reinterpret_cast<SecIdentityRef>(
376           const_cast<void*>(CFArrayGetValueAtIndex(result, i)));
377       AddIdentity(
378           ScopedCFTypeRef<SecIdentityRef>(item, base::scoped_policy::RETAIN),
379           preferred_sec_identity.get(), &regular_identities,
380           &preferred_identity);
381     }
382   }
383 
384   ClientCertIdentityList selected_identities;
385   GetClientCertsImpl(std::move(preferred_identity),
386                      std::move(regular_identities), request, true,
387                      &selected_identities);
388   return selected_identities;
389 }
390 
391 }  // namespace
392 
ClientCertStoreMac()393 ClientCertStoreMac::ClientCertStoreMac() {}
394 
~ClientCertStoreMac()395 ClientCertStoreMac::~ClientCertStoreMac() {}
396 
GetClientCerts(const SSLCertRequestInfo & request,ClientCertListCallback callback)397 void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request,
398                                         ClientCertListCallback callback) {
399   base::PostTaskAndReplyWithResult(
400       GetSSLPlatformKeyTaskRunner().get(), FROM_HERE,
401       // Caller is responsible for keeping the |request| alive
402       // until the callback is run, so std::cref is safe.
403       base::BindOnce(&GetClientCertsOnBackgroundThread, std::cref(request)),
404       std::move(callback));
405 }
406 
SelectClientCertsForTesting(ClientCertIdentityList input_identities,const SSLCertRequestInfo & request,ClientCertIdentityList * selected_identities)407 bool ClientCertStoreMac::SelectClientCertsForTesting(
408     ClientCertIdentityList input_identities,
409     const SSLCertRequestInfo& request,
410     ClientCertIdentityList* selected_identities) {
411   GetClientCertsImpl(NULL, std::move(input_identities), request, false,
412                      selected_identities);
413   return true;
414 }
415 
SelectClientCertsGivenPreferredForTesting(std::unique_ptr<ClientCertIdentity> preferred_identity,ClientCertIdentityList regular_identities,const SSLCertRequestInfo & request,ClientCertIdentityList * selected_identities)416 bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
417     std::unique_ptr<ClientCertIdentity> preferred_identity,
418     ClientCertIdentityList regular_identities,
419     const SSLCertRequestInfo& request,
420     ClientCertIdentityList* selected_identities) {
421   GetClientCertsImpl(std::move(preferred_identity),
422                      std::move(regular_identities), request, false,
423                      selected_identities);
424   return true;
425 }
426 
427 #pragma clang diagnostic pop  // "-Wdeprecated-declarations"
428 
429 }  // namespace net
430