diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 21dbf5ff8a136e..8e385c8fbececf 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -5,6 +5,7 @@ const { generateKeyPairRSA, generateKeyPairDSA, generateKeyPairEC, + generateKeyPairEdDSA, OPENSSL_EC_NAMED_CURVE, OPENSSL_EC_EXPLICIT_CURVE } = internalBinding('crypto'); @@ -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); @@ -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); @@ -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; @@ -185,19 +193,30 @@ function check(type, options, callback) { cipher, passphrase, wrap); } break; + case 'ed25519': + case 'ed448': + { + impl = (wrap) => generateKeyPairEdDSA(type, + publicFormat, publicType, + privateFormat, privateType, + cipher, passphrase, wrap); + } + break; default: throw new ERR_INVALID_ARG_VALUE('type', type, "must be one of 'rsa', 'dsa', 'ec'"); } - ({ - 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..37552a0f05e38f 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,22 @@ void GenerateKeyPairEC(const FunctionCallbackInfo& args) { GenerateKeyPair(args, 2, std::move(config)); } +void GenerateKeyPairEdDSA(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsString()); + String::Utf8Value curve_name(args.GetIsolate(), args[0].As()); + int id; + if (strcmp(*curve_name, "ed25519") == 0) { + id = EVP_PKEY_ED25519; + } else { + CHECK_EQ(strcmp(*curve_name, "ed448"), 0); + id = EVP_PKEY_ED448; + } + + 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 +6368,7 @@ 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, 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..a1421ce93616a5 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -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.