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