Skip to content

Commit 0d3e095

Browse files
tniessencodebytere
authored andcommitted
crypto: add DH support to generateKeyPair
This allows using the generateKeyPair API for DH instead of the old stateful DH APIs. PR-URL: #31178 Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
1 parent 15bd2c9 commit 0d3e095

File tree

7 files changed

+254
-7
lines changed

7 files changed

+254
-7
lines changed

doc/api/crypto.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,6 +2093,9 @@ algorithm names.
20932093
<!-- YAML
20942094
added: v10.12.0
20952095
changes:
2096+
- version: REPLACEME
2097+
pr-url: https://github.com/nodejs/node/pull/31178
2098+
description: Add support for Diffie-Hellman.
20962099
- version: v12.0.0
20972100
pr-url: https://github.com/nodejs/node/pull/26774
20982101
description: Add ability to generate X25519 and X448 key pairs.
@@ -2106,21 +2109,26 @@ changes:
21062109
-->
21072110

21082111
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`,
2109-
`'x25519'`, or `'x448'`.
2112+
`'x25519'`, `'x448'`, or `'dh'`.
21102113
* `options`: {Object}
21112114
* `modulusLength`: {number} Key size in bits (RSA, DSA).
21122115
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
21132116
* `divisorLength`: {number} Size of `q` in bits (DSA).
21142117
* `namedCurve`: {string} Name of the curve to use (EC).
2118+
* `prime`: {Buffer} The prime parameter (DH).
2119+
* `primeLength`: {number} Prime length in bits (DH).
2120+
* `generator`: {number} Custom generator (DH). **Default:** `2`.
2121+
* `groupName`: {string} Diffie-Hellman group name (DH). See
2122+
[`crypto.getDiffieHellman()`][].
21152123
* `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
21162124
* `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
21172125
* `callback`: {Function}
21182126
* `err`: {Error}
21192127
* `publicKey`: {string | Buffer | KeyObject}
21202128
* `privateKey`: {string | Buffer | KeyObject}
21212129

2122-
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
2123-
and Ed448 are currently supported.
2130+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519,
2131+
Ed448, X25519, X448, and DH are currently supported.
21242132

21252133
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
21262134
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.
21582166
<!-- YAML
21592167
added: v10.12.0
21602168
changes:
2169+
- version: REPLACEME
2170+
pr-url: https://github.com/nodejs/node/pull/31178
2171+
description: Add support for Diffie-Hellman.
21612172
- version: v12.0.0
21622173
pr-url: https://github.com/nodejs/node/pull/26554
21632174
description: Add ability to generate Ed25519 and Ed448 key pairs.
@@ -2167,20 +2178,26 @@ changes:
21672178
produce key objects if no encoding was specified.
21682179
-->
21692180

2170-
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
2181+
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, `'ed448'`,
2182+
`'x25519'`, `'x448'`, or `'dh'`.
21712183
* `options`: {Object}
21722184
* `modulusLength`: {number} Key size in bits (RSA, DSA).
21732185
* `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
21742186
* `divisorLength`: {number} Size of `q` in bits (DSA).
21752187
* `namedCurve`: {string} Name of the curve to use (EC).
2188+
* `prime`: {Buffer} The prime parameter (DH).
2189+
* `primeLength`: {number} Prime length in bits (DH).
2190+
* `generator`: {number} Custom generator (DH). **Default:** `2`.
2191+
* `groupName`: {string} Diffie-Hellman group name (DH). See
2192+
[`crypto.getDiffieHellman()`][].
21762193
* `publicKeyEncoding`: {Object} See [`keyObject.export()`][].
21772194
* `privateKeyEncoding`: {Object} See [`keyObject.export()`][].
21782195
* Returns: {Object}
21792196
* `publicKey`: {string | Buffer | KeyObject}
21802197
* `privateKey`: {string | Buffer | KeyObject}
21812198

2182-
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
2183-
and Ed448 are currently supported.
2199+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519,
2200+
Ed448, X25519, X448, and DH are currently supported.
21842201

21852202
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
21862203
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,

doc/api/errors.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,12 @@ A signing `key` was not provided to the [`sign.sign()`][] method.
826826
[`crypto.timingSafeEqual()`][] was called with `Buffer`, `TypedArray`, or
827827
`DataView` arguments of different lengths.
828828

829+
<a id="ERR_CRYPTO_UNKNOWN_DH_GROUP"></a>
830+
### `ERR_CRYPTO_UNKNOWN_DH_GROUP`
831+
832+
An unknown Diffie-Hellman group name was given. See
833+
[`crypto.getDiffieHellman()`][] for a list of valid group names.
834+
829835
<a id="ERR_DIR_CLOSED"></a>
830836
### `ERR_DIR_CLOSED`
831837

