Skip to content

Commit

Permalink
#26 want support for generating ecdsa keys
Browse files Browse the repository at this point in the history
Reviewed by: Trent Mick <[email protected]>
Reviewed by: Brittany Wald <[email protected]>
Approved by: Trent Mick <[email protected]>
  • Loading branch information
Alex Wilson committed Mar 9, 2017
1 parent 1479eb2 commit 941d67a
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 18 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,30 @@ Parameters
- `format` -- String name of format to use, valid options are:
- `auto`: choose automatically from all below
- `pem`: supports both PKCS#1 and PKCS#8
- `ssh`, `openssh`: new post-OpenSSH 6.5 internal format, produced by
- `ssh`, `openssh`: new post-OpenSSH 6.5 internal format, produced by
`ssh-keygen -o`
- `pkcs1`, `pkcs8`: variants of `pem`
- `rfc4253`: raw OpenSSH wire format
- `options` -- Optional Object, extra options, with keys:
- `filename` -- Optional String, name for the key being parsed
- `filename` -- Optional String, name for the key being parsed
(eg. the filename that was opened). Used to generate
Error messages
- `passphrase` -- Optional String, encryption passphrase used to decrypt an
encrypted PEM file

### `generatePrivateKey(type[, options])`

Generates a new private key of a certain key type, from random data.

Parameters

- `type` -- String, type of key to generate. Currently supported are `'ecdsa'`
and `'ed25519'`
- `options` -- optional Object, with keys:
- `curve` -- optional String, for `'ecdsa'` keys, specifies the curve to use.
If ECDSA is specified and this option is not given, defaults to
using `'nistp256'`.

### `PrivateKey.isPrivateKey(obj)`

Returns `true` if the given object is a valid `PrivateKey` object created by a
Expand Down
103 changes: 101 additions & 2 deletions lib/dhe.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright 2015 Joyent, Inc.
// Copyright 2017 Joyent, Inc.

module.exports = DiffieHellman;
module.exports = {
DiffieHellman: DiffieHellman,
generateECDSA: generateECDSA,
generateED25519: generateED25519
};

var assert = require('assert-plus');
var crypto = require('crypto');
var algs = require('./algs');
var utils = require('./utils');
var ed;
var nacl;

var Key = require('./key');
var PrivateKey = require('./private-key');
Expand Down Expand Up @@ -309,3 +314,97 @@ ECPrivate.prototype.deriveSharedSecret = function (pubKey) {
var S = pubKey._pub.multiply(this._priv);
return (new Buffer(S.getX().toBigInteger().toByteArray()));
};

function generateED25519() {
if (nacl === undefined)
nacl = require('tweetnacl');

var pair = nacl.sign.keyPair();
var priv = new Buffer(pair.secretKey);
var pub = new Buffer(pair.publicKey);
assert.strictEqual(priv.length, 64);
assert.strictEqual(pub.length, 32);

var parts = [];
parts.push({name: 'R', data: pub});
parts.push({name: 'r', data: priv});
var key = new PrivateKey({
type: 'ed25519',
parts: parts
});
return (key);
}

/* Generates a new ECDSA private key on a given curve. */
function generateECDSA(curve) {
var parts = [];
var key;

if (CRYPTO_HAVE_ECDH) {
/*
* Node crypto doesn't expose key generation directly, but the
* ECDH instances can generate keys. It turns out this just
* calls into the OpenSSL generic key generator, and we can
* read its output happily without doing an actual DH. So we
* use that here.
*/
var osCurve = {
'nistp256': 'prime256v1',
'nistp384': 'secp384r1',
'nistp521': 'secp521r1'
}[curve];

var dh = crypto.createECDH(osCurve);
dh.generateKeys();

parts.push({name: 'curve',
data: new Buffer(curve)});
parts.push({name: 'Q', data: dh.getPublicKey()});
parts.push({name: 'd', data: dh.getPrivateKey()});

key = new PrivateKey({
type: 'ecdsa',
curve: curve,
parts: parts
});
return (key);

} else {
if (ecdh === undefined)
ecdh = require('ecc-jsbn');
if (ec === undefined)
ec = require('ecc-jsbn/lib/ec');
if (jsbn === undefined)
jsbn = require('jsbn').BigInteger;

var ecParams = new X9ECParameters(curve);

/* This algorithm taken from FIPS PUB 186-4 (section B.4.1) */
var n = ecParams.getN();
/*
* The crypto.randomBytes() function can only give us whole
* bytes, so taking a nod from X9.62, we round up.
*/
var cByteLen = Math.ceil((n.bitLength() + 64) / 8);
var c = new jsbn(crypto.randomBytes(cByteLen));

var n1 = n.subtract(jsbn.ONE);
var priv = c.mod(n1).add(jsbn.ONE);
var pub = ecParams.getG().multiply(priv);

priv = new Buffer(priv.toByteArray());
pub = new Buffer(ecParams.getCurve().
encodePointHex(pub), 'hex');

parts.push({name: 'curve', data: new Buffer(curve)});
parts.push({name: 'Q', data: pub});
parts.push({name: 'd', data: priv});

key = new PrivateKey({
type: 'ecdsa',
curve: curve,
parts: parts
});
return (key);
}
}
2 changes: 1 addition & 1 deletion lib/identity.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 Joyent, Inc.
// Copyright 2017 Joyent, Inc.

