crypto: add support for EdDSA key pair generation

PR-URL: https://github.com/nodejs/node/pull/26554
Refs: https://github.com/nodejs/node/pull/26319
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
This commit is contained in:
Tobias Nießen 2019-03-10 00:51:56 +01:00
parent 1a6fb71f71
commit 3a9592496c
No known key found for this signature in database
GPG Key ID: 718207F8FD156B70
4 changed files with 99 additions and 21 deletions

View File

@ -1903,12 +1903,15 @@ algorithm names.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/26554
description: Add ability to generate Ed25519 and Ed448 key pairs.
- version: v11.6.0
pr-url: https://github.com/nodejs/node/pull/24234
description: The `generateKeyPair` and `generateKeyPairSync` functions now
produce key objects if no encoding was specified.
-->
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
* `options`: {Object}
- `modulusLength`: {number} Key size in bits (RSA, DSA).
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@ -1921,8 +1924,8 @@ changes:
- `publicKey`: {string | Buffer | KeyObject}
- `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
and Ed448 are currently supported.
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
@ -1960,12 +1963,15 @@ 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/26554
description: Add ability to generate Ed25519 and Ed448 key pairs.
- version: v11.6.0
pr-url: https://github.com/nodejs/node/pull/24234
description: The `generateKeyPair` and `generateKeyPairSync` functions now
produce key objects if no encoding was specified.
-->
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
* `options`: {Object}
- `modulusLength`: {number} Key size in bits (RSA, DSA).
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@ -1977,8 +1983,8 @@ changes:
- `publicKey`: {string | Buffer | KeyObject}
- `privateKey`: {string | Buffer | KeyObject}
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
are currently supported.
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
and Ed448 are currently supported.
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,

View File

@ -5,6 +5,9 @@ const {
generateKeyPairRSA,
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairEdDSA,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
OPENSSL_EC_NAMED_CURVE,
OPENSSL_EC_EXPLICIT_CURVE
} = internalBinding('crypto');
@ -119,18 +122,25 @@ function parseKeyEncoding(keyType, options) {
function check(type, options, callback) {
validateString(type, 'type');
if (options == null || typeof options !== 'object')
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
// These will be set after parsing the type and type-specific options to make
// the order a bit more intuitive.
let cipher, passphrase, publicType, publicFormat, privateType, privateFormat;
if (options !== undefined && typeof options !== 'object')
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
function needOptions() {
if (options == null)
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
return options;
}
let impl;
switch (type) {
case 'rsa':
{
const { modulusLength } = options;
const { modulusLength } = needOptions();
if (!isUint32(modulusLength))
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
@ -149,7 +159,7 @@ function check(type, options, callback) {
break;
case 'dsa':
{
const { modulusLength } = options;
const { modulusLength } = needOptions();
if (!isUint32(modulusLength))
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
@ -168,7 +178,7 @@ function check(type, options, callback) {
break;
case 'ec':
{
const { namedCurve } = options;
const { namedCurve } = needOptions();
if (typeof namedCurve !== 'string')
throw new ERR_INVALID_OPT_VALUE('namedCurve', namedCurve);
let { paramEncoding } = options;
@ -185,19 +195,32 @@ function check(type, options, callback) {
cipher, passphrase, wrap);
}
break;
case 'ed25519':
case 'ed448':
{
const id = type === 'ed25519' ? EVP_PKEY_ED25519 : EVP_PKEY_ED448;
impl = (wrap) => generateKeyPairEdDSA(id,
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
default:
throw new ERR_INVALID_ARG_VALUE('type', type,
"must be one of 'rsa', 'dsa', 'ec'");
"must be one of 'rsa', 'dsa', 'ec', " +
"'ed25519', 'ed448'");
}
({
cipher,
passphrase,
publicType,
publicFormat,
privateType,
privateFormat
} = parseKeyEncoding(type, options));
if (options) {
({
cipher,
passphrase,
publicType,
publicFormat,
privateType,
privateFormat
} = parseKeyEncoding(type, options));
}
return impl;
}

View File

@ -5739,6 +5739,18 @@ class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
const int param_encoding_;
};
class EdDSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
public:
explicit EdDSAKeyPairGenerationConfig(int id) : id_(id) {}
EVPKeyCtxPointer Setup() override {
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id_, nullptr));
}
private:
const int id_;
};
class GenerateKeyPairJob : public CryptoJob {
public:
GenerateKeyPairJob(Environment* env,
@ -5912,6 +5924,14 @@ void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
GenerateKeyPair(args, 2, std::move(config));
}
void GenerateKeyPairEdDSA(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsInt32());
const int id = args[0].As<Int32>()->Value();
std::unique_ptr<KeyPairGenerationConfig> config(
new EdDSAKeyPairGenerationConfig(id));
GenerateKeyPair(args, 1, std::move(config));
}
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
@ -6313,6 +6333,9 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
env->SetMethod(target, "generateKeyPairEdDSA", GenerateKeyPairEdDSA);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);

View File

@ -425,7 +425,7 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
type: TypeError,
code: 'ERR_INVALID_ARG_VALUE',
message: "The argument 'type' must be one of " +
"'rsa', 'dsa', 'ec'. Received 'rsa2'"
"'rsa', 'dsa', 'ec', 'ed25519', 'ed448'. Received 'rsa2'"
});
}
@ -437,6 +437,15 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
message: 'The "options" argument must be of ' +
'type object. Received type undefined'
});
// Even if no options are required, it should be impossible to pass anything
// but an object (or undefined).
common.expectsError(() => generateKeyPair('ed448', 0, common.mustNotCall()), {
type: TypeError,
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "options" argument must be of ' +
'type object. Received type number'
});
}
{
@ -774,6 +783,23 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
}));
}
// Test EdDSA key generation.
{
if (!/^1\.1\.0/.test(process.versions.openssl)) {
['ed25519', 'ed448'].forEach((keyType) => {
generateKeyPair(keyType, common.mustCall((err, publicKey, privateKey) => {
assert.ifError(err);
assert.strictEqual(publicKey.type, 'public');
assert.strictEqual(publicKey.asymmetricKeyType, keyType);
assert.strictEqual(privateKey.type, 'private');
assert.strictEqual(privateKey.asymmetricKeyType, keyType);
}));
});
}
}
// Test invalid key encoding types.
{
// Invalid public key type.