Skip to content

Commit

Permalink
crypto: add support for EdDSA key pair generation
Browse files Browse the repository at this point in the history
Refs: #26319
  • Loading branch information
tniessen committed Mar 12, 2019
1 parent f2064df commit 6730f43
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 13 deletions.
45 changes: 32 additions & 13 deletions lib/internal/crypto/keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
generateKeyPairRSA,
generateKeyPairDSA,
generateKeyPairEC,
generateKeyPairEdDSA,
OPENSSL_EC_NAMED_CURVE,
OPENSSL_EC_EXPLICIT_CURVE
} = internalBinding('crypto');
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
29 changes: 29 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -5939,6 +5951,22 @@ void GenerateKeyPairEC(const FunctionCallbackInfo<Value>& args) {
GenerateKeyPair(args, 2, std::move(config));
}

void GenerateKeyPairEdDSA(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
String::Utf8Value curve_name(args.GetIsolate(), args[0].As<String>());
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<KeyPairGenerationConfig> config(
new EdDSAKeyPairGenerationConfig(id));
GenerateKeyPair(args, 1, std::move(config));
}


void GetSSLCiphers(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -6340,6 +6368,7 @@ void Initialize(Local<Object> 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);
Expand Down
26 changes: 26 additions & 0 deletions test/parallel/test-crypto-keygen.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
});
}

{
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 6730f43

Please sign in to comment.