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.
5 #include "net/ssl/client_cert_store_mac.h"
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>
16 #include "base/callback.h"
17 #include "base/logging.h"
18 #include "base/mac/mac_logging.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #include "base/strings/sys_string_conversions.h"
21 #include "base/synchronization/lock.h"
22 #include "crypto/mac_security_services_lock.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/cert/x509_util.h"
25 #include "net/cert/x509_util_mac.h"
27 using base::ScopedCFTypeRef
;
33 // Gets the issuer for a given cert, starting with the cert itself and
34 // including the intermediate and finally root certificates (if any).
35 // This function calls SecTrust but doesn't actually pay attention to the trust
36 // result: it shouldn't be used to determine trust, just to traverse the chain.
37 // Caller is responsible for releasing the value stored into *out_cert_chain.
38 OSStatus
CopyCertChain(SecCertificateRef cert_handle
,
39 CFArrayRef
* out_cert_chain
) {
41 DCHECK(out_cert_chain
);
43 // Create an SSL policy ref configured for client cert evaluation.
44 SecPolicyRef ssl_policy
;
45 OSStatus result
= x509_util::CreateSSLClientPolicy(&ssl_policy
);
48 ScopedCFTypeRef
<SecPolicyRef
> scoped_ssl_policy(ssl_policy
);
50 // Create a SecTrustRef.
51 ScopedCFTypeRef
<CFArrayRef
> input_certs(CFArrayCreate(
52 NULL
, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle
)),
53 1, &kCFTypeArrayCallBacks
));
54 SecTrustRef trust_ref
= NULL
;
56 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
57 result
= SecTrustCreateWithCertificates(input_certs
, ssl_policy
,
62 ScopedCFTypeRef
<SecTrustRef
> trust(trust_ref
);
64 // Evaluate trust, which creates the cert chain.
65 SecTrustResultType status
;
66 CSSM_TP_APPLE_EVIDENCE_INFO
* status_chain
;
68 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
69 result
= SecTrustEvaluate(trust
, &status
);
74 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
75 result
= SecTrustGetResult(trust
, &status
, out_cert_chain
, &status_chain
);
80 // Returns true if |*cert| is issued by an authority in |valid_issuers|
81 // according to Keychain Services, rather than using |cert|'s intermediate
82 // certificates. If it is, |*cert| is updated to point to the completed
84 bool IsIssuedByInKeychain(const std::vector
<std::string
>& valid_issuers
,
85 scoped_refptr
<X509Certificate
>* cert
) {
89 X509Certificate::OSCertHandle cert_handle
= (*cert
)->os_cert_handle();
90 CFArrayRef cert_chain
= NULL
;
91 OSStatus result
= CopyCertChain(cert_handle
, &cert_chain
);
93 OSSTATUS_LOG(ERROR
, result
) << "CopyCertChain error";
100 X509Certificate::OSCertHandles intermediates
;
101 for (CFIndex i
= 1, chain_count
= CFArrayGetCount(cert_chain
);
102 i
< chain_count
; ++i
) {
103 SecCertificateRef cert
= reinterpret_cast<SecCertificateRef
>(
104 const_cast<void*>(CFArrayGetValueAtIndex(cert_chain
, i
)));
105 intermediates
.push_back(cert
);
108 scoped_refptr
<X509Certificate
> new_cert(X509Certificate::CreateFromHandle(
109 cert_handle
, intermediates
));
110 CFRelease(cert_chain
); // Also frees |intermediates|.
112 if (!new_cert
->IsIssuedByEncoded(valid_issuers
))
115 cert
->swap(new_cert
);
119 // Examines the certificates in |preferred_cert| and |regular_certs| to find
120 // all certificates that match the client certificate request in |request|,
121 // storing the matching certificates in |selected_certs|.
122 // If |query_keychain| is true, Keychain Services will be queried to construct
123 // full certificate chains. If it is false, only the the certificates and their
124 // intermediates (available via X509Certificate::GetIntermediateCertificates())
125 // will be considered.
126 void GetClientCertsImpl(const scoped_refptr
<X509Certificate
>& preferred_cert
,
127 const CertificateList
& regular_certs
,
128 const SSLCertRequestInfo
& request
,
130 CertificateList
* selected_certs
) {
131 CertificateList preliminary_list
;
132 if (preferred_cert
.get())
133 preliminary_list
.push_back(preferred_cert
);
134 preliminary_list
.insert(preliminary_list
.end(), regular_certs
.begin(),
135 regular_certs
.end());
137 selected_certs
->clear();
138 for (size_t i
= 0; i
< preliminary_list
.size(); ++i
) {
139 scoped_refptr
<X509Certificate
>& cert
= preliminary_list
[i
];
140 if (cert
->HasExpired() || !cert
->SupportsSSLClientAuth())
143 // Skip duplicates (a cert may be in multiple keychains).
144 const SHA1HashValue
& fingerprint
= cert
->fingerprint();
146 for (pos
= 0; pos
< selected_certs
->size(); ++pos
) {
147 if ((*selected_certs
)[pos
]->fingerprint().Equals(fingerprint
))
150 if (pos
< selected_certs
->size())
153 // Check if the certificate issuer is allowed by the server.
154 if (request
.cert_authorities
.empty() ||
155 cert
->IsIssuedByEncoded(request
.cert_authorities
) ||
157 IsIssuedByInKeychain(request
.cert_authorities
, &cert
))) {
158 selected_certs
->push_back(cert
);
162 // Preferred cert should appear first in the ui, so exclude it from the
164 CertificateList::iterator sort_begin
= selected_certs
->begin();
165 CertificateList::iterator sort_end
= selected_certs
->end();
166 if (preferred_cert
.get() && sort_begin
!= sort_end
&&
167 sort_begin
->get() == preferred_cert
.get()) {
170 sort(sort_begin
, sort_end
, x509_util::ClientCertSorter());
175 ClientCertStoreMac::ClientCertStoreMac() {}
177 ClientCertStoreMac::~ClientCertStoreMac() {}
179 void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo
& request
,
180 CertificateList
* selected_certs
,
181 const base::Closure
& callback
) {
182 std::string server_domain
= request
.host_and_port
.host();
184 ScopedCFTypeRef
<SecIdentityRef
> preferred_identity
;
185 if (!server_domain
.empty()) {
186 // See if there's an identity preference for this domain:
187 ScopedCFTypeRef
<CFStringRef
> domain_str(
188 base::SysUTF8ToCFStringRef("https://" + server_domain
));
189 SecIdentityRef identity
= NULL
;
190 // While SecIdentityCopyPreferences appears to take a list of CA issuers
191 // to restrict the identity search to, within Security.framework the
192 // argument is ignored and filtering unimplemented. See
193 // SecIdentity.cpp in libsecurity_keychain, specifically
194 // _SecIdentityCopyPreferenceMatchingName().
196 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
197 if (SecIdentityCopyPreference(domain_str
, 0, NULL
, &identity
) == noErr
)
198 preferred_identity
.reset(identity
);
202 // Now enumerate the identities in the available keychains.
203 scoped_refptr
<X509Certificate
> preferred_cert
= NULL
;
204 CertificateList regular_certs
;
206 SecIdentitySearchRef search
= NULL
;
209 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
210 err
= SecIdentitySearchCreate(NULL
, CSSM_KEYUSE_SIGN
, &search
);
213 selected_certs
->clear();
217 ScopedCFTypeRef
<SecIdentitySearchRef
> scoped_search(search
);
219 SecIdentityRef identity
= NULL
;
221 base::AutoLock
lock(crypto::GetMacSecurityServicesLock());
222 err
= SecIdentitySearchCopyNext(search
, &identity
);
226 ScopedCFTypeRef
<SecIdentityRef
> scoped_identity(identity
);
228 SecCertificateRef cert_handle
;
229 err
= SecIdentityCopyCertificate(identity
, &cert_handle
);
232 ScopedCFTypeRef
<SecCertificateRef
> scoped_cert_handle(cert_handle
);
234 scoped_refptr
<X509Certificate
> cert(
235 X509Certificate::CreateFromHandle(cert_handle
,
236 X509Certificate::OSCertHandles()));
238 if (preferred_identity
&& CFEqual(preferred_identity
, identity
)) {
239 // Only one certificate should match.
240 DCHECK(!preferred_cert
.get());
241 preferred_cert
= cert
;
243 regular_certs
.push_back(cert
);
247 if (err
!= errSecItemNotFound
) {
248 OSSTATUS_LOG(ERROR
, err
) << "SecIdentitySearch error";
249 selected_certs
->clear();
254 GetClientCertsImpl(preferred_cert
, regular_certs
, request
, true,
259 bool ClientCertStoreMac::SelectClientCertsForTesting(
260 const CertificateList
& input_certs
,
261 const SSLCertRequestInfo
& request
,
262 CertificateList
* selected_certs
) {
263 GetClientCertsImpl(NULL
, input_certs
, request
, false, selected_certs
);
267 bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
268 const scoped_refptr
<X509Certificate
>& preferred_cert
,
269 const CertificateList
& regular_certs
,
270 const SSLCertRequestInfo
& request
,
271 CertificateList
* selected_certs
) {
273 preferred_cert
, regular_certs
, request
, false, selected_certs
);