module.exports = Identity;

Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
parseSignature: Signature.parse,
PrivateKey: PrivateKey,
parsePrivateKey: PrivateKey.parse,
generatePrivateKey: PrivateKey.generate,
Certificate: Certificate,
parseCertificate: Certificate.parse,
createSelfSignedCertificate: Certificate.createSelfSigned,
Expand Down
4 changes: 2 additions & 2 deletions lib/key.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Joyent, Inc.
// Copyright 2017 Joyent, Inc.

module.exports = Key;

Expand All @@ -7,7 +7,7 @@ var algs = require('./algs');
var crypto = require('crypto');
var Fingerprint = require('./fingerprint');
var Signature = require('./signature');
var DiffieHellman = require('./dhe');
var DiffieHellman = require('./dhe').DiffieHellman;
var errs = require('./errors');
var utils = require('./utils');
var PrivateKey = require('./private-key');
Expand Down
24 changes: 23 additions & 1 deletion lib/private-key.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Joyent, Inc.
// Copyright 2017 Joyent, Inc.

module.exports = PrivateKey;

Expand All @@ -10,6 +10,9 @@ var Signature = require('./signature');
var errs = require('./errors');
var util = require('util');
var utils = require('./utils');
var dhe = require('./dhe');
var generateECDSA = dhe.generateECDSA;
var generateED25519 = dhe.generateED25519;
var edCompat;
var ed;

Expand Down Expand Up @@ -208,6 +211,25 @@ PrivateKey.isPrivateKey = function (obj, ver) {
return (utils.isCompatible(obj, PrivateKey, ver));
};

PrivateKey.generate = function (type, options) {
if (options === undefined)
options = {};
assert.object(options, 'options');

switch (type) {
case 'ecdsa':
if (options.curve === undefined)
options.curve = 'nistp256';
assert.string(options.curve, 'options.curve');
return (generateECDSA(options.curve));
case 'ed25519':
return (generateED25519());
default:
throw (new Error('Key generation not supported with key ' +
'type "' + type + '"'));
}
};

