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.
411 lines
11 KiB
411 lines
11 KiB
// Copyright 2017 Joyent, Inc. |
|
|
|
module.exports = { |
|
DiffieHellman: DiffieHellman, |
|
generateECDSA: generateECDSA, |
|
generateED25519: generateED25519 |
|
}; |
|
|
|
var assert = require('assert-plus'); |
|
var crypto = require('crypto'); |
|
var algs = require('./algs'); |
|
var utils = require('./utils'); |
|
var nacl; |
|
|
|
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 (nacl === undefined) |
|
nacl = require('tweetnacl'); |
|
|
|
if (this._isPriv) { |
|
this._priv = key.part.r.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') { |
|
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; |
|
while (pub[0] === 0x00 && pub.length > 32) |
|
pub = pub.slice(1); |
|
assert.strictEqual(pub.length, 32); |
|
assert.strictEqual(this._priv.length, 64); |
|
|
|
var priv = this._priv.slice(0, 32); |
|
|
|
var secret = nacl.box.before(new Uint8Array(pub), |
|
new Uint8Array(priv)); |
|
|
|
return (new Buffer(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: 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') { |
|
var pair = nacl.box.keyPair(); |
|
priv = new Buffer(pair.secretKey); |
|
pub = new Buffer(pair.publicKey); |
|
priv = Buffer.concat([priv, pub]); |
|
assert.strictEqual(priv.length, 64); |
|
assert.strictEqual(pub.length, 32); |
|
|
|
parts.push({name: 'R', data: pub}); |
|
parts.push({name: 'r', 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 (new Buffer(S.getX().toBigInteger().toByteArray())); |
|
}; |
|
|
|
function generateED25519() { |
|
if (nacl === undefined) |
|
nacl = require('tweetnacl'); |
|
|
|
var pair = nacl.sign.keyPair(); |
|
var priv = new Buffer(pair.secretKey); |
|
var pub = new Buffer(pair.publicKey); |
|
assert.strictEqual(priv.length, 64); |
|
assert.strictEqual(pub.length, 32); |
|
|
|
var parts = []; |
|
parts.push({name: 'R', data: pub}); |
|
parts.push({name: 'r', data: priv}); |
|
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: new Buffer(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 { |
|
if (ecdh === undefined) |
|
ecdh = require('ecc-jsbn'); |
|
if (ec === undefined) |
|
ec = require('ecc-jsbn/lib/ec'); |
|
if (jsbn === undefined) |
|
jsbn = require('jsbn').BigInteger; |
|
|
|
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 = new Buffer(priv.toByteArray()); |
|
pub = new Buffer(ecParams.getCurve(). |
|
encodePointHex(pub), 'hex'); |
|
|
|
parts.push({name: 'curve', data: new Buffer(curve)}); |
|
parts.push({name: 'Q', data: pub}); |
|
parts.push({name: 'd', data: priv}); |
|
|
|
key = new PrivateKey({ |
|
type: 'ecdsa', |
|
curve: curve, |
|
parts: parts |
|
}); |
|
return (key); |
|
} |
|
}
|
|
|