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

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: sigh, more android pedantry 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 ReadJwkCrv(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 crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey));
114 if (!ec.get())
115 return Status::ErrorUnexpected();
116
117 // TODO(eroman): Is this necessary? From my tests it seems that BoringSSL
118 // already does these checks when setting the public key's affine coordinates.
119 if (!EC_KEY_check_key(ec.get()))
120 return Status::ErrorEcKeyInvalid();
121
122 // Make sure the curve matches the expected curve name.
123 int curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get()));
124 blink::WebCryptoNamedCurve named_curve = blink::WebCryptoNamedCurveP256;
125 Status status = NidToWebCryptoCurve(curve_nid, &named_curve);
126 if (status.IsError())
127 return status;
128
129 if (named_curve != expected_named_curve)
130 return Status::ErrorImportedEcKeyIncorrectCurve();
131
132 return Status::Success();
133 }
134
135 // Creates an EC_KEY for the given WebCryptoNamedCurve.
136 Status CreateEC_KEY(blink::WebCryptoNamedCurve named_curve,
137 crypto::ScopedEC_KEY* ec) {
138 int curve_nid = 0;
139 Status status = WebCryptoCurveToNid(named_curve, &curve_nid);
140 if (status.IsError())
141 return status;
142
143 ec->reset(EC_KEY_new_by_curve_name(curve_nid));
144 if (!ec->get())
145 return Status::OperationError();
146
147 return Status::Success();
148 }
149
150 // Writes an unsigned BIGNUM into |jwk|, zero-padding it to a length of
151 // |padded_length|.
152 Status WritePaddedBIGNUM(const std::string& member_name,
153 const BIGNUM* value,
154 size_t padded_length,
155 JwkWriter* jwk) {
156 std::vector<uint8_t> padded_bytes(padded_length);
157 if (!BN_bn2bin_padded(&padded_bytes.front(), padded_bytes.size(), value))
158 return Status::OperationError();
159 jwk->SetBytes(member_name, CryptoData(padded_bytes));
160 return Status::Success();
161 }
162
163 // Reads a fixed length BIGNUM from a JWK.
164 Status ReadPaddedBIGNUM(const JwkReader& jwk,
165 const std::string& member_name,
166 size_t expected_length,
167 crypto::ScopedBIGNUM* out) {
168 std::string bytes;
169 Status status = jwk.GetBytes(member_name, &bytes);
170 if (status.IsError())
171 return status;
172
173 if (bytes.size() != expected_length) {
174 return Status::JwkOctetStringWrongLength(member_name, expected_length,
175 bytes.size());
176 }
177
178 out->reset(CreateBIGNUM(bytes));
179 return Status::Success();
180 }
181
182 int GetGroupDegreeInBytes(EC_KEY* ec) {
183 const EC_GROUP* group = EC_KEY_get0_group(ec);
184 return (EC_GROUP_get_degree(group) + 7) / 8;
185 }
186
187 // Extracts the public key as affine coordinates (x,y).
188 Status GetPublicKey(EC_KEY* ec,
189 crypto::ScopedBIGNUM* x,
190 crypto::ScopedBIGNUM* y) {
191 const EC_GROUP* group = EC_KEY_get0_group(ec);
192 const EC_POINT* point = EC_KEY_get0_public_key(ec);
193
194 x->reset(BN_new());
195 y->reset(BN_new());
196
197 if (!EC_POINT_get_affine_coordinates_GFp(group, point, x->get(), y->get(),
198 NULL)) {
199 return Status::OperationError();
200 }
201
202 return Status::Success();
203 }
204
205 } // namespace
206
207 Status EcAlgorithm::GenerateKey(const blink::WebCryptoAlgorithm& algorithm,
208 bool extractable,
209 blink::WebCryptoKeyUsageMask combined_usages,
210 GenerateKeyResult* result) const {
211 Status status = CheckKeyCreationUsages(
212 all_public_key_usages_ | all_private_key_usages_, combined_usages);
213 if (status.IsError())
214 return status;
215
216 const blink::WebCryptoKeyUsageMask public_usages =
217 combined_usages & all_public_key_usages_;
218 const blink::WebCryptoKeyUsageMask private_usages =
219 combined_usages & all_private_key_usages_;
220
221 const blink::WebCryptoEcKeyGenParams* params = algorithm.ecKeyGenParams();
222
223 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
224
225 // Generate an EC key pair.
226 crypto::ScopedEC_KEY ec_private_key;
227 status = CreateEC_KEY(params->namedCurve(), &ec_private_key);
228 if (status.IsError())
229 return status;
230
231 if (!EC_KEY_generate_key(ec_private_key.get()))
232 return Status::OperationError();
233
234 // Construct an EVP_PKEY for the private key.
235 crypto::ScopedEVP_PKEY private_pkey(EVP_PKEY_new());
236 if (!private_pkey ||
237 !EVP_PKEY_set1_EC_KEY(private_pkey.get(), ec_private_key.get())) {
238 return Status::OperationError();
239 }
240
241 // Construct an EVP_PKEY for just the public key.
242 crypto::ScopedEC_KEY ec_public_key;
243 crypto::ScopedEVP_PKEY public_pkey(EVP_PKEY_new());
244 status = CreateEC_KEY(params->namedCurve(), &ec_public_key);
245 if (status.IsError())
246 return status;
247 if (!EC_KEY_set_public_key(ec_public_key.get(),
248 EC_KEY_get0_public_key(ec_private_key.get()))) {
249 return Status::OperationError();
250 }
251 if (!public_pkey ||
252 !EVP_PKEY_set1_EC_KEY(public_pkey.get(), ec_public_key.get())) {
253 return Status::OperationError();
254 }
255
256 blink::WebCryptoKey public_key;
257 blink::WebCryptoKey private_key;
258
259 blink::WebCryptoKeyAlgorithm key_algorithm =
260 blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(),
261 params->namedCurve());
262
263 // Note that extractable is unconditionally set to true. This is because per
264 // the WebCrypto spec generated public keys are always public.
265 status = CreateWebCryptoPublicKey(public_pkey.Pass(), key_algorithm, true,
266 public_usages, &public_key);
267 if (status.IsError())
268 return status;
269
270 status = CreateWebCryptoPrivateKey(private_pkey.Pass(), key_algorithm,
271 extractable, private_usages, &private_key);
272 if (status.IsError())
273 return status;
274
275 result->AssignKeyPair(public_key, private_key);
276 return Status::Success();
277 }
278
279 // TODO(eroman): This is identical to RSA.
280 Status EcAlgorithm::VerifyKeyUsagesBeforeImportKey(
281 blink::WebCryptoKeyFormat format,
282 blink::WebCryptoKeyUsageMask usages) const {
283 switch (format) {
284 case blink::WebCryptoKeyFormatSpki:
285 return CheckKeyCreationUsages(all_public_key_usages_, usages);
286 case blink::WebCryptoKeyFormatPkcs8:
287 return CheckKeyCreationUsages(all_private_key_usages_, usages);
288 case blink::WebCryptoKeyFormatJwk:
289 // The JWK could represent either a public key or private key. The usages
290 // must make sense for one of the two. The usages will be checked again by
291 // ImportKeyJwk() once the key type has been determined.
292 if (CheckKeyCreationUsages(all_private_key_usages_, usages).IsSuccess() ||
293 CheckKeyCreationUsages(all_public_key_usages_, usages).IsSuccess()) {
294 return Status::Success();
295 }
296 return Status::ErrorCreateKeyBadUsages();
297 default:
298 return Status::ErrorUnsupportedImportKeyFormat();
299 }
300 }
301
302 Status EcAlgorithm::ImportKeyPkcs8(const CryptoData& key_data,
303 const blink::WebCryptoAlgorithm& algorithm,
304 bool extractable,
305 blink::WebCryptoKeyUsageMask usages,
306 blink::WebCryptoKey* key) const {
307 crypto::ScopedEVP_PKEY private_key;
308 Status status =
309 ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_EC, &private_key);
310 if (status.IsError())
311 return status;
312
313 const blink::WebCryptoEcKeyImportParams* params =
314 algorithm.ecKeyImportParams();
315
316 status = VerifyEcKeyAfterSpkiOrPkcs8Import(private_key.get(),
317 params->namedCurve());
318 if (status.IsError())
319 return status;
320
321 return CreateWebCryptoPrivateKey(private_key.Pass(),
322 blink::WebCryptoKeyAlgorithm::createEc(
323 algorithm.id(), params->namedCurve()),
324 extractable, usages, key);
325 }
326
327 Status EcAlgorithm::ImportKeySpki(const CryptoData& key_data,
328 const blink::WebCryptoAlgorithm& algorithm,
329 bool extractable,
330 blink::WebCryptoKeyUsageMask usages,
331 blink::WebCryptoKey* key) const {
332 crypto::ScopedEVP_PKEY public_key;
333 Status status =
334 ImportUnverifiedPkeyFromSpki(key_data, EVP_PKEY_EC, &public_key);
335 if (status.IsError())
336 return status;
337
338 const blink::WebCryptoEcKeyImportParams* params =
339 algorithm.ecKeyImportParams();
340
341 status =
342 VerifyEcKeyAfterSpkiOrPkcs8Import(public_key.get(), params->namedCurve());
343 if (status.IsError())
344 return status;
345
346 return CreateWebCryptoPublicKey(public_key.Pass(),
347 blink::WebCryptoKeyAlgorithm::createEc(
348 algorithm.id(), params->namedCurve()),
349 extractable, usages, key);
350 }
351
352 // The format for JWK EC keys is given by:
353 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6. 2
354 Status EcAlgorithm::ImportKeyJwk(const CryptoData& key_data,
355 const blink::WebCryptoAlgorithm& algorithm,
356 bool extractable,
357 blink::WebCryptoKeyUsageMask usages,
358 blink::WebCryptoKey* key) const {
359 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
360
361 const blink::WebCryptoEcKeyImportParams* params =
362 algorithm.ecKeyImportParams();
363
364 // When importing EC keys from JWK there may be up to *three* separate curve
365 // names:
366 //
367 // (1) The one given to WebCrypto's importKey (params->namedCurve()).
368 // (2) JWK's "crv" member
369 // (3) A curve implied by JWK's "alg" member.
370 //
371 // (In the case of ECDSA, the "alg" member implicitly names a curve and hash)
372
373 JwkReader jwk;
374 Status status = jwk.Init(key_data, extractable, usages, "EC",
375 GetJwkAlgorithm(params->namedCurve()));
376 if (status.IsError())
377 return status;
378
379 // Verify that "crv" matches expected curve.
380 blink::WebCryptoNamedCurve jwk_crv = blink::WebCryptoNamedCurveP256;
381 status = ReadJwkCrv(jwk, &jwk_crv);
382 if (status.IsError())
383 return status;
384 if (jwk_crv != params->namedCurve())
385 return Status::ErrorJwkIncorrectCrv();
386
387 // Only private keys have a "d" parameter. The key may still be invalid, but
388 // tentatively decide if it is a public or private key.
389 bool is_private_key = jwk.HasMember("d");
390
391 // Now that the key type is known, verify the usages.
392 status = CheckKeyCreationUsages(
393 is_private_key ? all_private_key_usages_ : all_public_key_usages_,
394 usages);
395 if (status.IsError())
396 return status;
397
398 // Create an EC_KEY.
399 crypto::ScopedEC_KEY ec;
400 status = CreateEC_KEY(params->namedCurve(), &ec);
401 if (status.IsError())
402 return status;
403
404 // JWK requires the length of x, y, d to match the group degree.
405 int degree_bytes = GetGroupDegreeInBytes(ec.get());
406
407 // Read the public key's uncompressed affine coordinates.
408 crypto::ScopedBIGNUM x;
409 status = ReadPaddedBIGNUM(jwk, "x", degree_bytes, &x);
410 if (status.IsError())
411 return status;
412
413 crypto::ScopedBIGNUM y;
414 status = ReadPaddedBIGNUM(jwk, "y", degree_bytes, &y);
415 if (status.IsError())
416 return status;
417
418 // TODO(eroman): This internally runs EC_KEY_check_key(). Can avoid calling it
419 // again by the JWK import code if private key were set before public key.
420 if (!EC_KEY_set_public_key_affine_coordinates(ec.get(), x.get(), y.get()))
421 return Status::OperationError();
422
423 // Extract the "d" parameters.
424 if (is_private_key) {
425 crypto::ScopedBIGNUM d;
426 status = ReadPaddedBIGNUM(jwk, "d", degree_bytes, &d);
427 if (status.IsError())
428 return status;
429
430 if (!EC_KEY_set_private_key(ec.get(), d.get()))
431 return Status::OperationError();
432 }
433
434 // Verify the key.
435 if (!EC_KEY_check_key(ec.get()))
436 return Status::ErrorEcKeyInvalid();
437
438 // Wrap the EC_KEY into an EVP_PKEY.
439 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
440 if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()))
441 return Status::OperationError();
442
443 blink::WebCryptoKeyAlgorithm key_algorithm =
444 blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(),
445 params->namedCurve());
446
447 // Wrap the EVP_PKEY into a WebCryptoKey
448 if (is_private_key) {
449 return CreateWebCryptoPrivateKey(pkey.Pass(), key_algorithm, extractable,
450 usages, key);
451 }
452 return CreateWebCryptoPublicKey(pkey.Pass(), key_algorithm, extractable,
453 usages, key);
454 }
455
456 Status EcAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key,
457 std::vector<uint8_t>* buffer) const {
458 if (key.type() != blink::WebCryptoKeyTypePrivate)
459 return Status::ErrorUnexpectedKeyType();
460 *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
461 return Status::Success();
462 }
463
464 Status EcAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key,
465 std::vector<uint8_t>* buffer) const {
466 if (key.type() != blink::WebCryptoKeyTypePublic)
467 return Status::ErrorUnexpectedKeyType();
468 *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
469 return Status::Success();
470 }
471
472 // The format for JWK EC keys is given by:
473 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6. 2
474 Status EcAlgorithm::ExportKeyJwk(const blink::WebCryptoKey& key,
475 std::vector<uint8_t>* buffer) const {
476 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
477
478 EVP_PKEY* pkey = AsymKeyOpenSsl::Cast(key)->key();
479
480 crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey));
481 if (!ec.get())
482 return Status::ErrorUnexpected();
483
484 // No "alg" is set for EC keys.
485 JwkWriter jwk(std::string(), key.extractable(), key.usages(), "EC");
486
487 // Set the crv
488 std::string crv;
489 Status status =
490 WebCryptoCurveToJwkCrv(key.algorithm().ecParams()->namedCurve(), &crv);
491 if (status.IsError())
492 return status;
493
494 int degree_bytes = GetGroupDegreeInBytes(ec.get());
495
496 jwk.SetString("crv", crv);
497
498 crypto::ScopedBIGNUM x;
499 crypto::ScopedBIGNUM y;
500 status = GetPublicKey(ec.get(), &x, &y);
501 if (status.IsError())
502 return status;
503
504 status = WritePaddedBIGNUM("x", x.get(), degree_bytes, &jwk);
505 if (status.IsError())
506 return status;
507
508 status = WritePaddedBIGNUM("y", y.get(), degree_bytes, &jwk);
509 if (status.IsError())
510 return status;
511
512 if (key.type() == blink::WebCryptoKeyTypePrivate) {
513 const BIGNUM* d = EC_KEY_get0_private_key(ec.get());
514 status = WritePaddedBIGNUM("d", d, degree_bytes, &jwk);
515 if (status.IsError())
516 return status;
517 }
518
519 jwk.ToJson(buffer);
520 return Status::Success();
521 }
522
523 Status EcAlgorithm::SerializeKeyForClone(
524 const blink::WebCryptoKey& key,
525 blink::WebVector<uint8_t>* key_data) const {
526 key_data->assign(AsymKeyOpenSsl::Cast(key)->serialized_key_data());
527 return Status::Success();
528 }
529
530 // TODO(eroman): Defer import to the crypto thread. http://crbug.com/430763
531 Status EcAlgorithm::DeserializeKeyForClone(
532 const blink::WebCryptoKeyAlgorithm& algorithm,
533 blink::WebCryptoKeyType type,
534 bool extractable,
535 blink::WebCryptoKeyUsageMask usages,
536 const CryptoData& key_data,
537 blink::WebCryptoKey* key) const {
538 blink::WebCryptoAlgorithm import_algorithm = CreateEcImportAlgorithm(
539 algorithm.id(), algorithm.ecParams()->namedCurve());
540
541 Status status;
542
543 switch (type) {
544 case blink::WebCryptoKeyTypePublic:
545 status =
546 ImportKeySpki(key_data, import_algorithm, extractable, usages, key);
547 break;
548 case blink::WebCryptoKeyTypePrivate:
549 status =
550 ImportKeyPkcs8(key_data, import_algorithm, extractable, usages, key);
551 break;
552 default:
553 return Status::ErrorUnexpected();
554 }
555
556 // There is some duplicated information in the serialized format used by
557 // structured clone (since the KeyAlgorithm is serialized separately from the
558 // key data). Use this extra information to further validate what was
559 // deserialized from the key data.
560
561 if (algorithm.id() != key->algorithm().id())
562 return Status::ErrorUnexpected();
563
564 if (type != key->type())
565 return Status::ErrorUnexpected();
566
567 if (algorithm.ecParams()->namedCurve() !=
568 key->algorithm().ecParams()->namedCurve()) {
569 return Status::ErrorUnexpected();
570 }
571
572 return Status::Success();
573 }
574
575 } // namespace webcrypto
576
577 } // namespace content
OLDNEW
« no previous file with comments | « content/child/webcrypto/openssl/ec_key_openssl.h ('k') | content/child/webcrypto/openssl/ecdsa_openssl.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698