Skip to content

Commit f4144d3

Browse files
tniessentargos
authored andcommitted
crypto: add support for EdDSA key pair generation
PR-URL: nodejs#26554 Refs: nodejs#26319 Reviewed-By: Sam Roberts <[email protected]>
1 parent a081b9b commit f4144d3

File tree

4 files changed

+99
-21
lines changed

4 files changed

+99
-21
lines changed

doc/api/crypto.md

+12-6
Original file line numberDiff line numberDiff line change
@@ -1891,12 +1891,15 @@ algorithm names.
18911891
<!-- YAML
18921892
added: v10.12.0
18931893
changes:
1894+
- version: REPLACEME
1895+
pr-url: https://github.com/nodejs/node/pull/26554
1896+
description: Add ability to generate Ed25519 and Ed448 key pairs.
18941897
- version: v11.6.0
18951898
pr-url: https://github.com/nodejs/node/pull/24234
18961899
description: The `generateKeyPair` and `generateKeyPairSync` functions now
18971900
produce key objects if no encoding was specified.
18981901
-->
1899-
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
1902+
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
19001903
* `options`: {Object}
19011904
- `modulusLength`: {number} Key size in bits (RSA, DSA).
19021905
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@@ -1909,8 +1912,8 @@ changes:
19091912
- `publicKey`: {string | Buffer | KeyObject}
19101913
- `privateKey`: {string | Buffer | KeyObject}
19111914

1912-
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
1913-
are currently supported.
1915+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
1916+
and Ed448 are currently supported.
19141917

19151918
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
19161919
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,
@@ -1948,12 +1951,15 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
19481951
<!-- YAML
19491952
added: v10.12.0
19501953
changes:
1954+
- version: REPLACEME
1955+
pr-url: https://github.com/nodejs/node/pull/26554
1956+
description: Add ability to generate Ed25519 and Ed448 key pairs.
19511957
- version: v11.6.0
19521958
pr-url: https://github.com/nodejs/node/pull/24234
19531959
description: The `generateKeyPair` and `generateKeyPairSync` functions now
19541960
produce key objects if no encoding was specified.
19551961
-->
1956-
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
1962+
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
19571963
* `options`: {Object}
19581964
- `modulusLength`: {number} Key size in bits (RSA, DSA).
19591965
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@@ -1965,8 +1971,8 @@ changes:
19651971
- `publicKey`: {string | Buffer | KeyObject}
19661972
- `privateKey`: {string | Buffer | KeyObject}
19671973

1968-
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
1969-
are currently supported.
1974+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
1975+
and Ed448 are currently supported.
19701976

19711977
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
19721978
behaves as if [`keyObject.export()`][] had been called on its result. Otherwise,

lib/internal/crypto/keygen.js

