Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(289)

Side by Side Diff: net/base/keygen_handler_win.cc

Issue 843005: Adds support for the <keygen> element to Windows, matching support present on... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: Fixing remaining issues Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/base/keygen_handler_unittest.cc ('k') | net/base/net_error_list.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/base/keygen_handler.h" 5 #include "net/base/keygen_handler.h"
6 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"
7 #include "base/logging.h" 19 #include "base/logging.h"
20 #include "base/string_piece.h"
21 #include "base/utf_string_conversions.h"
8 22
9 namespace net { 23 namespace net {
10 24
25 // TODO(rsleevi): The following encoding functions are adapted from
26 // base/crypto/rsa_private_key.h and can/should probably be refactored
27 static const uint8 kSequenceTag = 0x30;
28
29 void PrependLength(size_t size, std::list<BYTE>* data) {
30 // The high bit is used to indicate whether additional octets are needed to
31 // represent the length.
32 if (size < 0x80) {
33 data->push_front(static_cast<BYTE>(size));
34 } else {
35 uint8 num_bytes = 0;
36 while (size > 0) {
37 data->push_front(static_cast<BYTE>(size & 0xFF));
38 size >>= 8;
39 num_bytes++;
40 }
41 CHECK_LE(num_bytes, 4);
42 data->push_front(0x80 | num_bytes);
43 }
44 }
45
46 void PrependTypeHeaderAndLength(uint8 type, uint32 length,
47 std::vector<BYTE>* output) {
48 std::list<BYTE> type_and_length;
49
50 PrependLength(length, &type_and_length);
51 type_and_length.push_front(type);
52
53 output->insert(output->begin(), type_and_length.begin(),
54 type_and_length.end());
55 }
56
57 bool EncodeAndAppendType(LPCSTR type, const void* to_encode,
58 std::vector<BYTE>* output) {
59 BOOL ok;
60 DWORD size = 0;
61 ok = CryptEncodeObject(X509_ASN_ENCODING, type, to_encode, NULL, &size);
62 DCHECK(ok);
63 if (!ok)
64 return false;
65
66 std::vector<BYTE>::size_type old_size = output->size();
67 output->resize(old_size + size);
68
69 ok = CryptEncodeObject(X509_ASN_ENCODING, type, to_encode,
70 &(*output)[old_size], &size);
71 DCHECK(ok);
72 if (!ok)
73 return false;
74
75 // Sometimes the initial call to CryptEncodeObject gave a generous estimate of
76 // the size, so shrink back to what was actually used
77 output->resize(old_size + size);
78
79 return true;
80 }
81
82 // Appends a DER IA5String containing |challenge| to |output|
83 // Returns true if encoding was successful
84 bool EncodeChallenge(const std::string& challenge, std::vector<BYTE>* output) {
85 CERT_NAME_VALUE challenge_nv;
86 challenge_nv.dwValueType = CERT_RDN_IA5_STRING;
87 challenge_nv.Value.pbData = const_cast<BYTE*>(
88 reinterpret_cast<const BYTE*>(challenge.c_str()));
89 challenge_nv.Value.cbData = challenge.size();
90
91 return EncodeAndAppendType(X509_ANY_STRING, &challenge_nv, output);
92 }
93
94 // Appends a DER SubjectPublicKeyInfo structure for the signing key in |prov|
95 // to |output|
96 // Returns true if encoding was successful
97 bool EncodeSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) {
98 BOOL ok;
99 DWORD size = 0;
100
101 // From the private key stored in HCRYPTPROV, obtain the public key, stored as
102 // a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are
103 // supported
104 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
105 szOID_RSA_RSA, 0, NULL, NULL, &size);
106 DCHECK(ok);
107 if (!ok)
108 return false;
109
110 std::vector<BYTE> public_key_info(size);
111 PCERT_PUBLIC_KEY_INFO public_key_casted =
112 reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&public_key_info[0]);
113 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
114 szOID_RSA_RSA, 0, NULL, public_key_casted,
115 &size);
116 DCHECK(ok);
117 if (!ok)
118 return false;
119
120 public_key_info.resize(size);
121
122 return EncodeAndAppendType(X509_PUBLIC_KEY_INFO, &public_key_info[0],
123 output);
124 }
125
126 // Generates an ASN.1 DER representation of the PublicKeyAndChallenge structure
127 // from the signing key of |prov| and the specified |challenge| and appends it
128 // to |output|
129 // True if the the encoding was successfully generated
130 bool GetPublicKeyAndChallenge(HCRYPTPROV prov, const std::string& challenge,
131 std::vector<BYTE>* output) {
132 if (!EncodeSubjectPublicKeyInfo(prov, output) ||
133 !EncodeChallenge(challenge, output)) {
134 return false;
135 }
136
137 PrependTypeHeaderAndLength(kSequenceTag, output->size(), output);
138 return true;
139 }
140
141 // Generates a DER encoded SignedPublicKeyAndChallenge structure from the
142 // signing key of |prov| and the specified |challenge| string and appends it
143 // to |output|
144 // True if the encoding was successfully generated
145 bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
146 const std::string& challenge,
147 std::string* output) {
148 std::vector<BYTE> pkac;
149 if (!GetPublicKeyAndChallenge(prov, challenge, &pkac))
150 return false;
151
152 std::vector<BYTE> signature;
153 std::vector<BYTE> signed_pkac;
154 DWORD size = 0;
155 BOOL ok;
156
157 // While the MSDN documentation states that CERT_SIGNED_CONTENT_INFO should be
158 // an X.509 certificate type, for encoding this is not necessary. The result
159 // of encoding this structure will be a DER-encoded structure with the ASN.1
160 // equivalent of
161 // ::= SEQUENCE {
162 // ToBeSigned IMPLICIT OCTET STRING,
163 // SignatureAlgorithm AlgorithmIdentifier,
164 // Signature BIT STRING
165 // }
166 //
167 // This happens to be the same naive type as an SPKAC, so this works.
168 CERT_SIGNED_CONTENT_INFO info;
169 info.ToBeSigned.cbData = pkac.size();
170 info.ToBeSigned.pbData = &pkac[0];
171 info.SignatureAlgorithm.pszObjId = szOID_RSA_MD5RSA;
172 info.SignatureAlgorithm.Parameters.cbData = 0;
173 info.SignatureAlgorithm.Parameters.pbData = NULL;
174
175 ok = CryptSignCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
176 info.ToBeSigned.pbData, info.ToBeSigned.cbData,
177 &info.SignatureAlgorithm, NULL, NULL, &size);
178 DCHECK(ok);
179 if (!ok)
180 return false;
181
182 signature.resize(size);
183 info.Signature.cbData = signature.size();
184 info.Signature.pbData = &signature.front();
185 info.Signature.cUnusedBits = 0;
186
187 ok = CryptSignCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
188 info.ToBeSigned.pbData, info.ToBeSigned.cbData,
189 &info.SignatureAlgorithm, NULL,
190 info.Signature.pbData, &info.Signature.cbData);
191 DCHECK(ok);
192 if (!ok || !EncodeAndAppendType(X509_CERT, &info, &signed_pkac))
193 return false;
194
195 output->assign(reinterpret_cast<char*>(&signed_pkac.front()),
196 signed_pkac.size());
197
198 return true;
199 }
200
201 // Generates a unique name for the container which will store the key that is
202 // generated. The traditional Windows approach is to use a GUID here
203 std::wstring GetNewKeyContainerId() {
204 RPC_STATUS status = RPC_S_OK;
205 std::wstring result;
206
207 UUID id = { 0 };
208 status = UuidCreateSequential(&id);
209 if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
210 return result;
211
212 RPC_WSTR rpc_string = NULL;
213 status = UuidToString(&id, &rpc_string);
214 if (status != RPC_S_OK)
215 return result;
216
217 // TODO(rsleevi): Is there a less-hackish way to get this through to convert
218 // the unsigned short to wchar_t?
219 result.assign(reinterpret_cast<wchar_t*>(rpc_string));
220 RpcStringFree(&rpc_string);
221
222 return result;
223 }
224
225 void StoreKeyLocationInCache(HCRYPTPROV prov) {
226 BOOL ok;
227 DWORD size = 0;
228
229 // Though it is known the container and provider name, as they are supplied
230 // during GenKeyAndSignChallenge, explicitly resolving them via
231 // CryptGetProvParam ensures that any defaults (such as provider name being
232 // NULL) or any CSP modifications to the container name are properly reflected
233
234 // Find the container name. Though the MSDN documentation states it will
235 // return the exact same value as supplied whent he provider was aquired, it
236 // also notes the return type will be CHAR, /not/ WCHAR
237 ok = CryptGetProvParam(prov, PP_CONTAINER, NULL, &size, 0);
238 if (!ok)
239 return;
240
241 std::vector<BYTE> buffer(size);
242 ok = CryptGetProvParam(prov, PP_CONTAINER, &buffer.front(), &size, 0);
243 if (!ok)
244 return;
245
246 KeygenHandler::KeyLocation key_location;
247 UTF8ToWide(reinterpret_cast<char*>(&buffer.front()), size,
248 &key_location.container_name);
249
250 // Get the provider name. This will always resolve, even if NULL (indicating
251 // the default provider) was supplied to the CryptAcquireContext
252 size = 0;
253 ok = CryptGetProvParam(prov, PP_NAME, NULL, &size, 0);
254 if (!ok)
255 return;
256
257 buffer.resize(size);
258 ok = CryptGetProvParam(prov, PP_NAME, &buffer.front(), &size, 0);
259 if (!ok)
260 return;
261
262 UTF8ToWide(reinterpret_cast<char*>(&buffer.front()), size,
263 &key_location.provider_name);
264
265 std::vector<BYTE> public_key_info;
266 if (!EncodeSubjectPublicKeyInfo(prov, &public_key_info))
267 return;
268
269 KeygenHandler::Cache* cache = KeygenHandler::Cache::GetInstance();
270 cache->Insert(std::string(public_key_info.begin(), public_key_info.end()),
271 key_location);
272 }
273
274 bool KeygenHandler::KeyLocation::Equals(
275 const KeygenHandler::KeyLocation& location) const {
276 return container_name == location.container_name &&
277 provider_name == location.provider_name;
278 }
279
11 std::string KeygenHandler::GenKeyAndSignChallenge() { 280 std::string KeygenHandler::GenKeyAndSignChallenge() {
12 NOTIMPLEMENTED(); 281 std::string result;
13 return std::string(); 282
283 bool is_success = true;
284 HCRYPTPROV prov = NULL;
285 HCRYPTKEY key = NULL;
286 DWORD flags = (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE;
287 std::string spkac;
288
289 std::wstring new_key_id;
290
291 // TODO(rsleevi): Have the user choose which provider they should use,
292 // which needs to be filtered by those providers which can provide the keytype
293 // requested of the keysize requested. This is especially important for
294 // generating certificates that will be stored on smart-cards.
295 const int kMaxAttempts = 5;
296 BOOL ok = FALSE;
297 for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
298 // Per MSDN documentation for CryptAcquireContext, if applications will be
299 // creating their own keys, they should ensure unique naming schemes to
300 // prevent overlap with any other applications or consumers of CSPs, and
301 // /should not/ store new keys within the default, NULL key container.
302 new_key_id = GetNewKeyContainerId();
303 if (new_key_id.empty())
304 return result;
305
306 // Only create new key containers, so that existing key containers are not
307 // overwritten
308 ok = CryptAcquireContext(&prov, new_key_id.c_str(), NULL, PROV_RSA_FULL,
309 CRYPT_SILENT | CRYPT_NEWKEYSET);
310
311 if (ok != 0 || GetLastError() != NTE_BAD_KEYSET)
312 break;
313 }
314 if (!ok) {
315 LOG(ERROR) << "Couldn't acquire a CryptoAPI Provider Context: "
316 << GetLastError();
317 is_success = false;
318 goto failure;
319 }
320
321 if (!CryptGenKey(prov, CALG_RSA_KEYX, flags, &key)) {
322 LOG(ERROR) << "Couldn't generate an RSA key";
323 is_success = false;
324 goto failure;
325 }
326
327 if (!GetSignedPublicKeyAndChallenge(prov, challenge_, &spkac)) {
328 LOG(ERROR) << "Couldn't generate the signed public key and challenge";
329 is_success = false;
330 goto failure;
331 }
332
333 if (!base::Base64Encode(spkac, &result)) {
334 LOG(ERROR) << "Couldn't convert signed key into base64";
335 is_success = false;
336 goto failure;
337 }
338
339 failure:
340 if (!is_success) {
341 LOG(ERROR) << "SSL Keygen failed";
342 } else {
343 LOG(INFO) << "SSL Key succeeded";
344 StoreKeyLocationInCache(prov);
345 }
346 if (key) {
347 // Securely destroys the handle, but leaves the underlying key alone. The
348 // key can be obtained again by resolving the key location. If |stores_key_|
349 // is false, the underlying key will be destroyed below.
350 CryptDestroyKey(key);
351 }
352
353 if (prov) {
354 CryptReleaseContext(prov, 0);
355 prov = NULL;
356 if (!stores_key_) {
357 // Fully destroys any of the keys that were created and releases prov
358 CryptAcquireContext(&prov, new_key_id.c_str(), NULL, PROV_RSA_FULL,
359 CRYPT_SILENT | CRYPT_DELETEKEYSET);
360 }
361 }
362
363 return result;
14 } 364 }
15 365
16 } // namespace net 366 } // namespace net
OLDNEW
« no previous file with comments | « net/base/keygen_handler_unittest.cc ('k') | net/base/net_error_list.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698