Skip to content

Commit 5380c33

Browse files
tniessentargos
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: nodejs#31178 Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
1 parent fd6bdfb commit 5380c33

File tree

7 files changed

+254
-7
lines changed

7 files changed

+254
-7
lines changed

doc/api/crypto.md

+23-6
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

+13
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,12 @@ A signing `key` was not provided to the [`sign.sign()`][] method.
824824
[`crypto.timingSafeEqual()`][] was called with `Buffer`, `TypedArray`, or
825825
`DataView` arguments of different lengths.
826826

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

@@ -1524,6 +1530,12 @@ strict compliance with the API specification (which in some cases may accept
15241530
An [ES Module][] loader hook specified `format: 'dynamic'` but did not provide
15251531
a `dynamicInstantiate` hook.
15261532

1533+
<a id="ERR_MISSING_OPTION"></a>
1534+
### `ERR_MISSING_OPTION`
1535+
1536+
For APIs that accept options objects, some options might be mandatory. This code
1537+
is thrown if a required option is missing.
1538+
15271539
<a id="ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST"></a>
15281540
### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`
15291541

@@ -2439,6 +2451,7 @@ such as `process.stdout.on('data')`.
24392451
[`Writable`]: stream.html#stream_class_stream_writable
24402452
[`child_process`]: child_process.html
24412453
[`cipher.getAuthTag()`]: crypto.html#crypto_cipher_getauthtag
2454+
[`crypto.getDiffieHellman()`]: crypto.html#crypto_crypto_getdiffiehellman_groupname
24422455
[`crypto.scrypt()`]: crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback
24432456
[`crypto.scryptSync()`]: crypto.html#crypto_crypto_scryptsync_password_salt_keylen_options
24442457
[`crypto.timingSafeEqual()`]: crypto.html#crypto_crypto_timingsafeequal_a_b

lib/internal/crypto/keygen.js

+47-1
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

+1
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,7 @@ E('ERR_MISSING_ARGS',
12011201
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
12021202
'The ES Module loader may not return a format of \'dynamic\' when no ' +
12031203
'dynamicInstantiate function was provided', Error);
1204+
E('ERR_MISSING_OPTION', '%s is required', TypeError);
12041205
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
12051206
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
12061207
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',

src/node_crypto.cc

+99
Original file line numberDiff line numberDiff line change
@@ -6086,6 +6086,71 @@ class NidKeyPairGenerationConfig : public KeyPairGenerationConfig {
60866086
const int id_;
60876087
};
60886088

6089+
// TODO(tniessen): Use std::variant instead.
6090+
// Diffie-Hellman can either generate keys using a fixed prime, or by first
6091+
// generating a random prime of a given size (in bits). Only one of both options
6092+
// may be specified.
6093+
struct PrimeInfo {
6094+
BignumPointer fixed_value_;
6095+
unsigned int prime_size_;
6096+
};
6097+
6098+
class DHKeyPairGenerationConfig : public KeyPairGenerationConfig {
6099+
public:
6100+
explicit DHKeyPairGenerationConfig(PrimeInfo&& prime_info,
6101+
unsigned int generator)
6102+
: prime_info_(std::move(prime_info)),
6103+
generator_(generator) {}
6104+
6105+
EVPKeyCtxPointer Setup() override {
6106+
EVPKeyPointer params;
6107+
if (prime_info_.fixed_value_) {
6108+
DHPointer dh(DH_new());
6109+
if (!dh)
6110+
return nullptr;
6111+
6112+
BIGNUM* prime = prime_info_.fixed_value_.get();
6113+
BignumPointer bn_g(BN_new());
6114+
if (!BN_set_word(bn_g.get(), generator_) ||
6115+
!DH_set0_pqg(dh.get(), prime, nullptr, bn_g.get()))
6116+
return nullptr;
6117+
6118+
prime_info_.fixed_value_.release();
6119+
bn_g.release();
6120+
6121+
params = EVPKeyPointer(EVP_PKEY_new());
6122+
CHECK(params);
6123+
EVP_PKEY_assign_DH(params.get(), dh.release());
6124+
} else {
6125+
EVPKeyCtxPointer param_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nullptr));
6126+
if (!param_ctx)
6127+
return nullptr;
6128+
6129+
if (EVP_PKEY_paramgen_init(param_ctx.get()) <= 0)
6130+
return nullptr;
6131+
6132+
if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(param_ctx.get(),
6133+
prime_info_.prime_size_) <= 0)
6134+
return nullptr;
6135+
6136+
if (EVP_PKEY_CTX_set_dh_paramgen_generator(param_ctx.get(),
6137+
generator_) <= 0)
6138+
return nullptr;
6139+
6140+
EVP_PKEY* raw_params = nullptr;
6141+
if (EVP_PKEY_paramgen(param_ctx.get(), &raw_params) <= 0)
6142+
return nullptr;
6143+
params = EVPKeyPointer(raw_params);
6144+
}
6145+
6146+
return EVPKeyCtxPointer(EVP_PKEY_CTX_new(params.get(), nullptr));
6147+
}
6148+
6149+
private:
6150+
PrimeInfo prime_info_;
6151+
unsigned int generator_;
6152+
};
6153+
60896154
class GenerateKeyPairJob : public CryptoJob {
60906155
public:
60916156
GenerateKeyPairJob(Environment* env,
@@ -6299,6 +6364,39 @@ void GenerateKeyPairNid(const FunctionCallbackInfo<Value>& args) {
62996364
GenerateKeyPair(args, 1, std::move(config));
63006365
}
63016366

6367+
void GenerateKeyPairDH(const FunctionCallbackInfo<Value>& args) {
6368+
Environment* env = Environment::GetCurrent(args);
6369+
6370+
PrimeInfo prime_info = {};
6371+
unsigned int generator;
6372+
if (args[0]->IsString()) {
6373+
String::Utf8Value group_name(args.GetIsolate(), args[0].As<String>());
6374+
const modp_group* group = FindDiffieHellmanGroup(*group_name);
6375+
if (group == nullptr)
6376+
return THROW_ERR_CRYPTO_UNKNOWN_DH_GROUP(env);
6377+
6378+
prime_info.fixed_value_ = BignumPointer(
6379+
BN_bin2bn(reinterpret_cast<const unsigned char*>(group->prime),
6380+
group->prime_size, nullptr));
6381+
generator = group->gen;
6382+
} else {
6383+
if (args[0]->IsInt32()) {
6384+
prime_info.prime_size_ = args[0].As<Int32>()->Value();
6385+
} else {
6386+
ArrayBufferViewContents<unsigned char> input(args[0]);
6387+
prime_info.fixed_value_ = BignumPointer(
6388+
BN_bin2bn(input.data(), input.length(), nullptr));
6389+
}
6390+
6391+
CHECK(args[1]->IsInt32());
6392+
generator = args[1].As<Int32>()->Value();
6393+
}
6394+
6395+
std::unique_ptr<KeyPairGenerationConfig> config(
6396+
new DHKeyPairGenerationConfig(std::move(prime_info), generator));
6397+
GenerateKeyPair(args, 2, std::move(config));
6398+
}
6399+
63026400

63036401
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
63046402
Environment* env = Environment::GetCurrent(args);
@@ -6732,6 +6830,7 @@ void Initialize(Local<Object> target,
67326830
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
67336831
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
67346832
env->SetMethod(target, "generateKeyPairNid", GenerateKeyPairNid);
6833+
env->SetMethod(target, "generateKeyPairDH", GenerateKeyPairDH);
67356834
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
67366835
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
67376836
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);

src/node_errors.h

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void OnFatalError(const char* location, const char* message);
3737
V(ERR_BUFFER_TOO_LARGE, Error) \
3838
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
3939
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
40+
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \
4041
V(ERR_INVALID_ARG_VALUE, TypeError) \
4142
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
4243
V(ERR_INVALID_ARG_TYPE, TypeError) \
@@ -89,6 +90,7 @@ void OnFatalError(const char* location, const char* message);
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)