Skip to content

Commit 3a95924

Browse files
committed
crypto: add support for EdDSA key pair generation
PR-URL: #26554 Refs: #26319 Reviewed-By: Sam Roberts <[email protected]>
1 parent 1a6fb71 commit 3a95924

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
@@ -1903,12 +1903,15 @@ algorithm names.
19031903
<!-- YAML
19041904
added: v10.12.0
19051905
changes:
1906+
- version: REPLACEME
1907+
pr-url: https://github.com/nodejs/node/pull/26554
1908+
description: Add ability to generate Ed25519 and Ed448 key pairs.
19061909
- version: v11.6.0
19071910
pr-url: https://github.com/nodejs/node/pull/24234
19081911
description: The `generateKeyPair` and `generateKeyPairSync` functions now
19091912
produce key objects if no encoding was specified.
19101913
-->
1911-
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
1914+
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
19121915
* `options`: {Object}
19131916
- `modulusLength`: {number} Key size in bits (RSA, DSA).
19141917
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@@ -1921,8 +1924,8 @@ changes:
19211924
- `publicKey`: {string | Buffer | KeyObject}
19221925
- `privateKey`: {string | Buffer | KeyObject}
19231926

1924-
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
1925-
are currently supported.
1927+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
1928+
and Ed448 are currently supported.
19261929

19271930
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
19281931
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.
19601963
<!-- YAML
19611964
added: v10.12.0
19621965
changes:
1966+
- version: REPLACEME
1967+
pr-url: https://github.com/nodejs/node/pull/26554
1968+
description: Add ability to generate Ed25519 and Ed448 key pairs.
19631969
- version: v11.6.0
19641970
pr-url: https://github.com/nodejs/node/pull/24234
19651971
description: The `generateKeyPair` and `generateKeyPairSync` functions now
19661972
produce key objects if no encoding was specified.
19671973
-->
1968-
* `type`: {string} Must be `'rsa'`, `'dsa'` or `'ec'`.
1974+
* `type`: {string} Must be `'rsa'`, `'dsa'`, `'ec'`, `'ed25519'`, or `'ed448'`.
19691975
* `options`: {Object}
19701976
- `modulusLength`: {number} Key size in bits (RSA, DSA).
19711977
- `publicExponent`: {number} Public exponent (RSA). **Default:** `0x10001`.
@@ -1977,8 +1983,8 @@ changes:
19771983
- `publicKey`: {string | Buffer | KeyObject}
19781984
- `privateKey`: {string | Buffer | KeyObject}
19791985

1980-
Generates a new asymmetric key pair of the given `type`. Only RSA, DSA and EC
1981-
are currently supported.
1986+
Generates a new asymmetric key pair of the given `type`. RSA, DSA, EC, Ed25519
1987+
and Ed448 are currently supported.
19821988

19831989
If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
19841990
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
@@ -5739,6 +5739,18 @@ class ECKeyPairGenerationConfig : public KeyPairGenerationConfig {
57395739
const int param_encoding_;
57405740
};
57415741

5742+
class EdDSAKeyPairGenerationConfig : public KeyPairGenerationConfig {
5743+
public:
5744+
explicit EdDSAKeyPairGenerationConfig(int id) : id_(id) {}
5745+
5746+
EVPKeyCtxPointer Setup() override {
5747+
return EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(id_, nullptr));
5748+
}
5749+
5750+
private:
5751+
const int id_;
5752+
};
5753+
57425754
class GenerateKeyPairJob : public CryptoJob {
57435755
public:
57445756
GenerateKeyPairJob(Environment* env,
@@ -5912,6 +5924,14 @@ void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
59125924
GenerateKeyPair(args, 2, std::move(config));
59135925
}
59145926

5927+
void GenerateKeyPairEdDSA(const FunctionCallbackInfo<Value>& args) {
5928+
CHECK(args[0]->IsInt32());
5929+
const int id = args[0].As<Int32>()->Value();
5930+
std::unique_ptr<KeyPairGenerationConfig> config(
5931+
new EdDSAKeyPairGenerationConfig(id));
5932+
GenerateKeyPair(args, 1, std::move(config));
5933+
}
5934+
59155935

59165936
void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
59175937
Environment* env = Environment::GetCurrent(args);
@@ -6313,6 +6333,9 @@ void Initialize(Local<Object> target,
63136333
env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
63146334
env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
63156335
env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
6336+
env->SetMethod(target, "generateKeyPairEdDSA", GenerateKeyPairEdDSA);
6337+
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED25519);
6338+
NODE_DEFINE_CONSTANT(target, EVP_PKEY_ED448);
63166339
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
63176340
NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
63186341
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)