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