diff --git a/doc/api/crypto.md b/doc/api/crypto.md index cc310f67d1187c..c52f7e3e7bec10 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1908,12 +1908,15 @@ algorithm names. -* `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`. @@ -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. If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function behaves as if [`keyObject.export()`][] had been called on its result. Otherwise, @@ -1965,12 +1968,15 @@ a `Promise` for an `Object` with `publicKey` and `privateKey` properties. -* `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`. @@ -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, diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 21dbf5ff8a136e..efa6c6c31b854b 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -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; } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 57c738e225b113..aac1eab6e73adc 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -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, @@ -5939,6 +5951,14 @@ void GenerateKeyPairEC(const FunctionCallbackInfo& args) { GenerateKeyPair(args, 2, std::move(config)); } +void GenerateKeyPairEdDSA(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsInt32()); + const int id = args[0].As()->Value(); + std::unique_ptr config( + new EdDSAKeyPairGenerationConfig(id)); + GenerateKeyPair(args, 1, std::move(config)); +} + void GetSSLCiphers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -6340,6 +6360,9 @@ void Initialize(Local 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); diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 8aae0d5e124e07..5d8e3806f63d1c 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -427,7 +427,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'" }); } @@ -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' + }); } { @@ -778,6 +787,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.