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