Chromium Code Reviews| Index: content/child/webcrypto/openssl/ec_key_openssl.cc |
| diff --git a/content/child/webcrypto/openssl/ec_key_openssl.cc b/content/child/webcrypto/openssl/ec_key_openssl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..918497a7db9d22a3c598171d78f82ba2cf8e2f77 |
| --- /dev/null |
| +++ b/content/child/webcrypto/openssl/ec_key_openssl.cc |
| @@ -0,0 +1,511 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/child/webcrypto/openssl/ec_key_openssl.h" |
| + |
| +#include <openssl/ec.h> |
| +#include <openssl/ec_key.h> |
| +#include <openssl/evp.h> |
| +#include <openssl/pkcs12.h> |
| + |
| +#include "base/logging.h" |
| +#include "base/stl_util.h" |
| +#include "content/child/webcrypto/crypto_data.h" |
| +#include "content/child/webcrypto/generate_key_result.h" |
| +#include "content/child/webcrypto/jwk.h" |
| +#include "content/child/webcrypto/openssl/key_openssl.h" |
| +#include "content/child/webcrypto/openssl/util_openssl.h" |
| +#include "content/child/webcrypto/status.h" |
| +#include "content/child/webcrypto/webcrypto_util.h" |
| +#include "crypto/openssl_util.h" |
| +#include "crypto/scoped_openssl_types.h" |
| +#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h" |
| +#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h" |
| + |
| +namespace content { |
| + |
| +namespace webcrypto { |
| + |
| +namespace { |
| + |
| +// Maps a blink::WebCryptoNamedCurve to the corresponding NID used by |
| +// BoringSSL. |
| +Status WebCryptoCurveToNid(blink::WebCryptoNamedCurve named_curve, int* nid) { |
| + switch (named_curve) { |
| + case blink::WebCryptoNamedCurveP256: |
| + *nid = NID_X9_62_prime256v1; |
| + return Status::Success(); |
| + case blink::WebCryptoNamedCurveP384: |
| + *nid = NID_secp384r1; |
| + return Status::Success(); |
| + case blink::WebCryptoNamedCurveP521: |
| + *nid = NID_secp521r1; |
| + return Status::Success(); |
| + } |
| + return Status::ErrorUnsupported(); |
| +} |
| + |
| +// Maps a BoringSSL NID to the corresponding WebCrypto named curve. |
| +Status NidToWebCryptoCurve(int nid, blink::WebCryptoNamedCurve* named_curve) { |
| + switch (nid) { |
| + case NID_X9_62_prime256v1: |
| + *named_curve = blink::WebCryptoNamedCurveP256; |
| + return Status::Success(); |
| + case NID_secp384r1: |
| + *named_curve = blink::WebCryptoNamedCurveP384; |
| + return Status::Success(); |
| + case NID_secp521r1: |
| + *named_curve = blink::WebCryptoNamedCurveP521; |
| + return Status::Success(); |
| + } |
| + return Status::ErrorImportedEcKeyIncorrectCurve(); |
| +} |
| + |
| +struct JwkCrvMapping { |
| + const char* jwk_curve; |
| + blink::WebCryptoNamedCurve named_curve; |
| +}; |
| + |
| +const JwkCrvMapping kJwkCrvMappings[] = { |
| + {"P-256", blink::WebCryptoNamedCurveP256}, |
| + {"P-384", blink::WebCryptoNamedCurveP384}, |
| + {"P-521", blink::WebCryptoNamedCurveP521}, |
| +}; |
| + |
| +// Gets the "crv" parameter from a JWK and converts it to a WebCryptoNamedCurve. |
| +Status GetJwkCrv(const JwkReader& jwk, |
| + blink::WebCryptoNamedCurve* named_curve) { |
| + std::string jwk_curve; |
| + Status status = jwk.GetString("crv", &jwk_curve); |
| + if (status.IsError()) |
| + return status; |
| + |
| + for (size_t i = 0; i < arraysize(kJwkCrvMappings); ++i) { |
| + if (kJwkCrvMappings[i].jwk_curve == jwk_curve) { |
| + *named_curve = kJwkCrvMappings[i].named_curve; |
| + return Status::Success(); |
| + } |
| + } |
| + |
| + return Status::ErrorJwkIncorrectCrv(); |
| +} |
| + |
| +// Converts a WebCryptoNamedCurve to an equivalent JWK "crv". |
| +Status WebCryptoCurveToJwkCrv(blink::WebCryptoNamedCurve named_curve, |
| + std::string* jwk_crv) { |
| + for (size_t i = 0; i < arraysize(kJwkCrvMappings); ++i) { |
| + if (kJwkCrvMappings[i].named_curve == named_curve) { |
| + *jwk_crv = kJwkCrvMappings[i].jwk_curve; |
| + return Status::Success(); |
| + } |
| + } |
| + return Status::ErrorUnexpected(); |
| +} |
| + |
| +// Verifies that an EC key imported from PKCS8 or SPKI format is correct. |
| +// This involves verifying the key validity, and the NID for the named curve. |
| +Status VerifyEcKeyAfterSpkiOrPkcs8Import( |
| + EVP_PKEY* pkey, |
| + blink::WebCryptoNamedCurve expected_named_curve) { |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + // TODO(eroman): Validate the algorithm OID against the webcrypto provided |
| + // 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
|
| + |
| + crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey)); |
| + if (!ec.get()) |
| + return Status::ErrorUnexpected(); |
| + |
| + // TODO(eroman): Is this necessary? From my tests it seems that BoringSSL |
| + // already does these checks when setting the public key's affine coordinates. |
| + if (!EC_KEY_check_key(ec.get())) |
| + return Status::ErrorEcKeyInvalid(); |
| + |
| + // Make sure the curve matches the expected curve name. |
| + int curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec.get())); |
| + blink::WebCryptoNamedCurve named_curve; |
| + Status status = NidToWebCryptoCurve(curve_nid, &named_curve); |
| + if (status.IsError()) |
| + return status; |
| + |
| + if (named_curve != expected_named_curve) |
| + return Status::ErrorImportedEcKeyIncorrectCurve(); |
| + |
| + return Status::Success(); |
| +} |
| + |
| +// Creates an EC_KEY for the given WebCryptoNamedCurve. |
| +Status CreateEC_KEY(blink::WebCryptoNamedCurve named_curve, |
| + crypto::ScopedEC_KEY* ec) { |
| + int curve_nid; |
| + Status status = WebCryptoCurveToNid(named_curve, &curve_nid); |
| + if (status.IsError()) |
| + return status; |
| + |
| + ec->reset(EC_KEY_new_by_curve_name(curve_nid)); |
| + if (!ec->get()) |
| + return Status::OperationError(); |
| + |
| + return Status::Success(); |
| +} |
| + |
| +// Writes a BIGNUM into a JWK. |
| +void WriteBIGNUM(const std::string& member_name, |
| + const BIGNUM* value, |
| + JwkWriter* jwk) { |
| + 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
|
| +} |
| + |
| +// Reads a BIGNUM from a JWK. |
| +Status GetBIGNUM(const JwkReader& jwk, |
| + const std::string& key, |
| + 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.
|
| + std::string bytes; |
| + Status status = jwk.GetBytes(key, &bytes); |
| + if (status.IsError()) |
| + return status; |
| + out->reset(CreateBIGNUM(bytes)); |
| + return Status::Success(); |
| +} |
| + |
| +// Assigns the EC_KEY's public key using the (uncompressed) affine coordinates |
| +// specified in |jwk|. |
| +Status SetPublicKeyMembersFromJwk(const JwkReader& jwk, EC_KEY* ec) { |
| + crypto::ScopedBIGNUM x; |
| + Status status = GetBIGNUM(jwk, "x", &x); |
| + if (status.IsError()) |
| + return status; |
| + |
| + crypto::ScopedBIGNUM y; |
| + status = GetBIGNUM(jwk, "y", &y); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // TODO(eroman): This internally runs EC_KEY_check_key(). Can avoid calling it |
| + // again by the JWK import code if private key were set before public key. |
| + if (!EC_KEY_set_public_key_affine_coordinates(ec, x.get(), y.get())) |
| + return Status::OperationError(); |
| + |
| + return Status::Success(); |
| +} |
| + |
| +// Extracts the public key as affine coordinates (x,y). |
| +Status GetPublicKey(EC_KEY* ec, |
| + crypto::ScopedBIGNUM* x, |
| + crypto::ScopedBIGNUM* y) { |
| + const EC_GROUP* group = EC_KEY_get0_group(ec); |
| + const EC_POINT* point = EC_KEY_get0_public_key(ec); |
| + |
| + x->reset(BN_new()); |
| + y->reset(BN_new()); |
| + |
| + if (!EC_POINT_get_affine_coordinates_GFp(group, point, x->get(), y->get(), |
| + NULL)) { |
| + return Status::OperationError(); |
| + } |
| + |
| + return Status::Success(); |
| +} |
| + |
| +} // namespace |
| + |
| +Status EcAlgorithm::GenerateKey(const blink::WebCryptoAlgorithm& algorithm, |
| + bool extractable, |
| + blink::WebCryptoKeyUsageMask combined_usages, |
| + GenerateKeyResult* result) const { |
| + Status status = CheckKeyCreationUsages( |
| + all_public_key_usages_ | all_private_key_usages_, combined_usages); |
| + if (status.IsError()) |
| + return status; |
| + |
| + const blink::WebCryptoKeyUsageMask public_usages = |
| + combined_usages & all_public_key_usages_; |
| + const blink::WebCryptoKeyUsageMask private_usages = |
| + combined_usages & all_private_key_usages_; |
| + |
| + const blink::WebCryptoEcKeyGenParams* params = algorithm.ecKeyGenParams(); |
| + |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + // Generate an EC key pair. |
| + crypto::ScopedEC_KEY ec_private_key; |
| + status = CreateEC_KEY(params->namedCurve(), &ec_private_key); |
| + if (status.IsError()) |
| + return status; |
| + |
| + if (!EC_KEY_generate_key(ec_private_key.get())) |
| + return Status::OperationError(); |
| + |
| + // Construct an EVP_PKEY for the private key. |
| + crypto::ScopedEVP_PKEY private_pkey(EVP_PKEY_new()); |
| + if (!private_pkey || |
| + !EVP_PKEY_set1_EC_KEY(private_pkey.get(), ec_private_key.get())) { |
| + return Status::OperationError(); |
| + } |
| + |
| + // Construct an EVP_PKEY for just the public key. |
| + crypto::ScopedEC_KEY ec_public_key; |
| + crypto::ScopedEVP_PKEY public_pkey(EVP_PKEY_new()); |
| + status = CreateEC_KEY(params->namedCurve(), &ec_public_key); |
| + if (status.IsError()) |
| + return status; |
| + if (!EC_KEY_set_public_key(ec_public_key.get(), |
| + EC_KEY_get0_public_key(ec_private_key.get()))) { |
| + return Status::OperationError(); |
| + } |
| + if (!public_pkey || |
| + !EVP_PKEY_set1_EC_KEY(public_pkey.get(), ec_public_key.get())) { |
| + return Status::OperationError(); |
| + } |
| + |
| + blink::WebCryptoKey public_key; |
| + blink::WebCryptoKey private_key; |
| + |
| + blink::WebCryptoKeyAlgorithm key_algorithm = |
| + blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(), |
| + params->namedCurve()); |
| + |
| + // Note that extractable is unconditionally set to true. This is because per |
| + // the WebCrypto spec generated public keys are always public. |
| + status = CreateWebCryptoPublicKey(public_pkey.Pass(), key_algorithm, true, |
| + public_usages, &public_key); |
| + if (status.IsError()) |
| + return status; |
| + |
| + status = CreateWebCryptoPrivateKey(private_pkey.Pass(), key_algorithm, |
| + extractable, private_usages, &private_key); |
| + if (status.IsError()) |
| + return status; |
| + |
| + result->AssignKeyPair(public_key, private_key); |
| + return Status::Success(); |
| +} |
| + |
| +// TODO(eroman): This is identical to RSA. |
| +Status EcAlgorithm::VerifyKeyUsagesBeforeImportKey( |
| + blink::WebCryptoKeyFormat format, |
| + blink::WebCryptoKeyUsageMask usages) const { |
| + switch (format) { |
| + case blink::WebCryptoKeyFormatSpki: |
| + return CheckKeyCreationUsages(all_public_key_usages_, usages); |
| + case blink::WebCryptoKeyFormatPkcs8: |
| + return CheckKeyCreationUsages(all_private_key_usages_, usages); |
| + case blink::WebCryptoKeyFormatJwk: |
| + // The JWK could represent either a public key or private key. The usages |
| + // must make sense for one of the two. The usages will be checked again by |
| + // ImportKeyJwk() once the key type has been determined. |
| + if (CheckKeyCreationUsages(all_private_key_usages_, usages).IsSuccess() || |
| + CheckKeyCreationUsages(all_public_key_usages_, usages).IsSuccess()) { |
| + return Status::Success(); |
| + } |
| + return Status::ErrorCreateKeyBadUsages(); |
| + default: |
| + return Status::ErrorUnsupportedImportKeyFormat(); |
| + } |
| +} |
| + |
| +Status EcAlgorithm::ImportKeyPkcs8(const CryptoData& key_data, |
| + const blink::WebCryptoAlgorithm& algorithm, |
| + bool extractable, |
| + blink::WebCryptoKeyUsageMask usages, |
| + blink::WebCryptoKey* key) const { |
| + crypto::ScopedEVP_PKEY private_key; |
| + Status status = |
| + ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_EC, &private_key); |
| + if (status.IsError()) |
| + return status; |
| + |
| + const blink::WebCryptoEcKeyImportParams* params = |
| + 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
|
| + |
| + status = VerifyEcKeyAfterSpkiOrPkcs8Import(private_key.get(), |
| + params->namedCurve()); |
| + if (status.IsError()) |
| + return status; |
| + |
| + return CreateWebCryptoPrivateKey(private_key.Pass(), |
| + blink::WebCryptoKeyAlgorithm::createEc( |
| + algorithm.id(), params->namedCurve()), |
| + extractable, usages, key); |
| +} |
| + |
| +Status EcAlgorithm::ImportKeySpki(const CryptoData& key_data, |
| + const blink::WebCryptoAlgorithm& algorithm, |
| + bool extractable, |
| + blink::WebCryptoKeyUsageMask usages, |
| + blink::WebCryptoKey* key) const { |
| + crypto::ScopedEVP_PKEY public_key; |
| + Status status = |
| + ImportUnverifiedPkeyFromSpki(key_data, EVP_PKEY_EC, &public_key); |
| + if (status.IsError()) |
| + return status; |
| + |
| + const blink::WebCryptoEcKeyImportParams* params = |
| + algorithm.ecKeyImportParams(); |
| + |
| + status = |
| + VerifyEcKeyAfterSpkiOrPkcs8Import(public_key.get(), params->namedCurve()); |
| + if (status.IsError()) |
| + return status; |
| + |
| + return CreateWebCryptoPublicKey(public_key.Pass(), |
| + blink::WebCryptoKeyAlgorithm::createEc( |
| + algorithm.id(), params->namedCurve()), |
| + extractable, usages, key); |
| +} |
| + |
| +// The format for JWK EC keys is given by: |
| +// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6.2 |
| +Status EcAlgorithm::ImportKeyJwk(const CryptoData& key_data, |
| + const blink::WebCryptoAlgorithm& algorithm, |
| + bool extractable, |
| + blink::WebCryptoKeyUsageMask usages, |
| + blink::WebCryptoKey* key) const { |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + const blink::WebCryptoEcKeyImportParams* params = |
| + algorithm.ecKeyImportParams(); |
| + |
| + // When importing EC keys from JWK there are *three* separate curve |
| + // names: |
| + // |
| + // (1) The one given to WebCrypto's importKey (params->namedCurve()). |
| + // (2) JWK's "crv" member |
| + // (3) JWK's "alg" member. |
| + // |
| + // The optional "alg" member implicitly names a curve and hash. For instance |
| + // "ES512" would mean the key is for ECDSA using curve "P-521", and SHA-512. |
| + // The hash portion of the "alg" is disregarded, however the curve must |
| + // 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.
|
| + |
| + JwkReader jwk; |
| + Status status = jwk.Init(key_data, extractable, usages, "EC", |
| + GetJwkAlgorithm(params->namedCurve())); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // Verify that "crv" matches expected curve. |
| + blink::WebCryptoNamedCurve jwk_crv; |
| + status = GetJwkCrv(jwk, &jwk_crv); |
| + if (status.IsError()) |
| + return status; |
| + if (jwk_crv != params->namedCurve()) |
| + return Status::ErrorJwkIncorrectCrv(); |
| + |
| + // Only private keys have a "d" parameter. The key may still be invalid, but |
| + // tentatively decide if it is a public or private key. |
| + bool is_private_key = jwk.HasMember("d"); |
| + |
| + // Now that the key type is known, verify the usages. |
| + status = CheckKeyCreationUsages( |
| + is_private_key ? all_private_key_usages_ : all_public_key_usages_, |
| + usages); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // Create an EC_KEY. |
| + crypto::ScopedEC_KEY ec; |
| + status = CreateEC_KEY(params->namedCurve(), &ec); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // Extract the "x" and "y" parameters and apply them to |ec|. |
| + status = SetPublicKeyMembersFromJwk(jwk, ec.get()); |
| + if (status.IsError()) |
| + return status; |
| + |
| + // Extract the "d" parameters. |
| + if (is_private_key) { |
| + crypto::ScopedBIGNUM d; |
| + status = GetBIGNUM(jwk, "d", &d); |
| + if (status.IsError()) |
| + return status; |
| + |
| + if (!EC_KEY_set_private_key(ec.get(), d.get())) |
| + return Status::OperationError(); |
| + } |
| + |
| + // Verify the key. |
| + if (!EC_KEY_check_key(ec.get())) |
| + return Status::ErrorEcKeyInvalid(); |
| + |
| + // Wrap the EC_KEY into an EVP_PKEY. |
| + crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); |
| + if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get())) |
| + return Status::OperationError(); |
| + |
| + blink::WebCryptoKeyAlgorithm key_algorithm = |
| + blink::WebCryptoKeyAlgorithm::createEc(algorithm.id(), |
| + params->namedCurve()); |
| + |
| + // Wrap the EVP_PKEY into a WebCryptoKey |
| + if (is_private_key) { |
| + return CreateWebCryptoPrivateKey(pkey.Pass(), key_algorithm, extractable, |
| + usages, key); |
| + } |
| + return CreateWebCryptoPublicKey(pkey.Pass(), key_algorithm, extractable, |
| + usages, key); |
| +} |
| + |
| +Status EcAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key, |
| + std::vector<uint8_t>* buffer) const { |
| + if (key.type() != blink::WebCryptoKeyTypePrivate) |
| + return Status::ErrorUnexpectedKeyType(); |
| + *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data(); |
| + return Status::Success(); |
| +} |
| + |
| +Status EcAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key, |
| + std::vector<uint8_t>* buffer) const { |
| + if (key.type() != blink::WebCryptoKeyTypePublic) |
| + return Status::ErrorUnexpectedKeyType(); |
| + *buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data(); |
| + return Status::Success(); |
| +} |
| + |
| +// The format for JWK EC keys is given by: |
| +// https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-36#section-6.2 |
| +Status EcAlgorithm::ExportKeyJwk(const blink::WebCryptoKey& key, |
| + std::vector<uint8_t>* buffer) const { |
| + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); |
| + |
| + EVP_PKEY* pkey = AsymKeyOpenSsl::Cast(key)->key(); |
| + |
| + crypto::ScopedEC_KEY ec(EVP_PKEY_get1_EC_KEY(pkey)); |
| + if (!ec.get()) |
| + return Status::ErrorUnexpected(); |
| + |
| + // Note that no "alg" is set for ECDSA keys, since it implies a hash. |
| + JwkWriter jwk(std::string(), key.extractable(), key.usages(), "EC"); |
| + |
| + // Set the crv |
| + std::string crv; |
| + Status status = |
| + WebCryptoCurveToJwkCrv(key.algorithm().ecParams()->namedCurve(), &crv); |
| + if (status.IsError()) |
| + return status; |
| + |
| + jwk.SetString("crv", crv); |
| + |
| + crypto::ScopedBIGNUM x; |
| + crypto::ScopedBIGNUM y; |
| + status = GetPublicKey(ec.get(), &x, &y); |
| + if (status.IsError()) |
| + return status; |
| + |
| + WriteBIGNUM("x", x.get(), &jwk); |
| + WriteBIGNUM("y", y.get(), &jwk); |
| + |
| + if (key.type() == blink::WebCryptoKeyTypePrivate) { |
| + crypto::ScopedBIGNUM d; |
|
davidben
2014/11/07 00:17:21
Unused variable?
eroman
2014/11/08 01:52:56
Done.
|
| + WriteBIGNUM("d", EC_KEY_get0_private_key(ec.get()), &jwk); |
| + } |
| + |
| + jwk.ToJson(buffer); |
| + return Status::Success(); |
| +} |
| + |
| +} // namespace webcrypto |
| + |
| +} // namespace content |