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.
4
5#include "net/ssl/client_cert_store_win.h"
6
7#include <algorithm>
8#include <string>
9
10#define SECURITY_WIN32  // Needs to be defined before including security.h
11#include <windows.h>
12#include <wincrypt.h>
13#include <security.h>
14
15#include "base/callback.h"
16#include "base/logging.h"
17#include "crypto/scoped_capi_types.h"
18#include "net/cert/x509_util.h"
19
20namespace net {
21
22namespace {
23
24// Callback required by Windows API function CertFindChainInStore(). In addition
25// to filtering by extended/enhanced key usage, we do not show expired
26// certificates and require digital signature usage in the key usage extension.
27//
28// This matches our behavior on Mac OS X and that of NSS. It also matches the
29// default behavior of IE8. See http://support.microsoft.com/kb/890326 and
30// http://blogs.msdn.com/b/askie/archive/2009/06/09/my-expired-client-certifica
31//     tes-no-longer-display-when-connecting-to-my-web-server-using-ie8.aspx
32static BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context,
33                                          void* find_arg) {
34  // Verify the certificate key usage is appropriate or not specified.
35  BYTE key_usage;
36  if (CertGetIntendedKeyUsage(X509_ASN_ENCODING, cert_context->pCertInfo,
37                              &key_usage, 1)) {
38    if (!(key_usage & CERT_DIGITAL_SIGNATURE_KEY_USAGE))
39      return FALSE;
40  } else {
41    DWORD err = GetLastError();
42    // If |err| is non-zero, it's an actual error. Otherwise the extension
43    // just isn't present, and we treat it as if everything was allowed.
44    if (err) {
45      DLOG(ERROR) << "CertGetIntendedKeyUsage failed: " << err;
46      return FALSE;
47    }
48  }
49
50  // Verify the current time is within the certificate's validity period.
51  if (CertVerifyTimeValidity(NULL, cert_context->pCertInfo) != 0)
52    return FALSE;
53
54  // Verify private key metadata is associated with this certificate.
55  // TODO(ppi): Is this really needed? Isn't it equivalent to leaving
56  // CERT_CHAIN_FIND_BY_ISSUER_NO_KEY_FLAG not set in |find_flags| argument of
57  // CertFindChainInStore()?
58  DWORD size = 0;
59  if (!CertGetCertificateContextProperty(
60          cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size)) {
61    return FALSE;
62  }
63
64  return TRUE;
65}
66
67void GetClientCertsImpl(HCERTSTORE cert_store,
68                        const SSLCertRequestInfo& request,
69                        CertificateList* selected_certs) {
70  selected_certs->clear();
71
72  const size_t auth_count = request.cert_authorities.size();
73  std::vector<CERT_NAME_BLOB> issuers(auth_count);
74  for (size_t i = 0; i < auth_count; ++i) {
75    issuers[i].cbData = static_cast<DWORD>(request.cert_authorities[i].size());
76    issuers[i].pbData = reinterpret_cast<BYTE*>(
77        const_cast<char*>(request.cert_authorities[i].data()));
78  }
79
80  // Enumerate the client certificates.
81  CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
82  memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
83  find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
84  find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
85  find_by_issuer_para.cIssuer = static_cast<DWORD>(auth_count);
86  find_by_issuer_para.rgIssuer =
87      reinterpret_cast<CERT_NAME_BLOB*>(issuers.data());
88  find_by_issuer_para.pfnFindCallback = ClientCertFindCallback;
89
90  PCCERT_CHAIN_CONTEXT chain_context = NULL;
91  DWORD find_flags = CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG |
92                     CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG;
93  for (;;) {
94    // Find a certificate chain.
95    chain_context = CertFindChainInStore(cert_store,
96                                         X509_ASN_ENCODING,
97                                         find_flags,
98                                         CERT_CHAIN_FIND_BY_ISSUER,
99                                         &find_by_issuer_para,
100                                         chain_context);
101    if (!chain_context) {
102      if (GetLastError() != CRYPT_E_NOT_FOUND)
103        DPLOG(ERROR) << "CertFindChainInStore failed: ";
104      break;
105    }
106
107    // Get the leaf certificate.
108    PCCERT_CONTEXT cert_context =
109        chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
110    // Copy the certificate, so that it is valid after |cert_store| is closed.
111    PCCERT_CONTEXT cert_context2 = NULL;
112    BOOL ok = CertAddCertificateContextToStore(NULL, cert_context,
113                                               CERT_STORE_ADD_USE_EXISTING,
114                                               &cert_context2);
115    if (!ok) {
116      NOTREACHED();
117      continue;
118    }
119
120    // Grab the intermediates, if any.
121    X509Certificate::OSCertHandles intermediates;
122    for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; ++i) {
123      PCCERT_CONTEXT chain_intermediate =
124          chain_context->rgpChain[0]->rgpElement[i]->pCertContext;
125      PCCERT_CONTEXT copied_intermediate = NULL;
126      ok = CertAddCertificateContextToStore(NULL, chain_intermediate,
127                                            CERT_STORE_ADD_USE_EXISTING,
128                                            &copied_intermediate);
129      if (ok)
130        intermediates.push_back(copied_intermediate);
131    }
132    scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
133        cert_context2, intermediates);
134    selected_certs->push_back(cert);
135    CertFreeCertificateContext(cert_context2);
136    for (size_t i = 0; i < intermediates.size(); ++i)
137      CertFreeCertificateContext(intermediates[i]);
138  }
139
140  std::sort(selected_certs->begin(), selected_certs->end(),
141            x509_util::ClientCertSorter());
142}
143
144}  // namespace
145
146ClientCertStoreWin::ClientCertStoreWin() {}
147
148ClientCertStoreWin::~ClientCertStoreWin() {}
149
150void ClientCertStoreWin::GetClientCerts(const SSLCertRequestInfo& request,
151                                         CertificateList* selected_certs,
152                                         const base::Closure& callback) {
153  // Client certificates of the user are in the "MY" system certificate store.
154  HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
155  if (!my_cert_store) {
156    PLOG(ERROR) << "Could not open the \"MY\" system certificate store: ";
157    selected_certs->clear();
158    callback.Run();
159    return;
160  }
161
162  GetClientCertsImpl(my_cert_store, request, selected_certs);
163  if (!CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG))
164    PLOG(ERROR) << "Could not close the \"MY\" system certificate store: ";
165  callback.Run();
166}
167
168bool ClientCertStoreWin::SelectClientCertsForTesting(
169    const CertificateList& input_certs,
170    const SSLCertRequestInfo& request,
171    CertificateList* selected_certs) {
172  typedef crypto::ScopedCAPIHandle<
173      HCERTSTORE,
174      crypto::CAPIDestroyerWithFlags<HCERTSTORE,
175                                     CertCloseStore, 0> > ScopedHCERTSTORE;
176
177  ScopedHCERTSTORE test_store(CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0,
178                                            NULL));
179  if (!test_store)
180    return false;
181
182  // Add available certificates to the test store.
183  for (size_t i = 0; i < input_certs.size(); ++i) {
184    // Add the certificate to the test store.
185    PCCERT_CONTEXT cert = NULL;
186    if (!CertAddCertificateContextToStore(test_store,
187                                          input_certs[i]->os_cert_handle(),
188                                          CERT_STORE_ADD_NEW, &cert)) {
189      return false;
190    }
191    // Add dummy private key data to the certificate - otherwise the certificate
192    // would be discarded by the filtering routines.
193    CRYPT_KEY_PROV_INFO private_key_data;
194    memset(&private_key_data, 0, sizeof(private_key_data));
195    if (!CertSetCertificateContextProperty(cert,
196                                           CERT_KEY_PROV_INFO_PROP_ID,
197                                           0, &private_key_data)) {
198      return false;
199    }
200    // Decrement the reference count of the certificate (since we requested a
201    // copy).
202    if (!CertFreeCertificateContext(cert))
203      return false;
204  }
205
206  GetClientCertsImpl(test_store.get(), request, selected_certs);
207  return true;
208}
209
210}  // namespace net
211