1 // Copyright 2015 Joyent, Inc.
3 module.exports = DiffieHellman;
5 var assert = require('assert-plus');
6 var crypto = require('crypto');
7 var algs = require('./algs');
8 var utils = require('./utils');
11 var Key = require('./key');
12 var PrivateKey = require('./private-key');
14 var CRYPTO_HAVE_ECDH = (crypto.createECDH !== undefined);
18 function DiffieHellman(key) {
19 utils.assertCompatible(key, Key, [1, 4], 'key');
20 this._isPriv = PrivateKey.isPrivateKey(key, [1, 3]);
21 this._algo = key.type;
22 this._curve = key.curve;
24 if (key.type === 'dsa') {
25 if (!CRYPTO_HAVE_ECDH) {
26 throw (new Error('Due to bugs in the node 0.10 ' +
27 'crypto API, node 0.12.x or later is required ' +
30 this._dh = crypto.createDiffieHellman(
31 key.part.p.data, undefined,
32 key.part.g.data, undefined);
36 this._dh.setPrivateKey(key.part.x.data);
37 this._dh.setPublicKey(key.part.y.data);
39 } else if (key.type === 'ecdsa') {
40 if (!CRYPTO_HAVE_ECDH) {
41 if (ecdh === undefined)
42 ecdh = require('ecc-jsbn');
44 ec = require('ecc-jsbn/lib/ec');
45 if (jsbn === undefined)
46 jsbn = require('jsbn').BigInteger;
48 this._ecParams = new X9ECParameters(this._curve);
51 this._priv = new ECPrivate(
52 this._ecParams, key.part.d.data);
58 'nistp256': 'prime256v1',
59 'nistp384': 'secp384r1',
60 'nistp521': 'secp521r1'
62 this._dh = crypto.createECDH(curve);
63 if (typeof (this._dh) !== 'object' ||
64 typeof (this._dh.setPrivateKey) !== 'function') {
65 CRYPTO_HAVE_ECDH = false;
66 DiffieHellman.call(this, key);
70 this._dh.setPrivateKey(key.part.d.data);
71 this._dh.setPublicKey(key.part.Q.data);
73 } else if (key.type === 'curve25519') {
75 ed = require('jodid25519');
78 this._priv = key.part.r.data;
79 if (this._priv[0] === 0x00)
80 this._priv = this._priv.slice(1);
81 this._priv = this._priv.slice(0, 32);
85 throw (new Error('DH not supported for ' + key.type + ' keys'));
89 DiffieHellman.prototype.getPublicKey = function () {
91 return (this._key.toPublic());
95 DiffieHellman.prototype.getPrivateKey = function () {
101 DiffieHellman.prototype.getKey = DiffieHellman.prototype.getPrivateKey;
103 DiffieHellman.prototype._keyCheck = function (pk, isPub) {
104 assert.object(pk, 'key');
106 utils.assertCompatible(pk, PrivateKey, [1, 3], 'key');
107 utils.assertCompatible(pk, Key, [1, 4], 'key');
109 if (pk.type !== this._algo) {
110 throw (new Error('A ' + pk.type + ' key cannot be used in ' +
111 this._algo + ' Diffie-Hellman'));
114 if (pk.curve !== this._curve) {
115 throw (new Error('A key from the ' + pk.curve + ' curve ' +
116 'cannot be used with a ' + this._curve +
120 if (pk.type === 'dsa') {
121 assert.deepEqual(pk.part.p, this._p,
122 'DSA key prime does not match');
123 assert.deepEqual(pk.part.g, this._g,
124 'DSA key generator does not match');
128 DiffieHellman.prototype.setKey = function (pk) {
131 if (pk.type === 'dsa') {
132 this._dh.setPrivateKey(pk.part.x.data);
133 this._dh.setPublicKey(pk.part.y.data);
135 } else if (pk.type === 'ecdsa') {
136 if (CRYPTO_HAVE_ECDH) {
137 this._dh.setPrivateKey(pk.part.d.data);
138 this._dh.setPublicKey(pk.part.Q.data);
140 this._priv = new ECPrivate(
141 this._ecParams, pk.part.d.data);
144 } else if (pk.type === 'curve25519') {
145 this._priv = pk.part.r.data;
146 if (this._priv[0] === 0x00)
147 this._priv = this._priv.slice(1);
148 this._priv = this._priv.slice(0, 32);
153 DiffieHellman.prototype.setPrivateKey = DiffieHellman.prototype.setKey;
155 DiffieHellman.prototype.computeSecret = function (otherpk) {
156 this._keyCheck(otherpk, true);
158 throw (new Error('DH exchange has not been initialized with ' +
159 'a private key yet'));
162 if (this._algo === 'dsa') {
163 return (this._dh.computeSecret(
164 otherpk.part.y.data));
166 } else if (this._algo === 'ecdsa') {
167 if (CRYPTO_HAVE_ECDH) {
168 return (this._dh.computeSecret(
169 otherpk.part.Q.data));
172 this._ecParams, otherpk.part.Q.data);
173 return (this._priv.deriveSharedSecret(pub));
176 } else if (this._algo === 'curve25519') {
177 pub = otherpk.part.R.data;
181 var secret = ed.dh.computeKey(
182 this._priv.toString('binary'),
183 pub.toString('binary'));
185 return (new Buffer(secret, 'binary'));
188 throw (new Error('Invalid algorithm: ' + this._algo));
191 DiffieHellman.prototype.generateKey = function () {
194 if (this._algo === 'dsa') {
195 this._dh.generateKeys();
197 parts.push({name: 'p', data: this._p.data});
198 parts.push({name: 'q', data: this._key.part.q.data});
199 parts.push({name: 'g', data: this._g.data});
200 parts.push({name: 'y', data: this._dh.getPublicKey()});
201 parts.push({name: 'x', data: this._dh.getPrivateKey()});
202 this._key = new PrivateKey({
209 } else if (this._algo === 'ecdsa') {
210 if (CRYPTO_HAVE_ECDH) {
211 this._dh.generateKeys();
213 parts.push({name: 'curve',
214 data: new Buffer(this._curve)});
215 parts.push({name: 'Q', data: this._dh.getPublicKey()});
216 parts.push({name: 'd', data: this._dh.getPrivateKey()});
217 this._key = new PrivateKey({
226 var n = this._ecParams.getN();
227 var r = new jsbn(crypto.randomBytes(n.bitLength()));
228 var n1 = n.subtract(jsbn.ONE);
229 priv = r.mod(n1).add(jsbn.ONE);
230 pub = this._ecParams.getG().multiply(priv);
232 priv = new Buffer(priv.toByteArray());
233 pub = new Buffer(this._ecParams.getCurve().
234 encodePointHex(pub), 'hex');
236 this._priv = new ECPrivate(this._ecParams, priv);
238 parts.push({name: 'curve',
239 data: new Buffer(this._curve)});
240 parts.push({name: 'Q', data: pub});
241 parts.push({name: 'd', data: priv});
243 this._key = new PrivateKey({
252 } else if (this._algo === 'curve25519') {
253 priv = ed.dh.generateKey();
254 pub = ed.dh.publicKey(priv);
255 this._priv = priv = new Buffer(priv, 'binary');
256 pub = new Buffer(pub, 'binary');
258 parts.push({name: 'R', data: pub});
259 parts.push({name: 'r', data: Buffer.concat([priv, pub])});
260 this._key = new PrivateKey({
268 throw (new Error('Invalid algorithm: ' + this._algo));
270 DiffieHellman.prototype.generateKeys = DiffieHellman.prototype.generateKey;
272 /* These are helpers for using ecc-jsbn (for node 0.10 compatibility). */
274 function X9ECParameters(name) {
275 var params = algs.curves[name];
276 assert.object(params);
278 var p = new jsbn(params.p);
279 var a = new jsbn(params.a);
280 var b = new jsbn(params.b);
281 var n = new jsbn(params.n);
283 var curve = new ec.ECCurveFp(p, a, b);
284 var G = curve.decodePointHex(params.G.toString('hex'));
291 X9ECParameters.prototype.getCurve = function () { return (this.curve); };
292 X9ECParameters.prototype.getG = function () { return (this.g); };
293 X9ECParameters.prototype.getN = function () { return (this.n); };
294 X9ECParameters.prototype.getH = function () { return (this.h); };
296 function ECPublic(params, buffer) {
297 this._params = params;
298 if (buffer[0] === 0x00)
299 buffer = buffer.slice(1);
300 this._pub = params.getCurve().decodePointHex(buffer.toString('hex'));
303 function ECPrivate(params, buffer) {
304 this._params = params;
305 this._priv = new jsbn(utils.mpNormalize(buffer));
307 ECPrivate.prototype.deriveSharedSecret = function (pubKey) {
308 assert.ok(pubKey instanceof ECPublic);
309 var S = pubKey._pub.multiply(this._priv);
310 return (new Buffer(S.getX().toBigInteger().toByteArray()));