From 6730f4365c119279dd3a7c58e1a316e1b0f00b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sun, 10 Mar 2019 00:51:56 +0100 Subject: [PATCH 1/6] crypto: add support for EdDSA key pair generation Refs: https://github.com/nodejs/node/pull/26319 --- lib/internal/crypto/keygen.js | 45 ++++++++++++++++++++--------- src/node_crypto.cc | 29 +++++++++++++++++++ test/parallel/test-crypto-keygen.js | 26 +++++++++++++++++ 3 files changed, 87 insertions(+), 13 deletions(-) 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. From f46261b895dd4bbd524a61bc402994098c0a62a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Tue, 12 Mar 2019 17:05:15 +0100 Subject: [PATCH 2/6] fixup! crypto: add support for EdDSA key pair generation --- lib/internal/crypto/keygen.js | 2 +- src/node_crypto.cc | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 8e385c8fbececf..83bc271836267b 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -196,7 +196,7 @@ function check(type, options, callback) { case 'ed25519': case 'ed448': { - impl = (wrap) => generateKeyPairEdDSA(type, + impl = (wrap) => generateKeyPairEdDSA(type === 'ed25519', publicFormat, publicType, privateFormat, privateType, cipher, passphrase, wrap); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 37552a0f05e38f..6e0bc54c4c9626 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -5952,16 +5952,8 @@ void GenerateKeyPairEC(const FunctionCallbackInfo& args) { } 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; - } - + CHECK(args[0]->IsBoolean()); + const int id = args[0]->IsTrue() ? EVP_PKEY_ED25519 : EVP_PKEY_ED448; std::unique_ptr config( new EdDSAKeyPairGenerationConfig(id)); GenerateKeyPair(args, 1, std::move(config)); From febe5d9f13284ef8f50a238719e82b760c6098a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Tue, 12 Mar 2019 17:12:03 +0100 Subject: [PATCH 3/6] fixup! crypto: add support for EdDSA key pair generation --- doc/api/crypto.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index cc310f67d1187c..83bcc59c1d43ae 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -1908,6 +1908,9 @@ 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`. @@ -1970,13 +1970,13 @@ 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. + description: Add ability to generate Ed25519 and Ed448 key pairs. - version: v11.6.0 pr-url: https://github.com/nodejs/node/pull/24234 description: The `generateKeyPair` and `generateKeyPairSync` functions now produce key objects if no encoding was specified. --> -* `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`. From 12c4947aba557e53c0a80fa95946531e4b02459f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 16 Mar 2019 14:56:13 +0100 Subject: [PATCH 6/6] fixup! crypto: add support for EdDSA key pair generation --- lib/internal/crypto/keygen.js | 5 ++++- src/node_crypto.cc | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/internal/crypto/keygen.js b/lib/internal/crypto/keygen.js index 633a00b749c1e4..efa6c6c31b854b 100644 --- a/lib/internal/crypto/keygen.js +++ b/lib/internal/crypto/keygen.js @@ -6,6 +6,8 @@ const { generateKeyPairDSA, generateKeyPairEC, generateKeyPairEdDSA, + EVP_PKEY_ED25519, + EVP_PKEY_ED448, OPENSSL_EC_NAMED_CURVE, OPENSSL_EC_EXPLICIT_CURVE } = internalBinding('crypto'); @@ -196,7 +198,8 @@ function check(type, options, callback) { case 'ed25519': case 'ed448': { - impl = (wrap) => generateKeyPairEdDSA(type === 'ed25519', + const id = type === 'ed25519' ? EVP_PKEY_ED25519 : EVP_PKEY_ED448; + impl = (wrap) => generateKeyPairEdDSA(id, publicFormat, publicType, privateFormat, privateType, cipher, passphrase, wrap); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 6e0bc54c4c9626..aac1eab6e73adc 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -5952,8 +5952,8 @@ void GenerateKeyPairEC(const FunctionCallbackInfo& args) { } void GenerateKeyPairEdDSA(const FunctionCallbackInfo& args) { - CHECK(args[0]->IsBoolean()); - const int id = args[0]->IsTrue() ? EVP_PKEY_ED25519 : EVP_PKEY_ED448; + CHECK(args[0]->IsInt32()); + const int id = args[0].As()->Value(); std::unique_ptr config( new EdDSAKeyPairGenerationConfig(id)); GenerateKeyPair(args, 1, std::move(config)); @@ -6361,6 +6361,8 @@ void Initialize(Local target, 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);