test-protocol-crypto.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. 'use strict';
  2. const assert = require('assert');
  3. const { randomBytes } = require('crypto');
  4. const {
  5. CIPHER_INFO,
  6. MAC_INFO,
  7. bindingAvailable,
  8. NullCipher,
  9. createCipher,
  10. NullDecipher,
  11. createDecipher,
  12. init: cryptoInit,
  13. } = require('../lib/protocol/crypto.js');
  14. (async () => {
  15. await cryptoInit;
  16. console.log(`Crypto binding ${bindingAvailable ? '' : 'not '}available`);
  17. {
  18. const PAIRS = [
  19. // cipher, decipher
  20. ['native', 'native'],
  21. ['binding', 'native'],
  22. ['native', 'binding'],
  23. ['binding', 'binding'],
  24. ].slice(0, bindingAvailable ? 4 : 1);
  25. [
  26. { cipher: null },
  27. { cipher: 'chacha20-poly1305@openssh.com' },
  28. { cipher: 'aes128-gcm@openssh.com' },
  29. { cipher: 'aes128-cbc', mac: 'hmac-sha1-etm@openssh.com' },
  30. { cipher: 'aes128-ctr', mac: 'hmac-sha1' },
  31. { cipher: 'arcfour', mac: 'hmac-sha2-256-96' },
  32. ].forEach((testConfig) => {
  33. for (const pair of PAIRS) {
  34. function onCipherData(data) {
  35. ciphered = Buffer.concat([ciphered, data]);
  36. }
  37. function onDecipherPayload(payload) {
  38. deciphered.push(payload);
  39. }
  40. function reset() {
  41. ciphered = Buffer.alloc(0);
  42. deciphered = [];
  43. }
  44. function reinit() {
  45. if (testConfig.cipher === null) {
  46. cipher = new NullCipher(1, onCipherData);
  47. decipher = new NullDecipher(1, onDecipherPayload);
  48. } else {
  49. cipher = createCipher(config);
  50. decipher = createDecipher(config);
  51. }
  52. }
  53. let ciphered;
  54. let deciphered;
  55. let cipher;
  56. let decipher;
  57. let macSize;
  58. let packet;
  59. let payload;
  60. let cipherInfo;
  61. let config;
  62. console.log('Testing cipher: %s, mac: %s (%s encrypt, %s decrypt) ...',
  63. testConfig.cipher,
  64. testConfig.mac
  65. || (testConfig.cipher === null ? '<none>' : '<implicit>'),
  66. pair[0],
  67. pair[1]);
  68. if (testConfig.cipher === null) {
  69. cipher = new NullCipher(1, onCipherData);
  70. decipher = new NullDecipher(1, onDecipherPayload);
  71. macSize = 0;
  72. } else {
  73. cipherInfo = CIPHER_INFO[testConfig.cipher];
  74. let macInfo;
  75. let macKey;
  76. if (testConfig.mac) {
  77. macInfo = MAC_INFO[testConfig.mac];
  78. macKey = randomBytes(macInfo.len);
  79. macSize = macInfo.actualLen;
  80. } else if (cipherInfo.authLen) {
  81. macSize = cipherInfo.authLen;
  82. } else {
  83. throw new Error('Missing MAC for cipher');
  84. }
  85. const key = randomBytes(cipherInfo.keyLen);
  86. const iv = (cipherInfo.ivLen
  87. ? randomBytes(cipherInfo.ivLen)
  88. : Buffer.alloc(0));
  89. config = {
  90. outbound: {
  91. onWrite: onCipherData,
  92. cipherInfo,
  93. cipherKey: Buffer.from(key),
  94. cipherIV: Buffer.from(iv),
  95. seqno: 1,
  96. macInfo,
  97. macKey: (macKey && Buffer.from(macKey)),
  98. forceNative: (pair[0] === 'native'),
  99. },
  100. inbound: {
  101. onPayload: onDecipherPayload,
  102. decipherInfo: cipherInfo,
  103. decipherKey: Buffer.from(key),
  104. decipherIV: Buffer.from(iv),
  105. seqno: 1,
  106. macInfo,
  107. macKey: (macKey && Buffer.from(macKey)),
  108. forceNative: (pair[1] === 'native'),
  109. },
  110. };
  111. cipher = createCipher(config);
  112. decipher = createDecipher(config);
  113. if (pair[0] === 'binding')
  114. assert(/binding/i.test(cipher.constructor.name));
  115. else
  116. assert(/native/i.test(cipher.constructor.name));
  117. if (pair[1] === 'binding')
  118. assert(/binding/i.test(decipher.constructor.name));
  119. else
  120. assert(/native/i.test(decipher.constructor.name));
  121. }
  122. let expectedSeqno;
  123. // Test zero-length payload ============================================
  124. payload = Buffer.alloc(0);
  125. expectedSeqno = 2;
  126. reset();
  127. packet = cipher.allocPacket(payload.length);
  128. payload.copy(packet, 5);
  129. cipher.encrypt(packet);
  130. assert.strictEqual(decipher.decrypt(ciphered, 0, ciphered.length),
  131. undefined);
  132. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  133. assert(ciphered.length >= 9 + macSize);
  134. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  135. assert.strictEqual(deciphered.length, 1);
  136. assert.deepStrictEqual(deciphered[0], payload);
  137. // Test single byte payload ============================================
  138. payload = Buffer.from([ 0xEF ]);
  139. expectedSeqno = 3;
  140. reset();
  141. packet = cipher.allocPacket(payload.length);
  142. payload.copy(packet, 5);
  143. cipher.encrypt(packet);
  144. assert.strictEqual(decipher.decrypt(ciphered, 0, ciphered.length),
  145. undefined);
  146. assert.strictEqual(cipher.outSeqno, 3);
  147. assert(ciphered.length >= 9 + macSize);
  148. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  149. assert.strictEqual(deciphered.length, 1);
  150. assert.deepStrictEqual(deciphered[0], payload);
  151. // Test large payload ==================================================
  152. payload = randomBytes(32 * 1024);
  153. expectedSeqno = 4;
  154. reset();
  155. packet = cipher.allocPacket(payload.length);
  156. payload.copy(packet, 5);
  157. cipher.encrypt(packet);
  158. assert.strictEqual(decipher.decrypt(ciphered, 0, ciphered.length),
  159. undefined);
  160. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  161. assert(ciphered.length >= 9 + macSize);
  162. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  163. assert.strictEqual(deciphered.length, 1);
  164. assert.deepStrictEqual(deciphered[0], payload);
  165. // Test sequnce number rollover ========================================
  166. payload = randomBytes(4);
  167. expectedSeqno = 0;
  168. cipher.outSeqno = decipher.inSeqno = (2 ** 32) - 1;
  169. reset();
  170. packet = cipher.allocPacket(payload.length);
  171. payload.copy(packet, 5);
  172. cipher.encrypt(packet);
  173. assert.strictEqual(decipher.decrypt(ciphered, 0, ciphered.length),
  174. undefined);
  175. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  176. assert(ciphered.length >= 9 + macSize);
  177. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  178. assert.strictEqual(deciphered.length, 1);
  179. assert.deepStrictEqual(deciphered[0], payload);
  180. // Test chunked input -- split length bytes ============================
  181. payload = randomBytes(32 * 768);
  182. expectedSeqno = 1;
  183. reset();
  184. packet = cipher.allocPacket(payload.length);
  185. payload.copy(packet, 5);
  186. cipher.encrypt(packet);
  187. assert.strictEqual(decipher.decrypt(ciphered, 0, 2), undefined);
  188. assert.strictEqual(decipher.decrypt(ciphered, 2, ciphered.length),
  189. undefined);
  190. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  191. assert(ciphered.length >= 9 + macSize);
  192. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  193. assert.strictEqual(deciphered.length, 1);
  194. assert.deepStrictEqual(deciphered[0], payload);
  195. // Test chunked input -- split length from payload =====================
  196. payload = randomBytes(32 * 768);
  197. expectedSeqno = 2;
  198. reset();
  199. packet = cipher.allocPacket(payload.length);
  200. payload.copy(packet, 5);
  201. cipher.encrypt(packet);
  202. assert.strictEqual(decipher.decrypt(ciphered, 0, 4), undefined);
  203. assert.strictEqual(decipher.decrypt(ciphered, 4, ciphered.length),
  204. undefined);
  205. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  206. assert(ciphered.length >= 9 + macSize);
  207. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  208. assert.strictEqual(deciphered.length, 1);
  209. assert.deepStrictEqual(deciphered[0], payload);
  210. // Test chunked input -- split length and payload from MAC =============
  211. payload = randomBytes(32 * 768);
  212. expectedSeqno = 3;
  213. reset();
  214. packet = cipher.allocPacket(payload.length);
  215. payload.copy(packet, 5);
  216. cipher.encrypt(packet);
  217. assert.strictEqual(
  218. decipher.decrypt(ciphered, 0, ciphered.length - macSize),
  219. undefined
  220. );
  221. assert.strictEqual(
  222. decipher.decrypt(ciphered,
  223. ciphered.length - macSize,
  224. ciphered.length),
  225. undefined
  226. );
  227. assert.strictEqual(cipher.outSeqno, expectedSeqno);
  228. assert(ciphered.length >= 9 + macSize);
  229. assert.strictEqual(decipher.inSeqno, cipher.outSeqno);
  230. assert.strictEqual(deciphered.length, 1);
  231. assert.deepStrictEqual(deciphered[0], payload);
  232. // Test packet length checks ===========================================
  233. [0, 2 ** 32 - 1].forEach((n) => {
  234. reset();
  235. packet = cipher.allocPacket(0);
  236. packet.writeUInt32BE(n, 0); // Overwrite packet length field
  237. cipher.encrypt(packet);
  238. let threw = false;
  239. try {
  240. decipher.decrypt(ciphered, 0, ciphered.length);
  241. } catch (ex) {
  242. threw = true;
  243. assert(ex instanceof Error);
  244. assert(/packet length/i.test(ex.message));
  245. }
  246. if (!threw)
  247. throw new Error('Expected error');
  248. // Recreate deciphers since errors leave them in an unusable state.
  249. // We recreate the ciphers as well so that internal states of both
  250. // ends match again.
  251. reinit();
  252. });
  253. // Test minimum padding length check ===================================
  254. if (testConfig.cipher !== null) {
  255. let payloadLen;
  256. const blockLen = cipherInfo.blockLen;
  257. if (/chacha|gcm/i.test(testConfig.cipher)
  258. || /etm/i.test(testConfig.mac)) {
  259. payloadLen = blockLen - 2;
  260. } else {
  261. payloadLen = blockLen - 6;
  262. }
  263. const minLen = 4 + 1 + payloadLen + (blockLen + 1);
  264. // We don't do strict equality checks here since the length of the
  265. // returned Buffer can vary due to implementation details.
  266. assert(cipher.allocPacket(payloadLen).length >= minLen);
  267. }
  268. // =====================================================================
  269. cipher.free();
  270. decipher.free();
  271. if (testConfig.cipher === null)
  272. break;
  273. }
  274. });
  275. }
  276. // Test createCipher()/createDecipher() exceptions
  277. {
  278. [
  279. [
  280. [true, null],
  281. /invalid config/i
  282. ],
  283. [
  284. [{}],
  285. [/invalid outbound/i, /invalid inbound/i]
  286. ],
  287. [
  288. [{ outbound: {}, inbound: {} }],
  289. [/invalid outbound\.onWrite/i, /invalid inbound\.onPayload/i]
  290. ],
  291. [
  292. [
  293. { outbound: {
  294. onWrite: () => {},
  295. cipherInfo: true
  296. },
  297. inbound: {
  298. onPayload: () => {},
  299. decipherInfo: true
  300. },
  301. },
  302. { outbound: {
  303. onWrite: () => {},
  304. cipherInfo: null
  305. },
  306. inbound: {
  307. onPayload: () => {},
  308. decipherInfo: null
  309. },
  310. },
  311. ],
  312. [/invalid outbound\.cipherInfo/i, /invalid inbound\.decipherInfo/i]
  313. ],
  314. [
  315. [
  316. { outbound: {
  317. onWrite: () => {},
  318. cipherInfo: {},
  319. cipherKey: {},
  320. },
  321. inbound: {
  322. onPayload: () => {},
  323. decipherInfo: {},
  324. decipherKey: {},
  325. },
  326. },
  327. { outbound: {
  328. onWrite: () => {},
  329. cipherInfo: { keyLen: 32 },
  330. cipherKey: Buffer.alloc(8),
  331. },
  332. inbound: {
  333. onPayload: () => {},
  334. decipherInfo: { keyLen: 32 },
  335. decipherKey: Buffer.alloc(8),
  336. },
  337. },
  338. ],
  339. [/invalid outbound\.cipherKey/i, /invalid inbound\.decipherKey/i]
  340. ],
  341. [
  342. [
  343. { outbound: {
  344. onWrite: () => {},
  345. cipherInfo: { keyLen: 1, ivLen: 12 },
  346. cipherKey: Buffer.alloc(1),
  347. cipherIV: true
  348. },
  349. inbound: {
  350. onPayload: () => {},
  351. decipherInfo: { keyLen: 1, ivLen: 12 },
  352. decipherKey: Buffer.alloc(1),
  353. cipherIV: true
  354. },
  355. },
  356. { outbound: {
  357. onWrite: () => {},
  358. cipherInfo: { keyLen: 1, ivLen: 12 },
  359. cipherKey: Buffer.alloc(1),
  360. cipherIV: null
  361. },
  362. inbound: {
  363. onPayload: () => {},
  364. decipherInfo: { keyLen: 1, ivLen: 12 },
  365. decipherKey: Buffer.alloc(1),
  366. cipherIV: null
  367. },
  368. },
  369. { outbound: {
  370. onWrite: () => {},
  371. cipherInfo: { keyLen: 1, ivLen: 12 },
  372. cipherKey: Buffer.alloc(1),
  373. cipherIV: {}
  374. },
  375. inbound: {
  376. onPayload: () => {},
  377. decipherInfo: { keyLen: 1, ivLen: 12 },
  378. decipherKey: Buffer.alloc(1),
  379. cipherIV: {}
  380. },
  381. },
  382. { outbound: {
  383. onWrite: () => {},
  384. cipherInfo: { keyLen: 1, ivLen: 12 },
  385. cipherKey: Buffer.alloc(1),
  386. cipherIV: Buffer.alloc(1)
  387. },
  388. inbound: {
  389. onPayload: () => {},
  390. decipherInfo: { keyLen: 1, ivLen: 12 },
  391. decipherKey: Buffer.alloc(1),
  392. cipherIV: Buffer.alloc(1)
  393. },
  394. },
  395. ],
  396. [/invalid outbound\.cipherIV/i, /invalid inbound\.decipherIV/i]
  397. ],
  398. [
  399. [
  400. { outbound: {
  401. onWrite: () => {},
  402. cipherInfo: { keyLen: 1, ivLen: 0 },
  403. cipherKey: Buffer.alloc(1),
  404. seqno: true
  405. },
  406. inbound: {
  407. onPayload: () => {},
  408. decipherInfo: { keyLen: 1, ivLen: 0 },
  409. decipherKey: Buffer.alloc(1),
  410. seqno: true
  411. },
  412. },
  413. { outbound: {
  414. onWrite: () => {},
  415. cipherInfo: { keyLen: 1, ivLen: 0 },
  416. cipherKey: Buffer.alloc(1),
  417. seqno: -1
  418. },
  419. inbound: {
  420. onPayload: () => {},
  421. decipherInfo: { keyLen: 1, ivLen: 0 },
  422. decipherKey: Buffer.alloc(1),
  423. seqno: -1
  424. },
  425. },
  426. { outbound: {
  427. onWrite: () => {},
  428. cipherInfo: { keyLen: 1, ivLen: 0 },
  429. cipherKey: Buffer.alloc(1),
  430. seqno: 2 ** 32
  431. },
  432. inbound: {
  433. onPayload: () => {},
  434. decipherInfo: { keyLen: 1, ivLen: 0 },
  435. decipherKey: Buffer.alloc(1),
  436. seqno: 2 ** 32
  437. },
  438. },
  439. ],
  440. [/invalid outbound\.seqno/i, /invalid inbound\.seqno/i]
  441. ],
  442. [
  443. [
  444. { outbound: {
  445. onWrite: () => {},
  446. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  447. cipherKey: Buffer.alloc(1),
  448. seqno: 0
  449. },
  450. inbound: {
  451. onPayload: () => {},
  452. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  453. decipherKey: Buffer.alloc(1),
  454. seqno: 0
  455. },
  456. },
  457. { outbound: {
  458. onWrite: () => {},
  459. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  460. cipherKey: Buffer.alloc(1),
  461. seqno: 0,
  462. macInfo: true
  463. },
  464. inbound: {
  465. onPayload: () => {},
  466. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  467. decipherKey: Buffer.alloc(1),
  468. seqno: 0,
  469. macInfo: true
  470. },
  471. },
  472. { outbound: {
  473. onWrite: () => {},
  474. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  475. cipherKey: Buffer.alloc(1),
  476. seqno: 0,
  477. macInfo: null
  478. },
  479. inbound: {
  480. onPayload: () => {},
  481. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  482. decipherKey: Buffer.alloc(1),
  483. seqno: 0,
  484. macInfo: null
  485. },
  486. },
  487. ],
  488. [/invalid outbound\.macInfo/i, /invalid inbound\.macInfo/i]
  489. ],
  490. [
  491. [
  492. { outbound: {
  493. onWrite: () => {},
  494. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  495. cipherKey: Buffer.alloc(1),
  496. seqno: 0,
  497. macInfo: { keyLen: 16 }
  498. },
  499. inbound: {
  500. onPayload: () => {},
  501. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  502. decipherKey: Buffer.alloc(1),
  503. seqno: 0,
  504. macInfo: { keyLen: 16 }
  505. },
  506. },
  507. { outbound: {
  508. onWrite: () => {},
  509. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  510. cipherKey: Buffer.alloc(1),
  511. seqno: 0,
  512. macInfo: { keyLen: 16 },
  513. macKey: true
  514. },
  515. inbound: {
  516. onPayload: () => {},
  517. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  518. decipherKey: Buffer.alloc(1),
  519. seqno: 0,
  520. macInfo: { keyLen: 16 },
  521. macKey: true
  522. },
  523. },
  524. { outbound: {
  525. onWrite: () => {},
  526. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  527. cipherKey: Buffer.alloc(1),
  528. seqno: 0,
  529. macInfo: { keyLen: 16 },
  530. macKey: null
  531. },
  532. inbound: {
  533. onPayload: () => {},
  534. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  535. decipherKey: Buffer.alloc(1),
  536. seqno: 0,
  537. macInfo: { keyLen: 16 },
  538. macKey: null
  539. },
  540. },
  541. { outbound: {
  542. onWrite: () => {},
  543. cipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  544. cipherKey: Buffer.alloc(1),
  545. seqno: 0,
  546. macInfo: { keyLen: 16 },
  547. macKey: Buffer.alloc(1)
  548. },
  549. inbound: {
  550. onPayload: () => {},
  551. decipherInfo: { keyLen: 1, ivLen: 0, sslName: 'foo' },
  552. decipherKey: Buffer.alloc(1),
  553. seqno: 0,
  554. macInfo: { keyLen: 16 },
  555. macKey: Buffer.alloc(1)
  556. },
  557. },
  558. ],
  559. [/invalid outbound\.macKey/i, /invalid inbound\.macKey/i]
  560. ],
  561. ].forEach((testCase) => {
  562. let errorChecks = testCase[1];
  563. if (!Array.isArray(errorChecks))
  564. errorChecks = [errorChecks[0], errorChecks[0]];
  565. for (const input of testCase[0]) {
  566. assert.throws(() => createCipher(input), errorChecks[0]);
  567. assert.throws(() => createDecipher(input), errorChecks[1]);
  568. }
  569. });
  570. }
  571. })();