| #include "crypto/crypto_ec.h" |
| #include "async_wrap-inl.h" |
| #include "base_object-inl.h" |
| #include "crypto/crypto_common.h" |
| #include "crypto/crypto_util.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 <openssl/bn.h> |
| #include <openssl/ec.h> |
| #include <openssl/ecdh.h> |
| |
| #include <algorithm> |
| |
| namespace node { |
| |
| using ncrypto::BignumPointer; |
| using ncrypto::DataPointer; |
| using ncrypto::ECGroupPointer; |
| using ncrypto::ECKeyPointer; |
| using ncrypto::ECPointPointer; |
| using ncrypto::EVPKeyCtxPointer; |
| using ncrypto::EVPKeyPointer; |
| using ncrypto::MarkPopErrorOnReturn; |
| using v8::Array; |
| using v8::ArrayBuffer; |
| using v8::BackingStoreInitializationMode; |
| using v8::Context; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::Int32; |
| using v8::Isolate; |
| using v8::JustVoid; |
| using v8::Local; |
| using v8::LocalVector; |
| using v8::Maybe; |
| using v8::MaybeLocal; |
| 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; |
| } |
| |
| void ECDH::Initialize(Environment* env, Local<Object> target) { |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| |
| Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New); |
| |
| t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount); |
| |
| SetProtoMethod(isolate, t, "generateKeys", GenerateKeys); |
| SetProtoMethod(isolate, t, "computeSecret", ComputeSecret); |
| SetProtoMethodNoSideEffect(isolate, t, "getPublicKey", GetPublicKey); |
| SetProtoMethodNoSideEffect(isolate, t, "getPrivateKey", GetPrivateKey); |
| SetProtoMethod(isolate, t, "setPublicKey", SetPublicKey); |
| SetProtoMethod(isolate, t, "setPrivateKey", SetPrivateKey); |
| |
| SetConstructorFunction(context, target, "ECDH", t); |
| |
| SetMethodNoSideEffect(context, target, "ECDHConvertKey", ECDH::ConvertKey); |
| SetMethodNoSideEffect(context, 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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| const size_t num_curves = EC_get_builtin_curves(nullptr, 0); |
| std::vector<EC_builtin_curve> curves(num_curves); |
| CHECK_EQ(EC_get_builtin_curves(curves.data(), num_curves), num_curves); |
| |
| LocalVector<Value> arr(env->isolate(), num_curves); |
| std::transform(curves.begin(), curves.end(), arr.begin(), [env](auto& curve) { |
| return OneByteString(env->isolate(), OBJ_nid2sn(curve.nid)); |
| }); |
| args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size())); |
| } |
| |
| ECDH::ECDH(Environment* env, Local<Object> wrap, ECKeyPointer&& key) |
| : BaseObject(env, wrap), key_(std::move(key)), group_(key_.getGroup()) { |
| MakeWeak(); |
| CHECK_NOT_NULL(group_); |
| } |
| |
| void ECDH::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackFieldWithSize("key", key_ ? kSizeOf_EC_KEY : 0); |
| } |
| |
| void ECDH::New(const FunctionCallbackInfo<Value>& 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); |
| |
| auto key = ECKeyPointer::NewByCurveName(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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| if (!ecdh->key_.generate()) { |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to generate key"); |
| } |
| } |
| |
| ECPointPointer ECDH::BufferToPoint(Environment* env, |
| const EC_GROUP* group, |
| Local<Value> buf) { |
| ArrayBufferOrViewContents<unsigned char> input(buf); |
| if (!input.CheckSizeInt32()) [[unlikely]] { |
| THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); |
| return {}; |
| } |
| |
| auto pub = ECPointPointer::New(group); |
| if (!pub) { |
| THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to allocate EC_POINT for a public key"); |
| return pub; |
| } |
| |
| ncrypto::Buffer<const unsigned char> buffer{ |
| .data = input.data(), |
| .len = input.size(), |
| }; |
| if (!pub.setFromBuffer(buffer, group)) { |
| return {}; |
| } |
| |
| return pub; |
| } |
| |
| void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK(IsAnyBufferSource(args[0])); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| MarkPopErrorOnReturn mark_pop_error_on_return; |
| |
| if (!ecdh->IsKeyPairValid()) |
| return THROW_ERR_CRYPTO_INVALID_KEYPAIR(env); |
| |
| auto 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; |
| } |
| |
| int field_size = EC_GROUP_get_degree(ecdh->group_); |
| size_t out_len = (field_size + 7) / 8; |
| auto bs = ArrayBuffer::NewBackingStore( |
| env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized); |
| |
| if (!ECDH_compute_key( |
| bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr)) |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute ECDH key"); |
| |
| Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs)); |
| Local<Value> buffer; |
| if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return; |
| args.GetReturnValue().Set(buffer); |
| } |
| |
| void ECDH::GetPublicKey(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| // Conversion form |
| CHECK_EQ(args.Length(), 1); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| const auto group = ecdh->key_.getGroup(); |
| const auto pub = ecdh->key_.getPublicKey(); |
| 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<Uint32>()->Value(); |
| point_conversion_form_t form = static_cast<point_conversion_form_t>(val); |
| |
| const char* error; |
| Local<Object> 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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| auto b = ecdh->key_.getPrivateKey(); |
| if (b == nullptr) |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to get ECDH private key"); |
| |
| auto bs = ArrayBuffer::NewBackingStore( |
| env->isolate(), |
| BignumPointer::GetByteCount(b), |
| BackingStoreInitializationMode::kUninitialized); |
| |
| CHECK_EQ(bs->ByteLength(), |
| BignumPointer::EncodePaddedInto( |
| b, static_cast<unsigned char*>(bs->Data()), bs->ByteLength())); |
| |
| Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs)); |
| Local<Value> buffer; |
| if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return; |
| args.GetReturnValue().Set(buffer); |
| } |
| |
| void ECDH::SetPrivateKey(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| ArrayBufferOrViewContents<unsigned char> priv_buffer(args[0]); |
| if (!priv_buffer.CheckSizeInt32()) [[unlikely]] |
| return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); |
| |
| BignumPointer priv(priv_buffer.data(), priv_buffer.size()); |
| 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."); |
| } |
| |
| auto new_key = ecdh->key_.clone(); |
| CHECK(new_key); |
| |
| bool result = new_key.setPrivateKey(priv); |
| 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); |
| |
| auto priv_key = new_key.getPrivateKey(); |
| CHECK_NOT_NULL(priv_key); |
| |
| auto pub = ECPointPointer::New(ecdh->group_); |
| CHECK(pub); |
| |
| if (!pub.mul(ecdh->group_, priv_key)) { |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to generate ECDH public key"); |
| } |
| |
| if (!new_key.setPublicKey(pub)) { |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to set generated public key"); |
| } |
| |
| ecdh->key_ = std::move(new_key); |
| ecdh->group_ = ecdh->key_.getGroup(); |
| } |
| |
| void ECDH::SetPublicKey(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| ECDH* ecdh; |
| ASSIGN_OR_RETURN_UNWRAP(&ecdh, args.This()); |
| |
| CHECK(IsAnyBufferSource(args[0])); |
| |
| MarkPopErrorOnReturn mark_pop_error_on_return; |
| |
| auto pub = ECDH::BufferToPoint(env, ecdh->group_, args[0]); |
| if (!pub) { |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to convert Buffer to EC_POINT"); |
| } |
| |
| if (!ecdh->key_.setPublicKey(pub)) { |
| 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 (private_key < BignumPointer::One()) { |
| return false; |
| } |
| auto order = BignumPointer::New(); |
| CHECK(order); |
| return EC_GROUP_get_order(group_, order.get(), nullptr) && |
| private_key < order; |
| } |
| |
| bool ECDH::IsKeyPairValid() { |
| MarkPopErrorOnReturn mark_pop_error_on_return; |
| return key_.checkKey(); |
| } |
| |
| // Convert the input public key to compressed, uncompressed, or hybrid formats. |
| void ECDH::ConvertKey(const FunctionCallbackInfo<Value>& args) { |
| MarkPopErrorOnReturn mark_pop_error_on_return; |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK_EQ(args.Length(), 3); |
| CHECK(IsAnyBufferSource(args[0])); |
| |
| ArrayBufferOrViewContents<char> args0(args[0]); |
| if (!args0.CheckSizeInt32()) [[unlikely]] |
| return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); |
| if (args0.empty()) 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); |
| |
| auto group = ECGroupPointer::NewByCurveName(nid); |
| if (!group) |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to get EC_GROUP"); |
| |
| auto pub = ECDH::BufferToPoint(env, group, args[0]); |
| if (!pub) { |
| return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
| "Failed to convert Buffer to EC_POINT"); |
| } |
| |
| CHECK(args[2]->IsUint32()); |
| uint32_t val = args[2].As<Uint32>()->Value(); |
| point_conversion_form_t form = static_cast<point_conversion_form_t>(val); |
| |
| const char* error; |
| Local<Object> buf; |
| if (!ECPointToBuffer(env, group, pub, 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_); |
| } |
| |
| MaybeLocal<Value> ECDHBitsTraits::EncodeOutput(Environment* env, |
| const ECDHBitsConfig& params, |
| ByteSource* out) { |
| return out->ToArrayBuffer(env); |
| } |
| |
| Maybe<void> ECDHBitsTraits::AdditionalConfig( |
| CryptoJobMode mode, |
| const FunctionCallbackInfo<Value>& args, |
| unsigned int offset, |
| ECDHBitsConfig* params) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| CHECK(args[offset]->IsObject()); // public key |
| CHECK(args[offset + 1]->IsObject()); // private key |
| |
| KeyObjectHandle* private_key; |
| KeyObjectHandle* public_key; |
| |
| ASSIGN_OR_RETURN_UNWRAP(&public_key, args[offset], Nothing<void>()); |
| ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing<void>()); |
| |
| if (private_key->Data().GetKeyType() != kKeyTypePrivate || |
| public_key->Data().GetKeyType() != kKeyTypePublic) { |
| THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); |
| return Nothing<void>(); |
| } |
| |
| params->private_ = private_key->Data().addRef(); |
| params->public_ = public_key->Data().addRef(); |
| |
| return JustVoid(); |
| } |
| |
| bool ECDHBitsTraits::DeriveBits(Environment* env, |
| const ECDHBitsConfig& params, |
| ByteSource* out) { |
| size_t len = 0; |
| const auto& m_privkey = params.private_.GetAsymmetricKey(); |
| const auto& m_pubkey = params.public_.GetAsymmetricKey(); |
| |
| switch (m_privkey.id()) { |
| case EVP_PKEY_X25519: |
| // Fall through |
| case EVP_PKEY_X448: { |
| Mutex::ScopedLock pub_lock(params.public_.mutex()); |
| EVPKeyCtxPointer ctx = m_privkey.newCtx(); |
| if (!ctx.initForDerive(m_pubkey)) return false; |
| |
| auto data = ctx.derive(); |
| if (!data) return false; |
| |
| *out = ByteSource::Allocated(data.release()); |
| break; |
| } |
| default: { |
| const EC_KEY* private_key; |
| { |
| Mutex::ScopedLock priv_lock(params.private_.mutex()); |
| private_key = m_privkey; |
| } |
| |
| Mutex::ScopedLock pub_lock(params.public_.mutex()); |
| const EC_KEY* public_key = m_pubkey; |
| |
| const auto group = ECKeyPointer::GetGroup(private_key); |
| if (group == nullptr) |
| return false; |
| |
| CHECK(ECKeyPointer::Check(private_key)); |
| CHECK(ECKeyPointer::Check(public_key)); |
| const auto pub = ECKeyPointer::GetPublicKey(public_key); |
| int field_size = EC_GROUP_get_degree(group); |
| len = (field_size + 7) / 8; |
| auto buf = DataPointer::Alloc(len); |
| CHECK_NOT_NULL(pub); |
| CHECK_NOT_NULL(private_key); |
| if (ECDH_compute_key( |
| static_cast<char*>(buf.get()), len, pub, private_key, nullptr) <= |
| 0) { |
| return false; |
| } |
| |
| *out = ByteSource::Allocated(buf.release()); |
| } |
| } |
| |
| 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 = EVPKeyCtxPointer::NewFromID(params->params.curve_nid); |
| break; |
| default: { |
| auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC); |
| if (!param_ctx.initForParamgen() || |
| !param_ctx.setEcParameters(params->params.curve_nid, |
| params->params.param_encoding)) { |
| return {}; |
| } |
| |
| auto key_params = param_ctx.paramgen(); |
| if (!key_params) return {}; |
| |
| key_ctx = key_params.newCtx(); |
| } |
| } |
| |
| if (!key_ctx.initForKeygen()) return {}; |
| 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<void> EcKeyGenTraits::AdditionalConfig( |
| CryptoJobMode mode, |
| const FunctionCallbackInfo<Value>& 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<void>(); |
| } |
| |
| params->params.param_encoding = args[*offset + 1].As<Int32>()->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<void>(); |
| } |
| |
| *offset += 2; |
| |
| return JustVoid(); |
| } |
| |
| namespace { |
| WebCryptoKeyExportStatus EC_Raw_Export(const KeyObjectData& key_data, |
| const ECKeyExportConfig& params, |
| ByteSource* out) { |
| const auto& m_pkey = key_data.GetAsymmetricKey(); |
| CHECK(m_pkey); |
| Mutex::ScopedLock lock(key_data.mutex()); |
| |
| const EC_KEY* ec_key = m_pkey; |
| |
| if (ec_key == nullptr) { |
| switch (key_data.GetKeyType()) { |
| case kKeyTypePrivate: { |
| auto data = m_pkey.rawPrivateKey(); |
| if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; |
| *out = ByteSource::Allocated(data.release()); |
| break; |
| } |
| case kKeyTypePublic: { |
| auto data = m_pkey.rawPublicKey(); |
| if (!data) return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; |
| *out = ByteSource::Allocated(data.release()); |
| break; |
| } |
| case kKeyTypeSecret: |
| UNREACHABLE(); |
| } |
| } else { |
| if (key_data.GetKeyType() != kKeyTypePublic) |
| return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; |
| const auto group = ECKeyPointer::GetGroup(ec_key); |
| const auto point = ECKeyPointer::GetPublicKey(ec_key); |
| point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; |
| |
| // Get the allocated data size... |
| size_t len = EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); |
| if (len == 0) |
| return WebCryptoKeyExportStatus::FAILED; |
| auto data = DataPointer::Alloc(len); |
| size_t check_len = |
| EC_POINT_point2oct(group, |
| point, |
| form, |
| static_cast<unsigned char*>(data.get()), |
| len, |
| nullptr); |
| if (check_len == 0) |
| return WebCryptoKeyExportStatus::FAILED; |
| |
| CHECK_EQ(len, check_len); |
| *out = ByteSource::Allocated(data.release()); |
| } |
| |
| return WebCryptoKeyExportStatus::OK; |
| } |
| } // namespace |
| |
| Maybe<void> ECKeyExportTraits::AdditionalConfig( |
| const FunctionCallbackInfo<Value>& args, |
| unsigned int offset, |
| ECKeyExportConfig* params) { |
| return JustVoid(); |
| } |
| |
| WebCryptoKeyExportStatus ECKeyExportTraits::DoExport( |
| const KeyObjectData& 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, params, out); |
| case kWebCryptoKeyFormatPKCS8: |
| if (key_data.GetKeyType() != kKeyTypePrivate) |
| return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; |
| return PKEY_PKCS8_Export(key_data, out); |
| case kWebCryptoKeyFormatSPKI: { |
| if (key_data.GetKeyType() != kKeyTypePublic) |
| return WebCryptoKeyExportStatus::INVALID_KEY_TYPE; |
| |
| const auto& m_pkey = key_data.GetAsymmetricKey(); |
| if (m_pkey.id() != EVP_PKEY_EC) { |
| return PKEY_SPKI_Export(key_data, out); |
| } else { |
| // Ensure exported key is in uncompressed point format. |
| // The temporary EC key is so we can have i2d_PUBKEY_bio() write out |
| // the header but it is a somewhat silly hoop to jump through because |
| // the header is for all practical purposes a static 26 byte sequence |
| // where only the second byte changes. |
| Mutex::ScopedLock lock(key_data.mutex()); |
| const auto group = ECKeyPointer::GetGroup(m_pkey); |
| const auto point = ECKeyPointer::GetPublicKey(m_pkey); |
| const point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED; |
| const size_t need = |
| EC_POINT_point2oct(group, point, form, nullptr, 0, nullptr); |
| if (need == 0) return WebCryptoKeyExportStatus::FAILED; |
| auto data = DataPointer::Alloc(need); |
| const size_t have = |
| EC_POINT_point2oct(group, |
| point, |
| form, |
| static_cast<unsigned char*>(data.get()), |
| need, |
| nullptr); |
| if (have == 0) return WebCryptoKeyExportStatus::FAILED; |
| auto ec = ECKeyPointer::New(group); |
| CHECK(ec); |
| auto uncompressed = ECPointPointer::New(group); |
| ncrypto::Buffer<const unsigned char> buffer{ |
| .data = static_cast<unsigned char*>(data.get()), |
| .len = data.size(), |
| }; |
| CHECK(uncompressed.setFromBuffer(buffer, group)); |
| CHECK(ec.setPublicKey(uncompressed)); |
| auto pkey = EVPKeyPointer::New(); |
| CHECK(pkey.set(ec)); |
| auto bio = pkey.derPublicKey(); |
| if (!bio) return WebCryptoKeyExportStatus::FAILED; |
| *out = ByteSource::FromBIO(bio); |
| return WebCryptoKeyExportStatus::OK; |
| } |
| } |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| bool ExportJWKEcKey(Environment* env, |
| const KeyObjectData& key, |
| Local<Object> target) { |
| Mutex::ScopedLock lock(key.mutex()); |
| const auto& m_pkey = key.GetAsymmetricKey(); |
| CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); |
| |
| const EC_KEY* ec = m_pkey; |
| CHECK_NOT_NULL(ec); |
| |
| const auto pub = ECKeyPointer::GetPublicKey(ec); |
| const auto group = ECKeyPointer::GetGroup(ec); |
| |
| int degree_bits = EC_GROUP_get_degree(group); |
| int degree_bytes = |
| (degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8; |
| |
| auto x = BignumPointer::New(); |
| auto y = BignumPointer::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 false; |
| } |
| |
| if (target->Set( |
| env->context(), |
| env->jwk_kty_string(), |
| env->jwk_ec_string()).IsNothing()) { |
| return false; |
| } |
| |
| 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 false; |
| } |
| |
| Local<String> crv_name; |
| const int nid = EC_GROUP_get_curve_name(group); |
| switch (nid) { |
| case NID_X9_62_prime256v1: |
| crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-256"); |
| break; |
| case NID_secp256k1: |
| crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "secp256k1"); |
| break; |
| case NID_secp384r1: |
| crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-384"); |
| break; |
| case NID_secp521r1: |
| crv_name = FIXED_ONE_BYTE_STRING(env->isolate(), "P-521"); |
| break; |
| default: { |
| THROW_ERR_CRYPTO_JWK_UNSUPPORTED_CURVE( |
| env, "Unsupported JWK EC curve: %s.", OBJ_nid2sn(nid)); |
| return false; |
| } |
| } |
| if (target->Set( |
| env->context(), |
| env->jwk_crv_string(), |
| crv_name).IsNothing()) { |
| return false; |
| } |
| |
| if (key.GetKeyType() == kKeyTypePrivate) { |
| auto pvt = ECKeyPointer::GetPrivateKey(ec); |
| return SetEncodedValue(env, target, env->jwk_d_string(), pvt, degree_bytes) |
| .IsJust(); |
| } |
| |
| return true; |
| } |
| |
| bool ExportJWKEdKey(Environment* env, |
| const KeyObjectData& key, |
| Local<Object> target) { |
| Mutex::ScopedLock lock(key.mutex()); |
| const auto& pkey = key.GetAsymmetricKey(); |
| |
| const char* curve = ([&] { |
| switch (pkey.id()) { |
| case EVP_PKEY_ED25519: |
| return "Ed25519"; |
| case EVP_PKEY_ED448: |
| return "Ed448"; |
| case EVP_PKEY_X25519: |
| return "X25519"; |
| case EVP_PKEY_X448: |
| return "X448"; |
| default: |
| UNREACHABLE(); |
| } |
| })(); |
| |
| static constexpr auto trySetKey = [](Environment* env, |
| DataPointer data, |
| Local<Object> target, |
| Local<String> key) { |
| Local<Value> encoded; |
| Local<Value> error; |
| if (!data) return false; |
| const ncrypto::Buffer<const char> out = data; |
| if (!StringBytes::Encode( |
| env->isolate(), out.data, out.len, BASE64URL, &error) |
| .ToLocal(&encoded) || |
| target->Set(env->context(), key, encoded).IsNothing()) { |
| if (!error.IsEmpty()) env->isolate()->ThrowException(error); |
| return false; |
| } |
| return true; |
| }; |
| |
| return !( |
| target |
| ->Set(env->context(), |
| env->jwk_crv_string(), |
| OneByteString(env->isolate(), curve)) |
| .IsNothing() || |
| (key.GetKeyType() == kKeyTypePrivate && |
| !trySetKey(env, pkey.rawPrivateKey(), target, env->jwk_d_string())) || |
| !trySetKey(env, pkey.rawPublicKey(), target, env->jwk_x_string()) || |
| target->Set(env->context(), env->jwk_kty_string(), env->jwk_okp_string()) |
| .IsNothing()); |
| } |
| |
| KeyObjectData ImportJWKEcKey(Environment* env, |
| Local<Object> jwk, |
| const FunctionCallbackInfo<Value>& args, |
| unsigned int offset) { |
| CHECK(args[offset]->IsString()); // curve name |
| Utf8Value curve(env->isolate(), args[offset].As<String>()); |
| |
| int nid = GetCurveFromName(*curve); |
| if (nid == NID_undef) { // Unknown curve |
| THROW_ERR_CRYPTO_INVALID_CURVE(env); |
| return {}; |
| } |
| |
| Local<Value> x_value; |
| Local<Value> y_value; |
| Local<Value> 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 {}; |
| } |
| |
| if (!x_value->IsString() || |
| !y_value->IsString() || |
| (!d_value->IsUndefined() && !d_value->IsString())) { |
| THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); |
| return {}; |
| } |
| |
| KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; |
| |
| auto ec = ECKeyPointer::NewByCurveName(nid); |
| if (!ec) { |
| THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); |
| return {}; |
| } |
| |
| ByteSource x = ByteSource::FromEncodedString(env, x_value.As<String>()); |
| ByteSource y = ByteSource::FromEncodedString(env, y_value.As<String>()); |
| |
| if (!ec.setPublicKeyRaw(x.ToBN(), y.ToBN())) { |
| THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); |
| return {}; |
| } |
| |
| if (type == kKeyTypePrivate) { |
| ByteSource d = ByteSource::FromEncodedString(env, d_value.As<String>()); |
| if (!ec.setPrivateKey(d.ToBN())) { |
| THROW_ERR_CRYPTO_INVALID_JWK(env, "Invalid JWK EC key"); |
| return {}; |
| } |
| } |
| |
| auto pkey = EVPKeyPointer::New(); |
| if (!pkey) return {}; |
| CHECK(pkey.set(ec)); |
| |
| return KeyObjectData::CreateAsymmetric(type, std::move(pkey)); |
| } |
| |
| bool GetEcKeyDetail(Environment* env, |
| const KeyObjectData& key, |
| Local<Object> target) { |
| Mutex::ScopedLock lock(key.mutex()); |
| const auto& m_pkey = key.GetAsymmetricKey(); |
| CHECK_EQ(m_pkey.id(), EVP_PKEY_EC); |
| |
| const EC_KEY* ec = m_pkey; |
| CHECK_NOT_NULL(ec); |
| |
| const auto group = ECKeyPointer::GetGroup(ec); |
| int nid = EC_GROUP_get_curve_name(group); |
| |
| return target |
| ->Set(env->context(), |
| env->named_curve_string(), |
| OneByteString(env->isolate(), OBJ_nid2sn(nid))) |
| .IsJust(); |
| } |
| |
| // 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 EVPKeyPointer& key) { |
| const EC_KEY* ec = key; |
| CHECK_NOT_NULL(ec); |
| auto order = BignumPointer::New(); |
| CHECK(order); |
| CHECK(EC_GROUP_get_order(ECKeyPointer::GetGroup(ec), order.get(), nullptr)); |
| return order.byteLength(); |
| } |
| } // namespace crypto |
| } // namespace node |