Skip to content

Commit

Permalink
crypto: add support for EdDSA key pair generation
Browse files Browse the repository at this point in the history
PR-URL: #26554
Refs: #26319
Reviewed-By: Sam Roberts <[email protected]>
  • Loading branch information
tniessen committed Mar 18, 2019
1 parent 1a6fb71 commit 3a95924
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 21 deletions.
18 changes: 12 additions & 6 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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,
Expand Down Expand Up @@ -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`.
Expand All @@ -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,
Expand Down
51 changes: 37 additions & 14 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
23 changes: 23 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
28 changes: 27 additions & 1 deletion test/parallel/test-crypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
});
}

Expand All @@ -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'
});
}

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

0 comments on commit 3a95924

Please sign in to comment.