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 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 211