blob: 8f9548ce576a257bbdeb6814909e8bc4e2ecfcc4 [file] [log] [blame]
// Copyright 2015 Joyent, Inc.
module.exports = DiffieHellman;
var assert = require('assert-plus');
var crypto = require('crypto');
var algs = require('./algs');
var utils = require('./utils');
var ed;
var Key = require('./key');
var PrivateKey = require('./private-key');
var CRYPTO_HAVE_ECDH = (crypto.createECDH !== undefined);
var ecdh, ec, jsbn;
function DiffieHellman(key) {
utils.assertCompatible(key, Key, [1, 4], 'key');
this._isPriv = PrivateKey.isPrivateKey(key, [1, 3]);
this._algo = key.type;
this._curve = key.curve;
this._key = key;
if (key.type === 'dsa') {
if (!CRYPTO_HAVE_ECDH) {
throw (new Error('Due to bugs in the node 0.10 ' +
'crypto API, node 0.12.x or later is required ' +
'to use DH'));
}
this._dh = crypto.createDiffieHellman(
key.part.p.data, undefined,
key.part.g.data, undefined);
this._p = key.part.p;
this._g = key.part.g;
if (this._isPriv)
this._dh.setPrivateKey(key.part.x.data);
this._dh.setPublicKey(key.part.y.data);
} else if (key.type === 'ecdsa') {
if (!CRYPTO_HAVE_ECDH) {
if (ecdh === undefined)
ecdh = require('ecc-jsbn');
if (ec === undefined)
ec = require('ecc-jsbn/lib/ec');
if (jsbn === undefined)
jsbn = require('jsbn').BigInteger;
this._ecParams = new X9ECParameters(this._curve);
if (this._isPriv) {
this._priv = new ECPrivate(
this._ecParams, key.part.d.data);
}
return;
}
var curve = {
'nistp256': 'prime256v1',
'nistp384': 'secp384r1',
'nistp521': 'secp521r1'
}[key.curve];
this._dh = crypto.createECDH(curve);
if (typeof (this._dh) !== 'object' ||
typeof (this._dh.setPrivateKey) !== 'function') {
CRYPTO_HAVE_ECDH = false;
DiffieHellman.call(this, key);
return;
}
if (this._isPriv)
this._dh.setPrivateKey(key.part.d.data);
this._dh.setPublicKey(key.part.Q.data);
} else if (key.type === 'curve25519') {
if (ed === undefined)
ed = require('jodid25519');
if (this._isPriv) {
this._priv = key.part.r.data;
if (this._priv[0] === 0x00)
this._priv = this._priv.slice(1);
this._priv = this._priv.slice(0, 32);
}
} else {
throw (new Error('DH not supported for ' + key.type + ' keys'));
}
}
DiffieHellman.prototype.getPublicKey = function () {
if (this._isPriv)
return (this._key.toPublic());
return (this._key);
};
DiffieHellman.prototype.getPrivateKey = function () {
if (this._isPriv)
return (this._key);
else
return (undefined);
};
DiffieHellman.prototype.getKey = DiffieHellman.prototype.getPrivateKey;
DiffieHellman.prototype._keyCheck = function (pk, isPub) {
assert.object(pk, 'key');
if (!isPub)
utils.assertCompatible(pk, PrivateKey, [1, 3], 'key');
utils.assertCompatible(pk, Key, [1, 4], 'key');
if (pk.type !== this._algo) {
throw (new Error('A ' + pk.type + ' key cannot be used in ' +
this._algo + ' Diffie-Hellman'));
}
if (pk.curve !== this._curve) {
throw (new Error('A key from the ' + pk.curve + ' curve ' +
'cannot be used with a ' + this._curve +
' Diffie-Hellman'));
}
if (pk.type === 'dsa') {
assert.deepEqual(pk.part.p, this._p,
'DSA key prime does not match');
assert.deepEqual(pk.part.g, this._g,
'DSA key generator does not match');
}
};
DiffieHellman.prototype.setKey = function (pk) {
this._keyCheck(pk);
if (pk.type === 'dsa') {
this._dh.setPrivateKey(pk.part.x.data);
this._dh.setPublicKey(pk.part.y.data);
} else if (pk.type === 'ecdsa') {
if (CRYPTO_HAVE_ECDH) {
this._dh.setPrivateKey(pk.part.d.data);
this._dh.setPublicKey(pk.part.Q.data);
} else {
this._priv = new ECPrivate(
this._ecParams, pk.part.d.data);
}
} else if (pk.type === 'curve25519') {
this._priv = pk.part.r.data;
if (this._priv[0] === 0x00)
this._priv = this._priv.slice(1);
this._priv = this._priv.slice(0, 32);
}
this._key = pk;
this._isPriv = true;
};
DiffieHellman.prototype.setPrivateKey = DiffieHellman.prototype.setKey;
DiffieHellman.prototype.computeSecret = function (otherpk) {
this._keyCheck(otherpk, true);
if (!this._isPriv)
throw (new Error('DH exchange has not been initialized with ' +
'a private key yet'));
var pub;
if (this._algo === 'dsa') {
return (this._dh.computeSecret(
otherpk.part.y.data));
} else if (this._algo === 'ecdsa') {
if (CRYPTO_HAVE_ECDH) {
return (this._dh.computeSecret(
otherpk.part.Q.data));
} else {
pub = new ECPublic(
this._ecParams, otherpk.part.Q.data);
return (this._priv.deriveSharedSecret(pub));
}
} else if (this._algo === 'curve25519') {
pub = otherpk.part.R.data;
if (pub[0] === 0x00)
pub = pub.slice(1);
var secret = ed.dh.computeKey(
this._priv.toString('binary'),
pub.toString('binary'));
return (new Buffer(secret, 'binary'));
}
throw (new Error('Invalid algorithm: ' + this._algo));
};
DiffieHellman.prototype.generateKey = function () {
var parts = [];
var priv, pub;
if (this._algo === 'dsa') {
this._dh.generateKeys();
parts.push({name: 'p', data: this._p.data});
parts.push({name: 'q', data: this._key.part.q.data});
parts.push({name: 'g', data: this._g.data});
parts.push({name: 'y', data: this._dh.getPublicKey()});
parts.push({name: 'x', data: this._dh.getPrivateKey()});
this._key = new PrivateKey({
type: 'dsa',
parts: parts
});
this._isPriv = true;
return (this._key);
} else if (this._algo === 'ecdsa') {
if (CRYPTO_HAVE_ECDH) {
this._dh.generateKeys();
parts.push({name: 'curve',
data: new Buffer(this._curve)});
parts.push({name: 'Q', data: this._dh.getPublicKey()});
parts.push({name: 'd', data: this._dh.getPrivateKey()});
this._key = new PrivateKey({
type: 'ecdsa',
curve: this._curve,
parts: parts
});
this._isPriv = true;
return (this._key);
} else {
var n = this._ecParams.getN();
var r = new jsbn(crypto.randomBytes(n.bitLength()));
var n1 = n.subtract(jsbn.ONE);
priv = r.mod(n1).add(jsbn.ONE);
pub = this._ecParams.getG().multiply(priv);
priv = new Buffer(priv.toByteArray());
pub = new Buffer(this._ecParams.getCurve().
encodePointHex(pub), 'hex');
this._priv = new ECPrivate(this._ecParams, priv);
parts.push({name: 'curve',
data: new Buffer(this._curve)});
parts.push({name: 'Q', data: pub});
parts.push({name: 'd', data: priv});
this._key = new PrivateKey({
type: 'ecdsa',
curve: this._curve,
parts: parts
});
this._isPriv = true;
return (this._key);
}
} else if (this._algo === 'curve25519') {
priv = ed.dh.generateKey();
pub = ed.dh.publicKey(priv);
this._priv = priv = new Buffer(priv, 'binary');
pub = new Buffer(pub, 'binary');
parts.push({name: 'R', data: pub});
parts.push({name: 'r', data: Buffer.concat([priv, pub])});
this._key = new PrivateKey({
type: 'curve25519',
parts: parts
});
this._isPriv = true;
return (this._key);
}
throw (new Error('Invalid algorithm: ' + this._algo));
};
DiffieHellman.prototype.generateKeys = DiffieHellman.prototype.generateKey;
/* These are helpers for using ecc-jsbn (for node 0.10 compatibility). */
function X9ECParameters(name) {
var params = algs.curves[name];
assert.object(params);
var p = new jsbn(params.p);
var a = new jsbn(params.a);
var b = new jsbn(params.b);
var n = new jsbn(params.n);
var h = jsbn.ONE;
var curve = new ec.ECCurveFp(p, a, b);
var G = curve.decodePointHex(params.G.toString('hex'));
this.curve = curve;
this.g = G;
this.n = n;
this.h = h;
}
X9ECParameters.prototype.getCurve = function () { return (this.curve); };
X9ECParameters.prototype.getG = function () { return (this.g); };
X9ECParameters.prototype.getN = function () { return (this.n); };
X9ECParameters.prototype.getH = function () { return (this.h); };
function ECPublic(params, buffer) {
this._params = params;
if (buffer[0] === 0x00)
buffer = buffer.slice(1);
this._pub = params.getCurve().decodePointHex(buffer.toString('hex'));
}
function ECPrivate(params, buffer) {
this._params = params;
this._priv = new jsbn(utils.mpNormalize(buffer));
}
ECPrivate.prototype.deriveSharedSecret = function (pubKey) {
assert.ok(pubKey instanceof ECPublic);
var S = pubKey._pub.multiply(this._priv);
return (new Buffer(S.getX().toBigInteger().toByteArray()));
};