UNPKG

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