Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: add support for EdDSA key pair generation #26554

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,9 @@ algorithm names.
<!-- YAML
added: v10.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/26554
description: Ed25519 and Ed448 key pairs can now be generated.
tniessen marked this conversation as resolved.
Show resolved Hide resolved
- version: v11.6.0
pr-url: https://github.com/nodejs/node/pull/24234
description: The `generateKeyPair` and `generateKeyPairSync` functions now
Expand All @@ -1926,8 +1929,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.
tniessen marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -1965,6 +1968,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/26554
description: Ed25519 and Ed448 key pairs can now be generated.
- version: v11.6.0
pr-url: https://github.com/nodejs/node/pull/24234
description: The `generateKeyPair` and `generateKeyPairSync` functions now
Expand All @@ -1982,8 +1988,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
45 changes: 32 additions & 13 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
generateKeyPairRSA,
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairEdDSA,
OPENSSL_EC_NAMED_CURVE,
OPENSSL_EC_EXPLICIT_CURVE
} = internalBinding('crypto');
Expand Down Expand Up @@ -119,18 +120,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 +157,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 +176,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 +193,30 @@ function check(type, options, callback) {
cipher, passphrase, wrap);
}
break;
case 'ed25519':
case 'ed448':
{
impl = (wrap) => generateKeyPairEdDSA(type === 'ed25519',
publicFormat, publicType,
privateFormat, privateType,
cipher, passphrase, wrap);
}
break;
default:
throw new ERR_INVALID_ARG_VALUE('type', type,
"must be one of 'rsa', 'dsa', 'ec'");
tniessen marked this conversation as resolved.
Show resolved Hide resolved
}

({
cipher,
passphrase,
publicType,
publicFormat,
privateType,
privateFormat
} = parseKeyEncoding(type, options));
if (options) {
({
tniessen marked this conversation as resolved.
Show resolved Hide resolved
cipher,
passphrase,
publicType,
publicFormat,
privateType,
privateFormat
} = parseKeyEncoding(type, options));
}

return impl;
}
Expand Down
21 changes: 21 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5766,6 +5766,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 @@ -5939,6 +5951,14 @@ void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
GenerateKeyPair(args, 2, std::move(config));
}

void GenerateKeyPairEdDSA(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsBoolean());
const int id = args[0]->IsTrue() ? EVP_PKEY_ED25519 : EVP_PKEY_ED448;
tniessen marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -6340,6 +6360,7 @@ 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, OPENSSL_EC_NAMED_CURVE);
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
Expand Down
26 changes: 26 additions & 0 deletions test/parallel/test-crypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,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 @@ -778,6 +787,23 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
}));
}

// Test EdDSA key generation.
{
if (!/^1\.1\.0/.test(process.versions.openssl)) {
tniessen marked this conversation as resolved.
Show resolved Hide resolved
['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