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