// 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 "components/webcrypto/algorithms/ec.h" #include #include #include "base/stl_util.h" #include "components/webcrypto/algorithms/asymmetric_key_util.h" #include "components/webcrypto/algorithms/util.h" #include "components/webcrypto/blink_key_handle.h" #include "components/webcrypto/crypto_data.h" #include "components/webcrypto/generate_key_result.h" #include "components/webcrypto/jwk.h" #include "components/webcrypto/status.h" #include "crypto/openssl_util.h" #include "third_party/blink/public/platform/web_crypto_algorithm_params.h" #include "third_party/blink/public/platform/web_crypto_key_algorithm.h" #include "third_party/boringssl/src/include/openssl/bn.h" #include "third_party/boringssl/src/include/openssl/bytestring.h" #include "third_party/boringssl/src/include/openssl/ec.h" #include "third_party/boringssl/src/include/openssl/ec_key.h" #include "third_party/boringssl/src/include/openssl/evp.h" #include "third_party/boringssl/src/include/openssl/mem.h" 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::kWebCryptoNamedCurveP256: *nid = NID_X9_62_prime256v1; return Status::Success(); case blink::kWebCryptoNamedCurveP384: *nid = NID_secp384r1; return Status::Success(); case blink::kWebCryptoNamedCurveP521: *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::kWebCryptoNamedCurveP256; return Status::Success(); case NID_secp384r1: *named_curve = blink::kWebCryptoNamedCurveP384; return Status::Success(); case NID_secp521r1: *named_curve = blink::kWebCryptoNamedCurveP521; return Status::Success(); } return Status::ErrorImportedEcKeyIncorrectCurve(); } struct JwkCrvMapping { const char* jwk_curve; blink::WebCryptoNamedCurve named_curve; }; const JwkCrvMapping kJwkCrvMappings[] = { {"P-256", blink::kWebCryptoNamedCurveP256}, {"P-384", blink::kWebCryptoNamedCurveP384}, {"P-521", blink::kWebCryptoNamedCurveP521}, }; // Gets the "crv" parameter from a JWK and converts it to a WebCryptoNamedCurve. Status ReadJwkCrv(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 < base::size(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 < base::size(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. // Also removes the EC_PKEY_NO_PUBKEY flag if present. Status VerifyEcKeyAfterSpkiOrPkcs8Import( EVP_PKEY* pkey, blink::WebCryptoNamedCurve expected_named_curve) { crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey); if (!ec) return Status::ErrorUnexpected(); // When importing an ECPrivateKey, the public key is optional. If it was // omitted then the public key will be calculated by BoringSSL and added into // the EC_KEY. However an encoding flag is set such that when exporting to // PKCS8 format the public key is once again omitted. Remove this flag. unsigned int enc_flags = EC_KEY_get_enc_flags(ec); enc_flags &= ~EC_PKEY_NO_PUBKEY; EC_KEY_set_enc_flags(ec, enc_flags); if (!EC_KEY_check_key(ec)) 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)); blink::WebCryptoNamedCurve named_curve = blink::kWebCryptoNamedCurveP256; 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, bssl::UniquePtr* ec) { int curve_nid = 0; 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 an unsigned BIGNUM into |jwk|, zero-padding it to a length of // |padded_length|. Status WritePaddedBIGNUM(const std::string& member_name, const BIGNUM* value, size_t padded_length, JwkWriter* jwk) { std::vector padded_bytes(padded_length); if (!BN_bn2bin_padded(padded_bytes.data(), padded_bytes.size(), value)) return Status::OperationError(); jwk->SetBytes(member_name, CryptoData(padded_bytes)); return Status::Success(); } // Reads a fixed length BIGNUM from a JWK. Status ReadPaddedBIGNUM(const JwkReader& jwk, const std::string& member_name, size_t expected_length, bssl::UniquePtr* out) { std::string bytes; Status status = jwk.GetBytes(member_name, &bytes); if (status.IsError()) return status; if (bytes.size() != expected_length) { return Status::JwkOctetStringWrongLength(member_name, expected_length, bytes.size()); } out->reset(CreateBIGNUM(bytes)); return Status::Success(); } int GetGroupDegreeInBytes(EC_KEY* ec) { const EC_GROUP* group = EC_KEY_get0_group(ec); return NumBitsToBytes(EC_GROUP_get_degree(group)); } // Extracts the public key as affine coordinates (x,y). Status GetPublicKey(EC_KEY* ec, bssl::UniquePtr* x, bssl::UniquePtr* 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(), nullptr)) { return Status::OperationError(); } return Status::Success(); } // Synthesizes an import algorithm given a key algorithm, so that // deserialization can re-use the ImportKey*() methods. blink::WebCryptoAlgorithm SynthesizeImportAlgorithmForClone( const blink::WebCryptoKeyAlgorithm& algorithm) { return blink::WebCryptoAlgorithm::AdoptParamsAndCreate( algorithm.Id(), new blink::WebCryptoEcKeyImportParams( algorithm.EcParams()->NamedCurve())); } } // namespace Status EcAlgorithm::GenerateKey(const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask combined_usages, GenerateKeyResult* result) const { blink::WebCryptoKeyUsageMask public_usages = 0; blink::WebCryptoKeyUsageMask private_usages = 0; Status status = GetUsagesForGenerateAsymmetricKey( combined_usages, all_public_key_usages_, all_private_key_usages_, &public_usages, &private_usages); if (status.IsError()) return status; const blink::WebCryptoEcKeyGenParams* params = algorithm.EcKeyGenParams(); crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); // Generate an EC key pair. bssl::UniquePtr 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. bssl::UniquePtr 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. bssl::UniquePtr ec_public_key; bssl::UniquePtr 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 extractable. status = CreateWebCryptoPublicKey(std::move(public_pkey), key_algorithm, true, public_usages, &public_key); if (status.IsError()) return status; status = CreateWebCryptoPrivateKey(std::move(private_pkey), key_algorithm, extractable, private_usages, &private_key); if (status.IsError()) return status; result->AssignKeyPair(public_key, private_key); return Status::Success(); } Status EcAlgorithm::ImportKey(blink::WebCryptoKeyFormat format, const CryptoData& key_data, const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, blink::WebCryptoKey* key) const { switch (format) { case blink::kWebCryptoKeyFormatRaw: return ImportKeyRaw(key_data, algorithm, extractable, usages, key); case blink::kWebCryptoKeyFormatPkcs8: return ImportKeyPkcs8(key_data, algorithm, extractable, usages, key); case blink::kWebCryptoKeyFormatSpki: return ImportKeySpki(key_data, algorithm, extractable, usages, key); case blink::kWebCryptoKeyFormatJwk: return ImportKeyJwk(key_data, algorithm, extractable, usages, key); default: return Status::ErrorUnsupportedImportKeyFormat(); } } Status EcAlgorithm::ExportKey(blink::WebCryptoKeyFormat format, const blink::WebCryptoKey& key, std::vector* buffer) const { switch (format) { case blink::kWebCryptoKeyFormatRaw: return ExportKeyRaw(key, buffer); case blink::kWebCryptoKeyFormatPkcs8: return ExportKeyPkcs8(key, buffer); case blink::kWebCryptoKeyFormatSpki: return ExportKeySpki(key, buffer); case blink::kWebCryptoKeyFormatJwk: return ExportKeyJwk(key, buffer); default: return Status::ErrorUnsupportedExportKeyFormat(); } } Status EcAlgorithm::ImportKeyRaw(const CryptoData& key_data, const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, blink::WebCryptoKey* key) const { crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); Status status = CheckKeyCreationUsages(all_public_key_usages_, usages); if (status.IsError()) return status; const blink::WebCryptoEcKeyImportParams* params = algorithm.EcKeyImportParams(); // Create an EC_KEY. bssl::UniquePtr ec; status = CreateEC_KEY(params->NamedCurve(), &ec); if (status.IsError()) return status; bssl::UniquePtr point(EC_POINT_new(EC_KEY_get0_group(ec.get()))); if (!point.get()) return Status::OperationError(); // Convert the "raw" input from X9.62 format to an EC_POINT. if (!EC_POINT_oct2point(EC_KEY_get0_group(ec.get()), point.get(), key_data.bytes(), key_data.byte_length(), nullptr)) { return Status::DataError(); } // Copy the point (public key) into the EC_KEY. if (!EC_KEY_set_public_key(ec.get(), point.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. bssl::UniquePtr 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 return CreateWebCryptoPublicKey(std::move(pkey), key_algorithm, extractable, usages, key); } Status EcAlgorithm::ImportKeyPkcs8(const CryptoData& key_data, const blink::WebCryptoAlgorithm& algorithm, bool extractable, blink::WebCryptoKeyUsageMask usages, blink::WebCryptoKey* key) const { Status status = CheckKeyCreationUsages(all_private_key_usages_, usages); if (status.IsError()) return status; bssl::UniquePtr private_key; status = ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_EC, &private_key); if (status.IsError()) return status; const blink::WebCryptoEcKeyImportParams* params = algorithm.EcKeyImportParams(); status = VerifyEcKeyAfterSpkiOrPkcs8Import(private_key.get(), params->NamedCurve()); if (status.IsError()) return status; return CreateWebCryptoPrivateKey(std::move(private_key), 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 { Status status = CheckKeyCreationUsages(all_public_key_usages_, usages); if (status.IsError()) return status; bssl::UniquePtr public_key; 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(std::move(public_key), 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 may be up to *three* separate curve // names: // // (1) The one given to WebCrypto's importKey (params->namedCurve()). // (2) JWK's "crv" member // (3) A curve implied by JWK's "alg" member. // // (In the case of ECDSA, the "alg" member implicitly names a curve and hash) 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 = blink::kWebCryptoNamedCurveP256; status = ReadJwkCrv(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. if (is_private_key) { status = CheckKeyCreationUsages(all_private_key_usages_, usages); } else { status = CheckKeyCreationUsages(all_public_key_usages_, usages); } if (status.IsError()) return status; // Create an EC_KEY. bssl::UniquePtr ec; status = CreateEC_KEY(params->NamedCurve(), &ec); if (status.IsError()) return status; // JWK requires the length of x, y, d to match the group degree. int degree_bytes = GetGroupDegreeInBytes(ec.get()); // Read the public key's uncompressed affine coordinates. bssl::UniquePtr x; status = ReadPaddedBIGNUM(jwk, "x", degree_bytes, &x); if (status.IsError()) return status; bssl::UniquePtr y; status = ReadPaddedBIGNUM(jwk, "y", degree_bytes, &y); if (status.IsError()) return status; // TODO(eroman): Distinguish more accurately between a DataError and // OperationError. In general if this fails it was due to the key being an // invalid EC key. if (!EC_KEY_set_public_key_affine_coordinates(ec.get(), x.get(), y.get())) return Status::DataError(); // Extract the "d" parameters. if (is_private_key) { bssl::UniquePtr d; status = ReadPaddedBIGNUM(jwk, "d", degree_bytes, &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. bssl::UniquePtr 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(std::move(pkey), key_algorithm, extractable, usages, key); } return CreateWebCryptoPublicKey(std::move(pkey), key_algorithm, extractable, usages, key); } Status EcAlgorithm::ExportKeyRaw(const blink::WebCryptoKey& key, std::vector* buffer) const { crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); if (key.GetType() != blink::kWebCryptoKeyTypePublic) return Status::ErrorUnexpectedKeyType(); EVP_PKEY* pkey = GetEVP_PKEY(key); EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey); if (!ec) return Status::ErrorUnexpected(); // Serialize the public key as an uncompressed point in X9.62 form. uint8_t* raw; size_t raw_len; bssl::ScopedCBB cbb; if (!CBB_init(cbb.get(), 0) || !EC_POINT_point2cbb(cbb.get(), EC_KEY_get0_group(ec), EC_KEY_get0_public_key(ec), POINT_CONVERSION_UNCOMPRESSED, nullptr) || !CBB_finish(cbb.get(), &raw, &raw_len)) { return Status::OperationError(); } buffer->assign(raw, raw + raw_len); OPENSSL_free(raw); return Status::Success(); } Status EcAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key, std::vector* buffer) const { if (key.GetType() != blink::kWebCryptoKeyTypePrivate) return Status::ErrorUnexpectedKeyType(); // This relies on the fact that PKCS8 formatted data was already // associated with the key during its creation (used by // structured clone). *buffer = GetSerializedKeyData(key); return Status::Success(); } Status EcAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key, std::vector* buffer) const { if (key.GetType() != blink::kWebCryptoKeyTypePublic) return Status::ErrorUnexpectedKeyType(); // This relies on the fact that SPKI formatted data was already // associated with the key during its creation (used by // structured clone). *buffer = GetSerializedKeyData(key); 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* buffer) const { crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); EVP_PKEY* pkey = GetEVP_PKEY(key); EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey); if (!ec) return Status::ErrorUnexpected(); // No "alg" is set for EC keys. 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; int degree_bytes = GetGroupDegreeInBytes(ec); jwk.SetString("crv", crv); bssl::UniquePtr x; bssl::UniquePtr y; status = GetPublicKey(ec, &x, &y); if (status.IsError()) return status; status = WritePaddedBIGNUM("x", x.get(), degree_bytes, &jwk); if (status.IsError()) return status; status = WritePaddedBIGNUM("y", y.get(), degree_bytes, &jwk); if (status.IsError()) return status; if (key.GetType() == blink::kWebCryptoKeyTypePrivate) { const BIGNUM* d = EC_KEY_get0_private_key(ec); status = WritePaddedBIGNUM("d", d, degree_bytes, &jwk); if (status.IsError()) return status; } jwk.ToJson(buffer); return Status::Success(); } // TODO(eroman): Defer import to the crypto thread. http://crbug.com/430763 Status EcAlgorithm::DeserializeKeyForClone( const blink::WebCryptoKeyAlgorithm& algorithm, blink::WebCryptoKeyType type, bool extractable, blink::WebCryptoKeyUsageMask usages, const CryptoData& key_data, blink::WebCryptoKey* key) const { if (algorithm.ParamsType() != blink::kWebCryptoKeyAlgorithmParamsTypeEc) return Status::ErrorUnexpected(); blink::WebCryptoAlgorithm import_algorithm = SynthesizeImportAlgorithmForClone(algorithm); Status status; // The serialized data will be either SPKI or PKCS8 formatted. switch (type) { case blink::kWebCryptoKeyTypePublic: status = ImportKeySpki(key_data, import_algorithm, extractable, usages, key); break; case blink::kWebCryptoKeyTypePrivate: status = ImportKeyPkcs8(key_data, import_algorithm, extractable, usages, key); break; default: return Status::ErrorUnexpected(); } if (!status.IsSuccess()) return status; // There is some duplicated information in the serialized format used by // structured clone (since the KeyAlgorithm is serialized separately from the // key data). Use this extra information to further validate what was // deserialized from the key data. if (algorithm.Id() != key->Algorithm().Id()) return Status::ErrorUnexpected(); if (type != key->GetType()) return Status::ErrorUnexpected(); if (algorithm.EcParams()->NamedCurve() != key->Algorithm().EcParams()->NamedCurve()) { return Status::ErrorUnexpected(); } return Status::Success(); } } // namespace webcrypto