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

Side by Side Diff: content/child/webcrypto/openssl/ec_key_openssl.cc

Issue 698363002: webcrypto: Add ECDSA algorithm (chromium-side) (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@extract_more
Patch Set: rebase + add another test Created 6 years, 1 month 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
OLDNEW
(Empty)
1 // Copyright 2014 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 "content/child/webcrypto/openssl/ec_key_openssl.h"
6
7 #include <openssl/ec.h>
8 #include <openssl/ec_key.h>
9 #include <openssl/evp.h>
10 #include <openssl/pkcs12.h>
11
12 #include "base/logging.h"
13 #include "base/stl_util.h"
14 #include "content/child/webcrypto/crypto_data.h"
15 #include "content/child/webcrypto/generate_key_result.h"
16 #include "content/child/webcrypto/jwk.h"
17 #include "content/child/webcrypto/openssl/key_openssl.h"
18 #include "content/child/webcrypto/openssl/util_openssl.h"
19 #include "content/child/webcrypto/status.h"
20 #include "content/child/webcrypto/webcrypto_util.h"
21 #include "crypto/openssl_util.h"
22 #include "crypto/scoped_openssl_types.h"
23 #include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
24 #include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"
25
26 namespace content {
27
28 namespace webcrypto {
29
30 namespace {
31
32 // Maps a blink::WebCryptoNamedCurve to the corresponding NID used by
33 // BoringSSL.
34 Status WebCryptoCurveToNid(blink::WebCryptoNamedCurve named_curve, int* nid) {
35 switch (named_curve) {
36 case blink::WebCryptoNamedCurveP256:
37 *nid = NID_X9_62_prime256v1;
38 return Status::Success();
39 case blink::WebCryptoNamedCurveP384:
40 *nid = NID_secp384r1;
41 return Status::Success();
42 case blink::WebCryptoNamedCurveP521:
43 *nid = NID_secp521r1;
44 return Status::Success();
45 }
46 return Status::ErrorUnsupported();
47 }
48
49 // Maps a BoringSSL NID to the corresponding WebCrypto named curve.
50 Status NidToWebCryptoCurve(int nid, blink::WebCryptoNamedCurve* named_curve) {
51 switch (nid) {
52 case NID_X9_62_prime256v1:
53 *named_curve = blink::WebCryptoNamedCurveP256;
54 return Status::Success();
55 case NID_secp384r1:
56 *named_curve = blink::WebCryptoNamedCurveP384;
57 return Status::Success();
58 case NID_secp521r1:
59 *named_curve = blink::WebCryptoNamedCurveP521;
60 return Status::Success();
61 }
62 return Status::ErrorImportedEcKeyIncorrectCurve();
63 }
64
65 struct JwkCrvMapping {
66 const char* jwk_curve;
67 blink::WebCryptoNamedCurve named_curve;
68 };
69
70 const JwkCrvMapping kJwkCrvMappings[] = {
71 {"P-256", blink::WebCryptoNamedCurveP256},
72 {"P-384", blink::WebCryptoNamedCurveP384},
73 {"P-521", blink::WebCryptoNamedCurveP521},
74 };
75
76 // Gets the "crv" parameter from a JWK and converts it to a WebCryptoNamedCurve.
77 Status GetJwkCrv(const JwkReader& jwk,
78 blink::WebCryptoNamedCurve* named_curve) {
79 std::string jwk_curve;
80 Status status = jwk.GetString("crv", &jwk_curve);
81 if (status.IsError())
82 return status;
83
84 for (size_t i = 0; i < arraysize(kJwkCrvMappings); ++i) {
85 if (kJwkCrvMappings[i].jwk_curve == jwk_curve) {
86 *named_curve = kJwkCrvMappings[i].named_curve;
87 return Status::Success();
88 }
89 }
90
91 return Status::ErrorJwkIncorrectCrv();
92 }
93
94 // Converts a WebCryptoNamedCurve to an equivalent JWK "crv".
95 Status WebCryptoCurveToJwkCrv(blink::WebCryptoNamedCurve named_curve,
96 std::string* jwk_crv) {
97 for (size_t i = 0; i < arraysize(kJwkCrvMappings); ++i) {
98 if (kJwkCrvMappings[i].named_curve == named_curve) {
99 *jwk_crv = kJwkCrvMappings[i].jwk_curve;
100 return Status::Success();
101 }
102 }
103 return Status::ErrorUnexpected();
104 }
105
106 // Verifies that an EC key imported from PKCS8 or SPKI format is correct.
107 // This involves verifying the key validity, and the NID for the named curve.
108 Status VerifyEcKeyAfterSpkiOrPkcs8Import(
109 EVP_PKEY* pkey,
110 blink::WebCryptoNamedCurve expected_named_curve) {
111 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
112
113 // TODO(eroman): Validate the algorithm OID against the webcrypto provided
114 // ones. http://crbug.com/389400
davidben 2014/11/07 00:17:21 This is for checking id-ecDH and only allowing it
eroman 2014/11/08 01:52:56 Done. I have delete the comment, it was a copy-pa
115
116 crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey));
117 if (!ec.get())
118 return Status::ErrorUnexpected();
119
120 // TODO(eroman): Is this necessary? From my tests it seems that BoringSSL
121 // already does these checks when setting the public key's affine coordinates.
122 if (!EC_KEY_check_key(ec.get()))
123 return Status::ErrorEcKeyInvalid();
124
125 // Make sure the curve matches the expected curve name.
126 int curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get()));
127 blink::WebCryptoNamedCurve named_curve;
128 Status status = NidToWebCryptoCurve(curve_nid, &named_curve);
129 if (status.IsError())
130 return status;
131
132 if (named_curve != expected_named_curve)
133 return Status::ErrorImportedEcKeyIncorrectCurve();
134
135 return Status::Success();
136 }
137
138 // Creates an EC_KEY for the given WebCryptoNamedCurve.
139 Status CreateEC_KEY(blink::WebCryptoNamedCurve named_curve,
140 crypto::ScopedEC_KEY* ec) {
141 int curve_nid;
142 Status status = WebCryptoCurveToNid(named_curve, &curve_nid);
143 if (status.IsError())
144 return status;
145
146 ec->reset(EC_KEY_new_by_curve_name(curve_nid));
147 if (!ec->get())
148 return Status::OperationError();
149
150 return Status::Success();
151 }
152
153 // Writes a BIGNUM into a JWK.
154 void WriteBIGNUM(const std::string& member_name,
155 const BIGNUM* value,
156 JwkWriter* jwk) {
157 jwk->SetBytes(member_name, CryptoData(BIGNUMToVector(value)));
davidben 2014/11/07 00:17:21 JWK says of x/y that "The length of this octet str
eroman 2014/11/08 01:52:56 Thanks! Done. (I hadn't read that carefully enoug
158 }
159
160 // Reads a BIGNUM from a JWK.
161 Status GetBIGNUM(const JwkReader& jwk,
162 const std::string& key,
163 crypto::ScopedBIGNUM* out) {
davidben 2014/11/07 00:17:21 Likewise, this should be strict about the size her
eroman 2014/11/08 01:52:56 Done.
164 std::string bytes;
165 Status status = jwk.GetBytes(key, &bytes);
166 if (status.IsError())
167 return status;
168 out->reset(CreateBIGNUM(bytes));
169 return Status::Success();
170 }
171
172 // Assigns the EC_KEY's public key using the (uncompressed) affine coordinates
173 // specified in |jwk|.
174 Status SetPublicKeyMembersFromJwk(const JwkReader& jwk, EC_KEY* ec) {
175 crypto::ScopedBIGNUM x;
176 Status status = GetBIGNUM(jwk, "x", &x);
177 if (status.IsError())
178 return status;
179
180 crypto::ScopedBIGNUM y;
181 status = GetBIGNUM(jwk, "y", &y);
182 if (status.IsError())
183 return status;
184
185 // TODO(eroman): This internally runs EC_KEY_check_key(). Can avoid calling it
186 // again by the JWK import code if private key were set before public key.
187 if (!EC_KEY_set_public_key_affine_coordinates(ec, x.get(), y.get()))
188 return Status::OperationError();
189
190 return Status::Success();
191 }
192
193 // Extracts the public key as affine coordinates (x,y).
194 Status GetPublicKey(EC_KEY* ec,
195 crypto::ScopedBIGNUM* x,
196 crypto::ScopedBIGNUM* y) {
197 const EC_GROUP* group = EC_KEY_get0_group(ec);
198 const EC_POINT* point = EC_KEY_get0_public_key(ec);
199
200 x->reset(BN_new());
201 y->reset(BN_new());
202
203 if (!EC_POINT_get_affine_coordinates_GFp(group, point, x->get(), y->get(),
204 NULL)) {
205 return Status::OperationError();
206 }
207
208 return Status::Success();
209 }
210
211 } // namespace
212
213 Status EcAlgorithm::GenerateKey(const blink::WebCryptoAlgorithm& algorithm,
214 bool extractable,
215 blink::WebCryptoKeyUsageMask combined_usages,
216 GenerateKeyResult* result) const {
217 Status status = CheckKeyCreationUsages(
218 all_public_key_usages_ | all_private_key_usages_, combined_usages);
219 if (status.IsError())
220 return status;
221
222 const blink::WebCryptoKeyUsageMask public_usages =
223 combined_usages & all_public_key_usages_;
224 const blink::WebCryptoKeyUsageMask private_usages =
225 combined_usages & all_private_key_usages_;
226
227 const blink::WebCryptoEcKeyGenParams* params = algorithm.ecKeyGenParams();
228
229 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
230
231 // Generate an EC key pair.
232 crypto::ScopedEC_KEY ec_private_key;
233 status = CreateEC_KEY(params->namedCurve(), &ec_private_key);
234 if (status.IsError())
235 return status;
236
237 if (!EC_KEY_generate_key(ec_private_key.get()))
238 return Status::OperationError();
239
240 // Construct an EVP_PKEY for the private key.
241 crypto::ScopedEVP_PKEY private_pkey(EVP_PKEY_new());
242 if (!private_pkey ||
243 !EVP_PKEY_set1_EC_KEY(private_pkey.get(), ec_private_key.get())) {
244 return Status::OperationError();
245 }
246
247 // Construct an EVP_PKEY for just the public key.
248 crypto::ScopedEC_KEY ec_public_key;
249 crypto::ScopedEVP_PKEY public_pkey(EVP_PKEY_new());
250 status = CreateEC_KEY(params->namedCurve(), &ec_public_key);
251 if (status.IsError())
252 return status;
253 if (!EC_KEY_set_public_key(ec_public_key.get(),
254 EC_KEY_get0_public_key(ec_private_key.get()))) {
255 return Status::OperationError();
256 }
257 if (!public_pkey ||
258 !EVP_PKEY_set1_EC_KEY(public_pkey.get(), ec_public_key.get())) {
259 return Status::OperationError();
260 }
261
262 blink::WebCryptoKey public_key;
263 blink::WebCryptoKey private_key;
264
265 blink::WebCryptoKeyAlgorithm key_algorithm =
266 blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(),
267 params->namedCurve());
268
269 // Note that extractable is unconditionally set to true. This is because per
270 // the WebCrypto spec generated public keys are always public.
271 status = CreateWebCryptoPublicKey(public_pkey.Pass(), key_algorithm, true,
272 public_usages, &public_key);
273 if (status.IsError())
274 return status;
275
276 status = CreateWebCryptoPrivateKey(private_pkey.Pass(), key_algorithm,
277 extractable, private_usages, &private_key);
278 if (status.IsError())
279 return status;
280
281 result->AssignKeyPair(public_key, private_key);
282 return Status::Success();
283 }
284
285 // TODO(eroman): This is identical to RSA.
286 Status EcAlgorithm::VerifyKeyUsagesBeforeImportKey(
287 blink::WebCryptoKeyFormat format,
288 blink::WebCryptoKeyUsageMask usages) const {
289 switch (format) {
290 case blink::WebCryptoKeyFormatSpki:
291 return CheckKeyCreationUsages(all_public_key_usages_, usages);
292 case blink::WebCryptoKeyFormatPkcs8:
293 return CheckKeyCreationUsages(all_private_key_usages_, usages);
294 case blink::WebCryptoKeyFormatJwk:
295 // The JWK could represent either a public key or private key. The usages
296 // must make sense for one of the two. The usages will be checked again by
297 // ImportKeyJwk() once the key type has been determined.
298 if (CheckKeyCreationUsages(all_private_key_usages_, usages).IsSuccess() ||
299 CheckKeyCreationUsages(all_public_key_usages_, usages).IsSuccess()) {
300 return Status::Success();
301 }
302 return Status::ErrorCreateKeyBadUsages();
303 default:
304 return Status::ErrorUnsupportedImportKeyFormat();
305 }
306 }
307
308 Status EcAlgorithm::ImportKeyPkcs8(const CryptoData& key_data,
309 const blink::WebCryptoAlgorithm& algorithm,
310 bool extractable,
311 blink::WebCryptoKeyUsageMask usages,
312 blink::WebCryptoKey* key) const {
313 crypto::ScopedEVP_PKEY private_key;
314 Status status =
315 ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_EC, &private_key);
316 if (status.IsError())
317 return status;
318
319 const blink::WebCryptoEcKeyImportParams* params =
320 algorithm.ecKeyImportParams();
davidben 2014/11/07 00:17:21 Does this require a null check or is it already ch
eroman 2014/11/08 01:52:56 This is guaranteed to be filled by the Blink layer
321
322 status = VerifyEcKeyAfterSpkiOrPkcs8Import(private_key.get(),
323 params->namedCurve());
324 if (status.IsError())
325 return status;
326
327 return CreateWebCryptoPrivateKey(private_key.Pass(),
328 blink::WebCryptoKeyAlgorithm::createEc(
329 algorithm.id(), params->namedCurve()),
330 extractable, usages, key);
331 }
332
333 Status EcAlgorithm::ImportKeySpki(const CryptoData& key_data,
334 const blink::WebCryptoAlgorithm& algorithm,
335 bool extractable,
336 blink::WebCryptoKeyUsageMask usages,
337 blink::WebCryptoKey* key) const {
338 crypto::ScopedEVP_PKEY public_key;
339 Status status =
340 ImportUnverifiedPkeyFromSpki(key_data, EVP_PKEY_EC, &public_key);
341 if (status.IsError())
342 return status;
343
344 const blink::WebCryptoEcKeyImportParams* params =
345 algorithm.ecKeyImportParams();
346
347 status =
348 VerifyEcKeyAfterSpkiOrPkcs8Import(public_key.get(), params->namedCurve());
349 if (status.IsError())
350 return status;
351
352 return CreateWebCryptoPublicKey(public_key.Pass(),
353 blink::WebCryptoKeyAlgorithm::createEc(
354 algorithm.id(), params->namedCurve()),
355 extractable, usages, key);
356 }
357
358 // The format for JWK EC keys is given by:
359 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6. 2
360 Status EcAlgorithm::ImportKeyJwk(const CryptoData& key_data,
361 const blink::WebCryptoAlgorithm& algorithm,
362 bool extractable,
363 blink::WebCryptoKeyUsageMask usages,
364 blink::WebCryptoKey* key) const {
365 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
366
367 const blink::WebCryptoEcKeyImportParams* params =
368 algorithm.ecKeyImportParams();
369
370 // When importing EC keys from JWK there are *three* separate curve
371 // names:
372 //
373 // (1) The one given to WebCrypto's importKey (params->namedCurve()).
374 // (2) JWK's "crv" member
375 // (3) JWK's "alg" member.
376 //
377 // The optional "alg" member implicitly names a curve and hash. For instance
378 // "ES512" would mean the key is for ECDSA using curve "P-521", and SHA-512.
379 // The hash portion of the "alg" is disregarded, however the curve must
380 // match expectations.
davidben 2014/11/07 00:17:21 It looks like only ECDSA does anything with alg, n
eroman 2014/11/08 01:52:57 Done.
381
382 JwkReader jwk;
383 Status status = jwk.Init(key_data, extractable, usages, "EC",
384 GetJwkAlgorithm(params->namedCurve()));
385 if (status.IsError())
386 return status;
387
388 // Verify that "crv" matches expected curve.
389 blink::WebCryptoNamedCurve jwk_crv;
390 status = GetJwkCrv(jwk, &jwk_crv);
391 if (status.IsError())
392 return status;
393 if (jwk_crv != params->namedCurve())
394 return Status::ErrorJwkIncorrectCrv();
395
396 // Only private keys have a "d" parameter. The key may still be invalid, but
397 // tentatively decide if it is a public or private key.
398 bool is_private_key = jwk.HasMember("d");
399
400 // Now that the key type is known, verify the usages.
401 status = CheckKeyCreationUsages(
402 is_private_key ? all_private_key_usages_ : all_public_key_usages_,
403 usages);
404 if (status.IsError())
405 return status;
406
407 // Create an EC_KEY.
408 crypto::ScopedEC_KEY ec;
409 status = CreateEC_KEY(params->namedCurve(), &ec);
410 if (status.IsError())
411 return status;
412
413 // Extract the "x" and "y" parameters and apply them to |ec|.
414 status = SetPublicKeyMembersFromJwk(jwk, ec.get());
415 if (status.IsError())
416 return status;
417
418 // Extract the "d" parameters.
419 if (is_private_key) {
420 crypto::ScopedBIGNUM d;
421 status = GetBIGNUM(jwk, "d", &d);
422 if (status.IsError())
423 return status;
424
425 if (!EC_KEY_set_private_key(ec.get(), d.get()))
426 return Status::OperationError();
427 }
428
429 // Verify the key.
430 if (!EC_KEY_check_key(ec.get()))
431 return Status::ErrorEcKeyInvalid();
432
433 // Wrap the EC_KEY into an EVP_PKEY.
434 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
435 if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()))
436 return Status::OperationError();
437
438 blink::WebCryptoKeyAlgorithm key_algorithm =
439 blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(),
440 params->namedCurve());
441
442 // Wrap the EVP_PKEY into a WebCryptoKey
443 if (is_private_key) {
444 return CreateWebCryptoPrivateKey(pkey.Pass(), key_algorithm, extractable,
445 usages, key);
446 }
447 return CreateWebCryptoPublicKey(pkey.Pass(), key_algorithm, extractable,
448 usages, key);
449 }
450
451 Status EcAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key,
452 std::vector<uint8_t>* buffer) const {
453 if (key.type() != blink::WebCryptoKeyTypePrivate)
454 return Status::ErrorUnexpectedKeyType();
455 *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
456 return Status::Success();
457 }
458
459 Status EcAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key,
460 std::vector<uint8_t>* buffer) const {
461 if (key.type() != blink::WebCryptoKeyTypePublic)
462 return Status::ErrorUnexpectedKeyType();
463 *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
464 return Status::Success();
465 }
466
467 // The format for JWK EC keys is given by:
468 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6. 2
469 Status EcAlgorithm::ExportKeyJwk(const blink::WebCryptoKey& key,
470 std::vector<uint8_t>* buffer) const {
471 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
472
473 EVP_PKEY* pkey = AsymKeyOpenSsl::Cast(key)->key();
474
475 crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey));
476 if (!ec.get())
477 return Status::ErrorUnexpected();
478
479 // Note that no "alg" is set for ECDSA keys, since it implies a hash.
480 JwkWriter jwk(std::string(), key.extractable(), key.usages(), "EC");
481
482 // Set the crv
483 std::string crv;
484 Status status =
485 WebCryptoCurveToJwkCrv(key.algorithm().ecParams()->namedCurve(), &crv);
486 if (status.IsError())
487 return status;
488
489 jwk.SetString("crv", crv);
490
491 crypto::ScopedBIGNUM x;
492 crypto::ScopedBIGNUM y;
493 status = GetPublicKey(ec.get(), &x, &y);
494 if (status.IsError())
495 return status;
496
497 WriteBIGNUM("x", x.get(), &jwk);
498 WriteBIGNUM("y", y.get(), &jwk);
499
500 if (key.type() == blink::WebCryptoKeyTypePrivate) {
501 crypto::ScopedBIGNUM d;
davidben 2014/11/07 00:17:21 Unused variable?
eroman 2014/11/08 01:52:56 Done.
502 WriteBIGNUM("d", EC_KEY_get0_private_key(ec.get()), &jwk);
503 }
504
505 jwk.ToJson(buffer);
506 return Status::Success();
507 }
508
509 } // namespace webcrypto
510
511 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698