#include "crypto/crypto_dh.h" #include "crypto/crypto_keys.h" #include "crypto/crypto_groups.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 "threadpoolwork-inl.h" #include "v8.h" #include namespace node { using v8::ConstructorBehavior; using v8::DontDelete; using v8::FunctionCallback; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Int32; using v8::Just; using v8::Local; using v8::Maybe; using v8::Nothing; using v8::Object; using v8::PropertyAttribute; using v8::ReadOnly; using v8::SideEffectType; using v8::Signature; using v8::String; using v8::Uint8Array; using v8::Value; namespace crypto { namespace { static void ZeroPadDiffieHellmanSecret(size_t remainder_size, char* data, size_t length) { // DH_size returns number of bytes in a prime number. // DH_compute_key returns number of bytes in a remainder of exponent, which // may have less bytes than a prime number. Therefore add 0-padding to the // allocated buffer. const size_t prime_size = length; if (remainder_size != prime_size) { CHECK_LT(remainder_size, prime_size); const size_t padding = prime_size - remainder_size; memmove(data + padding, data, remainder_size); memset(data, 0, padding); } } static void ZeroPadDiffieHellmanSecret(size_t remainder_size, AllocatedBuffer* ret) { ZeroPadDiffieHellmanSecret(remainder_size, ret->data(), ret->size()); } } // namespace DiffieHellman::DiffieHellman(Environment* env, Local wrap) : BaseObject(env, wrap), verifyError_(0) { MakeWeak(); } void DiffieHellman::Initialize(Environment* env, Local target) { auto make = [&] (Local name, FunctionCallback callback) { Local t = env->NewFunctionTemplate(callback); const PropertyAttribute attributes = static_cast(ReadOnly | DontDelete); t->InstanceTemplate()->SetInternalFieldCount( DiffieHellman::kInternalFieldCount); t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); env->SetProtoMethodNoSideEffect(t, "getPrime", GetPrime); env->SetProtoMethodNoSideEffect(t, "getGenerator", GetGenerator); env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey); env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey); env->SetProtoMethod(t, "setPublicKey", SetPublicKey); env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); Local verify_error_getter_templ = FunctionTemplate::New(env->isolate(), DiffieHellman::VerifyErrorGetter, Local(), Signature::New(env->isolate(), t), /* length */ 0, ConstructorBehavior::kThrow, SideEffectType::kHasNoSideEffect); t->InstanceTemplate()->SetAccessorProperty( env->verify_error_string(), verify_error_getter_templ, Local(), attributes); env->SetConstructorFunction(target, name, t); }; make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellman"), New); make(FIXED_ONE_BYTE_STRING(env->isolate(), "DiffieHellmanGroup"), DiffieHellmanGroup); env->SetMethodNoSideEffect(target, "statelessDH", DiffieHellman::Stateless); DHKeyPairGenJob::Initialize(env, target); DHKeyExportJob::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(DiffieHellman::VerifyErrorGetter); registry->Register(DiffieHellman::Stateless); DHKeyPairGenJob::RegisterExternalReferences(registry); DHKeyExportJob::RegisterExternalReferences(registry); DHBitsJob::RegisterExternalReferences(registry); } bool DiffieHellman::Init(int primeLength, int g) { dh_.reset(DH_new()); if (!DH_generate_parameters_ex(dh_.get(), primeLength, g, nullptr)) return false; return VerifyContext(); } void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("dh", dh_ ? kSizeOf_DH : 0); } bool DiffieHellman::Init(const char* p, int p_len, int g) { dh_.reset(DH_new()); if (p_len <= 0) { ERR_put_error(ERR_LIB_BN, BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL, __FILE__, __LINE__); return false; } if (g <= 1) { ERR_put_error(ERR_LIB_DH, DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR, __FILE__, __LINE__); return false; } BIGNUM* bn_p = BN_bin2bn(reinterpret_cast(p), p_len, nullptr); BIGNUM* bn_g = BN_new(); if (!BN_set_word(bn_g, g) || !DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) { BN_free(bn_p); BN_free(bn_g); return false; } return VerifyContext(); } bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) { dh_.reset(DH_new()); if (p_len <= 0) { ERR_put_error(ERR_LIB_BN, BN_F_BN_GENERATE_PRIME_EX, BN_R_BITS_TOO_SMALL, __FILE__, __LINE__); return false; } if (g_len <= 0) { ERR_put_error(ERR_LIB_DH, DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR, __FILE__, __LINE__); return false; } BIGNUM* bn_g = BN_bin2bn(reinterpret_cast(g), g_len, nullptr); if (BN_is_zero(bn_g) || BN_is_one(bn_g)) { BN_free(bn_g); ERR_put_error(ERR_LIB_DH, DH_F_DH_BUILTIN_GENPARAMS, DH_R_BAD_GENERATOR, __FILE__, __LINE__); return false; } BIGNUM* bn_p = BN_bin2bn(reinterpret_cast(p), p_len, nullptr); if (!DH_set0_pqg(dh_.get(), bn_p, nullptr, bn_g)) { BN_free(bn_p); BN_free(bn_g); return false; } return VerifyContext(); } inline const modp_group* FindDiffieHellmanGroup(const char* name) { for (const modp_group& group : modp_groups) { if (StringEqualNoCase(name, group.name)) return &group; } return nullptr; } void DiffieHellman::DiffieHellmanGroup( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman = new DiffieHellman(env, args.This()); CHECK_EQ(args.Length(), 1); THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "Group name"); bool initialized = false; const node::Utf8Value group_name(env->isolate(), args[0]); const modp_group* group = FindDiffieHellmanGroup(*group_name); if (group == nullptr) return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env); initialized = diffieHellman->Init(group->prime, group->prime_size, group->gen); if (!initialized) THROW_ERR_CRYPTO_INITIALIZATION_FAILED(env); } void DiffieHellman::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman = new DiffieHellman(env, args.This()); bool initialized = false; if (args.Length() == 2) { if (args[0]->IsInt32()) { if (args[1]->IsInt32()) { initialized = diffieHellman->Init(args[0].As()->Value(), args[1].As()->Value()); } } else { ArrayBufferOrViewContents arg0(args[0]); if (UNLIKELY(!arg0.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "prime is too big"); if (args[1]->IsInt32()) { initialized = diffieHellman->Init(arg0.data(), arg0.size(), args[1].As()->Value()); } else { ArrayBufferOrViewContents arg1(args[1]); if (UNLIKELY(!arg1.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "generator is too big"); initialized = diffieHellman->Init(arg0.data(), arg0.size(), arg1.data(), arg1.size()); } } } if (!initialized) { return ThrowCryptoError(env, ERR_get_error(), "Initialization failed"); } } void DiffieHellman::GenerateKeys(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); if (!DH_generate_key(diffieHellman->dh_.get())) { return ThrowCryptoError(env, ERR_get_error(), "Key generation failed"); } const BIGNUM* pub_key; DH_get0_key(diffieHellman->dh_.get(), &pub_key, nullptr); const int size = BN_num_bytes(pub_key); CHECK_GE(size, 0); AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size); CHECK_EQ(size, BN_bn2binpad( pub_key, reinterpret_cast(data.data()), size)); args.GetReturnValue().Set(data.ToBuffer().FromMaybe(Local())); } void DiffieHellman::GetField(const FunctionCallbackInfo& args, const BIGNUM* (*get_field)(const DH*), const char* err_if_null) { Environment* env = Environment::GetCurrent(args); DiffieHellman* dh; ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder()); const BIGNUM* num = get_field(dh->dh_.get()); if (num == nullptr) return THROW_ERR_CRYPTO_INVALID_STATE(env, err_if_null); const int size = BN_num_bytes(num); CHECK_GE(size, 0); AllocatedBuffer data = AllocatedBuffer::AllocateManaged(env, size); CHECK_EQ( size, BN_bn2binpad(num, reinterpret_cast(data.data()), size)); args.GetReturnValue().Set(data.ToBuffer().FromMaybe(Local())); } void DiffieHellman::GetPrime(const FunctionCallbackInfo& args) { GetField(args, [](const DH* dh) -> const BIGNUM* { const BIGNUM* p; DH_get0_pqg(dh, &p, nullptr, nullptr); return p; }, "p is null"); } void DiffieHellman::GetGenerator(const FunctionCallbackInfo& args) { GetField(args, [](const DH* dh) -> const BIGNUM* { const BIGNUM* g; DH_get0_pqg(dh, nullptr, nullptr, &g); return g; }, "g is null"); } void DiffieHellman::GetPublicKey(const FunctionCallbackInfo& args) { GetField(args, [](const DH* dh) -> const BIGNUM* { const BIGNUM* pub_key; DH_get0_key(dh, &pub_key, nullptr); return pub_key; }, "No public key - did you forget to generate one?"); } void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo& args) { GetField(args, [](const DH* dh) -> const BIGNUM* { const BIGNUM* priv_key; DH_get0_key(dh, nullptr, &priv_key); return priv_key; }, "No private key - did you forget to generate one?"); } void DiffieHellman::ComputeSecret(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); ClearErrorOnReturn clear_error_on_return; CHECK_EQ(args.Length(), 1); ArrayBufferOrViewContents key_buf(args[0]); if (UNLIKELY(!key_buf.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "secret is too big"); BignumPointer key(BN_bin2bn(key_buf.data(), key_buf.size(), nullptr)); AllocatedBuffer ret = AllocatedBuffer::AllocateManaged(env, DH_size(diffieHellman->dh_.get())); int size = DH_compute_key(reinterpret_cast(ret.data()), key.get(), diffieHellman->dh_.get()); if (size == -1) { int checkResult; int checked; checked = DH_check_pub_key(diffieHellman->dh_.get(), key.get(), &checkResult); if (!checked) { return ThrowCryptoError(env, ERR_get_error(), "Invalid Key"); } else if (checkResult) { if (checkResult & DH_CHECK_PUBKEY_TOO_SMALL) { return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too small"); } else if (checkResult & DH_CHECK_PUBKEY_TOO_LARGE) { return THROW_ERR_CRYPTO_INVALID_KEYLEN(env, "Supplied key is too large"); } } return THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); } CHECK_GE(size, 0); ZeroPadDiffieHellmanSecret(static_cast(size), &ret); args.GetReturnValue().Set(ret.ToBuffer().FromMaybe(Local())); } void DiffieHellman::SetKey(const FunctionCallbackInfo& args, int (*set_field)(DH*, BIGNUM*), const char* what) { Environment* env = Environment::GetCurrent(args); DiffieHellman* dh; ASSIGN_OR_RETURN_UNWRAP(&dh, args.Holder()); CHECK_EQ(args.Length(), 1); ArrayBufferOrViewContents buf(args[0]); if (UNLIKELY(!buf.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "buf is too big"); BIGNUM* num = BN_bin2bn(buf.data(), buf.size(), nullptr); CHECK_NOT_NULL(num); CHECK_EQ(1, set_field(dh->dh_.get(), num)); } void DiffieHellman::SetPublicKey(const FunctionCallbackInfo& args) { SetKey(args, [](DH* dh, BIGNUM* num) { return DH_set0_key(dh, num, nullptr); }, "Public key"); } void DiffieHellman::SetPrivateKey(const FunctionCallbackInfo& args) { SetKey(args, [](DH* dh, BIGNUM* num) { return DH_set0_key(dh, nullptr, num); }, "Private key"); } void DiffieHellman::VerifyErrorGetter(const FunctionCallbackInfo& args) { HandleScope scope(args.GetIsolate()); DiffieHellman* diffieHellman; ASSIGN_OR_RETURN_UNWRAP(&diffieHellman, args.Holder()); args.GetReturnValue().Set(diffieHellman->verifyError_); } bool DiffieHellman::VerifyContext() { int codes; if (!DH_check(dh_.get(), &codes)) return false; verifyError_ = codes; return true; } // 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 DhKeyGenTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int* offset, DhKeyPairGenConfig* params) { Environment* env = Environment::GetCurrent(args); if (args[*offset]->IsString()) { Utf8Value group_name(env->isolate(), args[*offset]); const modp_group* group = FindDiffieHellmanGroup(*group_name); if (group == nullptr) { THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env); return Nothing(); } params->params.prime = BignumPointer( BN_bin2bn(reinterpret_cast(group->prime), group->prime_size, nullptr)); params->params.generator = group->gen; *offset += 1; } else { if (args[*offset]->IsInt32()) { int size = args[*offset].As()->Value(); if (size < 0) { THROW_ERR_OUT_OF_RANGE(env, "Invalid prime size"); return Nothing(); } params->params.prime = size; } else { ArrayBufferOrViewContents input(args[*offset]); if (UNLIKELY(!input.CheckSizeInt32())) { THROW_ERR_OUT_OF_RANGE(env, "prime is too big"); return Nothing(); } params->params.prime = BignumPointer( BN_bin2bn(input.data(), input.size(), nullptr)); } CHECK(args[*offset + 1]->IsInt32()); params->params.generator = args[*offset + 1].As()->Value(); *offset += 2; } return Just(true); } EVPKeyCtxPointer DhKeyGenTraits::Setup(DhKeyPairGenConfig* params) { EVPKeyPointer key_params; if (BignumPointer* prime_fixed_value = std::get_if(¶ms->params.prime)) { DHPointer dh(DH_new()); if (!dh) return EVPKeyCtxPointer(); BIGNUM* prime = prime_fixed_value->get(); BignumPointer bn_g(BN_new()); if (!BN_set_word(bn_g.get(), params->params.generator) || !DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get())) { return EVPKeyCtxPointer(); } prime_fixed_value->release(); bn_g.release(); key_params = EVPKeyPointer(EVP_PKEY_new()); CHECK(key_params); CHECK_EQ(EVP_PKEY_assign_DH(key_params.get(), dh.release()), 1); } else if (int* prime_size = std::get_if(¶ms->params.prime)) { EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr)); EVP_PKEY* raw_params = nullptr; if (!param_ctx || EVP_PKEY_paramgen_init(param_ctx.get()) <= 0 || EVP_PKEY_CTX_set_dh_paramgen_prime_len( param_ctx.get(), *prime_size) <= 0 || EVP_PKEY_CTX_set_dh_paramgen_generator( param_ctx.get(), params->params.generator) <= 0 || EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0) { return EVPKeyCtxPointer(); } key_params = EVPKeyPointer(raw_params); } else { UNREACHABLE(); } EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(key_params.get(), nullptr)); if (!ctx || EVP_PKEY_keygen_init(ctx.get()) <= 0) return EVPKeyCtxPointer(); return ctx; } Maybe DHKeyExportTraits::AdditionalConfig( const FunctionCallbackInfo& args, unsigned int offset, DHKeyExportConfig* params) { return Just(true); } WebCryptoKeyExportStatus DHKeyExportTraits::DoExport( std::shared_ptr key_data, WebCryptoKeyFormat format, const DHKeyExportConfig& params, ByteSource* out) { CHECK_NE(key_data->GetKeyType(), kKeyTypeSecret); switch (format) { 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(); } } namespace { ByteSource StatelessDiffieHellmanThreadsafe( const ManagedEVPPKey& our_key, const ManagedEVPPKey& their_key) { size_t out_size; EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr)); if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 || EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) <= 0 || EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0) return ByteSource(); char* buf = MallocOpenSSL(out_size); ByteSource out = ByteSource::Allocated(buf, out_size); if (EVP_PKEY_derive( ctx.get(), reinterpret_cast(buf), &out_size) <= 0) { return ByteSource(); } ZeroPadDiffieHellmanSecret(out_size, buf, out.size()); return out; } } // namespace void DiffieHellman::Stateless(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsObject() && args[1]->IsObject()); KeyObjectHandle* our_key_object; ASSIGN_OR_RETURN_UNWRAP(&our_key_object, args[0].As()); CHECK_EQ(our_key_object->Data()->GetKeyType(), kKeyTypePrivate); KeyObjectHandle* their_key_object; ASSIGN_OR_RETURN_UNWRAP(&their_key_object, args[1].As()); CHECK_NE(their_key_object->Data()->GetKeyType(), kKeyTypeSecret); ManagedEVPPKey our_key = our_key_object->Data()->GetAsymmetricKey(); ManagedEVPPKey their_key = their_key_object->Data()->GetAsymmetricKey(); Local out = StatelessDiffieHellmanThreadsafe(our_key, their_key) .ToBuffer(env).FromMaybe(Local()); if (Buffer::Length(out) == 0) return ThrowCryptoError(env, ERR_get_error(), "diffieHellman failed"); args.GetReturnValue().Set(out); } Maybe DHBitsTraits::AdditionalConfig( CryptoJobMode mode, const FunctionCallbackInfo& args, unsigned int offset, DHBitsConfig* 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()); ASSIGN_OR_RETURN_UNWRAP(&private_key, args[offset + 1], Nothing()); if (private_key->Data()->GetKeyType() != kKeyTypePrivate || public_key->Data()->GetKeyType() != kKeyTypePublic) { THROW_ERR_CRYPTO_INVALID_KEYTYPE(env); return Nothing(); } params->public_key = public_key->Data(); params->private_key = private_key->Data(); return Just(true); } Maybe DHBitsTraits::EncodeOutput( Environment* env, const DHBitsConfig& params, ByteSource* out, v8::Local* result) { *result = out->ToArrayBuffer(env); return Just(!result->IsEmpty()); } bool DHBitsTraits::DeriveBits( Environment* env, const DHBitsConfig& params, ByteSource* out) { *out = StatelessDiffieHellmanThreadsafe( params.private_key->GetAsymmetricKey(), params.public_key->GetAsymmetricKey()); return true; } Maybe GetDhKeyDetail( Environment* env, std::shared_ptr key, Local target) { ManagedEVPPKey pkey = key->GetAsymmetricKey(); CHECK_EQ(EVP_PKEY_id(pkey.get()), EVP_PKEY_DH); return Just(true); } } // namespace crypto } // namespace node