• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 Joyent, Inc.
2
3module.exports = {
4	DiffieHellman: DiffieHellman,
5	generateECDSA: generateECDSA,
6	generateED25519: generateED25519
7};
8
9var assert = require('assert-plus');
10var crypto = require('crypto');
11var Buffer = require('safer-buffer').Buffer;
12var algs = require('./algs');
13var utils = require('./utils');
14var nacl;
15
16var Key = require('./key');
17var PrivateKey = require('./private-key');
18
19var CRYPTO_HAVE_ECDH = (crypto.createECDH !== undefined);
20
21var ecdh, ec, jsbn;
22
23function DiffieHellman(key) {
24	utils.assertCompatible(key, Key, [1, 4], 'key');
25	this._isPriv = PrivateKey.isPrivateKey(key, [1, 3]);
26	this._algo = key.type;
27	this._curve = key.curve;
28	this._key = key;
29	if (key.type === 'dsa') {
30		if (!CRYPTO_HAVE_ECDH) {
31			throw (new Error('Due to bugs in the node 0.10 ' +
32			    'crypto API, node 0.12.x or later is required ' +
33			    'to use DH'));
34		}
35		this._dh = crypto.createDiffieHellman(
36		    key.part.p.data, undefined,
37		    key.part.g.data, undefined);
38		this._p = key.part.p;
39		this._g = key.part.g;
40		if (this._isPriv)
41			this._dh.setPrivateKey(key.part.x.data);
42		this._dh.setPublicKey(key.part.y.data);
43
44	} else if (key.type === 'ecdsa') {
45		if (!CRYPTO_HAVE_ECDH) {
46			if (ecdh === undefined)
47				ecdh = require('ecc-jsbn');
48			if (ec === undefined)
49				ec = require('ecc-jsbn/lib/ec');
50			if (jsbn === undefined)
51				jsbn = require('jsbn').BigInteger;
52
53			this._ecParams = new X9ECParameters(this._curve);
54
55			if (this._isPriv) {
56				this._priv = new ECPrivate(
57				    this._ecParams, key.part.d.data);
58			}
59			return;
60		}
61
62		var curve = {
63			'nistp256': 'prime256v1',
64			'nistp384': 'secp384r1',
65			'nistp521': 'secp521r1'
66		}[key.curve];
67		this._dh = crypto.createECDH(curve);
68		if (typeof (this._dh) !== 'object' ||
69		    typeof (this._dh.setPrivateKey) !== 'function') {
70			CRYPTO_HAVE_ECDH = false;
71			DiffieHellman.call(this, key);
72			return;
73		}
74		if (this._isPriv)
75			this._dh.setPrivateKey(key.part.d.data);
76		this._dh.setPublicKey(key.part.Q.data);
77
78	} else if (key.type === 'curve25519') {
79		if (nacl === undefined)
80			nacl = require('tweetnacl');
81
82		if (this._isPriv) {
83			utils.assertCompatible(key, PrivateKey, [1, 5], 'key');
84			this._priv = key.part.k.data;
85		}
86
87	} else {
88		throw (new Error('DH not supported for ' + key.type + ' keys'));
89	}
90}
91
92DiffieHellman.prototype.getPublicKey = function () {
93	if (this._isPriv)
94		return (this._key.toPublic());
95	return (this._key);
96};
97
98DiffieHellman.prototype.getPrivateKey = function () {
99	if (this._isPriv)
100		return (this._key);
101	else
102		return (undefined);
103};
104DiffieHellman.prototype.getKey = DiffieHellman.prototype.getPrivateKey;
105
106DiffieHellman.prototype._keyCheck = function (pk, isPub) {
107	assert.object(pk, 'key');
108	if (!isPub)
109		utils.assertCompatible(pk, PrivateKey, [1, 3], 'key');
110	utils.assertCompatible(pk, Key, [1, 4], 'key');
111
112	if (pk.type !== this._algo) {
113		throw (new Error('A ' + pk.type + ' key cannot be used in ' +
114		    this._algo + ' Diffie-Hellman'));
115	}
116
117	if (pk.curve !== this._curve) {
118		throw (new Error('A key from the ' + pk.curve + ' curve ' +
119		    'cannot be used with a ' + this._curve +
120		    ' Diffie-Hellman'));
121	}
122
123	if (pk.type === 'dsa') {
124		assert.deepEqual(pk.part.p, this._p,
125		    'DSA key prime does not match');
126		assert.deepEqual(pk.part.g, this._g,
127		    'DSA key generator does not match');
128	}
129};
130
131DiffieHellman.prototype.setKey = function (pk) {
132	this._keyCheck(pk);
133
134	if (pk.type === 'dsa') {
135		this._dh.setPrivateKey(pk.part.x.data);
136		this._dh.setPublicKey(pk.part.y.data);
137
138	} else if (pk.type === 'ecdsa') {
139		if (CRYPTO_HAVE_ECDH) {
140			this._dh.setPrivateKey(pk.part.d.data);
141			this._dh.setPublicKey(pk.part.Q.data);
142		} else {
143			this._priv = new ECPrivate(
144			    this._ecParams, pk.part.d.data);
145		}
146
147	} else if (pk.type === 'curve25519') {
148		var k = pk.part.k;
149		if (!pk.part.k)
150			k = pk.part.r;
151		this._priv = k.data;
152		if (this._priv[0] === 0x00)
153			this._priv = this._priv.slice(1);
154		this._priv = this._priv.slice(0, 32);
155	}
156	this._key = pk;
157	this._isPriv = true;
158};
159DiffieHellman.prototype.setPrivateKey = DiffieHellman.prototype.setKey;
160
161DiffieHellman.prototype.computeSecret = function (otherpk) {
162	this._keyCheck(otherpk, true);
163	if (!this._isPriv)
164		throw (new Error('DH exchange has not been initialized with ' +
165		    'a private key yet'));
166
167	var pub;
168	if (this._algo === 'dsa') {
169		return (this._dh.computeSecret(
170		    otherpk.part.y.data));
171
172	} else if (this._algo === 'ecdsa') {
173		if (CRYPTO_HAVE_ECDH) {
174			return (this._dh.computeSecret(
175			    otherpk.part.Q.data));
176		} else {
177			pub = new ECPublic(
178			    this._ecParams, otherpk.part.Q.data);
179			return (this._priv.deriveSharedSecret(pub));
180		}
181
182	} else if (this._algo === 'curve25519') {
183		pub = otherpk.part.A.data;
184		while (pub[0] === 0x00 && pub.length > 32)
185			pub = pub.slice(1);
186		var priv = this._priv;
187		assert.strictEqual(pub.length, 32);
188		assert.strictEqual(priv.length, 32);
189
190		var secret = nacl.box.before(new Uint8Array(pub),
191		    new Uint8Array(priv));
192
193		return (Buffer.from(secret));
194	}
195
196	throw (new Error('Invalid algorithm: ' + this._algo));
197};
198
199DiffieHellman.prototype.generateKey = function () {
200	var parts = [];
201	var priv, pub;
202	if (this._algo === 'dsa') {
203		this._dh.generateKeys();
204
205		parts.push({name: 'p', data: this._p.data});
206		parts.push({name: 'q', data: this._key.part.q.data});
207		parts.push({name: 'g', data: this._g.data});
208		parts.push({name: 'y', data: this._dh.getPublicKey()});
209		parts.push({name: 'x', data: this._dh.getPrivateKey()});
210		this._key = new PrivateKey({
211			type: 'dsa',
212			parts: parts
213		});
214		this._isPriv = true;
215		return (this._key);
216
217	} else if (this._algo === 'ecdsa') {
218		if (CRYPTO_HAVE_ECDH) {
219			this._dh.generateKeys();
220
221			parts.push({name: 'curve',
222			    data: Buffer.from(this._curve)});
223			parts.push({name: 'Q', data: this._dh.getPublicKey()});
224			parts.push({name: 'd', data: this._dh.getPrivateKey()});
225			this._key = new PrivateKey({
226				type: 'ecdsa',
227				curve: this._curve,
228				parts: parts
229			});
230			this._isPriv = true;
231			return (this._key);
232
233		} else {
234			var n = this._ecParams.getN();
235			var r = new jsbn(crypto.randomBytes(n.bitLength()));
236			var n1 = n.subtract(jsbn.ONE);
237			priv = r.mod(n1).add(jsbn.ONE);
238			pub = this._ecParams.getG().multiply(priv);
239
240			priv = Buffer.from(priv.toByteArray());
241			pub = Buffer.from(this._ecParams.getCurve().
242			    encodePointHex(pub), 'hex');
243
244			this._priv = new ECPrivate(this._ecParams, priv);
245
246			parts.push({name: 'curve',
247			    data: Buffer.from(this._curve)});
248			parts.push({name: 'Q', data: pub});
249			parts.push({name: 'd', data: priv});
250
251			this._key = new PrivateKey({
252				type: 'ecdsa',
253				curve: this._curve,
254				parts: parts
255			});
256			this._isPriv = true;
257			return (this._key);
258		}
259
260	} else if (this._algo === 'curve25519') {
261		var pair = nacl.box.keyPair();
262		priv = Buffer.from(pair.secretKey);
263		pub = Buffer.from(pair.publicKey);
264		priv = Buffer.concat([priv, pub]);
265		assert.strictEqual(priv.length, 64);
266		assert.strictEqual(pub.length, 32);
267
268		parts.push({name: 'A', data: pub});
269		parts.push({name: 'k', data: priv});
270		this._key = new PrivateKey({
271			type: 'curve25519',
272			parts: parts
273		});
274		this._isPriv = true;
275		return (this._key);
276	}
277
278	throw (new Error('Invalid algorithm: ' + this._algo));
279};
280DiffieHellman.prototype.generateKeys = DiffieHellman.prototype.generateKey;
281
282/* These are helpers for using ecc-jsbn (for node 0.10 compatibility). */
283
284function X9ECParameters(name) {
285	var params = algs.curves[name];
286	assert.object(params);
287
288	var p = new jsbn(params.p);
289	var a = new jsbn(params.a);
290	var b = new jsbn(params.b);
291	var n = new jsbn(params.n);
292	var h = jsbn.ONE;
293	var curve = new ec.ECCurveFp(p, a, b);
294	var G = curve.decodePointHex(params.G.toString('hex'));
295
296	this.curve = curve;
297	this.g = G;
298	this.n = n;
299	this.h = h;
300}
301X9ECParameters.prototype.getCurve = function () { return (this.curve); };
302X9ECParameters.prototype.getG = function () { return (this.g); };
303X9ECParameters.prototype.getN = function () { return (this.n); };
304X9ECParameters.prototype.getH = function () { return (this.h); };
305
306function ECPublic(params, buffer) {
307	this._params = params;
308	if (buffer[0] === 0x00)
309		buffer = buffer.slice(1);
310	this._pub = params.getCurve().decodePointHex(buffer.toString('hex'));
311}
312
313function ECPrivate(params, buffer) {
314	this._params = params;
315	this._priv = new jsbn(utils.mpNormalize(buffer));
316}
317ECPrivate.prototype.deriveSharedSecret = function (pubKey) {
318	assert.ok(pubKey instanceof ECPublic);
319	var S = pubKey._pub.multiply(this._priv);
320	return (Buffer.from(S.getX().toBigInteger().toByteArray()));
321};
322
323function generateED25519() {
324	if (nacl === undefined)
325		nacl = require('tweetnacl');
326
327	var pair = nacl.sign.keyPair();
328	var priv = Buffer.from(pair.secretKey);
329	var pub = Buffer.from(pair.publicKey);
330	assert.strictEqual(priv.length, 64);
331	assert.strictEqual(pub.length, 32);
332
333	var parts = [];
334	parts.push({name: 'A', data: pub});
335	parts.push({name: 'k', data: priv.slice(0, 32)});
336	var key = new PrivateKey({
337		type: 'ed25519',
338		parts: parts
339	});
340	return (key);
341}
342
343/* Generates a new ECDSA private key on a given curve. */
344function generateECDSA(curve) {
345	var parts = [];
346	var key;
347
348	if (CRYPTO_HAVE_ECDH) {
349		/*
350		 * Node crypto doesn't expose key generation directly, but the
351		 * ECDH instances can generate keys. It turns out this just
352		 * calls into the OpenSSL generic key generator, and we can
353		 * read its output happily without doing an actual DH. So we
354		 * use that here.
355		 */
356		var osCurve = {
357			'nistp256': 'prime256v1',
358			'nistp384': 'secp384r1',
359			'nistp521': 'secp521r1'
360		}[curve];
361
362		var dh = crypto.createECDH(osCurve);
363		dh.generateKeys();
364
365		parts.push({name: 'curve',
366		    data: Buffer.from(curve)});
367		parts.push({name: 'Q', data: dh.getPublicKey()});
368		parts.push({name: 'd', data: dh.getPrivateKey()});
369
370		key = new PrivateKey({
371			type: 'ecdsa',
372			curve: curve,
373			parts: parts
374		});
375		return (key);
376	} else {
377		if (ecdh === undefined)
378			ecdh = require('ecc-jsbn');
379		if (ec === undefined)
380			ec = require('ecc-jsbn/lib/ec');
381		if (jsbn === undefined)
382			jsbn = require('jsbn').BigInteger;
383
384		var ecParams = new X9ECParameters(curve);
385
386		/* This algorithm taken from FIPS PUB 186-4 (section B.4.1) */
387		var n = ecParams.getN();
388		/*
389		 * The crypto.randomBytes() function can only give us whole
390		 * bytes, so taking a nod from X9.62, we round up.
391		 */
392		var cByteLen = Math.ceil((n.bitLength() + 64) / 8);
393		var c = new jsbn(crypto.randomBytes(cByteLen));
394
395		var n1 = n.subtract(jsbn.ONE);
396		var priv = c.mod(n1).add(jsbn.ONE);
397		var pub = ecParams.getG().multiply(priv);
398
399		priv = Buffer.from(priv.toByteArray());
400		pub = Buffer.from(ecParams.getCurve().
401		    encodePointHex(pub), 'hex');
402
403		parts.push({name: 'curve', data: Buffer.from(curve)});
404		parts.push({name: 'Q', data: pub});
405		parts.push({name: 'd', data: priv});
406
407		key = new PrivateKey({
408			type: 'ecdsa',
409			curve: curve,
410			parts: parts
411		});
412		return (key);
413	}
414}
415