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