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