diff --git a/doc/api/crypto.md b/doc/api/crypto.md
index 14c80bf8d39448..1d411f8526365f 100644
--- a/doc/api/crypto.md
+++ b/doc/api/crypto.md
@@ -2093,6 +2093,9 @@ algorithm names.
* `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}
@@ -2119,8 +2127,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,
@@ -2158,6 +2166,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 a7d4583ea15ed4..18fe855719dfa6 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -824,6 +824,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`
@@ -1524,6 +1530,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`
@@ -2439,6 +2451,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/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 b1323018f4f659..591976e1e4c800 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1201,6 +1201,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/node_crypto.cc b/src/node_crypto.cc
index 2ba95a0541739c..fcbe50b47ad712 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -6086,6 +6086,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,
@@ -6299,6 +6364,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);
@@ -6732,6 +6830,7 @@ void Initialize(Local