• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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