| // 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())); |
| }; |