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_win.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <memory>
10 #include <string>
11
12 #define SECURITY_WIN32 // Needs to be defined before including security.h
13 #include <windows.h>
14 #include <security.h>
15
16 #include "base/functional/bind.h"
17 #include "base/functional/callback.h"
18 #include "base/functional/callback_helpers.h"
19 #include "base/logging.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/scoped_generic.h"
22 #include "base/task/single_thread_task_runner.h"
23 #include "base/win/wincrypt_shim.h"
24 #include "net/cert/x509_util.h"
25 #include "net/cert/x509_util_win.h"
26 #include "net/ssl/ssl_platform_key_util.h"
27 #include "net/ssl/ssl_platform_key_win.h"
28 #include "net/ssl/ssl_private_key.h"
29 #include "third_party/boringssl/src/include/openssl/pool.h"
30
31 namespace net {
32
33 namespace {
34
35 using ScopedHCERTSTOREWithChecks = base::ScopedGeneric<
36 HCERTSTORE,
37 crypto::CAPITraitsWithFlags<HCERTSTORE,
38 CertCloseStore,
39 CERT_CLOSE_STORE_CHECK_FLAG>>;
40
41 class ClientCertIdentityWin : public ClientCertIdentity {
42 public:
ClientCertIdentityWin(scoped_refptr<net::X509Certificate> cert,crypto::ScopedPCCERT_CONTEXT cert_context,scoped_refptr<base::SingleThreadTaskRunner> key_task_runner)43 ClientCertIdentityWin(
44 scoped_refptr<net::X509Certificate> cert,
45 crypto::ScopedPCCERT_CONTEXT cert_context,
46 scoped_refptr<base::SingleThreadTaskRunner> key_task_runner)
47 : ClientCertIdentity(std::move(cert)),
48 cert_context_(std::move(cert_context)),
49 key_task_runner_(std::move(key_task_runner)) {}
50
AcquirePrivateKey(base::OnceCallback<void (scoped_refptr<SSLPrivateKey>)> private_key_callback)51 void AcquirePrivateKey(base::OnceCallback<void(scoped_refptr<SSLPrivateKey>)>
52 private_key_callback) override {
53 key_task_runner_->PostTaskAndReplyWithResult(
54 FROM_HERE,
55 base::BindOnce(&FetchClientCertPrivateKey,
56 base::Unretained(certificate()), cert_context_.get()),
57 std::move(private_key_callback));
58 }
59
60 private:
61 crypto::ScopedPCCERT_CONTEXT cert_context_;
62 scoped_refptr<base::SingleThreadTaskRunner> key_task_runner_;
63 };
64
65 // Callback required by Windows API function CertFindChainInStore(). In addition
66 // to filtering by extended/enhanced key usage, we do not show expired
67 // certificates and require digital signature usage in the key usage extension.
68 //
69 // This matches our behavior on Mac OS X and that of NSS. It also matches the
70 // default behavior of IE8. See http://support.microsoft.com/kb/890326 and
71 // http://blogs.msdn.com/b/askie/archive/2009/06/09/my-expired-client-certifica
72 // tes-no-longer-display-when-connecting-to-my-web-server-using-ie8.aspx
ClientCertFindCallback(PCCERT_CONTEXT cert_context,void * find_arg)73 static BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context,
74 void* find_arg) {
75 // Verify the certificate key usage is appropriate or not specified.
76 BYTE key_usage;
77 if (CertGetIntendedKeyUsage(X509_ASN_ENCODING, cert_context->pCertInfo,
78 &key_usage, 1)) {
79 if (!(key_usage & CERT_DIGITAL_SIGNATURE_KEY_USAGE))
80 return FALSE;
81 } else {
82 DWORD err = GetLastError();
83 // If |err| is non-zero, it's an actual error. Otherwise the extension
84 // just isn't present, and we treat it as if everything was allowed.
85 if (err) {
86 DLOG(ERROR) << "CertGetIntendedKeyUsage failed: " << err;
87 return FALSE;
88 }
89 }
90
91 // Verify the current time is within the certificate's validity period.
92 if (CertVerifyTimeValidity(nullptr, cert_context->pCertInfo) != 0)
93 return FALSE;
94
95 // Verify private key metadata is associated with this certificate.
96 // TODO(ppi): Is this really needed? Isn't it equivalent to leaving
97 // CERT_CHAIN_FIND_BY_ISSUER_NO_KEY_FLAG not set in |find_flags| argument of
98 // CertFindChainInStore()?
99 DWORD size = 0;
100 if (!CertGetCertificateContextProperty(
101 cert_context, CERT_KEY_PROV_INFO_PROP_ID, nullptr, &size)) {
102 return FALSE;
103 }
104
105 return TRUE;
106 }
107
GetClientCertsImpl(HCERTSTORE cert_store,const SSLCertRequestInfo & request)108 ClientCertIdentityList GetClientCertsImpl(HCERTSTORE cert_store,
109 const SSLCertRequestInfo& request) {
110 ClientCertIdentityList selected_identities;
111
112 scoped_refptr<base::SingleThreadTaskRunner> current_thread =
113 base::SingleThreadTaskRunner::GetCurrentDefault();
114
115 const size_t auth_count = request.cert_authorities.size();
116 std::vector<CERT_NAME_BLOB> issuers(auth_count);
117 for (size_t i = 0; i < auth_count; ++i) {
118 issuers[i].cbData = static_cast<DWORD>(request.cert_authorities[i].size());
119 issuers[i].pbData = reinterpret_cast<BYTE*>(
120 const_cast<char*>(request.cert_authorities[i].data()));
121 }
122
123 // Enumerate the client certificates.
124 CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
125 memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
126 find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
127 find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
128 find_by_issuer_para.cIssuer = static_cast<DWORD>(auth_count);
129 find_by_issuer_para.rgIssuer =
130 reinterpret_cast<CERT_NAME_BLOB*>(issuers.data());
131 find_by_issuer_para.pfnFindCallback = ClientCertFindCallback;
132
133 PCCERT_CHAIN_CONTEXT chain_context = nullptr;
134 DWORD find_flags = CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG |
135 CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG;
136 for (;;) {
137 // Find a certificate chain.
138 chain_context = CertFindChainInStore(cert_store,
139 X509_ASN_ENCODING,
140 find_flags,
141 CERT_CHAIN_FIND_BY_ISSUER,
142 &find_by_issuer_para,
143 chain_context);
144 if (!chain_context) {
145 if (GetLastError() != static_cast<DWORD>(CRYPT_E_NOT_FOUND))
146 DPLOG(ERROR) << "CertFindChainInStore failed: ";
147 break;
148 }
149
150 // Get the leaf certificate.
151 PCCERT_CONTEXT cert_context =
152 chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
153 // Copy the certificate, so that it is valid after |cert_store| is closed.
154 crypto::ScopedPCCERT_CONTEXT cert_context2;
155 PCCERT_CONTEXT raw = nullptr;
156 BOOL ok = CertAddCertificateContextToStore(
157 nullptr, cert_context, CERT_STORE_ADD_USE_EXISTING, &raw);
158 if (!ok) {
159 NOTREACHED();
160 continue;
161 }
162 cert_context2.reset(raw);
163
164 // Grab the intermediates, if any.
165 std::vector<crypto::ScopedPCCERT_CONTEXT> intermediates_storage;
166 std::vector<PCCERT_CONTEXT> intermediates;
167 for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; ++i) {
168 PCCERT_CONTEXT chain_intermediate =
169 chain_context->rgpChain[0]->rgpElement[i]->pCertContext;
170 PCCERT_CONTEXT copied_intermediate = nullptr;
171 ok = CertAddCertificateContextToStore(nullptr, chain_intermediate,
172 CERT_STORE_ADD_USE_EXISTING,
173 &copied_intermediate);
174 if (ok) {
175 intermediates.push_back(copied_intermediate);
176 intermediates_storage.emplace_back(copied_intermediate);
177 }
178 }
179
180 // Drop the self-signed root, if any. Match Internet Explorer in not sending
181 // it. Although the root's signature is irrelevant for authentication, some
182 // servers reject chains if the root is explicitly sent and has a weak
183 // signature algorithm. See https://crbug.com/607264.
184 //
185 // The leaf or a intermediate may also have a weak signature algorithm but,
186 // in that case, assume it is a configuration error.
187 if (!intermediates.empty() &&
188 x509_util::IsSelfSigned(intermediates.back())) {
189 intermediates.pop_back();
190 intermediates_storage.pop_back();
191 }
192
193 // Allow UTF-8 inside PrintableStrings in client certificates. See
194 // crbug.com/770323.
195 X509Certificate::UnsafeCreateOptions options;
196 options.printable_string_is_utf8 = true;
197 scoped_refptr<X509Certificate> cert =
198 x509_util::CreateX509CertificateFromCertContexts(
199 cert_context2.get(), intermediates, options);
200 if (cert) {
201 selected_identities.push_back(std::make_unique<ClientCertIdentityWin>(
202 std::move(cert),
203 std::move(cert_context2), // Takes ownership of |cert_context2|.
204 current_thread)); // The key must be acquired on the same thread, as
205 // the PCCERT_CONTEXT may not be thread safe.
206 }
207 }
208
209 std::sort(selected_identities.begin(), selected_identities.end(),
210 ClientCertIdentitySorter());
211 return selected_identities;
212 }
213
214 } // namespace
215
216 ClientCertStoreWin::ClientCertStoreWin() = default;
217
ClientCertStoreWin(base::RepeatingCallback<crypto::ScopedHCERTSTORE ()> cert_store_callback)218 ClientCertStoreWin::ClientCertStoreWin(
219 base::RepeatingCallback<crypto::ScopedHCERTSTORE()> cert_store_callback)
220 : cert_store_callback_(std::move(cert_store_callback)) {
221 DCHECK(!cert_store_callback_.is_null());
222 }
223
224 ClientCertStoreWin::~ClientCertStoreWin() = default;
225
GetClientCerts(const SSLCertRequestInfo & request,ClientCertListCallback callback)226 void ClientCertStoreWin::GetClientCerts(const SSLCertRequestInfo& request,
227 ClientCertListCallback callback) {
228 GetSSLPlatformKeyTaskRunner()->PostTaskAndReplyWithResult(
229 FROM_HERE,
230 // Caller is responsible for keeping the |request| alive
231 // until the callback is run, so std::cref is safe.
232 base::BindOnce(&ClientCertStoreWin::GetClientCertsWithCertStore,
233 std::cref(request), cert_store_callback_),
234 std::move(callback));
235 }
236
237 // static
GetClientCertsWithCertStore(const SSLCertRequestInfo & request,const base::RepeatingCallback<crypto::ScopedHCERTSTORE ()> & cert_store_callback)238 ClientCertIdentityList ClientCertStoreWin::GetClientCertsWithCertStore(
239 const SSLCertRequestInfo& request,
240 const base::RepeatingCallback<crypto::ScopedHCERTSTORE()>&
241 cert_store_callback) {
242 ScopedHCERTSTOREWithChecks cert_store;
243 if (cert_store_callback.is_null()) {
244 // Always open a new instance of the "MY" store, to ensure that there
245 // are no previously cached certificates being reused after they're
246 // no longer available (some smartcard providers fail to update the "MY"
247 // store handles and instead interpose CertOpenSystemStore). To help confirm
248 // this, use `ScopedHCERTSTOREWithChecks` and `CERT_CLOSE_STORE_CHECK_FLAG`
249 // to DCHECK that `cert_store` is not inadvertently ref-counted.
250 cert_store.reset(CertOpenSystemStore(NULL, L"MY"));
251 } else {
252 cert_store.reset(cert_store_callback.Run().release());
253 }
254 if (!cert_store.is_valid()) {
255 PLOG(ERROR) << "Could not open certificate store: ";
256 return ClientCertIdentityList();
257 }
258 return GetClientCertsImpl(cert_store.get(), request);
259 }
260
SelectClientCertsForTesting(const CertificateList & input_certs,const SSLCertRequestInfo & request,ClientCertIdentityList * selected_identities)261 bool ClientCertStoreWin::SelectClientCertsForTesting(
262 const CertificateList& input_certs,
263 const SSLCertRequestInfo& request,
264 ClientCertIdentityList* selected_identities) {
265 ScopedHCERTSTOREWithChecks test_store(
266 CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, nullptr));
267 if (!test_store.is_valid())
268 return false;
269
270 // Add available certificates to the test store.
271 for (const auto& input_cert : input_certs) {
272 // Add the certificate to the test store.
273 PCCERT_CONTEXT cert = nullptr;
274 if (!CertAddEncodedCertificateToStore(
275 test_store.get(), X509_ASN_ENCODING,
276 reinterpret_cast<const BYTE*>(
277 CRYPTO_BUFFER_data(input_cert->cert_buffer())),
278 base::checked_cast<DWORD>(
279 CRYPTO_BUFFER_len(input_cert->cert_buffer())),
280 CERT_STORE_ADD_NEW, &cert)) {
281 return false;
282 }
283 // Hold the reference to the certificate (since we requested a copy).
284 crypto::ScopedPCCERT_CONTEXT scoped_cert(cert);
285
286 // Add dummy private key data to the certificate - otherwise the certificate
287 // would be discarded by the filtering routines.
288 CRYPT_KEY_PROV_INFO private_key_data;
289 memset(&private_key_data, 0, sizeof(private_key_data));
290 if (!CertSetCertificateContextProperty(cert,
291 CERT_KEY_PROV_INFO_PROP_ID,
292 0, &private_key_data)) {
293 return false;
294 }
295 }
296
297 *selected_identities = GetClientCertsImpl(test_store.get(), request);
298 return true;
299 }
300
301 } // namespace net
302