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 ®ular_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(), ®ular_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