utils.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // Copyright 2015 Joyent, Inc.
  2. module.exports = {
  3. bufferSplit: bufferSplit,
  4. addRSAMissing: addRSAMissing,
  5. calculateDSAPublic: calculateDSAPublic,
  6. calculateED25519Public: calculateED25519Public,
  7. calculateX25519Public: calculateX25519Public,
  8. mpNormalize: mpNormalize,
  9. mpDenormalize: mpDenormalize,
  10. ecNormalize: ecNormalize,
  11. countZeros: countZeros,
  12. assertCompatible: assertCompatible,
  13. isCompatible: isCompatible,
  14. opensslKeyDeriv: opensslKeyDeriv,
  15. opensshCipherInfo: opensshCipherInfo,
  16. publicFromPrivateECDSA: publicFromPrivateECDSA,
  17. zeroPadToLength: zeroPadToLength,
  18. writeBitString: writeBitString,
  19. readBitString: readBitString
  20. };
  21. var assert = require('assert-plus');
  22. var Buffer = require('safer-buffer').Buffer;
  23. var PrivateKey = require('./private-key');
  24. var Key = require('./key');
  25. var crypto = require('crypto');
  26. var algs = require('./algs');
  27. var asn1 = require('asn1');
  28. var ec = require('ecc-jsbn/lib/ec');
  29. var jsbn = require('jsbn').BigInteger;
  30. var nacl = require('tweetnacl');
  31. var MAX_CLASS_DEPTH = 3;
  32. function isCompatible(obj, klass, needVer) {
  33. if (obj === null || typeof (obj) !== 'object')
  34. return (false);
  35. if (needVer === undefined)
  36. needVer = klass.prototype._sshpkApiVersion;
  37. if (obj instanceof klass &&
  38. klass.prototype._sshpkApiVersion[0] == needVer[0])
  39. return (true);
  40. var proto = Object.getPrototypeOf(obj);
  41. var depth = 0;
  42. while (proto.constructor.name !== klass.name) {
  43. proto = Object.getPrototypeOf(proto);
  44. if (!proto || ++depth > MAX_CLASS_DEPTH)
  45. return (false);
  46. }
  47. if (proto.constructor.name !== klass.name)
  48. return (false);
  49. var ver = proto._sshpkApiVersion;
  50. if (ver === undefined)
  51. ver = klass._oldVersionDetect(obj);
  52. if (ver[0] != needVer[0] || ver[1] < needVer[1])
  53. return (false);
  54. return (true);
  55. }
  56. function assertCompatible(obj, klass, needVer, name) {
  57. if (name === undefined)
  58. name = 'object';
  59. assert.ok(obj, name + ' must not be null');
  60. assert.object(obj, name + ' must be an object');
  61. if (needVer === undefined)
  62. needVer = klass.prototype._sshpkApiVersion;
  63. if (obj instanceof klass &&
  64. klass.prototype._sshpkApiVersion[0] == needVer[0])
  65. return;
  66. var proto = Object.getPrototypeOf(obj);
  67. var depth = 0;
  68. while (proto.constructor.name !== klass.name) {
  69. proto = Object.getPrototypeOf(proto);
  70. assert.ok(proto && ++depth <= MAX_CLASS_DEPTH,
  71. name + ' must be a ' + klass.name + ' instance');
  72. }
  73. assert.strictEqual(proto.constructor.name, klass.name,
  74. name + ' must be a ' + klass.name + ' instance');
  75. var ver = proto._sshpkApiVersion;
  76. if (ver === undefined)
  77. ver = klass._oldVersionDetect(obj);
  78. assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1],
  79. name + ' must be compatible with ' + klass.name + ' klass ' +
  80. 'version ' + needVer[0] + '.' + needVer[1]);
  81. }
  82. var CIPHER_LEN = {
  83. 'des-ede3-cbc': { key: 24, iv: 8 },
  84. 'aes-128-cbc': { key: 16, iv: 16 },
  85. 'aes-256-cbc': { key: 32, iv: 16 }
  86. };
  87. var PKCS5_SALT_LEN = 8;
  88. function opensslKeyDeriv(cipher, salt, passphrase, count) {
  89. assert.buffer(salt, 'salt');
  90. assert.buffer(passphrase, 'passphrase');
  91. assert.number(count, 'iteration count');
  92. var clen = CIPHER_LEN[cipher];
  93. assert.object(clen, 'supported cipher');
  94. salt = salt.slice(0, PKCS5_SALT_LEN);
  95. var D, D_prev, bufs;
  96. var material = Buffer.alloc(0);
  97. while (material.length < clen.key + clen.iv) {
  98. bufs = [];
  99. if (D_prev)
  100. bufs.push(D_prev);
  101. bufs.push(passphrase);
  102. bufs.push(salt);
  103. D = Buffer.concat(bufs);
  104. for (var j = 0; j < count; ++j)
  105. D = crypto.createHash('md5').update(D).digest();
  106. material = Buffer.concat([material, D]);
  107. D_prev = D;
  108. }
  109. return ({
  110. key: material.slice(0, clen.key),
  111. iv: material.slice(clen.key, clen.key + clen.iv)
  112. });
  113. }
  114. /* Count leading zero bits on a buffer */
  115. function countZeros(buf) {
  116. var o = 0, obit = 8;
  117. while (o < buf.length) {
  118. var mask = (1 << obit);
  119. if ((buf[o] & mask) === mask)
  120. break;
  121. obit--;
  122. if (obit < 0) {
  123. o++;
  124. obit = 8;
  125. }
  126. }
  127. return (o*8 + (8 - obit) - 1);
  128. }
  129. function bufferSplit(buf, chr) {
  130. assert.buffer(buf);
  131. assert.string(chr);
  132. var parts = [];
  133. var lastPart = 0;
  134. var matches = 0;
  135. for (var i = 0; i < buf.length; ++i) {
  136. if (buf[i] === chr.charCodeAt(matches))
  137. ++matches;
  138. else if (buf[i] === chr.charCodeAt(0))
  139. matches = 1;
  140. else
  141. matches = 0;
  142. if (matches >= chr.length) {
  143. var newPart = i + 1;
  144. parts.push(buf.slice(lastPart, newPart - matches));
  145. lastPart = newPart;
  146. matches = 0;
  147. }
  148. }
  149. if (lastPart <= buf.length)
  150. parts.push(buf.slice(lastPart, buf.length));
  151. return (parts);
  152. }
  153. function ecNormalize(buf, addZero) {
  154. assert.buffer(buf);
  155. if (buf[0] === 0x00 && buf[1] === 0x04) {
  156. if (addZero)
  157. return (buf);
  158. return (buf.slice(1));
  159. } else if (buf[0] === 0x04) {
  160. if (!addZero)
  161. return (buf);
  162. } else {
  163. while (buf[0] === 0x00)
  164. buf = buf.slice(1);
  165. if (buf[0] === 0x02 || buf[0] === 0x03)
  166. throw (new Error('Compressed elliptic curve points ' +
  167. 'are not supported'));
  168. if (buf[0] !== 0x04)
  169. throw (new Error('Not a valid elliptic curve point'));
  170. if (!addZero)
  171. return (buf);
  172. }
  173. var b = Buffer.alloc(buf.length + 1);
  174. b[0] = 0x0;
  175. buf.copy(b, 1);
  176. return (b);
  177. }
  178. function readBitString(der, tag) {
  179. if (tag === undefined)
  180. tag = asn1.Ber.BitString;
  181. var buf = der.readString(tag, true);
  182. assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' +
  183. 'not supported (0x' + buf[0].toString(16) + ')');
  184. return (buf.slice(1));
  185. }
  186. function writeBitString(der, buf, tag) {
  187. if (tag === undefined)
  188. tag = asn1.Ber.BitString;
  189. var b = Buffer.alloc(buf.length + 1);
  190. b[0] = 0x00;
  191. buf.copy(b, 1);
  192. der.writeBuffer(b, tag);
  193. }
  194. function mpNormalize(buf) {
  195. assert.buffer(buf);
  196. while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00)
  197. buf = buf.slice(1);
  198. if ((buf[0] & 0x80) === 0x80) {
  199. var b = Buffer.alloc(buf.length + 1);
  200. b[0] = 0x00;
  201. buf.copy(b, 1);
  202. buf = b;
  203. }
  204. return (buf);
  205. }
  206. function mpDenormalize(buf) {
  207. assert.buffer(buf);
  208. while (buf.length > 1 && buf[0] === 0x00)
  209. buf = buf.slice(1);
  210. return (buf);
  211. }
  212. function zeroPadToLength(buf, len) {
  213. assert.buffer(buf);
  214. assert.number(len);
  215. while (buf.length > len) {
  216. assert.equal(buf[0], 0x00);
  217. buf = buf.slice(1);
  218. }
  219. while (buf.length < len) {
  220. var b = Buffer.alloc(buf.length + 1);
  221. b[0] = 0x00;
  222. buf.copy(b, 1);
  223. buf = b;
  224. }
  225. return (buf);
  226. }
  227. function bigintToMpBuf(bigint) {
  228. var buf = Buffer.from(bigint.toByteArray());
  229. buf = mpNormalize(buf);
  230. return (buf);
  231. }
  232. function calculateDSAPublic(g, p, x) {
  233. assert.buffer(g);
  234. assert.buffer(p);
  235. assert.buffer(x);
  236. g = new jsbn(g);
  237. p = new jsbn(p);
  238. x = new jsbn(x);
  239. var y = g.modPow(x, p);
  240. var ybuf = bigintToMpBuf(y);
  241. return (ybuf);
  242. }
  243. function calculateED25519Public(k) {
  244. assert.buffer(k);
  245. var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k));
  246. return (Buffer.from(kp.publicKey));
  247. }
  248. function calculateX25519Public(k) {
  249. assert.buffer(k);
  250. var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k));
  251. return (Buffer.from(kp.publicKey));
  252. }
  253. function addRSAMissing(key) {
  254. assert.object(key);
  255. assertCompatible(key, PrivateKey, [1, 1]);
  256. var d = new jsbn(key.part.d.data);
  257. var buf;
  258. if (!key.part.dmodp) {
  259. var p = new jsbn(key.part.p.data);
  260. var dmodp = d.mod(p.subtract(1));
  261. buf = bigintToMpBuf(dmodp);
  262. key.part.dmodp = {name: 'dmodp', data: buf};
  263. key.parts.push(key.part.dmodp);
  264. }
  265. if (!key.part.dmodq) {
  266. var q = new jsbn(key.part.q.data);
  267. var dmodq = d.mod(q.subtract(1));
  268. buf = bigintToMpBuf(dmodq);
  269. key.part.dmodq = {name: 'dmodq', data: buf};
  270. key.parts.push(key.part.dmodq);
  271. }
  272. }
  273. function publicFromPrivateECDSA(curveName, priv) {
  274. assert.string(curveName, 'curveName');
  275. assert.buffer(priv);
  276. var params = algs.curves[curveName];
  277. var p = new jsbn(params.p);
  278. var a = new jsbn(params.a);
  279. var b = new jsbn(params.b);
  280. var curve = new ec.ECCurveFp(p, a, b);
  281. var G = curve.decodePointHex(params.G.toString('hex'));
  282. var d = new jsbn(mpNormalize(priv));
  283. var pub = G.multiply(d);
  284. pub = Buffer.from(curve.encodePointHex(pub), 'hex');
  285. var parts = [];
  286. parts.push({name: 'curve', data: Buffer.from(curveName)});
  287. parts.push({name: 'Q', data: pub});
  288. var key = new Key({type: 'ecdsa', curve: curve, parts: parts});
  289. return (key);
  290. }
  291. function opensshCipherInfo(cipher) {
  292. var inf = {};
  293. switch (cipher) {
  294. case '3des-cbc':
  295. inf.keySize = 24;
  296. inf.blockSize = 8;
  297. inf.opensslName = 'des-ede3-cbc';
  298. break;
  299. case 'blowfish-cbc':
  300. inf.keySize = 16;
  301. inf.blockSize = 8;
  302. inf.opensslName = 'bf-cbc';
  303. break;
  304. case 'aes128-cbc':
  305. case 'aes128-ctr':
  306. case 'aes128-gcm@openssh.com':
  307. inf.keySize = 16;
  308. inf.blockSize = 16;
  309. inf.opensslName = 'aes-128-' + cipher.slice(7, 10);
  310. break;
  311. case 'aes192-cbc':
  312. case 'aes192-ctr':
  313. case 'aes192-gcm@openssh.com':
  314. inf.keySize = 24;
  315. inf.blockSize = 16;
  316. inf.opensslName = 'aes-192-' + cipher.slice(7, 10);
  317. break;
  318. case 'aes256-cbc':
  319. case 'aes256-ctr':
  320. case 'aes256-gcm@openssh.com':
  321. inf.keySize = 32;
  322. inf.blockSize = 16;
  323. inf.opensslName = 'aes-256-' + cipher.slice(7, 10);
  324. break;
  325. default:
  326. throw (new Error(
  327. 'Unsupported openssl cipher "' + cipher + '"'));
  328. }
  329. return (inf);
  330. }