OLD | NEW |
| (Empty) |
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 <security.h> | |
13 | |
14 #include "base/callback.h" | |
15 #include "base/logging.h" | |
16 #include "crypto/scoped_capi_types.h" | |
17 #include "crypto/wincrypt_shim.h" | |
18 #include "net/cert/x509_util.h" | |
19 | |
20 namespace net { | |
21 | |
22 namespace { | |
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 | |
32 static 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 | |
67 void 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 | |
146 ClientCertStoreWin::ClientCertStoreWin() {} | |
147 | |
148 ClientCertStoreWin::~ClientCertStoreWin() {} | |
149 | |
150 void 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 | |
168 bool 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 | |
OLD | NEW |