Skip to content

Commit

Permalink
crypto: add DH support to generateKeyPair
Browse files Browse the repository at this point in the history
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 1977136
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 7 deletions.
29 changes: 23 additions & 6 deletions doc/api/crypto.md
Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions doc/api/errors.md
Expand Up @@ -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`

Expand Down Expand Up @@ -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`

Expand Down Expand Up @@ -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
Expand Down
48 changes: 47 additions & 1 deletion lib/internal/crypto/keygen.js
Expand Up @@ -11,6 +11,7 @@ const {
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairNid,
generateKeyPairDH,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
EVP_PKEY_X25519,
Expand All @@ -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');
Expand Down Expand Up @@ -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');
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Expand Up @@ -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',
Expand Down
99 changes: 99 additions & 0 deletions src/node_crypto.cc
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/node_errors.h
Expand Up @@ -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) \
Expand Down Expand Up @@ -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") \
Expand Down

0 comments on commit 1977136

Please sign in to comment.