Skip to content
Permalink
Browse files
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>
  • Loading branch information
tniessen authored and targos committed Apr 28, 2020
1 parent 5dab489 commit 1977136a1941a2dd141699bf214843039c8430da
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 7 deletions.
@@ -2093,6 +2093,9 @@ algorithm names.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/31178
description: Add support for Diffie-Hellman.
- version: v12.0.0
pr-url: https://github.com/nodejs/node/pull/26774
description: Add ability to generate X25519 and X448 key pairs.
@@ -2106,21 +2109,26 @@ changes:
-->

* `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}
* `err`: {Error}
* `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.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/31178
description: Add support for Diffie-Hellman.
- version: v12.0.0
pr-url: https://github.com/nodejs/node/pull/26554
description: Add ability to generate Ed25519 and Ed448 key pairs.
@@ -2167,20 +2178,26 @@ changes:
produce key objects if no encoding was specified.
-->

* `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,
@@ -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.

<a id="ERR_CRYPTO_UNKNOWN_DH_GROUP"></a>
### `ERR_CRYPTO_UNKNOWN_DH_GROUP`

An unknown Diffie-Hellman group name was given. See
[`crypto.getDiffieHellman()`][] for a list of valid group names.

<a id="ERR_DIR_CLOSED"></a>
### `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.

<a id="ERR_MISSING_OPTION"></a>
### `ERR_MISSING_OPTION`

For APIs that accept options objects, some options might be mandatory. This code
is thrown if a required option is missing.

<a id="ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST"></a>
### `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
@@ -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');
@@ -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',
@@ -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<Value>& args) {
GenerateKeyPair(args, 1, std::move(config));
}

void GenerateKeyPairDH(const FunctionCallbackInfo<Value>& 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<String>());
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<const unsigned char*>(group->prime),
group->prime_size, nullptr));
generator = group->gen;
} else {
if (args[0]->IsInt32()) {
prime_info.prime_size_ = args[0].As<Int32>()->Value();
} else {
ArrayBufferViewContents<unsigned char> input(args[0]);
prime_info.fixed_value_ = BignumPointer(
BN_bin2bn(input.data(), input.length(), nullptr));
}

CHECK(args[1]->IsInt32());
generator = args[1].As<Int32>()->Value();
}

std::unique_ptr<KeyPairGenerationConfig> config(
new DHKeyPairGenerationConfig(std::move(prime_info), generator));
GenerateKeyPair(args, 2, std::move(config));
}


void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
@@ -6732,6 +6830,7 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
env->SetMethod(target, "generateKeyPairNid", GenerateKeyPairNid);
env->SetMethod(target, "generateKeyPairDH", GenerateKeyPairDH);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_X25519);
@@ -37,6 +37,7 @@ void OnFatalError(const char* location, const char* message);
V(ERR_BUFFER_TOO_LARGE, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
V(ERR_INVALID_ARG_TYPE, TypeError) \
@@ -89,6 +90,7 @@ void OnFatalError(const char* location, const char* message);
"Buffer is not available for the current Context") \
V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
V(ERR_CRYPTO_UNKNOWN_DH_GROUP, "Unknown DH group") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \

0 comments on commit 1977136

Please sign in to comment.