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