1 // Copyright (c) 2011 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/base/keygen_handler.h" 6 7 #include <windows.h> 8 #include <wincrypt.h> 9 #pragma comment(lib, "crypt32.lib") 10 #include <rpc.h> 11 #pragma comment(lib, "rpcrt4.lib") 12 13 #include <list> 14 #include <string> 15 #include <vector> 16 17 #include "base/base64.h" 18 #include "base/basictypes.h" 19 #include "base/logging.h" 20 #include "base/strings/string_piece.h" 21 #include "base/strings/string_util.h" 22 #include "base/strings/utf_string_conversions.h" 23 #include "crypto/capi_util.h" 24 #include "crypto/scoped_capi_types.h" 25 26 27 namespace net { 28 29 // Assigns the contents of a CERT_PUBLIC_KEY_INFO structure for the signing 30 // key in |prov| to |output|. Returns true if encoding was successful. 31 bool GetSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) { 32 BOOL ok; 33 DWORD size = 0; 34 35 // From the private key stored in HCRYPTPROV, obtain the public key, stored 36 // as a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are 37 // supported. 38 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, 39 szOID_RSA_RSA, 0, NULL, NULL, &size); 40 DCHECK(ok); 41 if (!ok) 42 return false; 43 44 output->resize(size); 45 46 PCERT_PUBLIC_KEY_INFO public_key_casted = 47 reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&(*output)[0]); 48 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, 49 szOID_RSA_RSA, 0, NULL, public_key_casted, 50 &size); 51 DCHECK(ok); 52 if (!ok) 53 return false; 54 55 output->resize(size); 56 57 return true; 58 } 59 60 // Generates a DER encoded SignedPublicKeyAndChallenge structure from the 61 // signing key of |prov| and the specified ASCII |challenge| string and 62 // appends it to |output|. 63 // True if the encoding was successfully generated. 64 bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov, 65 const std::string& challenge, 66 std::string* output) { 67 std::wstring wide_challenge = ASCIIToWide(challenge); 68 std::vector<BYTE> spki; 69 70 if (!GetSubjectPublicKeyInfo(prov, &spki)) 71 return false; 72 73 // PublicKeyAndChallenge ::= SEQUENCE { 74 // spki SubjectPublicKeyInfo, 75 // challenge IA5STRING 76 // } 77 CERT_KEYGEN_REQUEST_INFO pkac; 78 pkac.dwVersion = CERT_KEYGEN_REQUEST_V1; 79 pkac.SubjectPublicKeyInfo = 80 *reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&spki[0]); 81 pkac.pwszChallengeString = const_cast<wchar_t*>(wide_challenge.c_str()); 82 83 CRYPT_ALGORITHM_IDENTIFIER sig_alg; 84 memset(&sig_alg, 0, sizeof(sig_alg)); 85 sig_alg.pszObjId = szOID_RSA_MD5RSA; 86 87 BOOL ok; 88 DWORD size = 0; 89 std::vector<BYTE> signed_pkac; 90 ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, 91 X509_KEYGEN_REQUEST_TO_BE_SIGNED, 92 &pkac, &sig_alg, NULL, 93 NULL, &size); 94 DCHECK(ok); 95 if (!ok) 96 return false; 97 98 signed_pkac.resize(size); 99 ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, 100 X509_KEYGEN_REQUEST_TO_BE_SIGNED, 101 &pkac, &sig_alg, NULL, 102 &signed_pkac[0], &size); 103 DCHECK(ok); 104 if (!ok) 105 return false; 106 107 output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size); 108 return true; 109 } 110 111 // Generates a unique name for the container which will store the key that is 112 // generated. The traditional Windows approach is to use a GUID here. 113 std::wstring GetNewKeyContainerId() { 114 RPC_STATUS status = RPC_S_OK; 115 std::wstring result; 116 117 UUID id = { 0 }; 118 status = UuidCreateSequential(&id); 119 if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) 120 return result; 121 122 RPC_WSTR rpc_string = NULL; 123 status = UuidToString(&id, &rpc_string); 124 if (status != RPC_S_OK) 125 return result; 126 127 // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++, 128 // so the type cast is necessary. 129 result.assign(reinterpret_cast<wchar_t*>(rpc_string)); 130 RpcStringFree(&rpc_string); 131 132 return result; 133 } 134 135 // This is a helper struct designed to optionally delete a key after releasing 136 // the associated provider. 137 struct KeyContainer { 138 public: 139 explicit KeyContainer(bool delete_keyset) 140 : delete_keyset_(delete_keyset) {} 141 142 ~KeyContainer() { 143 if (provider_) { 144 provider_.reset(); 145 if (delete_keyset_ && !key_id_.empty()) { 146 HCRYPTPROV provider; 147 crypto::CryptAcquireContextLocked(&provider, key_id_.c_str(), NULL, 148 PROV_RSA_FULL, CRYPT_SILENT | CRYPT_DELETEKEYSET); 149 } 150 } 151 } 152 153 crypto::ScopedHCRYPTPROV provider_; 154 std::wstring key_id_; 155 156 private: 157 bool delete_keyset_; 158 }; 159 160 std::string KeygenHandler::GenKeyAndSignChallenge() { 161 KeyContainer key_container(!stores_key_); 162 163 // TODO(rsleevi): Have the user choose which provider they should use, which 164 // needs to be filtered by those providers which can provide the key type 165 // requested or the key size requested. This is especially important for 166 // generating certificates that will be stored on smart cards. 167 const int kMaxAttempts = 5; 168 int attempt; 169 for (attempt = 0; attempt < kMaxAttempts; ++attempt) { 170 // Per MSDN documentation for CryptAcquireContext, if applications will be 171 // creating their own keys, they should ensure unique naming schemes to 172 // prevent overlap with any other applications or consumers of CSPs, and 173 // *should not* store new keys within the default, NULL key container. 174 key_container.key_id_ = GetNewKeyContainerId(); 175 if (key_container.key_id_.empty()) 176 return std::string(); 177 178 // Only create new key containers, so that existing key containers are not 179 // overwritten. 180 if (crypto::CryptAcquireContextLocked(key_container.provider_.receive(), 181 key_container.key_id_.c_str(), NULL, PROV_RSA_FULL, 182 CRYPT_SILENT | CRYPT_NEWKEYSET)) 183 break; 184 185 if (GetLastError() != NTE_BAD_KEYSET) { 186 LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider " 187 "context: " << GetLastError(); 188 return std::string(); 189 } 190 } 191 if (attempt == kMaxAttempts) { 192 LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider " 193 "context: Max retries exceeded"; 194 return std::string(); 195 } 196 197 { 198 crypto::ScopedHCRYPTKEY key; 199 if (!CryptGenKey(key_container.provider_, CALG_RSA_KEYX, 200 (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE, key.receive())) { 201 LOG(ERROR) << "Keygen failed: Couldn't generate an RSA key"; 202 return std::string(); 203 } 204 205 std::string spkac; 206 if (!GetSignedPublicKeyAndChallenge(key_container.provider_, challenge_, 207 &spkac)) { 208 LOG(ERROR) << "Keygen failed: Couldn't generate the signed public key " 209 "and challenge"; 210 return std::string(); 211 } 212 213 std::string result; 214 if (!base::Base64Encode(spkac, &result)) { 215 LOG(ERROR) << "Keygen failed: Couldn't convert signed key into base64"; 216 return std::string(); 217 } 218 219 VLOG(1) << "Keygen succeeded"; 220 return result; 221 } 222 } 223 224 } // namespace net 225