diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 7d65fc78cf2999..d924c8643cf050 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -1232,6 +1232,9 @@ passing keys as strings or `Buffer`s due to improved security features.
+
+* `options`: {Object}
+ * `privateKey`: {KeyObject}
+ * `publicKey`: {KeyObject}
+* Returns: {Buffer}
+
+Computes the Diffie-Hellman secret based on a `privateKey` and a `publicKey`.
+Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'`
+(for Diffie-Hellman), `'ec'` (for ECDH), `'x448'`, or `'x25519'` (for ECDH-ES).
+
### `crypto.generateKeyPair(type, options, callback)`
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`,
- `'x25519'`, or `'x448'`.
+ `'x25519'`, `'x448'`, or `'dh'`.
* `options`: {Object}
* `modulusLength`: {number} Key size in bits (RSA, DSA).
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
* `divisorLength`: {number} Size of `q` in bits (DSA).
* `namedCurve`: {string} Name of the curve to use (EC).
+ * `prime`: {Buffer} The prime parameter (DH).
+ * `primeLength`: {number} Prime length in bits (DH).
+ * `generator`: {number} Custom generator (DH). **Default:** `2`.
+ * `groupName`: {string} Diffie-Hellman group name (DH). See
+ [`crypto.getDiffieHellman()`][].
* `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
* `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
* `callback`: {Function}
@@ -2115,8 +2141,8 @@ changes:
* `publicKey`: {string | Buffer | KeyObject}
* `privateKey`: {string | Buffer | KeyObject}
-Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
-and Ed448 are currently supported.
+Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519,
+Ed448, X25519, X448, and DH are currently supported.
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
@@ -2154,6 +2180,9 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
-* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
+* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`,
+ `'x25519'`, `'x448'`, or `'dh'`.
* `options`: {Object}
* `modulusLength`: {number} Key size in bits (RSA, DSA).
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
* `divisorLength`: {number} Size of `q` in bits (DSA).
* `namedCurve`: {string} Name of the curve to use (EC).
+ * `prime`: {Buffer} The prime parameter (DH).
+ * `primeLength`: {number} Prime length in bits (DH).
+ * `generator`: {number} Custom generator (DH). **Default:** `2`.
+ * `groupName`: {string} Diffie-Hellman group name (DH). See
+ [`crypto.getDiffieHellman()`][].
* `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
* `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
* Returns: {Object}
* `publicKey`: {string | Buffer | KeyObject}
* `privateKey`: {string | Buffer | KeyObject}
-Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
-and Ed448 are currently supported.
+Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519,
+Ed448, X25519, X448, and DH are currently supported.
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 63d2e1c20d5346..524daa21c515e0 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -776,6 +776,11 @@ be called no more than one time per instance of a `Hash` object.
[`hash.update()`][] failed for any reason. This should rarely, if ever, happen.
+
+### `ERR_CRYPTO_INCOMPATIBLE_KEY`
+
+The given crypto keys are incompatible with the attempted operation.
+
### `ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS`
@@ -826,6 +831,12 @@ A signing `key` was not provided to the [`sign.sign()`][] method.
[`crypto.timingSafeEqual()`][] was called with `Buffer`, `TypedArray`, or
`DataView` arguments of different lengths.
+
+### `ERR_CRYPTO_UNKNOWN_DH_GROUP`
+
+An unknown Diffie-Hellman group name was given. See
+[`crypto.getDiffieHellman()`][] for a list of valid group names.
+
### `ERR_DIR_CLOSED`
@@ -1514,6 +1525,12 @@ strict compliance with the API specification (which in some cases may accept
An [ES Module][] loader hook specified `format: 'dynamic'` but did not provide
a `dynamicInstantiate` hook.
+
+### `ERR_MISSING_OPTION`
+
+For APIs that accept options objects, some options might be mandatory. This code
+is thrown if a required option is missing.
+
### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`
@@ -2423,6 +2440,7 @@ such as `process.stdout.on('data')`.
[`Writable`]: stream.html#stream_class_stream_writable
[`child_process`]: child_process.html
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
+[`crypto.getDiffieHellman()`]: crypto.html#crypto_crypto_getdiffiehellman_groupname
[`crypto.scrypt()`]: crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback
[`crypto.scryptSync()`]: crypto.html#crypto_crypto_scryptsync_password_salt_keylen_options
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b
diff --git a/lib/crypto.js b/lib/crypto.js
index e2d1875d8d0c8b..cec0b2c094fa70 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -70,7 +70,8 @@ const {
const {
DiffieHellman,
DiffieHellmanGroup,
- ECDH
+ ECDH,
+ diffieHellman
} = require('internal/crypto/diffiehellman');
const {
Cipher,
@@ -163,6 +164,7 @@ module.exports = {
createSecretKey,
createSign,
createVerify,
+ diffieHellman,
getCiphers,
getCurves,
getDiffieHellman: createDiffieHellmanGroup,
diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js
index da8f87bf16d4f6..ae6b68b73b5767 100644
--- a/lib/internal/crypto/diffiehellman.js
+++ b/lib/internal/crypto/diffiehellman.js
@@ -2,16 +2,21 @@
const {
ObjectDefineProperty,
+ Set
} = primordials;
const { Buffer } = require('buffer');
const {
ERR_CRYPTO_ECDH_INVALID_FORMAT,
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
- ERR_INVALID_ARG_TYPE
+ ERR_CRYPTO_INCOMPATIBLE_KEY,
+ ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const { isArrayBufferView } = require('internal/util/types');
+const { KeyObject } = require('internal/crypto/keys');
const {
getDefaultEncoding,
kHandle,
@@ -21,7 +26,8 @@ const {
DiffieHellman: _DiffieHellman,
DiffieHellmanGroup: _DiffieHellmanGroup,
ECDH: _ECDH,
- ECDHConvertKey: _ECDHConvertKey
+ ECDHConvertKey: _ECDHConvertKey,
+ statelessDH
} = internalBinding('crypto');
const {
POINT_CONVERSION_COMPRESSED,
@@ -232,8 +238,40 @@ function getFormat(format) {
return POINT_CONVERSION_UNCOMPRESSED;
}
+const dhEnabledKeyTypes = new Set(['dh', 'ec', 'x448', 'x25519']);
+
+function diffieHellman(options) {
+ if (typeof options !== 'object')
+ throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
+
+ const { privateKey, publicKey } = options;
+ if (!(privateKey instanceof KeyObject))
+ throw new ERR_INVALID_OPT_VALUE('privateKey', privateKey);
+
+ if (!(publicKey instanceof KeyObject))
+ throw new ERR_INVALID_OPT_VALUE('publicKey', publicKey);
+
+ if (privateKey.type !== 'private')
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
+
+ if (publicKey.type !== 'public' && publicKey.type !== 'private') {
+ throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
+ 'private or public');
+ }
+
+ const privateType = privateKey.asymmetricKeyType;
+ const publicType = publicKey.asymmetricKeyType;
+ if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
+ throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
+ `${privateType} and ${publicType}`);
+ }
+
+ return statelessDH(privateKey[kHandle], publicKey[kHandle]);
+}
+
module.exports = {
DiffieHellman,
DiffieHellmanGroup,
- ECDH
+ ECDH,
+ diffieHellman
};
diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js
index 88d2822fa6fad0..ced1a0608fa4aa 100644
--- a/lib/internal/crypto/keygen.js
+++ b/lib/internal/crypto/keygen.js
@@ -11,6 +11,7 @@ const {
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairNid,
+ generateKeyPairDH,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
EVP_PKEY_X25519,
@@ -28,10 +29,12 @@ const {
const { customPromisifyArgs } = require('internal/util');
const { isUint32, validateString } = require('internal/validators');
const {
+ ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_CALLBACK,
- ERR_INVALID_OPT_VALUE
+ ERR_INVALID_OPT_VALUE,
+ ERR_MISSING_OPTION
} = require('internal/errors').codes;
const { isArrayBufferView } = require('internal/util/types');
@@ -245,6 +248,49 @@ function check(type, options, callback) {
cipher, passphrase, wrap);
}
break;
+ case 'dh':
+ {
+ const { group, primeLength, prime, generator } = needOptions();
+ let args;
+ if (group != null) {
+ if (prime != null)
+ throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
+ if (primeLength != null)
+ throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
+ if (generator != null)
+ throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
+ if (typeof group !== 'string')
+ throw new ERR_INVALID_OPT_VALUE('group', group);
+ args = [group];
+ } else {
+ if (prime != null) {
+ if (primeLength != null)
+ throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
+ if (!isArrayBufferView(prime))
+ throw new ERR_INVALID_OPT_VALUE('prime', prime);
+ } else if (primeLength != null) {
+ if (!isUint32(primeLength))
+ throw new ERR_INVALID_OPT_VALUE('primeLength', primeLength);
+ } else {
+ throw new ERR_MISSING_OPTION(
+ 'At least one of the group, prime, or primeLength options');
+ }
+
+ if (generator != null) {
+ if (!isUint32(generator))
+ throw new ERR_INVALID_OPT_VALUE('generator', generator);
+ }
+
+ args = [prime != null ? prime : primeLength,
+ generator == null ? 2 : generator];
+ }
+
+ impl = (wrap) => generateKeyPairDH(...args,
+ publicFormat, publicType,
+ privateFormat, privateType,
+ cipher, passphrase, wrap);
+ }
+ break;
default:
throw new ERR_INVALID_ARG_VALUE('type', type,
'must be a supported key type');
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 206724eacb6f52..edd286a20afee4 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -767,6 +767,7 @@ E('ERR_CRYPTO_FIPS_UNAVAILABLE', 'Cannot set FIPS mode in a non-FIPS build.',
Error);
E('ERR_CRYPTO_HASH_FINALIZED', 'Digest already called', Error);
E('ERR_CRYPTO_HASH_UPDATE_FAILED', 'Hash update failed', Error);
+E('ERR_CRYPTO_INCOMPATIBLE_KEY', 'Incompatible %s: %s', Error);
E('ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS', 'The selected key encoding %s %s.',
Error);
E('ERR_CRYPTO_INVALID_DIGEST', 'Invalid digest: %s', TypeError);
@@ -1187,6 +1188,7 @@ E('ERR_MISSING_ARGS',
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
'The ES Module loader may not return a format of \'dynamic\' when no ' +
'dynamicInstantiate function was provided', Error);
+E('ERR_MISSING_OPTION', '%s is required', TypeError);
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',
diff --git a/src/env.h b/src/env.h
index 558ef5cbde8cbf..fcba4dcde67f53 100644
--- a/src/env.h
+++ b/src/env.h
@@ -192,6 +192,7 @@ constexpr size_t kFsStatsBufferLength =
V(commonjs_string, "commonjs") \
V(config_string, "config") \
V(constants_string, "constants") \
+ V(crypto_dh_string, "dh") \
V(crypto_dsa_string, "dsa") \
V(crypto_ec_string, "ec") \
V(crypto_ed25519_string, "ed25519") \
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 9165e9171e6e6f..f03c1e31b994f8 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -3900,6 +3900,8 @@ Local KeyObject::GetAsymmetricKeyType() const {
return env()->crypto_rsa_pss_string();
case EVP_PKEY_DSA:
return env()->crypto_dsa_string();
+ case EVP_PKEY_DH:
+ return env()->crypto_dh_string();
case EVP_PKEY_EC:
return env()->crypto_ec_string();
case EVP_PKEY_ED25519:
@@ -5682,6 +5684,13 @@ bool DiffieHellman::Init(const char* p, int p_len, const char* g, int g_len) {
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) {
@@ -5697,22 +5706,15 @@ void DiffieHellman::DiffieHellmanGroup(
bool initialized = false;
const node::Utf8Value group_name(env->isolate(), args[0]);
- for (size_t i = 0; i < arraysize(modp_groups); ++i) {
- const modp_group* it = modp_groups + i;
-
- if (!StringEqualNoCase(*group_name, it->name))
- continue;
-
- initialized = diffieHellman->Init(it->prime,
- it->prime_size,
- it->gen,
- it->gen_size);
- if (!initialized)
- env->ThrowError("Initialization failed");
- return;
- }
+ const modp_group* group = FindDiffieHellmanGroup(*group_name);
+ if (group == nullptr)
+ return env->ThrowError("Unknown group");
- env->ThrowError("Unknown group");
+ initialized = diffieHellman->Init(group->prime,
+ group->prime_size,
+ group->gen);
+ if (!initialized)
+ env->ThrowError("Initialization failed");
}
@@ -5825,6 +5827,20 @@ void DiffieHellman::GetPrivateKey(const FunctionCallbackInfo& args) {
}, "No private key - did you forget to generate one?");
}
+static void ZeroPadDiffieHellmanSecret(size_t remainder_size,
+ AllocatedBuffer* ret) {
+ // 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 = ret->size();
+ if (remainder_size != prime_size) {
+ CHECK_LT(remainder_size, prime_size);
+ const size_t padding = prime_size - remainder_size;
+ memmove(ret->data() + padding, ret->data(), remainder_size);
+ memset(ret->data(), 0, padding);
+ }
+}
void DiffieHellman::ComputeSecret(const FunctionCallbackInfo& args) {
Environment* env = Environment::GetCurrent(args);
@@ -5875,16 +5891,7 @@ void DiffieHellman::ComputeSecret(const FunctionCallbackInfo& args) {
}
CHECK_GE(size, 0);
-
- // 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.
- if (static_cast(size) != ret.size()) {
- CHECK_GT(ret.size(), static_cast(size));
- memmove(ret.data() + ret.size() - size, ret.data(), size);
- memset(ret.data(), 0, ret.size() - size);
- }
+ ZeroPadDiffieHellmanSecret(static_cast(size), &ret);
args.GetReturnValue().Set(ret.ToBuffer().ToLocalChecked());
}
@@ -6623,6 +6630,71 @@ class NidKeyPairGenerationConfig : public KeyPairGenerationConfig {
const int id_;
};
+// TODO(tniessen): Use std::variant instead.
+// Diffie-Hellman can either generate keys using a fixed prime, or by first
+// generating a random prime of a given size (in bits). Only one of both options
+// may be specified.
+struct PrimeInfo {
+ BignumPointer fixed_value_;
+ unsigned int prime_size_;
+};
+
+class DHKeyPairGenerationConfig : public KeyPairGenerationConfig {
+ public:
+ explicit DHKeyPairGenerationConfig(PrimeInfo&& prime_info,
+ unsigned int generator)
+ : prime_info_(std::move(prime_info)),
+ generator_(generator) {}
+
+ EVPKeyCtxPointer Setup() override {
+ EVPKeyPointer params;
+ if (prime_info_.fixed_value_) {
+ DHPointer dh(DH_new());
+ if (!dh)
+ return nullptr;
+
+ BIGNUM* prime = prime_info_.fixed_value_.get();
+ BignumPointer bn_g(BN_new());
+ if (!BN_set_word(bn_g.get(), generator_) ||
+ !DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get()))
+ return nullptr;
+
+ prime_info_.fixed_value_.release();
+ bn_g.release();
+
+ params = EVPKeyPointer(EVP_PKEY_new());
+ CHECK(params);
+ EVP_PKEY_assign_DH(params.get(), dh.release());
+ } else {
+ EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr));
+ if (!param_ctx)
+ return nullptr;
+
+ if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
+ return nullptr;
+
+ if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(param_ctx.get(),
+ prime_info_.prime_size_) <= 0)
+ return nullptr;
+
+ if (EVP_PKEY_CTX_set_dh_paramgen_generator(param_ctx.get(),
+ generator_) <= 0)
+ return nullptr;
+
+ EVP_PKEY* raw_params = nullptr;
+ if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
+ return nullptr;
+ params = EVPKeyPointer(raw_params);
+ }
+
+ return EVPKeyCtxPointer(EVP_PKEY_CTX_new(params.get(), nullptr));
+ }
+
+ private:
+ PrimeInfo prime_info_;
+ unsigned int generator_;
+};
+
class GenerateKeyPairJob : public CryptoJob {
public:
GenerateKeyPairJob(Environment* env,
@@ -6842,6 +6914,39 @@ void GenerateKeyPairNid(const FunctionCallbackInfo& args) {
GenerateKeyPair(args, 1, std::move(config));
}
+void GenerateKeyPairDH(const FunctionCallbackInfo& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ PrimeInfo prime_info = {};
+ unsigned int generator;
+ if (args[0]->IsString()) {
+ String::Utf8Value group_name(args.GetIsolate(), args[0].As());
+ const modp_group* group = FindDiffieHellmanGroup(*group_name);
+ if (group == nullptr)
+ return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
+
+ prime_info.fixed_value_ = BignumPointer(
+ BN_bin2bn(reinterpret_cast(group->prime),
+ group->prime_size, nullptr));
+ generator = group->gen;
+ } else {
+ if (args[0]->IsInt32()) {
+ prime_info.prime_size_ = args[0].As()->Value();
+ } else {
+ ArrayBufferViewContents input(args[0]);
+ prime_info.fixed_value_ = BignumPointer(
+ BN_bin2bn(input.data(), input.length(), nullptr));
+ }
+
+ CHECK(args[1]->IsInt32());
+ generator = args[1].As()->Value();
+ }
+
+ std::unique_ptr config(
+ new DHKeyPairGenerationConfig(std::move(prime_info), generator));
+ GenerateKeyPair(args, 2, std::move(config));
+}
+
void GetSSLCiphers(const FunctionCallbackInfo& args) {
Environment* env = Environment::GetCurrent(args);
@@ -7097,6 +7202,49 @@ void ConvertKey(const FunctionCallbackInfo& args) {
args.GetReturnValue().Set(buf);
}
+AllocatedBuffer StatelessDiffieHellman(Environment* env, ManagedEVPPKey our_key,
+ 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 AllocatedBuffer();
+
+ AllocatedBuffer result = env->AllocateManaged(out_size);
+ CHECK_NOT_NULL(result.data());
+
+ unsigned char* data = reinterpret_cast(result.data());
+ if (EVP_PKEY_derive(ctx.get(), data, &out_size) <= 0)
+ return AllocatedBuffer();
+
+ ZeroPadDiffieHellmanSecret(out_size, &result);
+ return result;
+}
+
+void StatelessDiffieHellman(const FunctionCallbackInfo& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ CHECK(args[0]->IsObject() && args[1]->IsObject());
+ KeyObject* our_key_object;
+ ASSIGN_OR_RETURN_UNWRAP(&our_key_object, args[0].As