/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/crypto/crypto_dh.cc
607 строк
19 KB
Filip Skokan
crypto: handle DH operation failures
01 авг 2026, 23:34
01 авг 2026, 23:34
a382db5
Код
Авторство
О чём код?
#include "crypto/crypto_dh.h" #include "async_wrap-inl.h" #include "base_object-inl.h" #include "crypto/crypto_keys.h" #include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "ncrypto.h" #include "node_errors.h" #ifndef OPENSSL_IS_BORINGSSL #include "openssl/bnerr.h" #endif #include "openssl/dh.h" #include "threadpoolwork-inl.h" #include "v8.h" namespace node { using ncrypto::BignumPointer; using ncrypto::DataPointer; using ncrypto::DHPointer; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using v8::ArrayBuffer; using v8::BackingStoreInitializationMode; using v8::BackingStoreOnFailureMode; using v8::ConstructorBehavior; using v8::Context; using v8::DontDelete; using v8::FunctionCallback; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Int32; using v8::Isolate; using v8::JustVoid; using v8::Local; using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Object; using v8::PropertyAttribute; using v8::ReadOnly; using v8::SideEffectType; using v8::Signature; using v8::String; using v8::Value; namespace crypto { DiffieHellman::DiffieHellman(Environment* env, Local<Object> wrap, DHPointer dh) : BaseObject(env, wrap), dh_(std::move(dh)) { MakeWeak(); } void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("dh", dh_ ? kSizeOf_DH : 0); } namespace { MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) { struct Flag { bool secure; }; #ifdef V8_ENABLE_SANDBOX auto backing = ArrayBuffer::NewBackingStore( env->isolate(), data.size(), BackingStoreInitializationMode::kUninitialized, BackingStoreOnFailureMode::kReturnNull); if (!backing) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return MaybeLocal<Value>(); } if (data.size() > 0) { memcpy(backing->Data(), data.get(), data.size()); } #else auto backing = ArrayBuffer::NewBackingStore( data.get(), data.size(), [](void* data, size_t len, void* ptr) { std::unique_ptr<Flag> flag(static_cast<Flag*>(ptr)); DataPointer free_me(data, len, flag->secure); }, new Flag{data.isSecure()}); data.release(); #endif // V8_ENABLE_SANDBOX auto ab = ArrayBuffer::New(env->isolate(), std::move(backing)); return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>()); } void PutDhError(int reason) { #ifdef OPENSSL_IS_BORINGSSL OPENSSL_PUT_ERROR(DH, reason); #elif NCRYPTO_USE_OPENSSL3_PROVIDER ERR_raise(ERR_LIB_DH, reason); #else ERR_put_error(ERR_LIB_DH, 0, reason, __FILE__, __LINE__); #endif } #if defined(OPENSSL_IS_BORINGSSL) || !NCRYPTO_USE_OPENSSL3_PROVIDER void PutBnError(int reason) { #ifdef OPENSSL_IS_BORINGSSL OPENSSL_PUT_ERROR(BN, reason); #else ERR_put_error(ERR_LIB_BN, 0, reason, __FILE__, __LINE__); #endif } #endif void DiffieHellmanGroup(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Group name"); const node::Utf8Value group_name(env->isolate(), args[0]); DHPointer dh = DHPointer::FromGroup(group_name.ToStringView()); if (!dh) { return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env); } new DiffieHellman(env, args.This(), std::move(dh)); } void New(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); if (args.Length() != 2) { return THROW_ERR_MISSING_ARGS(env, "Constructor must have two arguments"); } if (args[0]->IsInt32()) { int32_t bits = args[0].As<Int32>()->Value(); if (bits < 2) { #ifndef OPENSSL_IS_BORINGSSL #if OPENSSL_VERSION_MAJOR >= 3 PutDhError(DH_R_MODULUS_TOO_SMALL); #else PutBnError(BN_R_BITS_TOO_SMALL); #endif // OPENSSL_VERSION_MAJOR >= 3 #else // OPENSSL_IS_BORINGSSL PutBnError(BN_R_BITS_TOO_SMALL); #endif // OPENSSL_IS_BORINGSSL return ThrowCryptoError(env, ERR_get_error(), "Invalid prime length"); } // If the first argument is an Int32 then we are generating a new // prime and then using that to generate the Diffie-Hellman parameters. // The second argument must be an Int32 as well. if (!args[1]->IsInt32()) { return THROW_ERR_INVALID_ARG_TYPE(env, "Second argument must be an int32"); } int32_t generator = args[1].As<Int32>()->Value(); if (generator < 2) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } auto dh = DHPointer::New(bits, generator); if (!dh) { return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid DH parameters"); } new DiffieHellman(env, args.This(), std::move(dh)); return; } // The first argument must be an ArrayBuffer or ArrayBufferView with the // prime, and the second argument must be an int32 with the generator // or an ArrayBuffer or ArrayBufferView with the generator. ArrayBufferOrViewContents<char> arg0(args[0]); if (!arg0.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "prime is too big"); BignumPointer bn_p(reinterpret_cast<uint8_t*>(arg0.data()), arg0.size()); BignumPointer bn_g; if (!bn_p) { return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid prime"); } if (args[1]->IsInt32()) { int32_t generator = args[1].As<Int32>()->Value(); if (generator < 2) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } bn_g = BignumPointer::New(); if (!bn_g.setWord(generator)) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } } else { ArrayBufferOrViewContents<char> arg1(args[1]); if (!arg1.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "generator is too big"); bn_g = BignumPointer(reinterpret_cast<uint8_t*>(arg1.data()), arg1.size()); if (!bn_g) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } if (bn_g.getWord().has_value() && bn_g.getWord().value() < 2) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } } #if NCRYPTO_USE_OPENSSL3_PROVIDER if (BN_num_bits(bn_p.get()) >= 512 && BN_cmp(bn_g.get(), bn_p.get()) >= 0) { PutDhError(DH_R_BAD_GENERATOR); return ThrowCryptoError(env, ERR_get_error(), "Invalid generator"); } #endif auto dh = DHPointer::New(std::move(bn_p), std::move(bn_g)); if (!dh) { return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid DH parameters"); } new DiffieHellman(env, args.This(), std::move(dh)); } void GenerateKeys(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto dp = dh.generateKeys(); if (!dp) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Key generation failed"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void GetPrime(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto dp = dh.getPrime(); if (!dp) { return THROW_ERR_CRYPTO_INVALID_STATE(env, "p is null"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void GetGenerator(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto dp = dh.getGenerator(); if (!dp) { return THROW_ERR_CRYPTO_INVALID_STATE(env, "g is null"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void GetPublicKey(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto dp = dh.getPublicKey(); if (!dp) { return THROW_ERR_CRYPTO_INVALID_STATE( env, "No public key - did you forget to generate one?"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void GetPrivateKey(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto dp = dh.getPrivateKey(); if (!dp) { return THROW_ERR_CRYPTO_INVALID_STATE( env, "No private key - did you forget to generate one?"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void ComputeSecret(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; CHECK_EQ(args.Length(), 1); ArrayBufferOrViewContents<unsigned char> key_buf(args[0]); if (!key_buf.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "secret is too big"); BignumPointer key(key_buf.data(), key_buf.size()); switch (dh.checkPublicKey(key)) { case DHPointer::CheckPublicKeyResult::CHECK_FAILED: return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env, "Unspecified validation error"); #ifndef OPENSSL_IS_BORINGSSL case DHPointer::CheckPublicKeyResult::TOO_SMALL: return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too small"); case DHPointer::CheckPublicKeyResult::TOO_LARGE: return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too large"); #endif case DHPointer::CheckPublicKeyResult::INVALID: return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env, "Supplied key is invalid"); case DHPointer::CheckPublicKeyResult::NONE: break; } if (!dh.hasPrivateKey()) { return THROW_ERR_CRYPTO_INVALID_STATE( env, "Cannot compute shared secret without a private key"); } auto dp = dh.computeSecret(key); if (!dp) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to compute shared secret"); } Local<Value> buffer; if (DataPointerToBuffer(env, std::move(dp)).ToLocal(&buffer)) { args.GetReturnValue().Set(buffer); } } void SetPublicKey(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; CHECK_EQ(args.Length(), 1); ArrayBufferOrViewContents<unsigned char> buf(args[0]); if (!buf.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "buf is too big"); BignumPointer num(buf.data(), buf.size()); if (!num || !dh.setPublicKey(std::move(num))) return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid public key"); } void SetPrivateKey(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; CHECK_EQ(args.Length(), 1); ArrayBufferOrViewContents<unsigned char> buf(args[0]); if (!buf.CheckSizeInt32()) [[unlikely]] return THROW_ERR_OUT_OF_RANGE(env, "buf is too big"); BignumPointer num(buf.data(), buf.size()); if (!num || !dh.setPrivateKey(std::move(num))) return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid private key"); } void Check(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.This()); DHPointer& dh = *diffieHellman; auto result = dh.check(); if (result == DHPointer::CheckResult::CHECK_FAILED) { return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Checking DH parameters failed"); } args.GetReturnValue().Set(static_cast<int>(result)); } } // namespace // The input arguments to DhKeyPairGenJob can vary // 1. CryptoJobMode // and either // 2. Group name (as a string) // or // 2. Prime or Prime Length // 3. Generator // Followed by the public and private key encoding parameters: // * Public format // * Public type // * Private format // * Private type // * Cipher // * Passphrase Maybe<void> DhKeyGenTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo<Value>& args, unsigned int* offset, DhKeyPairGenConfig* params) { Environment* env = Environment::GetCurrent(args); if (args[*offset]->IsString()) { Utf8Value group_name(env->isolate(), args[*offset]); auto group = DHPointer::FindGroup(group_name.ToStringView()); if (!group) { THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env); return Nothing<void>(); } static constexpr int kStandardizedGenerator = 2; params->params.prime = std::move(group); params->params.generator = kStandardizedGenerator; *offset += 1; } else { if (args[*offset]->IsInt32()) { int size = args[*offset].As<Int32>()->Value(); if (size < 0) { THROW_ERR_OUT_OF_RANGE(env, "Invalid prime size"); return Nothing<void>(); } params->params.prime = size; } else { ArrayBufferOrViewContents<unsigned char> input(args[*offset]); if (!input.CheckSizeInt32()) [[unlikely]] { THROW_ERR_OUT_OF_RANGE(env, "prime is too big"); return Nothing<void>(); } params->params.prime = BignumPointer(input.data(), input.size()); } CHECK(args[*offset + 1]->IsInt32()); params->params.generator = args[*offset + 1].As<Int32>()->Value(); *offset += 2; } return JustVoid(); } EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) { EVPKeyPointer key_params; if (BignumPointer* prime_fixed_value = std::get_if<BignumPointer>(¶ms->params.prime)) { auto prime = prime_fixed_value->clone(); auto bn_g = BignumPointer::New(); if (!prime || !bn_g || !bn_g.setWord(params->params.generator)) { return {}; } auto dh = DHPointer::New(std::move(prime), std::move(bn_g)); if (!dh) return {}; key_params = EVPKeyPointer::NewDH(std::move(dh)); } else if (int* prime_size = std::get_if<int>(¶ms->params.prime)) { auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH); #ifndef OPENSSL_IS_BORINGSSL if (!param_ctx.initForParamgen() || !param_ctx.setDhParameters(*prime_size, params->params.generator)) { return {}; } key_params = param_ctx.paramgen(); #else return {}; #endif } else { UNREACHABLE(); } if (!key_params) return {}; EVPKeyCtxPointer ctx = key_params.newCtx(); if (!ctx.initForKeygen()) return {}; return ctx; } Maybe<void> DHBitsTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo<Value>& args, unsigned int offset, DHBitsConfig* params) { auto public_key = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset); if (!public_key) [[unlikely]] return Nothing<void>(); auto private_key = KeyObjectData::GetPrivateKeyFromJs(args, &offset, true); if (!private_key) [[unlikely]] return Nothing<void>(); params->public_key = std::move(public_key); params->private_key = std::move(private_key); return JustVoid(); } MaybeLocal<Value> DHBitsTraits::EncodeOutput(Environment* env, const DHBitsConfig& params, ByteSource* out) { return out->ToArrayBuffer(env); } bool DHBitsTraits::DeriveBits(Environment* env, const DHBitsConfig& params, ByteSource* out, CryptoJobMode mode, CryptoErrorStore* errors) { auto dp = DHPointer::stateless(params.private_key.GetAsymmetricKey(), params.public_key.GetAsymmetricKey()); if (!dp) { return false; } *out = ByteSource::Allocated(dp.release()); CHECK(!out->empty()); return true; } bool GetDhKeyDetail(Environment* env, const KeyObjectData& key, Local<Object> target) { CHECK_EQ(key.GetAsymmetricKey().id(), EVP_PKEY_DH); return true; } void DiffieHellman::Initialize(Environment* env, Local<Object> target) { Isolate* isolate = env->isolate(); Local<Context> context = env->context(); auto make = [&](Local<String> name, FunctionCallback callback) { Local<FunctionTemplate> t = NewFunctionTemplate(isolate, callback); const PropertyAttribute attributes = static_cast<PropertyAttribute>(ReadOnly | DontDelete); t->InstanceTemplate()->SetInternalFieldCount( DiffieHellman::kInternalFieldCount); SetProtoMethod(isolate, t, "generateKeys", GenerateKeys); SetProtoMethod(isolate, t, "computeSecret", ComputeSecret); SetProtoMethodNoSideEffect(isolate, t, "getPrime", GetPrime); SetProtoMethodNoSideEffect(isolate, t, "getGenerator", GetGenerator); SetProtoMethodNoSideEffect(isolate, t, "getPublicKey", GetPublicKey); SetProtoMethodNoSideEffect(isolate, t, "getPrivateKey", GetPrivateKey); SetProtoMethod(isolate, t, "setPublicKey", SetPublicKey); SetProtoMethod(isolate, t, "setPrivateKey", SetPrivateKey); Local<FunctionTemplate> verify_error_getter_templ = FunctionTemplate::New(isolate, Check, Local<Value>(), Signature::New(env->isolate(), t), /* length */ 0, ConstructorBehavior::kThrow, SideEffectType::kHasNoSideEffect); t->InstanceTemplate()->SetAccessorProperty(env->verify_error_string(), verify_error_getter_templ, Local<FunctionTemplate>(), attributes); SetConstructorFunction(context, target, name, t); }; make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), New); make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"), DiffieHellmanGroup); DHKeyPairGenJob::Initialize(env, target); DHBitsJob::Initialize(env, target); } void DiffieHellman::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(New); registry->Register(DiffieHellmanGroup); registry->Register(GenerateKeys); registry->Register(ComputeSecret); registry->Register(GetPrime); registry->Register(GetGenerator); registry->Register(GetPublicKey); registry->Register(GetPrivateKey); registry->Register(SetPublicKey); registry->Register(SetPrivateKey); registry->Register(Check); DHKeyPairGenJob::RegisterExternalReferences(registry); DHBitsJob::RegisterExternalReferences(registry); } } // namespace crypto } // namespace node