#include "crypto/crypto_ec.h" #include "crypto/crypto_common.h" #include "crypto/crypto_util.h" #include "allocated_buffer-inl.h" #include "async_wrap-inl.h" #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node_buffer.h" #include "string_bytes.h" #include "threadpoolwork-inl.h" #include "v8.h" #include #include #include #include namespace node { using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Int32; using v8::Just; using v8::JustVoid; using v8::Local; using v8::Maybe; using v8::Nothing; using v8::Object; using v8::String; using v8::Uint32; using v8::Value; namespace crypto { int GetCurveFromName(const char* name) { int nid = EC_curve_nist2nid(name); if (nid == NID_undef) nid = OBJ_sn2nid(name); return nid; } int GetOKPCurveFromName(const char* name) { int nid; if (strcmp(name, "NODE-ED25519") == 0) { nid = EVP_PKEY_ED25519; } else if (strcmp(name, "NODE-ED448") == 0) { nid = EVP_PKEY_ED448; } else if (strcmp(name, "NODE-X25519") == 0) { nid = EVP_PKEY_X25519; } else if (strcmp(name, "NODE-X448") == 0) { nid = EVP_PKEY_X448; } else { nid = NID_undef; } return nid; } void ECDH::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey); env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey); env->SetProtoMethod(t, "setPublicKey", SetPublicKey); env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); env->SetConstructorFunction(target, "ECDH", t); env->SetMethodNoSideEffect(target, "ECDHConvertKey", ECDH::ConvertKey); env->SetMethodNoSideEffect(target, "getCurves", ECDH::GetCurves); ECDHBitsJob::Initialize(env, target); ECKeyPairGenJob::Initialize(env, target); ECKeyExportJob::Initialize(env, target); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE); NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE); } void ECDH::RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(New); registry->Register(GenerateKeys); registry->Register(ComputeSecret); registry->Register(GetPublicKey); registry->Register(GetPrivateKey); registry->Register(SetPublicKey); registry->Register(SetPrivateKey); registry->Register(ECDH::ConvertKey); registry->Register(ECDH::GetCurves); ECDHBitsJob::RegisterExternalReferences(registry); ECKeyPairGenJob::RegisterExternalReferences(registry); ECKeyExportJob::RegisterExternalReferences(registry); } void ECDH::GetCurves(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); const size_t num_curves = EC_get_builtin_curves(nullptr, 0); if (num_curves) { std::vector curves(num_curves); if (EC_get_builtin_curves(curves.data(), num_curves)) { std::vector> arr(num_curves); for (size_t i = 0; i < num_curves; i++) arr[i] = OneByteString(env->isolate(), OBJ_nid2sn(curves[i].nid)); args.GetReturnValue().Set( Array::New(env->isolate(), arr.data(), arr.size())); return; } } args.GetReturnValue().Set(Array::New(env->isolate())); } ECDH::ECDH(Environment* env, Local wrap, ECKeyPointer&& key) : BaseObject(env, wrap), key_(std::move(key)), group_(EC_KEY_get0_group(key_.get())) { MakeWeak(); CHECK_NOT_NULL(group_); } void ECDH::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("key", key_ ? kSizeOf_EC_KEY : 0); } ECDH::~ECDH() {} void ECDH::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); MarkPopErrorOnReturn mark_pop_error_on_return; // TODO(indutny): Support raw curves? CHECK(args[0]->IsString()); node::Utf8Value curve(env->isolate(), args[0]); int nid = OBJ_sn2nid(*curve); if (nid == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); ECKeyPointer key(EC_KEY_new_by_curve_name(nid)); if (!key) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to create key using named curve"); new ECDH(env, args.This(), std::move(key)); } void ECDH::GenerateKeys(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); if (!EC_KEY_generate_key(ecdh->key_.get())) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate key"); } ECPointPointer ECDH::BufferToPoint(Environment* env, const EC_GROUP* group, Local buf) { int r; ECPointPointer pub(EC_POINT_new(group)); if (!pub) { THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to allocate EC_POINT for a public key"); return pub; } ArrayBufferOrViewContents input(buf); if (UNLIKELY(!input.CheckSizeInt32())) { THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); return ECPointPointer(); } r = EC_POINT_oct2point( group, pub.get(), input.data(), input.size(), nullptr); if (!r) return ECPointPointer(); return pub; } void ECDH::ComputeSecret(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(IsAnyByteSource(args[0])); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); MarkPopErrorOnReturn mark_pop_error_on_return; if (!ecdh->IsKeyPairValid()) return THROW_ERR_CRYPTO_INVALID_KEYPAIR(env); ECPointPointer pub( ECDH::BufferToPoint(env, ecdh->group_, args[0])); if (!pub) { args.GetReturnValue().Set( FIXED_ONE_BYTE_STRING(env->isolate(), "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY")); return; } std::unique_ptr bs; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); // NOTE: field_size is in bits int field_size = EC_GROUP_get_degree(ecdh->group_); size_t out_len = (field_size + 7) / 8; bs = ArrayBuffer::NewBackingStore(env->isolate(), out_len); } if (!ECDH_compute_key( bs->Data(), bs->ByteLength(), pub.get(), ecdh->key_.get(), nullptr)) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key"); Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); Local buffer; if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return; args.GetReturnValue().Set(buffer); } void ECDH::GetPublicKey(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); // Conversion form CHECK_EQ(args.Length(), 1); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); const EC_GROUP* group = EC_KEY_get0_group(ecdh->key_.get()); const EC_POINT* pub = EC_KEY_get0_public_key(ecdh->key_.get()); if (pub == nullptr) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get ECDH public key"); CHECK(args[0]->IsUint32()); uint32_t val = args[0].As()->Value(); point_conversion_form_t form = static_cast(val); const char* error; Local buf; if (!ECPointToBuffer(env, group, pub, form, &error).ToLocal(&buf)) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, error); args.GetReturnValue().Set(buf); } void ECDH::GetPrivateKey(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); const BIGNUM* b = EC_KEY_get0_private_key(ecdh->key_.get()); if (b == nullptr) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get ECDH private key"); std::unique_ptr bs; { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); bs = ArrayBuffer::NewBackingStore(env->isolate(), BN_num_bytes(b)); } CHECK_EQ(static_cast(bs->ByteLength()), BN_bn2binpad( b, static_cast(bs->Data()), bs->ByteLength())); Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); Local buffer; if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return; args.GetReturnValue().Set(buffer); } void ECDH::SetPrivateKey(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); ArrayBufferOrViewContents priv_buffer(args[0]); if (UNLIKELY(!priv_buffer.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); BignumPointer priv(BN_bin2bn( priv_buffer.data(), priv_buffer.size(), nullptr)); if (!priv) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to convert Buffer to BN"); } if (!ecdh->IsKeyValidForCurve(priv)) { return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env, "Private key is not valid for specified curve."); } ECKeyPointer new_key(EC_KEY_dup(ecdh->key_.get())); CHECK(new_key); int result = EC_KEY_set_private_key(new_key.get(), priv.get()); priv.reset(); if (!result) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to convert BN to a private key"); } MarkPopErrorOnReturn mark_pop_error_on_return; USE(&mark_pop_error_on_return); const BIGNUM* priv_key = EC_KEY_get0_private_key(new_key.get()); CHECK_NOT_NULL(priv_key); ECPointPointer pub(EC_POINT_new(ecdh->group_)); CHECK(pub); if (!EC_POINT_mul(ecdh->group_, pub.get(), priv_key, nullptr, nullptr, nullptr)) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate ECDH public key"); } if (!EC_KEY_set_public_key(new_key.get(), pub.get())) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to set generated public key"); ecdh->key_ = std::move(new_key); ecdh->group_ = EC_KEY_get0_group(ecdh->key_.get()); } void ECDH::SetPublicKey(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); ECDH* ecdh; ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.Holder()); CHECK(IsAnyByteSource(args[0])); MarkPopErrorOnReturn mark_pop_error_on_return; ECPointPointer pub( ECDH::BufferToPoint(env, ecdh->group_, args[0])); if (!pub) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to convert Buffer to EC_POINT"); } int r = EC_KEY_set_public_key(ecdh->key_.get(), pub.get()); if (!r) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to set EC_POINT as the public key"); } } bool ECDH::IsKeyValidForCurve(const BignumPointer& private_key) { CHECK(group_); CHECK(private_key); // Private keys must be in the range [1, n-1]. // Ref: Section 3.2.1 - http://www.secg.org/sec1-v2.pdf if (BN_cmp(private_key.get(), BN_value_one()) < 0) { return false; } BignumPointer order(BN_new()); CHECK(order); return EC_GROUP_get_order(group_, order.get(), nullptr) && BN_cmp(private_key.get(), order.get()) < 0; } bool ECDH::IsKeyPairValid() { MarkPopErrorOnReturn mark_pop_error_on_return; USE(&mark_pop_error_on_return); return 1 == EC_KEY_check_key(key_.get()); } // Convert the input public key to compressed, uncompressed, or hybrid formats. void ECDH::ConvertKey(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 3); CHECK(IsAnyByteSource(args[0])); ArrayBufferOrViewContents args0(args[0]); if (UNLIKELY(!args0.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); if (args0.size() == 0) return args.GetReturnValue().SetEmptyString(); node::Utf8Value curve(env->isolate(), args[1]); int nid = OBJ_sn2nid(*curve); if (nid == NID_undef) return THROW_ERR_CRYPTO_INVALID_CURVE(env); ECGroupPointer group( EC_GROUP_new_by_curve_name(nid)); if (group == nullptr) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC_GROUP"); ECPointPointer pub( ECDH::BufferToPoint(env, group.get(), args[0])); if (pub == nullptr) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to convert Buffer to EC_POINT"); } CHECK(args[2]->IsUint32()); uint32_t val = args[2].As()->Value(); point_conversion_form_t form = static_cast(val); const char* error; Local buf; if (!ECPointToBuffer(env, group.get(), pub.get(), form, &error).ToLocal(&buf)) return THROW_ERR_CRYPTO_OPERATION_FAILED(env, error); args.GetReturnValue().Set(buf); } void ECDHBitsConfig::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("public", public_); tracker->TrackField("private", private_); } Maybe ECDHBitsTraits::EncodeOutput( Environment* env, const ECDHBitsConfig& params, ByteSource* out, v8::Local* result) { *result = out->ToArrayBuffer(env); return Just(!result->IsEmpty()); } Maybe ECDHBitsTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int offset, ECDHBitsConfig* params) { Environment* env = Environment::GetCurrent(args); CHECK(args[offset]->IsString()); // curve name CHECK(args[offset + 1]->IsObject()); // public key CHECK(args[offset + 2]->IsObject()); // private key KeyObjectHandle* private_key; KeyObjectHandle* public_key; Utf8Value name(env->isolate(), args[offset]); ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset + 1], Nothing()); ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 2], Nothing()); if (private_key->Data()->GetKeyType() != kKeyTypePrivate || public_key->Data()->GetKeyType() != kKeyTypePublic) { THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); return Nothing(); } params->id_ = GetOKPCurveFromName(*name); params->private_ = private_key->Data(); params->public_ = public_key->Data(); return Just(true); } bool ECDHBitsTraits::DeriveBits( Environment* env, const ECDHBitsConfig& params, ByteSource* out) { char* data = nullptr; size_t len = 0; ManagedEVPPKey m_privkey = params.private_->GetAsymmetricKey(); ManagedEVPPKey m_pubkey = params.public_->GetAsymmetricKey(); switch (params.id_) { case EVP_PKEY_X25519: // Fall through case EVP_PKEY_X448: { EVPKeyCtxPointer ctx = nullptr; { ctx.reset(EVP_PKEY_CTX_new(m_privkey.get(), nullptr)); } Mutex::ScopedLock pub_lock(*m_pubkey.mutex()); if (EVP_PKEY_derive_init(ctx.get()) <= 0 || EVP_PKEY_derive_set_peer( ctx.get(), m_pubkey.get()) <= 0 || EVP_PKEY_derive(ctx.get(), nullptr, &len) <= 0) { return false; } data = MallocOpenSSL(len); if (EVP_PKEY_derive( ctx.get(), reinterpret_cast(data), &len) <= 0) { return false; } break; } default: { const EC_KEY* private_key; { Mutex::ScopedLock priv_lock(*m_privkey.mutex()); private_key = EVP_PKEY_get0_EC_KEY(m_privkey.get()); } Mutex::ScopedLock pub_lock(*m_pubkey.mutex()); const EC_KEY* public_key = EVP_PKEY_get0_EC_KEY(m_pubkey.get()); const EC_GROUP* group = EC_KEY_get0_group(private_key); if (group == nullptr) return false; CHECK_EQ(EC_KEY_check_key(private_key), 1); CHECK_EQ(EC_KEY_check_key(public_key), 1); const EC_POINT* pub = EC_KEY_get0_public_key(public_key); int field_size = EC_GROUP_get_degree(group); len = (field_size + 7) / 8; data = MallocOpenSSL(len); CHECK_NOT_NULL(data); CHECK_NOT_NULL(pub); CHECK_NOT_NULL(private_key); if (ECDH_compute_key( data, len, pub, private_key, nullptr) <= 0) { return false; } } } ByteSource buf = ByteSource::Allocated(data, len); *out = std::move(buf); return true; } EVPKeyCtxPointer EcKeyGenTraits::Setup(EcKeyPairGenConfig* params) { EVPKeyCtxPointer key_ctx; switch (params->params.curve_nid) { case EVP_PKEY_ED25519: // Fall through case EVP_PKEY_ED448: // Fall through case EVP_PKEY_X25519: // Fall through case EVP_PKEY_X448: key_ctx.reset(EVP_PKEY_CTX_new_id(params->params.curve_nid, nullptr)); break; default: { EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); EVP_PKEY* raw_params = nullptr; if (!param_ctx || EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 || EVP_PKEY_CTX_set_ec_paramgen_curve_nid( param_ctx.get(), params->params.curve_nid) <= 0 || EVP_PKEY_CTX_set_ec_param_enc( param_ctx.get(), params->params.param_encoding) <= 0 || EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) { return EVPKeyCtxPointer(); } EVPKeyPointer key_params(raw_params); key_ctx.reset(EVP_PKEY_CTX_new(key_params.get(), nullptr)); } } if (key_ctx && EVP_PKEY_keygen_init(key_ctx.get()) <= 0) key_ctx.reset(); return key_ctx; } // EcKeyPairGenJob input arguments // 1. CryptoJobMode // 2. Curve Name // 3. Param Encoding // 4. Public Format // 5. Public Type // 6. Private Format // 7. Private Type // 8. Cipher // 9. Passphrase Maybe EcKeyGenTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int* offset, EcKeyPairGenConfig* params) { Environment* env = Environment::GetCurrent(args); CHECK(args[*offset]->IsString()); // curve name CHECK(args[*offset + 1]->IsInt32()); // param encoding Utf8Value curve_name(env->isolate(), args[*offset]); params->params.curve_nid = GetCurveFromName(*curve_name); if (params->params.curve_nid == NID_undef) { THROW_ERR_CRYPTO_INVALID_CURVE(env); return Nothing(); } params->params.param_encoding = args[*offset + 1].As()->Value(); if (params->params.param_encoding != OPENSSL_EC_NAMED_CURVE && params->params.param_encoding != OPENSSL_EC_EXPLICIT_CURVE) { THROW_ERR_OUT_OF_RANGE(env, "Invalid param_encoding specified"); return Nothing(); } *offset += 2; return Just(true); } namespace { WebCryptoKeyExportStatus EC_Raw_Export( KeyObjectData* key_data, const ECKeyExportConfig& params, ByteSource* out) { ManagedEVPPKey m_pkey = key_data->GetAsymmetricKey(); CHECK(m_pkey); Mutex::ScopedLock lock(*m_pkey.mutex()); const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(m_pkey.get()); unsigned char* data; size_t len = 0; if (ec_key == nullptr) { typedef int (*export_fn)(const EVP_PKEY*, unsigned char*, size_t* len); export_fn fn = nullptr; switch (key_data->GetKeyType()) { case kKeyTypePrivate: fn = EVP_PKEY_get_raw_private_key; break; case kKeyTypePublic: fn = EVP_PKEY_get_raw_public_key; break; case kKeyTypeSecret: UNREACHABLE(); } CHECK_NOT_NULL(fn); // Get the size of the raw key data if (fn(m_pkey.get(), nullptr, &len) == 0) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; data = MallocOpenSSL(len); if (fn(m_pkey.get(), data, &len) == 0) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; } else { if (key_data->GetKeyType() != kKeyTypePublic) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; const EC_GROUP* group = EC_KEY_get0_group(ec_key); const EC_POINT* point = EC_KEY_get0_public_key(ec_key); point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; // Get the allocated data size... len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); if (len == 0) return WebCryptoKeyExportStatus::FAILED; data = MallocOpenSSL(len); size_t check_len = EC_POINT_point2oct(group, point, form, data, len, nullptr); if (check_len == 0) return WebCryptoKeyExportStatus::FAILED; CHECK_EQ(len, check_len); } *out = ByteSource::Allocated(reinterpret_cast(data), len); return WebCryptoKeyExportStatus::OK; } } // namespace Maybe ECKeyExportTraits::AdditionalConfig( const FunctionCallbackInfo& args, unsigned int offset, ECKeyExportConfig* params) { return Just(true); } WebCryptoKeyExportStatus ECKeyExportTraits::DoExport( std::shared_ptr key_data, WebCryptoKeyFormat format, const ECKeyExportConfig& params, ByteSource* out) { CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret); switch (format) { case kWebCryptoKeyFormatRaw: return EC_Raw_Export(key_data.get(), params, out); case kWebCryptoKeyFormatPKCS8: if (key_data->GetKeyType() != kKeyTypePrivate) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; return PKEY_PKCS8_Export(key_data.get(), out); case kWebCryptoKeyFormatSPKI: if (key_data->GetKeyType() != kKeyTypePublic) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; return PKEY_SPKI_Export(key_data.get(), out); default: UNREACHABLE(); } } Maybe ExportJWKEcKey( Environment* env, std::shared_ptr key, Local target) { ManagedEVPPKey m_pkey = key->GetAsymmetricKey(); Mutex::ScopedLock lock(*m_pkey.mutex()); CHECK_EQ(EVP_PKEY_id(m_pkey.get()), EVP_PKEY_EC); const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get()); CHECK_NOT_NULL(ec); const EC_POINT* pub = EC_KEY_get0_public_key(ec); const EC_GROUP* group = EC_KEY_get0_group(ec); int degree_bits = EC_GROUP_get_degree(group); int degree_bytes = (degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8; BignumPointer x(BN_new()); BignumPointer y(BN_new()); if (!EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr)) { ThrowCryptoError(env, ERR_get_error(), "Failed to get elliptic-curve point coordinates"); return Nothing(); } if (target->Set( env->context(), env->jwk_kty_string(), env->jwk_ec_string()).IsNothing()) { return Nothing(); } if (SetEncodedValue( env, target, env->jwk_x_string(), x.get(), degree_bytes).IsNothing() || SetEncodedValue( env, target, env->jwk_y_string(), y.get(), degree_bytes).IsNothing()) { return Nothing(); } Local crv_name; const int nid = EC_GROUP_get_curve_name(group); switch (nid) { case NID_X9_62_prime256v1: crv_name = OneByteString(env->isolate(), "P-256"); break; case NID_secp256k1: crv_name = OneByteString(env->isolate(), "secp256k1"); break; case NID_secp384r1: crv_name = OneByteString(env->isolate(), "P-384"); break; case NID_secp521r1: crv_name = OneByteString(env->isolate(), "P-521"); break; default: { THROW_ERR_CRYPTO_JWK_UNSUPPORTED_CURVE( env, "Unsupported JWK EC curve: %s.", OBJ_nid2sn(nid)); return Nothing(); } } if (target->Set( env->context(), env->jwk_crv_string(), crv_name).IsNothing()) { return Nothing(); } if (key->GetKeyType() == kKeyTypePrivate) { const BIGNUM* pvt = EC_KEY_get0_private_key(ec); return SetEncodedValue( env, target, env->jwk_d_string(), pvt, degree_bytes).IsJust() ? JustVoid() : Nothing(); } return JustVoid(); } Maybe ExportJWKEdKey( Environment* env, std::shared_ptr key, Local target) { ManagedEVPPKey pkey = key->GetAsymmetricKey(); Mutex::ScopedLock lock(*pkey.mutex()); const char* curve = nullptr; switch (EVP_PKEY_id(pkey.get())) { case EVP_PKEY_ED25519: curve = "Ed25519"; break; case EVP_PKEY_ED448: curve = "Ed448"; break; case EVP_PKEY_X25519: curve = "X25519"; break; case EVP_PKEY_X448: curve = "X448"; break; default: UNREACHABLE(); } if (target->Set( env->context(), env->jwk_crv_string(), OneByteString(env->isolate(), curve)).IsNothing()) { return Nothing(); } size_t len = 0; Local encoded; Local error; if (!EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &len)) return Nothing(); unsigned char* data = MallocOpenSSL(len); ByteSource out = ByteSource::Allocated(reinterpret_cast(data), len); if (key->GetKeyType() == kKeyTypePrivate) { if (!EVP_PKEY_get_raw_private_key(pkey.get(), data, &len) || !StringBytes::Encode( env->isolate(), reinterpret_cast(data), len, BASE64URL, &error).ToLocal(&encoded) || !target->Set( env->context(), env->jwk_d_string(), encoded).IsJust()) { if (!error.IsEmpty()) env->isolate()->ThrowException(error); return Nothing(); } } if (!EVP_PKEY_get_raw_public_key(pkey.get(), data, &len) || !StringBytes::Encode( env->isolate(), reinterpret_cast(data), len, BASE64URL, &error).ToLocal(&encoded) || !target->Set( env->context(), env->jwk_x_string(), encoded).IsJust()) { if (!error.IsEmpty()) env->isolate()->ThrowException(error); return Nothing(); } if (target->Set( env->context(), env->jwk_kty_string(), env->jwk_okp_string()).IsNothing()) { return Nothing(); } return Just(true); } std::shared_ptr ImportJWKEcKey( Environment* env, Local jwk, const FunctionCallbackInfo& args, unsigned int offset) { CHECK(args[offset]->IsString()); // curve name Utf8Value curve(env->isolate(), args[offset].As()); int nid = GetCurveFromName(*curve); if (nid == NID_undef) { // Unknown curve THROW_ERR_CRYPTO_INVALID_CURVE(env); return std::shared_ptr(); } Local x_value; Local y_value; Local d_value; if (!jwk->Get(env->context(), env->jwk_x_string()).ToLocal(&x_value) || !jwk->Get(env->context(), env->jwk_y_string()).ToLocal(&y_value) || !jwk->Get(env->context(), env->jwk_d_string()).ToLocal(&d_value)) { return std::shared_ptr(); } if (!x_value->IsString() || !y_value->IsString() || (!d_value->IsUndefined() && !d_value->IsString())) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return std::shared_ptr(); } KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; ECKeyPointer ec(EC_KEY_new_by_curve_name(nid)); if (!ec) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return std::shared_ptr(); } ByteSource x = ByteSource::FromEncodedString(env, x_value.As()); ByteSource y = ByteSource::FromEncodedString(env, y_value.As()); if (!EC_KEY_set_public_key_affine_coordinates( ec.get(), x.ToBN().get(), y.ToBN().get())) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return std::shared_ptr(); } if (type == kKeyTypePrivate) { ByteSource d = ByteSource::FromEncodedString(env, d_value.As()); if (!EC_KEY_set_private_key(ec.get(), d.ToBN().get())) { THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); return std::shared_ptr(); } } EVPKeyPointer pkey(EVP_PKEY_new()); CHECK_EQ(EVP_PKEY_set1_EC_KEY(pkey.get(), ec.get()), 1); return KeyObjectData::CreateAsymmetric(type, ManagedEVPPKey(std::move(pkey))); } Maybe GetEcKeyDetail( Environment* env, std::shared_ptr key, Local target) { ManagedEVPPKey m_pkey = key->GetAsymmetricKey(); Mutex::ScopedLock lock(*m_pkey.mutex()); CHECK_EQ(EVP_PKEY_id(m_pkey.get()), EVP_PKEY_EC); const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(m_pkey.get()); CHECK_NOT_NULL(ec); const EC_GROUP* group = EC_KEY_get0_group(ec); int nid = EC_GROUP_get_curve_name(group); return target->Set( env->context(), env->named_curve_string(), OneByteString(env->isolate(), OBJ_nid2sn(nid))); } // WebCrypto requires a different format for ECDSA signatures than // what OpenSSL produces, so we need to convert between them. The // implementation here is a adapted from Chromium's impl here: // https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/ecdsa.cc size_t GroupOrderSize(const ManagedEVPPKey& key) { const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(key.get()); CHECK_NOT_NULL(ec); const EC_GROUP* group = EC_KEY_get0_group(ec); BignumPointer order(BN_new()); CHECK(EC_GROUP_get_order(group, order.get(), nullptr)); return BN_num_bytes(order.get()); } } // namespace crypto } // namespace node