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