+37-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const {
55
generateKeyPairRSA,
66
generateKeyPairDSA,
77
generateKeyPairEC,
8+
generateKeyPairEdDSA,
9+
EVP_PKEY_ED25519,
10+
EVP_PKEY_ED448,
811
OPENSSL_EC_NAMED_CURVE,
912
OPENSSL_EC_EXPLICIT_CURVE
1013
} = internalBinding('crypto');
@@ -119,18 +122,25 @@ function parseKeyEncoding(keyType, options) {
119122

120123
function check(type, options, callback) {
121124
validateString(type, 'type');
122-
if (options == null || typeof options !== 'object')
123-
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
124125

125126
// These will be set after parsing the type and type-specific options to make
126127
// the order a bit more intuitive.
127128
let cipher, passphrase, publicType, publicFormat, privateType, privateFormat;
128129

130+
if (options !== undefined && typeof options !== 'object')
131+
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
132+
133+
function needOptions() {
134+
if (options == null)
135+
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
136+
return options;
137+
}
138+
129139
let impl;
130140
switch (type) {
131141
case 'rsa':
132142
{
133-
const { modulusLength } = options;
143+
const { modulusLength } = needOptions();
134144
if (!isUint32(modulusLength))
135145
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
136146

@@ -149,7 +159,7 @@ function check(type, options, callback) {
149159
break;
150160
case 'dsa':
151161
{
152-
const { modulusLength } = options;
162+
const { modulusLength } = needOptions();
153163
if (!isUint32(modulusLength))
154164
throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
155165

@@ -168,7 +178,7 @@ function check(type, options, callback) {
168178
break;
169179
case 'ec':
170180
{
171-
const { namedCurve } = options;
181+
const { namedCurve } = needOptions();
172182
if (typeof namedCurve !== 'string')
173183
throw new ERR_INVALID_OPT_VALUE('namedCurve', namedCurve);
174184
let { paramEncoding } = options;
@@ -185,19 +195,32 @@ function check(type, options, callback) {
185195
cipher, passphrase, wrap);
186196
}
187197
break;
198+
case 'ed25519':
199+
case 'ed448':
200+
{
201+
const id = type === 'ed25519' ? EVP_PKEY_ED25519 : EVP_PKEY_ED448;
202+
impl = (wrap) => generateKeyPairEdDSA(id,
203+
publicFormat, publicType,
204+
privateFormat, privateType,
205+
cipher, passphrase, wrap);
206+
}
207+
break;
188208
default:
189209
throw new ERR_INVALID_ARG_VALUE('type', type,
190-
"must be one of 'rsa', 'dsa', 'ec'");
210+
"must be one of 'rsa', 'dsa', 'ec', " +
211+
"'ed25519', 'ed448'");
191212
}
192213

193-
({
194-
cipher,
195-
passphrase,
196-
publicType,
197-
publicFormat,
198-
privateType,
199-
privateFormat
200-
} = parseKeyEncoding(type, options));
214+
if (options) {
215+
({
216+
cipher,
217+
passphrase,
218+
publicType,
219+
publicFormat,
220+
privateType,
221+
privateFormat
222+
} = parseKeyEncoding(type, options));
223+
}
201224

202225
return impl;
203226
}

src/node_crypto.cc

+23
Original file line numberDiff line numberDiff line change
@@ -5707,6 +5707,18 @@ class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
57075707
const int param_encoding_;
57085708
};
57095709

5710+
class EdDSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
5711+
public:
5712+
explicit EdDSAKeyPairGenerationConfig(int id) : id_(id) {}
5713+
5714+
EVPKeyCtxPointer Setup() override {
5715+
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id_, nullptr));
5716+
}
5717+
5718+
private:
5719+
const int id_;
5720+
};
5721+
57105722
class GenerateKeyPairJob : public CryptoJob {
57115723
public:
57125724
GenerateKeyPairJob(Environment* env,
@@ -5880,6 +5892,14 @@ void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
58805892
GenerateKeyPair(args, 2, std::move(config));
58815893
}
58825894

5895+
void GenerateKeyPairEdDSA(const FunctionCallbackInfo<Value>& args) {
5896+
CHECK(args[0]->IsInt32());
5897+
const int id = args[0].As<Int32>()->Value();
5898+
std::unique_ptr<KeyPairGenerationConfig> config(
5899+
new EdDSAKeyPairGenerationConfig(id));
5900+
GenerateKeyPair(args, 1, std::move(config));
5901+
}
5902+
58835903

58845904
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
58855905
Environment* env = Environment::GetCurrent(args);
@@ -6277,6 +6297,9 @@ void Initialize(Local<Object> target,
62776297
env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
62786298
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
62796299
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
6300+
env->SetMethod(target, "generateKeyPairEdDSA", GenerateKeyPairEdDSA);
6301+
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
6302+
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
62806303
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
62816304
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
62826305
NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);

test/parallel/test-crypto-keygen.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
425425
type: TypeError,
426426
code: 'ERR_INVALID_ARG_VALUE',
427427
message: "The argument 'type' must be one of " +
428-
"'rsa', 'dsa', 'ec'. Received 'rsa2'"
428+
"'rsa', 'dsa', 'ec', 'ed25519', 'ed448'. Received 'rsa2'"
429429
});
430430
}
431431

@@ -437,6 +437,15 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
437437
message: 'The "options" argument must be of ' +
438438
'type object. Received type undefined'
439439
});
440+
441+
// Even if no options are required, it should be impossible to pass anything
442+
// but an object (or undefined).
443+
common.expectsError(() => generateKeyPair('ed448', 0, common.mustNotCall()), {
444+
type: TypeError,
445+
code: 'ERR_INVALID_ARG_TYPE',
446+
message: 'The "options" argument must be of ' +
447+
'type object. Received type number'
448+
});
440449
}
441450

442451
{
@@ -774,6 +783,23 @@ const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);
774783
}));
775784
}
776785

786+
// Test EdDSA key generation.
787+
{
788+
if (!/^1\.1\.0/.test(process.versions.openssl)) {
789+
['ed25519', 'ed448'].forEach((keyType) => {
790+
generateKeyPair(keyType, common.mustCall((err, publicKey, privateKey) => {
791+
assert.ifError(err);
792+
793+
assert.strictEqual(publicKey.type, 'public');
794+
assert.strictEqual(publicKey.asymmetricKeyType, keyType);
795+
796+
assert.strictEqual(privateKey.type, 'private');
797+
assert.strictEqual(privateKey.asymmetricKeyType, keyType);
798+
}));
799+
});
800+
}
801+
}
802+
777803
// Test invalid key encoding types.
778804
{
779805
// Invalid public key type.

0 commit comments

Comments
 (0)