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.
5 #include "components/webcrypto/algorithms/ec.h"
7 #include <openssl/ec.h>
8 #include <openssl/ec_key.h>
9 #include <openssl/evp.h>
10 #include <openssl/pkcs12.h>
12 #include "base/logging.h"
13 #include "base/stl_util.h"
14 #include "components/webcrypto/algorithms/util_openssl.h"
15 #include "components/webcrypto/crypto_data.h"
16 #include "components/webcrypto/generate_key_result.h"
17 #include "components/webcrypto/jwk.h"
18 #include "components/webcrypto/key.h"
19 #include "components/webcrypto/status.h"
20 #include "components/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"
30 // Maps a blink::WebCryptoNamedCurve to the corresponding NID used by
32 Status
WebCryptoCurveToNid(blink::WebCryptoNamedCurve named_curve
, int* nid
) {
33 switch (named_curve
) {
34 case blink::WebCryptoNamedCurveP256
:
35 *nid
= NID_X9_62_prime256v1
;
36 return Status::Success();
37 case blink::WebCryptoNamedCurveP384
:
39 return Status::Success();
40 case blink::WebCryptoNamedCurveP521
:
42 return Status::Success();
44 return Status::ErrorUnsupported();
47 // Maps a BoringSSL NID to the corresponding WebCrypto named curve.
48 Status
NidToWebCryptoCurve(int nid
, blink::WebCryptoNamedCurve
* named_curve
) {
50 case NID_X9_62_prime256v1
:
51 *named_curve
= blink::WebCryptoNamedCurveP256
;
52 return Status::Success();
54 *named_curve
= blink::WebCryptoNamedCurveP384
;
55 return Status::Success();
57 *named_curve
= blink::WebCryptoNamedCurveP521
;
58 return Status::Success();
60 return Status::ErrorImportedEcKeyIncorrectCurve();
63 struct JwkCrvMapping
{
64 const char* jwk_curve
;
65 blink::WebCryptoNamedCurve named_curve
;
68 const JwkCrvMapping kJwkCrvMappings
[] = {
69 {"P-256", blink::WebCryptoNamedCurveP256
},
70 {"P-384", blink::WebCryptoNamedCurveP384
},
71 {"P-521", blink::WebCryptoNamedCurveP521
},
74 // Gets the "crv" parameter from a JWK and converts it to a WebCryptoNamedCurve.
75 Status
ReadJwkCrv(const JwkReader
& jwk
,
76 blink::WebCryptoNamedCurve
* named_curve
) {
77 std::string jwk_curve
;
78 Status status
= jwk
.GetString("crv", &jwk_curve
);
82 for (size_t i
= 0; i
< arraysize(kJwkCrvMappings
); ++i
) {
83 if (kJwkCrvMappings
[i
].jwk_curve
== jwk_curve
) {
84 *named_curve
= kJwkCrvMappings
[i
].named_curve
;
85 return Status::Success();
89 return Status::ErrorJwkIncorrectCrv();
92 // Converts a WebCryptoNamedCurve to an equivalent JWK "crv".
93 Status
WebCryptoCurveToJwkCrv(blink::WebCryptoNamedCurve named_curve
,
94 std::string
* jwk_crv
) {
95 for (size_t i
= 0; i
< arraysize(kJwkCrvMappings
); ++i
) {
96 if (kJwkCrvMappings
[i
].named_curve
== named_curve
) {
97 *jwk_crv
= kJwkCrvMappings
[i
].jwk_curve
;
98 return Status::Success();
101 return Status::ErrorUnexpected();
104 // Verifies that an EC key imported from PKCS8 or SPKI format is correct.
105 // This involves verifying the key validity, and the NID for the named curve.
106 // Also removes the EC_PKEY_NO_PUBKEY flag if present.
107 Status
VerifyEcKeyAfterSpkiOrPkcs8Import(
109 blink::WebCryptoNamedCurve expected_named_curve
) {
110 crypto::OpenSSLErrStackTracer
err_tracer(FROM_HERE
);
112 crypto::ScopedEC_KEY
ec(EVP_PKEY_get1_EC_KEY(pkey
));
114 return Status::ErrorUnexpected();
116 // When importing an ECPrivateKey, the public key is optional. If it was
117 // omitted then the public key will be calculated by BoringSSL and added into
118 // the EC_KEY. However an encoding flag is set such that when exporting to
119 // PKCS8 format the public key is once again omitted. Remove this flag.
120 unsigned int enc_flags
= EC_KEY_get_enc_flags(ec
.get());
121 enc_flags
&= ~EC_PKEY_NO_PUBKEY
;
122 EC_KEY_set_enc_flags(ec
.get(), enc_flags
);
124 if (!EC_KEY_check_key(ec
.get()))
125 return Status::ErrorEcKeyInvalid();
127 // Make sure the curve matches the expected curve name.
128 int curve_nid
= EC_GROUP_get_curve_name(EC_KEY_get0_group(ec
.get()));
129 blink::WebCryptoNamedCurve named_curve
= blink::WebCryptoNamedCurveP256
;
130 Status status
= NidToWebCryptoCurve(curve_nid
, &named_curve
);
131 if (status
.IsError())
134 if (named_curve
!= expected_named_curve
)
135 return Status::ErrorImportedEcKeyIncorrectCurve();
137 return Status::Success();
140 // Creates an EC_KEY for the given WebCryptoNamedCurve.
141 Status
CreateEC_KEY(blink::WebCryptoNamedCurve named_curve
,
142 crypto::ScopedEC_KEY
* ec
) {
144 Status status
= WebCryptoCurveToNid(named_curve
, &curve_nid
);
145 if (status
.IsError())
148 ec
->reset(EC_KEY_new_by_curve_name(curve_nid
));
150 return Status::OperationError();
152 return Status::Success();
155 // Writes an unsigned BIGNUM into |jwk|, zero-padding it to a length of
157 Status
WritePaddedBIGNUM(const std::string
& member_name
,
159 size_t padded_length
,
161 std::vector
<uint8_t> padded_bytes(padded_length
);
162 if (!BN_bn2bin_padded(vector_as_array(&padded_bytes
), padded_bytes
.size(),
164 return Status::OperationError();
166 jwk
->SetBytes(member_name
, CryptoData(padded_bytes
));
167 return Status::Success();
170 // Reads a fixed length BIGNUM from a JWK.
171 Status
ReadPaddedBIGNUM(const JwkReader
& jwk
,
172 const std::string
& member_name
,
173 size_t expected_length
,
174 crypto::ScopedBIGNUM
* out
) {
176 Status status
= jwk
.GetBytes(member_name
, &bytes
);
177 if (status
.IsError())
180 if (bytes
.size() != expected_length
) {
181 return Status::JwkOctetStringWrongLength(member_name
, expected_length
,
185 out
->reset(CreateBIGNUM(bytes
));
186 return Status::Success();
189 int GetGroupDegreeInBytes(EC_KEY
* ec
) {
190 const EC_GROUP
* group
= EC_KEY_get0_group(ec
);
191 return NumBitsToBytes(EC_GROUP_get_degree(group
));
194 // Extracts the public key as affine coordinates (x,y).
195 Status
GetPublicKey(EC_KEY
* ec
,
196 crypto::ScopedBIGNUM
* x
,
197 crypto::ScopedBIGNUM
* y
) {
198 const EC_GROUP
* group
= EC_KEY_get0_group(ec
);
199 const EC_POINT
* point
= EC_KEY_get0_public_key(ec
);
204 if (!EC_POINT_get_affine_coordinates_GFp(group
, point
, x
->get(), y
->get(),
206 return Status::OperationError();
209 return Status::Success();
214 Status
EcAlgorithm::GenerateKey(const blink::WebCryptoAlgorithm
& algorithm
,
216 blink::WebCryptoKeyUsageMask combined_usages
,
217 GenerateKeyResult
* result
) const {
218 blink::WebCryptoKeyUsageMask public_usages
= 0;
219 blink::WebCryptoKeyUsageMask private_usages
= 0;
221 Status status
= GetUsagesForGenerateAsymmetricKey(
222 combined_usages
, all_public_key_usages_
, all_private_key_usages_
,
223 &public_usages
, &private_usages
);
224 if (status
.IsError())
227 const blink::WebCryptoEcKeyGenParams
* params
= algorithm
.ecKeyGenParams();
229 crypto::OpenSSLErrStackTracer
err_tracer(FROM_HERE
);
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())
237 if (!EC_KEY_generate_key(ec_private_key
.get()))
238 return Status::OperationError();
240 // Construct an EVP_PKEY for the private key.
241 crypto::ScopedEVP_PKEY
private_pkey(EVP_PKEY_new());
243 !EVP_PKEY_set1_EC_KEY(private_pkey
.get(), ec_private_key
.get())) {
244 return Status::OperationError();
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())
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();
258 !EVP_PKEY_set1_EC_KEY(public_pkey
.get(), ec_public_key
.get())) {
259 return Status::OperationError();
262 blink::WebCryptoKey public_key
;
263 blink::WebCryptoKey private_key
;
265 blink::WebCryptoKeyAlgorithm key_algorithm
=
266 blink::WebCryptoKeyAlgorithm::createEc(algorithm
.id(),
267 params
->namedCurve());
269 // Note that extractable is unconditionally set to true. This is because per
270 // the WebCrypto spec generated public keys are always extractable.
271 status
= CreateWebCryptoPublicKey(public_pkey
.Pass(), key_algorithm
, true,
272 public_usages
, &public_key
);
273 if (status
.IsError())
276 status
= CreateWebCryptoPrivateKey(private_pkey
.Pass(), key_algorithm
,
277 extractable
, private_usages
, &private_key
);
278 if (status
.IsError())
281 result
->AssignKeyPair(public_key
, private_key
);
282 return Status::Success();
285 Status
EcAlgorithm::VerifyKeyUsagesBeforeImportKey(
286 blink::WebCryptoKeyFormat format
,
287 blink::WebCryptoKeyUsageMask usages
) const {
288 return VerifyUsagesBeforeImportAsymmetricKey(format
, all_public_key_usages_
,
289 all_private_key_usages_
, usages
);
292 Status
EcAlgorithm::ImportKeyPkcs8(const CryptoData
& key_data
,
293 const blink::WebCryptoAlgorithm
& algorithm
,
295 blink::WebCryptoKeyUsageMask usages
,
296 blink::WebCryptoKey
* key
) const {
297 crypto::ScopedEVP_PKEY private_key
;
299 ImportUnverifiedPkeyFromPkcs8(key_data
, EVP_PKEY_EC
, &private_key
);
300 if (status
.IsError())
303 const blink::WebCryptoEcKeyImportParams
* params
=
304 algorithm
.ecKeyImportParams();
306 status
= VerifyEcKeyAfterSpkiOrPkcs8Import(private_key
.get(),
307 params
->namedCurve());
308 if (status
.IsError())
311 return CreateWebCryptoPrivateKey(private_key
.Pass(),
312 blink::WebCryptoKeyAlgorithm::createEc(
313 algorithm
.id(), params
->namedCurve()),
314 extractable
, usages
, key
);
317 Status
EcAlgorithm::ImportKeySpki(const CryptoData
& key_data
,
318 const blink::WebCryptoAlgorithm
& algorithm
,
320 blink::WebCryptoKeyUsageMask usages
,
321 blink::WebCryptoKey
* key
) const {
322 crypto::ScopedEVP_PKEY public_key
;
324 ImportUnverifiedPkeyFromSpki(key_data
, EVP_PKEY_EC
, &public_key
);
325 if (status
.IsError())
328 const blink::WebCryptoEcKeyImportParams
* params
=
329 algorithm
.ecKeyImportParams();
332 VerifyEcKeyAfterSpkiOrPkcs8Import(public_key
.get(), params
->namedCurve());
333 if (status
.IsError())
336 return CreateWebCryptoPublicKey(public_key
.Pass(),
337 blink::WebCryptoKeyAlgorithm::createEc(
338 algorithm
.id(), params
->namedCurve()),
339 extractable
, usages
, key
);
342 // The format for JWK EC keys is given by:
343 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6.2
344 Status
EcAlgorithm::ImportKeyJwk(const CryptoData
& key_data
,
345 const blink::WebCryptoAlgorithm
& algorithm
,
347 blink::WebCryptoKeyUsageMask usages
,
348 blink::WebCryptoKey
* key
) const {
349 crypto::OpenSSLErrStackTracer
err_tracer(FROM_HERE
);
351 const blink::WebCryptoEcKeyImportParams
* params
=
352 algorithm
.ecKeyImportParams();
354 // When importing EC keys from JWK there may be up to *three* separate curve
357 // (1) The one given to WebCrypto's importKey (params->namedCurve()).
358 // (2) JWK's "crv" member
359 // (3) A curve implied by JWK's "alg" member.
361 // (In the case of ECDSA, the "alg" member implicitly names a curve and hash)
364 Status status
= jwk
.Init(key_data
, extractable
, usages
, "EC",
365 GetJwkAlgorithm(params
->namedCurve()));
366 if (status
.IsError())
369 // Verify that "crv" matches expected curve.
370 blink::WebCryptoNamedCurve jwk_crv
= blink::WebCryptoNamedCurveP256
;
371 status
= ReadJwkCrv(jwk
, &jwk_crv
);
372 if (status
.IsError())
374 if (jwk_crv
!= params
->namedCurve())
375 return Status::ErrorJwkIncorrectCrv();
377 // Only private keys have a "d" parameter. The key may still be invalid, but
378 // tentatively decide if it is a public or private key.
379 bool is_private_key
= jwk
.HasMember("d");
381 // Now that the key type is known, verify the usages.
382 status
= CheckKeyCreationUsages(
383 is_private_key
? all_private_key_usages_
: all_public_key_usages_
, usages
,
385 if (status
.IsError())
389 crypto::ScopedEC_KEY ec
;
390 status
= CreateEC_KEY(params
->namedCurve(), &ec
);
391 if (status
.IsError())
394 // JWK requires the length of x, y, d to match the group degree.
395 int degree_bytes
= GetGroupDegreeInBytes(ec
.get());
397 // Read the public key's uncompressed affine coordinates.
398 crypto::ScopedBIGNUM x
;
399 status
= ReadPaddedBIGNUM(jwk
, "x", degree_bytes
, &x
);
400 if (status
.IsError())
403 crypto::ScopedBIGNUM y
;
404 status
= ReadPaddedBIGNUM(jwk
, "y", degree_bytes
, &y
);
405 if (status
.IsError())
408 // TODO(eroman): Distinguish more accurately between a DataError and
409 // OperationError. In general if this fails it was due to the key being an
411 if (!EC_KEY_set_public_key_affine_coordinates(ec
.get(), x
.get(), y
.get()))
412 return Status::DataError();
414 // Extract the "d" parameters.
415 if (is_private_key
) {
416 crypto::ScopedBIGNUM d
;
417 status
= ReadPaddedBIGNUM(jwk
, "d", degree_bytes
, &d
);
418 if (status
.IsError())
421 if (!EC_KEY_set_private_key(ec
.get(), d
.get()))
422 return Status::OperationError();
426 if (!EC_KEY_check_key(ec
.get()))
427 return Status::ErrorEcKeyInvalid();
429 // Wrap the EC_KEY into an EVP_PKEY.
430 crypto::ScopedEVP_PKEY
pkey(EVP_PKEY_new());
431 if (!pkey
|| !EVP_PKEY_set1_EC_KEY(pkey
.get(), ec
.get()))
432 return Status::OperationError();
434 blink::WebCryptoKeyAlgorithm key_algorithm
=
435 blink::WebCryptoKeyAlgorithm::createEc(algorithm
.id(),
436 params
->namedCurve());
438 // Wrap the EVP_PKEY into a WebCryptoKey
439 if (is_private_key
) {
440 return CreateWebCryptoPrivateKey(pkey
.Pass(), key_algorithm
, extractable
,
443 return CreateWebCryptoPublicKey(pkey
.Pass(), key_algorithm
, extractable
,
447 Status
EcAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey
& key
,
448 std::vector
<uint8_t>* buffer
) const {
449 if (key
.type() != blink::WebCryptoKeyTypePrivate
)
450 return Status::ErrorUnexpectedKeyType();
451 // This relies on the fact that PKCS8 formatted data was already
452 // associated with the key during its creation (used by
453 // structured clone).
454 *buffer
= GetSerializedKeyData(key
);
455 return Status::Success();
458 Status
EcAlgorithm::ExportKeySpki(const blink::WebCryptoKey
& key
,
459 std::vector
<uint8_t>* buffer
) const {
460 if (key
.type() != blink::WebCryptoKeyTypePublic
)
461 return Status::ErrorUnexpectedKeyType();
462 // This relies on the fact that SPKI formatted data was already
463 // associated with the key during its creation (used by
464 // structured clone).
465 *buffer
= GetSerializedKeyData(key
);
466 return Status::Success();
469 // The format for JWK EC keys is given by:
470 // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6.2
471 Status
EcAlgorithm::ExportKeyJwk(const blink::WebCryptoKey
& key
,
472 std::vector
<uint8_t>* buffer
) const {
473 crypto::OpenSSLErrStackTracer
err_tracer(FROM_HERE
);
475 EVP_PKEY
* pkey
= GetEVP_PKEY(key
);
477 crypto::ScopedEC_KEY
ec(EVP_PKEY_get1_EC_KEY(pkey
));
479 return Status::ErrorUnexpected();
481 // No "alg" is set for EC keys.
482 JwkWriter
jwk(std::string(), key
.extractable(), key
.usages(), "EC");
487 WebCryptoCurveToJwkCrv(key
.algorithm().ecParams()->namedCurve(), &crv
);
488 if (status
.IsError())
491 int degree_bytes
= GetGroupDegreeInBytes(ec
.get());
493 jwk
.SetString("crv", crv
);
495 crypto::ScopedBIGNUM x
;
496 crypto::ScopedBIGNUM y
;
497 status
= GetPublicKey(ec
.get(), &x
, &y
);
498 if (status
.IsError())
501 status
= WritePaddedBIGNUM("x", x
.get(), degree_bytes
, &jwk
);
502 if (status
.IsError())
505 status
= WritePaddedBIGNUM("y", y
.get(), degree_bytes
, &jwk
);
506 if (status
.IsError())
509 if (key
.type() == blink::WebCryptoKeyTypePrivate
) {
510 const BIGNUM
* d
= EC_KEY_get0_private_key(ec
.get());
511 status
= WritePaddedBIGNUM("d", d
, degree_bytes
, &jwk
);
512 if (status
.IsError())
517 return Status::Success();
520 // TODO(eroman): Defer import to the crypto thread. http://crbug.com/430763
521 Status
EcAlgorithm::DeserializeKeyForClone(
522 const blink::WebCryptoKeyAlgorithm
& algorithm
,
523 blink::WebCryptoKeyType type
,
525 blink::WebCryptoKeyUsageMask usages
,
526 const CryptoData
& key_data
,
527 blink::WebCryptoKey
* key
) const {
528 blink::WebCryptoAlgorithm import_algorithm
= CreateEcImportAlgorithm(
529 algorithm
.id(), algorithm
.ecParams()->namedCurve());
534 case blink::WebCryptoKeyTypePublic
:
536 ImportKeySpki(key_data
, import_algorithm
, extractable
, usages
, key
);
538 case blink::WebCryptoKeyTypePrivate
:
540 ImportKeyPkcs8(key_data
, import_algorithm
, extractable
, usages
, key
);
543 return Status::ErrorUnexpected();
546 // There is some duplicated information in the serialized format used by
547 // structured clone (since the KeyAlgorithm is serialized separately from the
548 // key data). Use this extra information to further validate what was
549 // deserialized from the key data.
551 if (algorithm
.id() != key
->algorithm().id())
552 return Status::ErrorUnexpected();
554 if (type
!= key
->type())
555 return Status::ErrorUnexpected();
557 if (algorithm
.ecParams()->namedCurve() !=
558 key
->algorithm().ecParams()->namedCurve()) {
559 return Status::ErrorUnexpected();
562 return Status::Success();
565 } // namespace webcrypto