@@ -1514,6 +1520,12 @@ strict compliance with the API specification (which in some cases may accept
15141520
An [ES Module][] loader hook specified `format: 'dynamic'` but did not provide
15151521
a `dynamicInstantiate` hook.
15161522

1523+
<a id="ERR_MISSING_OPTION"></a>
1524+
### `ERR_MISSING_OPTION`
1525+
1526+
For APIs that accept options objects, some options might be mandatory. This code
1527+
is thrown if a required option is missing.
1528+
15171529
<a id="ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST"></a>
15181530
### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`
15191531

@@ -2423,6 +2435,7 @@ such as `process.stdout.on('data')`.
24232435
[`Writable`]: stream.html#stream_class_stream_writable
24242436
[`child_process`]: child_process.html
24252437
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
2438+
[`crypto.getDiffieHellman()`]: crypto.html#crypto_crypto_getdiffiehellman_groupname
24262439
[`crypto.scrypt()`]: crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback
24272440
[`crypto.scryptSync()`]: crypto.html#crypto_crypto_scryptsync_password_salt_keylen_options
24282441
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b

lib/internal/crypto/keygen.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
generateKeyPairDSA,
1212
generateKeyPairEC,
1313
generateKeyPairNid,
14+
generateKeyPairDH,
1415
EVP_PKEY_ED25519,
1516
EVP_PKEY_ED448,
1617
EVP_PKEY_X25519,
@@ -28,10 +29,12 @@ const {
2829
const { customPromisifyArgs } = require('internal/util');
2930
const { isUint32, validateString } = require('internal/validators');
3031
const {
32+
ERR_INCOMPATIBLE_OPTION_PAIR,
3133
ERR_INVALID_ARG_TYPE,
3234
ERR_INVALID_ARG_VALUE,
3335
ERR_INVALID_CALLBACK,
34-
ERR_INVALID_OPT_VALUE
36+
ERR_INVALID_OPT_VALUE,
37+
ERR_MISSING_OPTION
3538
} = require('internal/errors').codes;
3639

3740
const { isArrayBufferView } = require('internal/util/types');
@@ -245,6 +248,49 @@ function check(type, options, callback) {
245248
cipher, passphrase, wrap);
246249
}
247250
break;
251+
case 'dh':
252+
{
253+
const { group, primeLength, prime, generator } = needOptions();
254+
let args;
255+
if (group != null) {
256+
if (prime != null)
257+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
258+
if (primeLength != null)
259+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
260+
if (generator != null)
261+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
262+
if (typeof group !== 'string')
263+
throw new ERR_INVALID_OPT_VALUE('group', group);
264+
args = [group];
265+
} else {
266+
if (prime != null) {
267+
if (primeLength != null)
268+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
269+
if (!isArrayBufferView(prime))
270+
throw new ERR_INVALID_OPT_VALUE('prime', prime);
271+
} else if (primeLength != null) {
272+
if (!isUint32(primeLength))
273+
throw new ERR_INVALID_OPT_VALUE('primeLength', primeLength);
274+
} else {
275+
throw new ERR_MISSING_OPTION(
276+
'At least one of the group, prime, or primeLength options');
277+
}
278+
279+
if (generator != null) {
280+
if (!isUint32(generator))
281+
throw new ERR_INVALID_OPT_VALUE('generator', generator);
282+
}
283+
284+
args = [prime != null ? prime : primeLength,
285+
generator == null ? 2 : generator];
286+
}
287+
288+
impl = (wrap) => generateKeyPairDH(...args,
289+
publicFormat, publicType,
290+
privateFormat, privateType,
291+
cipher, passphrase, wrap);
292+
}
293+
break;
248294
default:
249295
throw new ERR_INVALID_ARG_VALUE('type', type,
250296
'must be a supported key type');

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ E('ERR_MISSING_ARGS',
11871187
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
11881188
'The ES Module loader may not return a format of \'dynamic\' when no ' +
11891189
'dynamicInstantiate function was provided', Error);
1190+
E('ERR_MISSING_OPTION', '%s is required', TypeError);
11901191
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
11911192
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
11921193
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',

src/node_crypto.cc

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6628,6 +6628,71 @@ class NidKeyPairGenerationConfig : public KeyPairGenerationConfig {
66286628
const int id_;
66296629
};
66306630

6631+
// TODO(tniessen): Use std::variant instead.
6632+
// Diffie-Hellman can either generate keys using a fixed prime, or by first
6633+
// generating a random prime of a given size (in bits). Only one of both options
6634+
// may be specified.
6635+
struct PrimeInfo {
6636+
BignumPointer fixed_value_;
6637+
unsigned int prime_size_;
6638+
};
6639+
6640+
class DHKeyPairGenerationConfig : public KeyPairGenerationConfig {
6641+
public:
6642+
explicit DHKeyPairGenerationConfig(PrimeInfo&& prime_info,
6643+
unsigned int generator)
6644+
: prime_info_(std::move(prime_info)),
6645+
generator_(generator) {}
6646+
6647+
EVPKeyCtxPointer Setup() override {
6648+
EVPKeyPointer params;
6649+
if (prime_info_.fixed_value_) {
6650+
DHPointer dh(DH_new());
6651+
if (!dh)
6652+
return nullptr;
6653+
6654+
BIGNUM* prime = prime_info_.fixed_value_.get();
6655+
BignumPointer bn_g(BN_new());
6656+
if (!BN_set_word(bn_g.get(), generator_) ||
6657+
!DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get()))
6658+
return nullptr;
6659+
6660+
prime_info_.fixed_value_.release();
6661+
bn_g.release();
6662+
6663+
params = EVPKeyPointer(EVP_PKEY_new());
6664+
CHECK(params);
6665+
EVP_PKEY_assign_DH(params.get(), dh.release());
6666+
} else {
6667+
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr));
6668+
if (!param_ctx)
6669+
return nullptr;
6670+
6671+
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
6672+
return nullptr;
6673+
6674+
if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(param_ctx.get(),
6675+
prime_info_.prime_size_) <= 0)
6676+
return nullptr;
6677+
6678+
if (EVP_PKEY_CTX_set_dh_paramgen_generator(param_ctx.get(),
6679+
generator_) <= 0)
6680+
return nullptr;
6681+
6682+
EVP_PKEY* raw_params = nullptr;
6683+
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
6684+
return nullptr;
6685+
params = EVPKeyPointer(raw_params);
6686+
}
6687+
6688+
return EVPKeyCtxPointer(EVP_PKEY_CTX_new(params.get(), nullptr));
6689+
}
6690+
6691+
private:
6692+
PrimeInfo prime_info_;
6693+
unsigned int generator_;
6694+
};
6695+
66316696
class GenerateKeyPairJob : public CryptoJob {
66326697
public:
66336698
GenerateKeyPairJob(Environment* env,
@@ -6847,6 +6912,39 @@ void GenerateKeyPairNid(const FunctionCallbackInfo<Value>& args) {
68476912
GenerateKeyPair(args, 1, std::move(config));
68486913
}
68496914

6915+
void GenerateKeyPairDH(const FunctionCallbackInfo<Value>& args) {
6916+
Environment* env = Environment::GetCurrent(args);
6917+
6918+
PrimeInfo prime_info = {};
6919+
unsigned int generator;
6920+
if (args[0]->IsString()) {
6921+
String::Utf8Value group_name(args.GetIsolate(), args[0].As<String>());
6922+
const modp_group* group = FindDiffieHellmanGroup(*group_name);
6923+
if (group == nullptr)
6924+
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
6925+
6926+
prime_info.fixed_value_ = BignumPointer(
6927+
BN_bin2bn(reinterpret_cast<const unsigned char*>(group->prime),
6928+
group->prime_size, nullptr));
6929+
generator = group->gen;
6930+
} else {
6931+
if (args[0]->IsInt32()) {
6932+
prime_info.prime_size_ = args[0].As<Int32>()->Value();
6933+
} else {
6934+
ArrayBufferViewContents<unsigned char> input(args[0]);
6935+
prime_info.fixed_value_ = BignumPointer(
6936+
BN_bin2bn(input.data(), input.length(), nullptr));
6937+
}
6938+
6939+
CHECK(args[1]->IsInt32());
6940+
generator = args[1].As<Int32>()->Value();
6941+
}
6942+
6943+
std::unique_ptr<KeyPairGenerationConfig> config(
6944+
new DHKeyPairGenerationConfig(std::move(prime_info), generator));
6945+
GenerateKeyPair(args, 2, std::move(config));
6946+
}
6947+
68506948

68516949
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
68526950
Environment* env = Environment::GetCurrent(args);
@@ -7253,6 +7351,7 @@ void Initialize(Local<Object> target,
72537351
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
72547352
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
72557353
env->SetMethod(target, "generateKeyPairNid", GenerateKeyPairNid);
7354+
env->SetMethod(target, "generateKeyPairDH", GenerateKeyPairDH);
72567355
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
72577356
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
72587357
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);

src/node_errors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ void PrintErrorString(const char* format, ...);
3939
V(ERR_BUFFER_TOO_LARGE, Error) \
4040
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
4141
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
42+
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \
4243
V(ERR_INVALID_ARG_VALUE, TypeError) \
4344
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
4445
V(ERR_INVALID_ARG_TYPE, TypeError) \
@@ -89,6 +90,7 @@ void PrintErrorString(const char* format, ...);
8990
"Buffer is not available for the current Context") \
9091
V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \
9192
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
93+
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, "Unknown DH group") \
9294
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
9395
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
9496
V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \

0 commit comments

Comments
 (0)