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