UNPKG

36.5 kBJavaScriptView Raw
1'use strict';
2
3const assert = require('assert');
4const http = require('http');
5const https = require('https');
6const { createHash } = require('crypto');
7const net = require('net');
8const { inspect } = require('util');
9
10const Client = require('../lib/client.js');
11const {
12 SSHTTPAgent: HTTPAgent,
13 SSHTTPSAgent: HTTPSAgent,
14} = require('../lib/http-agents.js');
15const Server = require('../lib/server.js');
16const { KexInit } = require('../lib/protocol/kex.js');
17
18const {
19 fixture,
20 mustCall,
21 mustCallAtLeast,
22 mustNotCall,
23 setup: setup_,
24 setupSimple,
25} = require('./common.js');
26
27const KEY_RSA_BAD = fixture('bad_rsa_private_key');
28const HOST_RSA_MD5 = '64254520742d3d0792e918f3ce945a64';
29const clientCfg = { username: 'foo', password: 'bar' };
30const serverCfg = { hostKeys: [ fixture('ssh_host_rsa_key') ] };
31
32const debug = false;
33
34const setup = setupSimple.bind(undefined, debug);
35
36
37{
38 const { server } = setup_(
39 'Verify host fingerprint (sync success, hostHash set)',
40 {
41 client: {
42 ...clientCfg,
43 hostHash: 'md5',
44 hostVerifier: mustCall((hash) => {
45 assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
46 return true;
47 }),
48 },
49 server: serverCfg,
50 },
51 );
52
53 server.on('connection', mustCall((conn) => {
54 conn.on('authentication', mustCall((ctx) => {
55 ctx.accept();
56 })).on('ready', mustCall(() => {
57 conn.end();
58 }));
59 }));
60}
61
62{
63 const { server } = setup_(
64 'Verify host fingerprint (sync success, hostHash not set)',
65 {
66 client: {
67 ...clientCfg,
68 hostVerifier: mustCall((key) => {
69 assert(Buffer.isBuffer(key), 'Expected buffer');
70 let hash = createHash('md5');
71 hash.update(key);
72 hash = hash.digest('hex');
73 assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
74 return true;
75 }),
76 },
77 server: serverCfg,
78 }
79 );
80
81 server.on('connection', mustCall((conn) => {
82 conn.on('authentication', mustCall((ctx) => {
83 ctx.accept();
84 })).on('ready', mustCall(() => {
85 conn.end();
86 }));
87 }));
88}
89
90{
91 const { server } = setup_(
92 'Verify host fingerprint (async success)',
93 {
94 client: {
95 ...clientCfg,
96 hostVerifier: mustCall((key, cb) => {
97 assert(Buffer.isBuffer(key), 'Expected buffer');
98 let hash = createHash('md5');
99 hash.update(key);
100 hash = hash.digest('hex');
101 assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
102 process.nextTick(cb, true);
103 }),
104 },
105 server: serverCfg,
106 }
107 );
108
109 server.on('connection', mustCall((conn) => {
110 conn.on('authentication', mustCall((ctx) => {
111 ctx.accept();
112 })).on('ready', mustCall(() => {
113 conn.end();
114 }));
115 }));
116}
117
118{
119 const { client, server } = setup_(
120 'Verify host fingerprint (sync failure)',
121 {
122 client: {
123 ...clientCfg,
124 hostVerifier: mustCall((key) => {
125 return false;
126 }),
127 },
128 server: serverCfg,
129
130 noForceClientReady: true,
131 noForceServerReady: true,
132 },
133 );
134
135 client.removeAllListeners('error');
136 client.on('ready', mustNotCall())
137 .on('error', mustCall((err) => {
138 assert(/verification failed/.test(err.message),
139 'Wrong client error message');
140 }));
141
142 server.on('connection', mustCall((conn) => {
143 conn.removeAllListeners('error');
144
145 conn.on('authentication', mustNotCall())
146 .on('ready', mustNotCall())
147 .on('error', mustCall((err) => {
148 assert(/KEY_EXCHANGE_FAILED/.test(err.message),
149 'Wrong server error message');
150 }));
151 }));
152}
153
154{
155 // connect() on connected client
156
157 const clientCfg_ = { ...clientCfg };
158 const client = new Client();
159 const server = new Server(serverCfg);
160
161 server.listen(0, 'localhost', mustCall(() => {
162 clientCfg_.host = 'localhost';
163 clientCfg_.port = server.address().port;
164 client.connect(clientCfg_);
165 }));
166
167 let connections = 0;
168 server.on('connection', mustCall((conn) => {
169 if (++connections === 2)
170 server.close();
171 conn.on('authentication', mustCall((ctx) => {
172 ctx.accept();
173 })).on('ready', mustCall(() => {}));
174 }, 2)).on('close', mustCall(() => {}));
175
176 let reconnect = false;
177 client.on('ready', mustCall(() => {
178 if (reconnect) {
179 client.end();
180 } else {
181 reconnect = true;
182 client.connect(clientCfg_);
183 }
184 }, 2)).on('close', mustCall(() => {}, 2));
185}
186
187{
188 // Throw when not connected
189
190 const client = new Client({
191 username: 'foo',
192 password: 'bar',
193 });
194
195 assert.throws(mustCall(() => {
196 client.exec('uptime', mustNotCall());
197 }));
198}
199
200{
201 const { client, server } = setup(
202 'Outstanding callbacks called on disconnect'
203 );
204
205 server.on('connection', mustCall((conn) => {
206 conn.on('session', mustCall(() => {}, 3));
207 }));
208
209 client.on('ready', mustCall(() => {
210 function callback(err, stream) {
211 assert(err, 'Expected error');
212 assert(err.message === 'No response from server',
213 `Wrong error message: ${err.message}`);
214 }
215 client.exec('uptime', mustCall(callback));
216 client.shell(mustCall(callback));
217 client.sftp(mustCall(callback));
218 client.end();
219 }));
220}
221
222{
223 const { client, server } = setup('Pipelined requests');
224
225 server.on('connection', mustCall((conn) => {
226 conn.on('ready', mustCall(() => {
227 conn.on('session', mustCall((accept, reject) => {
228 const session = accept();
229 session.on('exec', mustCall((accept, reject, info) => {
230 const stream = accept();
231 stream.exit(0);
232 stream.end();
233 }));
234 }, 3));
235 }));
236 }));
237
238 client.on('ready', mustCall(() => {
239 let calledBack = 0;
240 function callback(err, stream) {
241 assert(!err, `Unexpected error: ${err}`);
242 stream.resume();
243 if (++calledBack === 3)
244 client.end();
245 }
246 client.exec('foo', mustCall(callback));
247 client.exec('bar', mustCall(callback));
248 client.exec('baz', mustCall(callback));
249 }));
250}
251
252{
253 const { client, server } = setup(
254 'Pipelined requests with intermediate rekeying'
255 );
256
257 server.on('connection', mustCall((conn) => {
258 conn.on('ready', mustCall(() => {
259 const reqs = [];
260 conn.on('session', mustCall((accept, reject) => {
261 if (reqs.length === 0) {
262 conn.rekey(mustCall((err) => {
263 assert(!err, `Unexpected rekey error: ${err}`);
264 reqs.forEach((accept) => {
265 const session = accept();
266 session.on('exec', mustCall((accept, reject, info) => {
267 const stream = accept();
268 stream.exit(0);
269 stream.end();
270 }));
271 });
272 }));
273 }
274 reqs.push(accept);
275 }, 3));
276 }));
277 }));
278
279 client.on('ready', mustCall(() => {
280 let calledBack = 0;
281 function callback(err, stream) {
282 assert(!err, `Unexpected error: ${err}`);
283 stream.resume();
284 if (++calledBack === 3)
285 client.end();
286 }
287 client.exec('foo', mustCall(callback));
288 client.exec('bar', mustCall(callback));
289 client.exec('baz', mustCall(callback));
290 }));
291}
292
293{
294 const { client, server } = setup('Ignore outgoing after stream close');
295
296 server.on('connection', mustCall((conn) => {
297 conn.on('ready', mustCall(() => {
298 conn.on('session', mustCall((accept, reject) => {
299 const session = accept();
300 session.on('exec', mustCall((accept, reject, info) => {
301 const stream = accept();
302 stream.exit(0);
303 stream.end();
304 }));
305 }));
306 }));
307 }));
308
309 client.on('ready', mustCall(() => {
310 client.exec('foo', mustCall((err, stream) => {
311 assert(!err, `Unexpected error: ${err}`);
312 stream.on('exit', mustCall((code, signal) => {
313 client.end();
314 }));
315 }));
316 }));
317}
318
319{
320 const { client, server } = setup_(
321 'Double pipe on unconnected, passed in net.Socket',
322 {
323 client: {
324 ...clientCfg,
325 sock: new net.Socket(),
326 },
327 server: serverCfg,
328 },
329 );
330
331 server.on('connection', mustCall((conn) => {
332 conn.on('authentication', mustCall((ctx) => {
333 ctx.accept();
334 })).on('ready', mustCall(() => {}));
335 }));
336 client.on('ready', mustCall(() => {
337 client.end();
338 }));
339}
340
341{
342 const { client, server } = setup(
343 'Client auto-rejects inbound connections to unknown bound address'
344 );
345
346 const assignedPort = 31337;
347
348 server.on('connection', mustCall((conn) => {
349 conn.on('ready', mustCall(() => {
350 conn.on('request', mustCall((accept, reject, name, info) => {
351 assert(name === 'tcpip-forward', 'Wrong request name');
352 assert.deepStrictEqual(
353 info,
354 { bindAddr: 'good', bindPort: 0 },
355 'Wrong request info'
356 );
357 accept(assignedPort);
358 conn.forwardOut(info.bindAddr,
359 assignedPort,
360 'remote',
361 12345,
362 mustCall((err, ch) => {
363 assert(!err, `Unexpected error: ${err}`);
364 conn.forwardOut('bad',
365 assignedPort,
366 'remote',
367 12345,
368 mustCall((err, ch) => {
369 assert(err, 'Should receive error');
370 client.end();
371 }));
372 }));
373 }));
374 }));
375 }));
376
377 client.on('ready', mustCall(() => {
378 // request forwarding
379 client.forwardIn('good', 0, mustCall((err, port) => {
380 assert(!err, `Unexpected error: ${err}`);
381 assert(port === assignedPort, 'Wrong assigned port');
382 }));
383 })).on('tcp connection', mustCall((details, accept, reject) => {
384 assert.deepStrictEqual(
385 details,
386 { destIP: 'good',
387 destPort: assignedPort,
388 srcIP: 'remote',
389 srcPort: 12345
390 },
391 'Wrong connection details'
392 );
393 accept();
394 }));
395}
396
397{
398 const { client, server } = setup(
399 'Client auto-rejects inbound connections to unknown bound port'
400 );
401
402 const assignedPort = 31337;
403
404 server.on('connection', mustCall((conn) => {
405 conn.on('ready', mustCall(() => {
406 conn.on('request', mustCall((accept, reject, name, info) => {
407 assert(name === 'tcpip-forward', 'Wrong request name');
408 assert.deepStrictEqual(
409 info,
410 { bindAddr: 'good', bindPort: 0 },
411 'Wrong request info'
412 );
413 accept(assignedPort);
414 conn.forwardOut(info.bindAddr,
415 assignedPort,
416 'remote',
417 12345,
418 mustCall((err, ch) => {
419 assert(!err, `Unexpected error: ${err}`);
420 conn.forwardOut(info.bindAddr,
421 99999,
422 'remote',
423 12345,
424 mustCall((err, ch) => {
425 assert(err, 'Should receive error');
426 client.end();
427 }));
428 }));
429 }));
430 }));
431 }));
432
433 client.on('ready', mustCall(() => {
434 // request forwarding
435 client.forwardIn('good', 0, mustCall((err, port) => {
436 assert(!err, `Unexpected error: ${err}`);
437 assert(port === assignedPort, 'Wrong assigned port');
438 }));
439 })).on('tcp connection', mustCall((details, accept, reject) => {
440 assert.deepStrictEqual(
441 details,
442 { destIP: 'good',
443 destPort: assignedPort,
444 srcIP: 'remote',
445 srcPort: 12345
446 },
447 'Wrong connection details'
448 );
449 accept();
450 }));
451}
452
453{
454 const GREETING = 'Hello world!';
455
456 const { client, server } = setup_(
457 'Server greeting',
458 {
459 client: {
460 ...clientCfg,
461 ident: 'node.js rules',
462 },
463 server: {
464 ...serverCfg,
465 greeting: GREETING,
466 }
467 },
468 );
469
470 let sawGreeting = false;
471
472 server.on('connection', mustCall((conn, info) => {
473 assert.deepStrictEqual(info.header, {
474 identRaw: 'SSH-2.0-node.js rules',
475 greeting: '',
476 versions: {
477 protocol: '2.0',
478 software: 'node.js'
479 },
480 comments: 'rules'
481 });
482 conn.on('handshake', mustCall((details) => {
483 assert(sawGreeting, 'Client did not see greeting before handshake');
484 })).on('authentication', mustCall((ctx) => {
485 ctx.accept();
486 })).on('ready', mustCall(() => {
487 conn.end();
488 }));
489 }));
490
491 client.on('greeting', mustCall((greeting) => {
492 assert.strictEqual(greeting, `${GREETING}\r\n`);
493 sawGreeting = true;
494 })).on('banner', mustNotCall());
495}
496
497{
498 const { client, server } = setup_(
499 'Correct ident parsing',
500 {
501 client: {
502 ...clientCfg,
503 ident: 'node.js rules\n',
504 },
505 server: serverCfg,
506
507 noServerError: true,
508 noClientError: true,
509 noForceServerReady: true,
510 noForceClientReady: true,
511 },
512 );
513
514 server.on('connection', mustCall((conn, info) => {
515 assert.deepStrictEqual(info.header, {
516 identRaw: 'SSH-2.0-node.js rules',
517 greeting: '',
518 versions: {
519 protocol: '2.0',
520 software: 'node.js'
521 },
522 comments: 'rules'
523 });
524 conn.once('error', mustCall((err) => {
525 assert(/bad packet length/i.test(err.message), 'Wrong error message');
526 }));
527 conn.on('handshake', mustNotCall())
528 .on('authentication', mustNotCall())
529 .on('ready', mustNotCall());
530 }));
531
532 client.on('greeting', mustNotCall())
533 .on('banner', mustNotCall())
534 .on('ready', mustNotCall());
535}
536
537{
538 const BANNER = 'Hello world!';
539
540 const { client, server } = setup_(
541 'Server banner',
542 {
543 client: clientCfg,
544 server: {
545 ...serverCfg,
546 banner: BANNER,
547 }
548 },
549 );
550
551 let sawBanner = false;
552
553 server.on('connection', mustCall((conn) => {
554 conn.on('handshake', mustCall((details) => {
555 assert(!sawBanner, 'Client saw banner too early');
556 })).on('authentication', mustCall((ctx) => {
557 assert(sawBanner, 'Client did not see banner before auth');
558 ctx.accept();
559 })).on('ready', mustCall(() => {
560 conn.end();
561 }));
562 }));
563
564 client.on('greeting', mustNotCall())
565 .on('banner', mustCall((message) => {
566 assert.strictEqual(message, 'Hello world!\r\n');
567 sawBanner = true;
568 }));
569}
570
571{
572 const { client, server } = setup(
573 'Server responds to global requests in the right order'
574 );
575
576 function sendAcceptLater(accept) {
577 if (fastRejectSent)
578 accept();
579 else
580 setImmediate(sendAcceptLater, accept);
581 }
582
583 let fastRejectSent = false;
584
585 server.on('connection', mustCall((conn) => {
586 conn.on('ready', mustCall(() => {
587 conn.on('request', mustCall((accept, reject, name, info) => {
588 if (info.bindAddr === 'fastReject') {
589 // Will call reject on 'fastReject' soon ...
590 reject();
591 fastRejectSent = true;
592 } else {
593 // ... but accept on 'slowAccept' later
594 sendAcceptLater(accept);
595 }
596 }, 2));
597 }));
598 }));
599
600 client.on('ready', mustCall(() => {
601 let replyCnt = 0;
602
603 client.forwardIn('slowAccept', 0, mustCall((err) => {
604 assert(!err, `Unexpected error: ${err}`);
605 if (++replyCnt === 2)
606 client.end();
607 }));
608
609 client.forwardIn('fastReject', 0, mustCall((err) => {
610 assert(err, 'Expected error');
611 if (++replyCnt === 2)
612 client.end();
613 }));
614 }));
615}
616
617{
618 const { client, server } = setup(
619 'Cleanup outstanding channel requests on channel close'
620 );
621
622 server.on('connection', mustCall((conn) => {
623 conn.on('ready', mustCall(() => {
624 conn.on('session', mustCall((accept, reject) => {
625 const session = accept();
626 session.on('subsystem', mustCall((accept, reject, info) => {
627 assert(info.name === 'netconf', `Wrong subsystem name: ${info.name}`);
628
629 // XXX: hack to prevent success reply from being sent
630 conn._protocol.channelSuccess = () => {};
631
632 accept().close();
633 }));
634 }));
635 }));
636 }));
637
638 client.on('ready', mustCall(() => {
639 client.subsys('netconf', mustCall((err, stream) => {
640 assert(err, 'Expected error');
641 client.end();
642 }));
643 }));
644}
645
646{
647 const { client, server } = setup_(
648 'Handshake errors are emitted',
649 {
650 client: {
651 ...clientCfg,
652 algorithms: { cipher: [ 'aes128-cbc' ] },
653 },
654 server: {
655 ...serverCfg,
656 algorithms: { cipher: [ 'aes128-ctr' ] },
657 },
658
659 noForceClientReady: true,
660 noForceServerReady: true,
661 },
662 );
663
664 client.removeAllListeners('error');
665
666 function onError(err) {
667 assert.strictEqual(err.level, 'handshake');
668 assert(/handshake failed/i.test(err.message), 'Wrong error message');
669 }
670
671 server.on('connection', mustCall((conn) => {
672 conn.removeAllListeners('error');
673
674 conn.on('authentication', mustNotCall())
675 .on('ready', mustNotCall())
676 .on('handshake', mustNotCall())
677 .on('error', mustCall(onError))
678 .on('close', mustCall(() => {}));
679 }));
680
681 client.on('ready', mustNotCall())
682 .on('error', mustCall(onError))
683 .on('close', mustCall(() => {}));
684}
685
686{
687 const { client, server } = setup_(
688 'Client signing errors are caught and emitted',
689 {
690 client: {
691 username: 'foo',
692 privateKey: KEY_RSA_BAD,
693 },
694 server: serverCfg,
695
696 noForceClientReady: true,
697 noForceServerReady: true,
698 },
699 );
700
701 client.removeAllListeners('error');
702
703 server.on('connection', mustCall((conn) => {
704 let authAttempt = 0;
705 conn.on('authentication', mustCall((ctx) => {
706 assert(!ctx.signature, 'Unexpected signature');
707 switch (++authAttempt) {
708 case 1:
709 assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
710 return ctx.reject();
711 case 2:
712 assert(ctx.method === 'publickey',
713 `Wrong auth method: ${ctx.method}`);
714 ctx.accept();
715 break;
716 }
717 }, 2)).on('ready', mustNotCall()).on('close', mustCall(() => {}));
718 }));
719
720 let cliError;
721 client.on('ready', mustNotCall()).on('error', mustCall((err) => {
722 if (cliError) {
723 assert(/all configured/i.test(err.message), 'Wrong error message');
724 } else {
725 cliError = err;
726 assert(/signing/i.test(err.message), 'Wrong error message');
727 }
728 }, 2)).on('close', mustCall(() => {}));
729}
730
731{
732 const { client, server } = setup_(
733 'Server signing errors are caught and emitted',
734 {
735 client: clientCfg,
736 server: { hostKeys: [KEY_RSA_BAD] },
737
738 noForceClientReady: true,
739 noForceServerReady: true,
740 },
741 );
742
743 client.removeAllListeners('error');
744
745 server.on('connection', mustCall((conn) => {
746 conn.removeAllListeners('error');
747
748 conn.on('error', mustCall((err) => {
749 assert(/signature generation failed/i.test(err.message),
750 'Wrong error message');
751 })).on('authentication', mustNotCall())
752 .on('ready', mustNotCall())
753 .on('close', mustCall(() => {}));
754 }));
755
756 client.on('ready', mustNotCall()).on('error', mustCall((err) => {
757 assert(/KEY_EXCHANGE_FAILED/.test(err.message), 'Wrong error message');
758 })).on('close', mustCall(() => {}));
759}
760
761{
762 const { client, server } = setup_(
763 'Rekeying with AES-GCM',
764 {
765 client: {
766 ...clientCfg,
767 algorithms: { cipher: [ 'aes128-gcm@openssh.com' ] },
768 },
769 server: {
770 ...serverCfg,
771 algorithms: { cipher: [ 'aes128-gcm@openssh.com' ] },
772 },
773 },
774 );
775
776 server.on('connection', mustCall((conn) => {
777 conn.on('authentication', mustCall((ctx) => {
778 ctx.accept();
779 })).on('ready', mustCall(() => {
780 const reqs = [];
781 conn.on('session', mustCall((accept, reject) => {
782 if (reqs.length === 0) {
783 conn.rekey(mustCall((err) => {
784 assert(!err, `Unexpected rekey error: ${err}`);
785 reqs.forEach((accept) => {
786 const session = accept();
787 session.on('exec', mustCall((accept, reject, info) => {
788 const stream = accept();
789 stream.exit(0);
790 stream.end();
791 }));
792 });
793 }));
794 }
795 reqs.push(accept);
796 }, 3));
797 }));
798 }));
799
800 client.on('ready', mustCall(() => {
801 let calledBack = 0;
802 function callback(err, stream) {
803 assert(!err, `Unexpected error: ${err}`);
804 stream.resume();
805 if (++calledBack === 3)
806 client.end();
807 }
808 client.exec('foo', mustCall(callback));
809 client.exec('bar', mustCall(callback));
810 client.exec('baz', mustCall(callback));
811 }));
812}
813
814{
815 const { client, server } = setup_(
816 'Switch from no compression to compression',
817 {
818 client: {
819 ...clientCfg,
820 algorithms: { compress: [ 'none' ] },
821 },
822 server: {
823 ...serverCfg,
824 algorithms: { compress: [ 'none', 'zlib@openssh.com' ] },
825 },
826 },
827 );
828
829 server.on('connection', mustCall((conn) => {
830 conn.on('authentication', mustCall((ctx) => {
831 ctx.accept();
832 })).on('ready', mustCall(() => {
833 const reqs = [];
834 conn.on('session', mustCall((accept, reject) => {
835 if (reqs.length === 0) {
836 // XXX: hack to change algorithms after initial handshake
837 client._protocol._offer = new KexInit({
838 kex: [ 'ecdh-sha2-nistp256' ],
839 serverHostKey: [ 'rsa-sha2-256' ],
840 cs: {
841 cipher: [ 'aes128-gcm@openssh.com' ],
842 mac: [],
843 compress: [ 'zlib@openssh.com' ],
844 lang: [],
845 },
846 sc: {
847 cipher: [ 'aes128-gcm@openssh.com' ],
848 mac: [],
849 compress: [ 'zlib@openssh.com' ],
850 lang: [],
851 },
852 });
853
854 conn.rekey(mustCall((err) => {
855 assert(!err, `Unexpected rekey error: ${err}`);
856 reqs.forEach((accept) => {
857 const session = accept();
858 session.on('exec', mustCall((accept, reject, info) => {
859 const stream = accept();
860 stream.exit(0);
861 stream.end();
862 }));
863 });
864 }));
865 }
866 reqs.push(accept);
867 }, 3));
868 }));
869 }));
870
871 let handshakes = 0;
872 client.on('handshake', mustCall((info) => {
873 switch (++handshakes) {
874 case 1:
875 assert(info.cs.compress === 'none', 'wrong compress value');
876 assert(info.sc.compress === 'none', 'wrong compress value');
877 break;
878 case 2:
879 assert(info.cs.compress === 'zlib@openssh.com',
880 'wrong compress value');
881 assert(info.sc.compress === 'zlib@openssh.com',
882 'wrong compress value');
883 break;
884 }
885 }, 2)).on('ready', mustCall(() => {
886 let calledBack = 0;
887 function callback(err, stream) {
888 assert(!err, `Unexpected error: ${err}`);
889 stream.resume();
890 if (++calledBack === 3)
891 client.end();
892 }
893 client.exec('foo', mustCall(callback));
894 client.exec('bar', mustCall(callback));
895 client.exec('baz', mustCall(callback));
896 }));
897}
898
899{
900 const { client, server } = setup_(
901 'Switch from compression to no compression',
902 {
903 client: {
904 ...clientCfg,
905 algorithms: { compress: [ 'zlib' ] },
906 },
907 server: {
908 ...serverCfg,
909 algorithms: { compress: [ 'zlib', 'none' ] },
910 }
911 },
912 );
913
914 server.on('connection', mustCall((conn) => {
915 conn.on('authentication', mustCall((ctx) => {
916 ctx.accept();
917 })).on('ready', mustCall(() => {
918 const reqs = [];
919 conn.on('session', mustCall((accept, reject) => {
920 if (reqs.length === 0) {
921 // XXX: hack to change algorithms after initial handshake
922 client._protocol._offer = new KexInit({
923 kex: [ 'ecdh-sha2-nistp256' ],
924 serverHostKey: [ 'rsa-sha2-256' ],
925 cs: {
926 cipher: [ 'aes128-gcm@openssh.com' ],
927 mac: [],
928 compress: [ 'none' ],
929 lang: [],
930 },
931 sc: {
932 cipher: [ 'aes128-gcm@openssh.com' ],
933 mac: [],
934 compress: [ 'none' ],
935 lang: [],
936 },
937 });
938
939 conn.rekey(mustCall((err) => {
940 assert(!err, `Unexpected rekey error: ${err}`);
941 reqs.forEach((accept) => {
942 const session = accept();
943 session.on('exec', mustCall((accept, reject, info) => {
944 const stream = accept();
945 stream.exit(0);
946 stream.end();
947 }));
948 });
949 }));
950 }
951 reqs.push(accept);
952 }, 3));
953 }));
954 }));
955
956 let handshakes = 0;
957 client.on('handshake', mustCall((info) => {
958 switch (++handshakes) {
959 case 1:
960 assert(info.cs.compress === 'zlib', 'wrong compress value');
961 assert(info.sc.compress === 'zlib', 'wrong compress value');
962 break;
963 case 2:
964 assert(info.cs.compress === 'none', 'wrong compress value');
965 assert(info.sc.compress === 'none', 'wrong compress value');
966 break;
967 }
968 }, 2)).on('ready', mustCall(() => {
969 let calledBack = 0;
970 function callback(err, stream) {
971 assert(!err, `Unexpected error: ${err}`);
972 stream.resume();
973 if (++calledBack === 3)
974 client.end();
975 }
976 client.exec('foo', mustCall(callback));
977 client.exec('bar', mustCall(callback));
978 client.exec('baz', mustCall(callback));
979 }));
980}
981
982{
983 const { client, server } = setup_(
984 'Large data compression',
985 {
986 client: {
987 ...clientCfg,
988 algorithms: { compress: [ 'zlib' ] },
989 },
990 server: {
991 ...serverCfg,
992 algorithms: { compress: [ 'zlib' ] },
993 }
994 },
995 );
996
997 const chunk = Buffer.alloc(1024 * 1024, 'a');
998 const chunkCount = 10;
999
1000 server.on('connection', mustCall((conn) => {
1001 conn.on('authentication', mustCall((ctx) => {
1002 ctx.accept();
1003 })).on('ready', mustCall(() => {
1004 conn.on('session', mustCall((accept, reject) => {
1005 accept().on('exec', mustCall((accept, reject, info) => {
1006 const stream = accept();
1007 for (let i = 0; i < chunkCount; ++i)
1008 stream.write(chunk);
1009 stream.exit(0);
1010 stream.end();
1011 }));
1012 }));
1013 }));
1014 }));
1015
1016 client.on('ready', mustCall(() => {
1017 client.exec('foo', mustCall((err, stream) => {
1018 assert(!err, `Unexpected exec error: ${err}`);
1019 let nb = 0;
1020 stream.on('data', mustCallAtLeast((data) => {
1021 nb += data.length;
1022 })).on('end', mustCall(() => {
1023 assert(nb === (chunkCount * chunk.length),
1024 `Wrong stream byte count: ${nb}`);
1025 client.end();
1026 }));
1027 }));
1028 }));
1029}
1030
1031{
1032 const { client, server } = setup_(
1033 'Debug output',
1034 {
1035 client: {
1036 ...clientCfg,
1037 debug: mustCallAtLeast((msg) => {
1038 assert(typeof msg === 'string',
1039 `Wrong debug argument type: ${typeof msg}`);
1040 assert(msg.length > 0, 'Unexpected empty debug message');
1041 }),
1042 },
1043 server: {
1044 ...serverCfg,
1045 debug: mustCallAtLeast((msg) => {
1046 assert(typeof msg === 'string',
1047 `Wrong debug argument type: ${typeof msg}`);
1048 assert(msg.length > 0, 'Unexpected empty debug message');
1049 }),
1050 },
1051 },
1052 );
1053
1054 server.on('connection', mustCall((conn) => {
1055 conn.on('authentication', mustCall((ctx) => {
1056 ctx.accept();
1057 })).on('ready', mustCall(() => {
1058 conn.on('session', mustCall((accept, reject) => {
1059 accept().on('exec', mustCall((accept, reject, info) => {
1060 assert(info.command === 'foo --bar',
1061 `Wrong exec command: ${info.command}`);
1062 const stream = accept();
1063 stream.exit(100);
1064 stream.end();
1065 conn.end();
1066 }));
1067 }));
1068 }));
1069 }));
1070
1071 client.on('ready', mustCall(() => {
1072 client.exec('foo --bar', mustCall((err, stream) => {
1073 assert(!err, `Unexpected exec error: ${err}`);
1074 stream.resume();
1075 }));
1076 }));
1077}
1078
1079{
1080 const { server } = setup_(
1081 'HTTP agent',
1082 {
1083 // No automatic client, the agent will create one
1084
1085 server: serverCfg,
1086
1087 debug,
1088 },
1089 );
1090
1091 let httpServer;
1092 server.on('listening', () => {
1093 httpServer = http.createServer((req, res) => {
1094 httpServer.close();
1095 res.end('hello world!');
1096 });
1097 httpServer.listen(0, 'localhost', () => {
1098 const agent = new HTTPAgent({
1099 host: 'localhost',
1100 port: server.address().port,
1101 username: 'foo',
1102 password: 'bar',
1103 });
1104 http.get({
1105 host: 'localhost',
1106 port: httpServer.address().port,
1107 agent,
1108 headers: { Connection: 'close' },
1109 }, (res) => {
1110 assert(res.statusCode === 200,
1111 `Wrong http status code: ${res.statusCode}`);
1112 let buf = '';
1113 res.on('data', mustCallAtLeast((chunk) => {
1114 buf += chunk;
1115 })).on('end', mustCall(() => {
1116 assert(buf === 'hello world!',
1117 `Wrong http response body: ${inspect(buf)}`);
1118 }));
1119 });
1120 });
1121 });
1122
1123 server.on('connection', mustCall((conn) => {
1124 conn.on('authentication', mustCall((ctx) => {
1125 ctx.accept();
1126 })).on('ready', mustCall(() => {
1127 conn.on('tcpip', mustCall((accept, reject, info) => {
1128 assert(info.destIP === 'localhost', `Wrong destIP: ${info.destIP}`);
1129 assert(info.destPort === httpServer.address().port,
1130 `Wrong destPort: ${info.destPort}`);
1131 assert(info.srcIP === 'localhost', `Wrong srcIP: ${info.srcIP}`);
1132
1133 const stream = accept();
1134 const tcp = new net.Socket();
1135 tcp.pipe(stream).pipe(tcp);
1136 tcp.connect(httpServer.address().port, 'localhost');
1137 }));
1138 }));
1139 }));
1140}
1141
1142{
1143 const { server } = setup_(
1144 'HTTPS agent',
1145 {
1146 // No automatic client, the agent will create one
1147
1148 server: serverCfg,
1149
1150 debug,
1151 },
1152 );
1153
1154 let httpsServer;
1155 server.on('listening', () => {
1156 httpsServer = https.createServer({
1157 key: fixture('https_key.pem'),
1158 cert: fixture('https_cert.pem'),
1159 }, (req, res) => {
1160 httpsServer.close();
1161 res.end('hello world!');
1162 });
1163 httpsServer.listen(0, 'localhost', () => {
1164 const agent = new HTTPSAgent({
1165 host: 'localhost',
1166 port: server.address().port,
1167 username: 'foo',
1168 password: 'bar',
1169 });
1170 https.get({
1171 host: 'localhost',
1172 port: httpsServer.address().port,
1173 agent,
1174 headers: { Connection: 'close' },
1175 ca: fixture('https_cert.pem'),
1176 }, (res) => {
1177 assert(res.statusCode === 200,
1178 `Wrong http status code: ${res.statusCode}`);
1179 let buf = '';
1180 res.on('data', mustCallAtLeast((chunk) => {
1181 buf += chunk;
1182 })).on('end', mustCall(() => {
1183 assert(buf === 'hello world!',
1184 `Wrong http response body: ${inspect(buf)}`);
1185 }));
1186 }).on('error', (err) => {
1187 // This workaround is necessary for some reason on node < v14.x
1188 if (!/write after end/i.test(err.message))
1189 throw err;
1190 });
1191 });
1192 });
1193
1194 server.on('connection', mustCall((conn) => {
1195 conn.on('authentication', mustCall((ctx) => {
1196 ctx.accept();
1197 })).on('ready', mustCall(() => {
1198 conn.on('tcpip', mustCall((accept, reject, info) => {
1199 assert(info.destIP === 'localhost', `Wrong destIP: ${info.destIP}`);
1200 assert(info.destPort === httpsServer.address().port,
1201 `Wrong destPort: ${info.destPort}`);
1202 assert(info.srcIP === 'localhost', `Wrong srcIP: ${info.srcIP}`);
1203
1204 const stream = accept();
1205 const tcp = new net.Socket();
1206 tcp.pipe(stream).pipe(tcp);
1207 tcp.connect(httpsServer.address().port, 'localhost');
1208 }));
1209 }));
1210 }));
1211}
1212
1213[
1214 { desc: 'remove/append/prepend (regexps)',
1215 config: {
1216 remove: /.*/,
1217 append: /gcm/,
1218 prepend: /ctr/,
1219 },
1220 expected: [
1221 'aes128-ctr',
1222 'aes192-ctr',
1223 'aes256-ctr',
1224 'aes128-gcm',
1225 'aes128-gcm@openssh.com',
1226 'aes256-gcm',
1227 'aes256-gcm@openssh.com',
1228 ],
1229 },
1230 { desc: 'remove/append/prepend (strings)',
1231 config: {
1232 remove: /.*/,
1233 append: 'aes256-ctr',
1234 prepend: [ 'aes256-gcm', 'aes128-gcm' ],
1235 },
1236 expected: [
1237 'aes256-gcm',
1238 'aes128-gcm',
1239 'aes256-ctr',
1240 ],
1241 },
1242].forEach((info) => {
1243 const { client, server } = setup_(
1244 `Client algorithms option (${info.desc})`,
1245 {
1246 client: {
1247 ...clientCfg,
1248 algorithms: { cipher: info.config },
1249 },
1250 server: serverCfg,
1251
1252 debug,
1253 },
1254 );
1255
1256 server.on('connection', mustCall((conn) => {
1257 conn.on('authentication', mustCall((ctx) => {
1258 ctx.accept();
1259 })).on('ready', mustCall(() => {
1260 conn.end();
1261 }));
1262 }));
1263 client.on('ready', mustCall(() => {
1264 // XXX: hack to easily verify computed offer
1265 const offer = client._protocol._offer.lists;
1266 assert.deepStrictEqual(
1267 offer.cs.cipher.array,
1268 info.expected,
1269 `Wrong algorithm list: ${offer.cs.cipher.array}`
1270 );
1271 }));
1272});
1273
1274{
1275 const { client } = setup_(
1276 `Safely end() from Client 'error' event handler`,
1277 {
1278 client: clientCfg,
1279 noClientError: true,
1280 noForceClientReady: true,
1281 },
1282 );
1283
1284 const badServer = net.createServer((s) => {});
1285 badServer.listen(0, 'localhost', mustCall(() => {
1286 badServer.unref();
1287
1288 client.on('error', mustCallAtLeast((err) => {
1289 client.end();
1290 })).on('ready', mustNotCall()).on('close', mustCall(() => {}));
1291 client.connect({
1292 host: 'localhost',
1293 port: badServer.address().port,
1294 user: 'foo',
1295 password: 'bar',
1296 readyTimeout: 1,
1297 });
1298 }));
1299}
1300
1301{
1302 const { client } = setup_(
1303 'Client error should be emitted on bad/nonexistent greeting',
1304 {
1305 client: clientCfg,
1306 noClientError: true,
1307 noForceClientReady: true,
1308 },
1309 );
1310
1311 const badServer = net.createServer(mustCall((s) => {
1312 badServer.close();
1313 s.end();
1314 })).listen(0, 'localhost', mustCall(() => {
1315 client.on('error', mustCall((err) => {
1316 client.end();
1317 })).on('ready', mustNotCall()).on('close', mustCall(() => {}));
1318 client.connect({
1319 host: 'localhost',
1320 port: badServer.address().port,
1321 user: 'foo',
1322 password: 'bar',
1323 });
1324 }));
1325}
1326
1327{
1328 const { client } = setup_(
1329 'Only one client error on connection failure',
1330 {
1331 client: clientCfg,
1332 noClientError: true,
1333 noForceClientReady: true,
1334 },
1335 );
1336
1337 client.on('error', mustCall((err) => {
1338 assert.strictEqual(err.syscall, 'getaddrinfo');
1339 }));
1340 client.connect({
1341 host: 'blerbblubblubblerb',
1342 port: 9999,
1343 user: 'foo',
1344 password: 'bar'
1345 });
1346}
1347
1348{
1349 const { client, server } = setup(
1350 'Client should remove reserved channels on incoming channel rejection'
1351 );
1352
1353 const assignedPort = 31337;
1354
1355 server.on('connection', mustCall((conn) => {
1356 conn.on('ready', mustCall(() => {
1357 conn.on('request', mustCall((accept, reject, name, info) => {
1358 assert(name === 'tcpip-forward', 'Wrong request name');
1359 assert.deepStrictEqual(
1360 info,
1361 { bindAddr: 'good', bindPort: 0 },
1362 'Wrong request info'
1363 );
1364 accept(assignedPort);
1365 conn.forwardOut(info.bindAddr,
1366 assignedPort,
1367 'remote',
1368 12345,
1369 mustCall((err, ch) => {
1370 assert(err, 'Should receive error');
1371 client.end();
1372 }));
1373 }));
1374 }));
1375 }));
1376
1377 client.on('ready', mustCall(() => {
1378 // request forwarding
1379 client.forwardIn('good', 0, mustCall((err, port) => {
1380 assert(!err, `Unexpected error: ${err}`);
1381 assert(port === assignedPort, 'Wrong assigned port');
1382 }));
1383 })).on('tcp connection', mustCall((details, accept, reject) => {
1384 assert.deepStrictEqual(
1385 details,
1386 { destIP: 'good',
1387 destPort: assignedPort,
1388 srcIP: 'remote',
1389 srcPort: 12345
1390 },
1391 'Wrong connection details'
1392 );
1393 assert.strictEqual(Object.keys(client._chanMgr._channels).length, 1);
1394 assert.strictEqual(client._chanMgr._count, 1);
1395 reject();
1396 assert.strictEqual(Object.keys(client._chanMgr._channels).length, 0);
1397 assert.strictEqual(client._chanMgr._count, 0);
1398 }));
1399}