You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					398 lines
				
				10 KiB
			
		
		
			
		
	
	
					398 lines
				
				10 KiB
			| 
											3 years ago
										 | // Copyright 2017 Joyent, Inc.
 | ||
|  | 
 | ||
|  | module.exports = { | ||
|  | 	DiffieHellman: DiffieHellman, | ||
|  | 	generateECDSA: generateECDSA, | ||
|  | 	generateED25519: generateED25519 | ||
|  | }; | ||
|  | 
 | ||
|  | var assert = require('assert-plus'); | ||
|  | var crypto = require('crypto'); | ||
|  | var Buffer = require('safer-buffer').Buffer; | ||
|  | var algs = require('./algs'); | ||
|  | var utils = require('./utils'); | ||
|  | var nacl = require('tweetnacl'); | ||
|  | 
 | ||
|  | var Key = require('./key'); | ||
|  | var PrivateKey = require('./private-key'); | ||
|  | 
 | ||
|  | var CRYPTO_HAVE_ECDH = (crypto.createECDH !== undefined); | ||
|  | 
 | ||
|  | var ecdh = require('ecc-jsbn'); | ||
|  | var ec = require('ecc-jsbn/lib/ec'); | ||
|  | var jsbn = require('jsbn').BigInteger; | ||
|  | 
 | ||
|  | 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) { | ||
|  | 			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 (this._isPriv) { | ||
|  | 			utils.assertCompatible(key, PrivateKey, [1, 5], 'key'); | ||
|  | 			this._priv = key.part.k.data; | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} 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') { | ||
|  | 		var k = pk.part.k; | ||
|  | 		if (!pk.part.k) | ||
|  | 			k = pk.part.r; | ||
|  | 		this._priv = k.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.A.data; | ||
|  | 		while (pub[0] === 0x00 && pub.length > 32) | ||
|  | 			pub = pub.slice(1); | ||
|  | 		var priv = this._priv; | ||
|  | 		assert.strictEqual(pub.length, 32); | ||
|  | 		assert.strictEqual(priv.length, 32); | ||
|  | 
 | ||
|  | 		var secret = nacl.box.before(new Uint8Array(pub), | ||
|  | 		    new Uint8Array(priv)); | ||
|  | 
 | ||
|  | 		return (Buffer.from(secret)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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: Buffer.from(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 = Buffer.from(priv.toByteArray()); | ||
|  | 			pub = Buffer.from(this._ecParams.getCurve(). | ||
|  | 			    encodePointHex(pub), 'hex'); | ||
|  | 
 | ||
|  | 			this._priv = new ECPrivate(this._ecParams, priv); | ||
|  | 
 | ||
|  | 			parts.push({name: 'curve', | ||
|  | 			    data: Buffer.from(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') { | ||
|  | 		var pair = nacl.box.keyPair(); | ||
|  | 		priv = Buffer.from(pair.secretKey); | ||
|  | 		pub = Buffer.from(pair.publicKey); | ||
|  | 		priv = Buffer.concat([priv, pub]); | ||
|  | 		assert.strictEqual(priv.length, 64); | ||
|  | 		assert.strictEqual(pub.length, 32); | ||
|  | 
 | ||
|  | 		parts.push({name: 'A', data: pub}); | ||
|  | 		parts.push({name: 'k', data: priv}); | ||
|  | 		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 (Buffer.from(S.getX().toBigInteger().toByteArray())); | ||
|  | }; | ||
|  | 
 | ||
|  | function generateED25519() { | ||
|  | 	var pair = nacl.sign.keyPair(); | ||
|  | 	var priv = Buffer.from(pair.secretKey); | ||
|  | 	var pub = Buffer.from(pair.publicKey); | ||
|  | 	assert.strictEqual(priv.length, 64); | ||
|  | 	assert.strictEqual(pub.length, 32); | ||
|  | 
 | ||
|  | 	var parts = []; | ||
|  | 	parts.push({name: 'A', data: pub}); | ||
|  | 	parts.push({name: 'k', data: priv.slice(0, 32)}); | ||
|  | 	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: Buffer.from(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 { | ||
|  | 
 | ||
|  | 		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 = Buffer.from(priv.toByteArray()); | ||
|  | 		pub = Buffer.from(ecParams.getCurve(). | ||
|  | 		    encodePointHex(pub), 'hex'); | ||
|  | 
 | ||
|  | 		parts.push({name: 'curve', data: Buffer.from(curve)}); | ||
|  | 		parts.push({name: 'Q', data: pub}); | ||
|  | 		parts.push({name: 'd', data: priv}); | ||
|  | 
 | ||
|  | 		key = new PrivateKey({ | ||
|  | 			type: 'ecdsa', | ||
|  | 			curve: curve, | ||
|  | 			parts: parts | ||
|  | 		}); | ||
|  | 		return (key); | ||
|  | 	} | ||
|  | } |