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