/*
* API versions for PrivateKey:
* [1,0] -- initial ver
Expand Down
16 changes: 8 additions & 8 deletions test/dhe_compat.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Joyent, Inc. All rights reserved.
// Copyright 2017 Joyent, Inc. All rights reserved.

var test = require('tape').test;

Expand Down Expand Up @@ -54,33 +54,33 @@ test('setup', function (t) {
});

test('ecdhe shared secret', function (t) {
var dh1 = new sshpk_dhe(EC_KEY);
var dh1 = new sshpk_dhe.DiffieHellman(EC_KEY);
var secret1 = dh1.computeSecret(EC2_KEY.toPublic());
t.ok(Buffer.isBuffer(secret1));
t.deepEqual(secret1, new Buffer(
'UoKiio/gnWj4BdV41YvoHu9yhjynGBmphZ1JFbpk30o=', 'base64'));

var dh2 = new sshpk_dhe(EC2_KEY);
var dh2 = new sshpk_dhe.DiffieHellman(EC2_KEY);
var secret2 = dh2.computeSecret(EC_KEY.toPublic());
t.deepEqual(secret1, secret2);
t.end();
});

test('ecdhe generate ephemeral', function (t) {
var dh = new sshpk_dhe(EC_KEY);
var dh = new sshpk_dhe.DiffieHellman(EC_KEY);
var ek = dh.generateKey();
t.ok(ek instanceof sshpk.PrivateKey);
t.strictEqual(ek.type, 'ecdsa');
t.strictEqual(ek.curve, 'nistp256');

var secret1 = dh.computeSecret(EC_KEY);
var secret2 = (new sshpk_dhe(EC_KEY)).computeSecret(ek);
var secret2 = (new sshpk_dhe.DiffieHellman(EC_KEY)).computeSecret(ek);
t.deepEqual(secret1, secret2);
t.end();
});

test('ecdhe reject diff curves', function (t) {
var dh = new sshpk_dhe(EC_KEY);
var dh = new sshpk_dhe.DiffieHellman(EC_KEY);
t.throws(function () {
dh.computeSecret(ECOUT_KEY.toPublic());
});
Expand All @@ -93,12 +93,12 @@ test('ecdhe reject diff curves', function (t) {
t.strictEqual(dh.getPublicKey().fingerprint().toString(),
EC2_KEY.fingerprint().toString());

var dh2 = new sshpk_dhe(ECOUT_KEY);
var dh2 = new sshpk_dhe.DiffieHellman(ECOUT_KEY);
t.throws(function () {
dh2.setKey(EC_KEY);
});

dh2 = new sshpk_dhe(EC_KEY);
dh2 = new sshpk_dhe.DiffieHellman(EC_KEY);
t.throws(function () {
dh2.setKey(C_KEY);
});
Expand Down
31 changes: 30 additions & 1 deletion test/openssl-cmd.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Joyent, Inc. All rights reserved.
// Copyright 2017 Joyent, Inc. All rights reserved.

var test = require('tape').test;
var sshpk = require('../lib/index');
Expand Down Expand Up @@ -358,6 +358,35 @@ function genTests() {
kid.stdin.write(certPem);
kid.stdin.end();
});

test('make a self-signed cert with generated key', function (t) {
if (algo !== 'ecdsa') {
t.end();
return;
}

var key = sshpk.generatePrivateKey(algo);

var id = sshpk.identityFromDN('cn=' + algo);
var cert = sshpk.createSelfSignedCertificate(id, key,
{ purposes: ['ca'] });
var certPem = cert.toBuffer('pem');

fs.writeFileSync(path.join(tmp, 'ca.pem'), certPem);

var kid = spawn('openssl', ['verify',
'-CAfile', path.join(tmp, 'ca.pem')]);
var bufs = [];
kid.stdout.on('data', bufs.push.bind(bufs));
kid.on('close', function (rc) {
t.equal(rc, 0);
var output = Buffer.concat(bufs).toString();
t.strictEqual(output.trim(), 'stdin: OK');
t.end();
});
kid.stdin.write(certPem);
kid.stdin.end();
});
});

test('teardown', function (t) {
Expand Down
59 changes: 58 additions & 1 deletion test/private-key.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015 Joyent, Inc. All rights reserved.
// Copyright 2017 Joyent, Inc. All rights reserved.

var test = require('tape').test;
var sshpk = require('../lib/index');
Expand Down Expand Up @@ -253,9 +253,66 @@ test('PrivateKey#createSign on ECDSA 256 key', function (t) {
t.end();
});

test('PrivateKey.generate ecdsa default', function (t) {
var key = sshpk.generatePrivateKey('ecdsa');
t.ok(sshpk.PrivateKey.isPrivateKey(key));
t.strictEqual(key.type, 'ecdsa');
t.strictEqual(key.curve, 'nistp256');
t.strictEqual(key.size, 256);

var s = key.createSign('sha256');
s.update('foobar');
var sig = s.sign();
t.ok(sig);
t.ok(sig instanceof sshpk.Signature);

var key2 = sshpk.parsePrivateKey(key.toBuffer('pem'));

var v = key2.createVerify('sha256');
v.update('foobar');
t.ok(v.verify(sig));

var key3 = sshpk.generatePrivateKey('ecdsa');
t.ok(!key3.fingerprint().matches(key));

t.end();
});

test('PrivateKey.generate ecdsa p-384', function (t) {
var key = sshpk.generatePrivateKey('ecdsa', { curve: 'nistp384' });
t.ok(sshpk.PrivateKey.isPrivateKey(key));
t.strictEqual(key.type, 'ecdsa');
t.strictEqual(key.curve, 'nistp384');
t.strictEqual(key.size, 384);
t.end();
});

if (process.version.match(/^v0\.[0-9]\./))
return;

test('PrivateKey.generate ed25519', function (t) {
var key = sshpk.generatePrivateKey('ed25519');
t.ok(sshpk.PrivateKey.isPrivateKey(key));
t.strictEqual(key.type, 'ed25519');
t.strictEqual(key.size, 256);

var s = key.createSign('sha512');
s.update('foobar');
var sig = s.sign();
t.ok(sig);
t.ok(sig instanceof sshpk.Signature);

var sshPub = key.toPublic().toBuffer('ssh');
var key2 = sshpk.parseKey(sshPub);
t.ok(key2.fingerprint().matches(key));

var v = key2.createVerify('sha512');
v.update('foobar');
t.ok(v.verify(sig));

t.end();
});

test('PrivateKey#createSign on ED25519 key', function (t) {
var s = KEY_ED25519.createSign('sha512');
s.write('foobar');
Expand Down

0 comments on commit 941d67a

Please sign in to comment.