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.

397 lines
10 KiB

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