test-misc-client-server.js 36 KB

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