123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428 |
- 'use strict';
- const assert = require('assert');
- const { createHash } = require('crypto');
- const http = require('http');
- const https = require('https');
- const net = require('net');
- const { Transform } = require('stream');
- const { inspect } = require('util');
- const Client = require('../lib/client.js');
- const {
- SSHTTPAgent: HTTPAgent,
- SSHTTPSAgent: HTTPSAgent,
- } = require('../lib/http-agents.js');
- const Server = require('../lib/server.js');
- const { KexInit } = require('../lib/protocol/kex.js');
- const {
- fixture,
- mustCall,
- mustCallAtLeast,
- mustNotCall,
- setup: setup_,
- setupSimple,
- } = require('./common.js');
- const KEY_RSA_BAD = fixture('bad_rsa_private_key');
- const HOST_RSA_MD5 = '64254520742d3d0792e918f3ce945a64';
- const clientCfg = { username: 'foo', password: 'bar' };
- const serverCfg = { hostKeys: [ fixture('ssh_host_rsa_key') ] };
- const debug = false;
- const setup = setupSimple.bind(undefined, debug);
- {
- const { server } = setup_(
- 'Verify host fingerprint (sync success, hostHash set)',
- {
- client: {
- ...clientCfg,
- hostHash: 'md5',
- hostVerifier: mustCall((hash) => {
- assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
- return true;
- }),
- },
- server: serverCfg,
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- }
- {
- const { server } = setup_(
- 'Verify host fingerprint (sync success, hostHash not set)',
- {
- client: {
- ...clientCfg,
- hostVerifier: mustCall((key) => {
- assert(Buffer.isBuffer(key), 'Expected buffer');
- let hash = createHash('md5');
- hash.update(key);
- hash = hash.digest('hex');
- assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
- return true;
- }),
- },
- server: serverCfg,
- }
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- }
- {
- const { server } = setup_(
- 'Verify host fingerprint (async success)',
- {
- client: {
- ...clientCfg,
- hostVerifier: mustCall((key, cb) => {
- assert(Buffer.isBuffer(key), 'Expected buffer');
- let hash = createHash('md5');
- hash.update(key);
- hash = hash.digest('hex');
- assert(hash === HOST_RSA_MD5, 'Host fingerprint mismatch');
- process.nextTick(cb, true);
- }),
- },
- server: serverCfg,
- }
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- }
- {
- const { client, server } = setup_(
- 'Verify host fingerprint (sync failure)',
- {
- client: {
- ...clientCfg,
- hostVerifier: mustCall((key) => {
- return false;
- }),
- },
- server: serverCfg,
- noForceClientReady: true,
- noForceServerReady: true,
- },
- );
- client.removeAllListeners('error');
- client.on('ready', mustNotCall())
- .on('error', mustCall((err) => {
- assert(/verification failed/.test(err.message),
- 'Wrong client error message');
- }));
- server.on('connection', mustCall((conn) => {
- conn.removeAllListeners('error');
- conn.on('authentication', mustNotCall())
- .on('ready', mustNotCall())
- .on('error', mustCall((err) => {
- assert(/KEY_EXCHANGE_FAILED/.test(err.message),
- 'Wrong server error message');
- }));
- }));
- }
- {
- // connect() on connected client
- const clientCfg_ = { ...clientCfg };
- const client = new Client();
- const server = new Server(serverCfg);
- server.listen(0, 'localhost', mustCall(() => {
- clientCfg_.host = 'localhost';
- clientCfg_.port = server.address().port;
- client.connect(clientCfg_);
- }));
- let connections = 0;
- server.on('connection', mustCall((conn) => {
- if (++connections === 2)
- server.close();
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {}));
- }, 2)).on('close', mustCall(() => {}));
- let reconnect = false;
- client.on('ready', mustCall(() => {
- if (reconnect) {
- client.end();
- } else {
- reconnect = true;
- client.connect(clientCfg_);
- }
- }, 2)).on('close', mustCall(() => {}, 2));
- }
- {
- // Throw when not connected
- const client = new Client({
- username: 'foo',
- password: 'bar',
- });
- assert.throws(mustCall(() => {
- client.exec('uptime', mustNotCall());
- }));
- }
- {
- const { client, server } = setup(
- 'Outstanding callbacks called on disconnect'
- );
- server.on('connection', mustCall((conn) => {
- conn.on('session', mustCall(() => {}, 3));
- }));
- client.on('ready', mustCall(() => {
- function callback(err, stream) {
- assert(err, 'Expected error');
- assert(err.message === 'No response from server',
- `Wrong error message: ${err.message}`);
- }
- client.exec('uptime', mustCall(callback));
- client.shell(mustCall(callback));
- client.sftp(mustCall(callback));
- client.end();
- }));
- }
- {
- const { client, server } = setup('Pipelined requests');
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('session', mustCall((accept, reject) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- }, 3));
- }));
- }));
- client.on('ready', mustCall(() => {
- let calledBack = 0;
- function callback(err, stream) {
- assert(!err, `Unexpected error: ${err}`);
- stream.resume();
- if (++calledBack === 3)
- client.end();
- }
- client.exec('foo', mustCall(callback));
- client.exec('bar', mustCall(callback));
- client.exec('baz', mustCall(callback));
- }));
- }
- {
- const { client, server } = setup(
- 'Pipelined requests with intermediate rekeying'
- );
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- const reqs = [];
- conn.on('session', mustCall((accept, reject) => {
- if (reqs.length === 0) {
- conn.rekey(mustCall((err) => {
- assert(!err, `Unexpected rekey error: ${err}`);
- reqs.forEach((accept) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- });
- }));
- }
- reqs.push(accept);
- }, 3));
- }));
- }));
- client.on('ready', mustCall(() => {
- let calledBack = 0;
- function callback(err, stream) {
- assert(!err, `Unexpected error: ${err}`);
- stream.resume();
- if (++calledBack === 3)
- client.end();
- }
- client.exec('foo', mustCall(callback));
- client.exec('bar', mustCall(callback));
- client.exec('baz', mustCall(callback));
- }));
- }
- {
- const { client, server } = setup('Ignore outgoing after stream close');
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('session', mustCall((accept, reject) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- client.exec('foo', mustCall((err, stream) => {
- assert(!err, `Unexpected error: ${err}`);
- stream.on('exit', mustCall((code, signal) => {
- client.end();
- }));
- }));
- }));
- }
- {
- const { client, server } = setup_(
- 'Double pipe on unconnected, passed in net.Socket',
- {
- client: {
- ...clientCfg,
- sock: new net.Socket(),
- },
- server: serverCfg,
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {}));
- }));
- client.on('ready', mustCall(() => {
- client.end();
- }));
- }
- {
- const { client, server } = setup(
- 'Client auto-rejects inbound connections to unknown bound address'
- );
- const assignedPort = 31337;
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('request', mustCall((accept, reject, name, info) => {
- assert(name === 'tcpip-forward', 'Wrong request name');
- assert.deepStrictEqual(
- info,
- { bindAddr: 'good', bindPort: 0 },
- 'Wrong request info'
- );
- accept(assignedPort);
- conn.forwardOut(info.bindAddr,
- assignedPort,
- 'remote',
- 12345,
- mustCall((err, ch) => {
- assert(!err, `Unexpected error: ${err}`);
- conn.forwardOut('bad',
- assignedPort,
- 'remote',
- 12345,
- mustCall((err, ch) => {
- assert(err, 'Should receive error');
- client.end();
- }));
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- // request forwarding
- client.forwardIn('good', 0, mustCall((err, port) => {
- assert(!err, `Unexpected error: ${err}`);
- assert(port === assignedPort, 'Wrong assigned port');
- }));
- })).on('tcp connection', mustCall((details, accept, reject) => {
- assert.deepStrictEqual(
- details,
- { destIP: 'good',
- destPort: assignedPort,
- srcIP: 'remote',
- srcPort: 12345
- },
- 'Wrong connection details'
- );
- accept();
- }));
- }
- {
- const { client, server } = setup(
- 'Client auto-rejects inbound connections to unknown bound port'
- );
- const assignedPort = 31337;
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('request', mustCall((accept, reject, name, info) => {
- assert(name === 'tcpip-forward', 'Wrong request name');
- assert.deepStrictEqual(
- info,
- { bindAddr: 'good', bindPort: 0 },
- 'Wrong request info'
- );
- accept(assignedPort);
- conn.forwardOut(info.bindAddr,
- assignedPort,
- 'remote',
- 12345,
- mustCall((err, ch) => {
- assert(!err, `Unexpected error: ${err}`);
- conn.forwardOut(info.bindAddr,
- 99999,
- 'remote',
- 12345,
- mustCall((err, ch) => {
- assert(err, 'Should receive error');
- client.end();
- }));
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- // request forwarding
- client.forwardIn('good', 0, mustCall((err, port) => {
- assert(!err, `Unexpected error: ${err}`);
- assert(port === assignedPort, 'Wrong assigned port');
- }));
- })).on('tcp connection', mustCall((details, accept, reject) => {
- assert.deepStrictEqual(
- details,
- { destIP: 'good',
- destPort: assignedPort,
- srcIP: 'remote',
- srcPort: 12345
- },
- 'Wrong connection details'
- );
- accept();
- }));
- }
- {
- const GREETING = 'Hello world!';
- const { client, server } = setup_(
- 'Server greeting',
- {
- client: {
- ...clientCfg,
- ident: 'node.js rules',
- },
- server: {
- ...serverCfg,
- greeting: GREETING,
- }
- },
- );
- let sawGreeting = false;
- server.on('connection', mustCall((conn, info) => {
- assert.deepStrictEqual(info.header, {
- identRaw: 'SSH-2.0-node.js rules',
- greeting: '',
- versions: {
- protocol: '2.0',
- software: 'node.js'
- },
- comments: 'rules'
- });
- conn.on('handshake', mustCall((details) => {
- assert(sawGreeting, 'Client did not see greeting before handshake');
- })).on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- client.on('greeting', mustCall((greeting) => {
- assert.strictEqual(greeting, `${GREETING}\r\n`);
- sawGreeting = true;
- })).on('banner', mustNotCall());
- }
- {
- const { client, server } = setup_(
- 'Correct ident parsing',
- {
- client: {
- ...clientCfg,
- ident: 'node.js rules\n',
- },
- server: serverCfg,
- noServerError: true,
- noClientError: true,
- noForceServerReady: true,
- noForceClientReady: true,
- },
- );
- server.on('connection', mustCall((conn, info) => {
- assert.deepStrictEqual(info.header, {
- identRaw: 'SSH-2.0-node.js rules',
- greeting: '',
- versions: {
- protocol: '2.0',
- software: 'node.js'
- },
- comments: 'rules'
- });
- conn.once('error', mustCall((err) => {
- assert(/bad packet length/i.test(err.message), 'Wrong error message');
- }));
- conn.on('handshake', mustNotCall())
- .on('authentication', mustNotCall())
- .on('ready', mustNotCall());
- }));
- client.on('greeting', mustNotCall())
- .on('banner', mustNotCall())
- .on('ready', mustNotCall());
- }
- {
- const BANNER = 'Hello world!';
- const { client, server } = setup_(
- 'Server banner',
- {
- client: clientCfg,
- server: {
- ...serverCfg,
- banner: BANNER,
- }
- },
- );
- let sawBanner = false;
- server.on('connection', mustCall((conn) => {
- conn.on('handshake', mustCall((details) => {
- assert(!sawBanner, 'Client saw banner too early');
- })).on('authentication', mustCall((ctx) => {
- assert(sawBanner, 'Client did not see banner before auth');
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- client.on('greeting', mustNotCall())
- .on('banner', mustCall((message) => {
- assert.strictEqual(message, 'Hello world!\r\n');
- sawBanner = true;
- }));
- }
- {
- const { client, server } = setup(
- 'Server responds to global requests in the right order'
- );
- function sendAcceptLater(accept) {
- if (fastRejectSent)
- accept();
- else
- setImmediate(sendAcceptLater, accept);
- }
- let fastRejectSent = false;
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('request', mustCall((accept, reject, name, info) => {
- if (info.bindAddr === 'fastReject') {
- // Will call reject on 'fastReject' soon ...
- reject();
- fastRejectSent = true;
- } else {
- // ... but accept on 'slowAccept' later
- sendAcceptLater(accept);
- }
- }, 2));
- }));
- }));
- client.on('ready', mustCall(() => {
- let replyCnt = 0;
- client.forwardIn('slowAccept', 0, mustCall((err) => {
- assert(!err, `Unexpected error: ${err}`);
- if (++replyCnt === 2)
- client.end();
- }));
- client.forwardIn('fastReject', 0, mustCall((err) => {
- assert(err, 'Expected error');
- if (++replyCnt === 2)
- client.end();
- }));
- }));
- }
- {
- const { client, server } = setup(
- 'Cleanup outstanding channel requests on channel close'
- );
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('session', mustCall((accept, reject) => {
- const session = accept();
- session.on('subsystem', mustCall((accept, reject, info) => {
- assert(info.name === 'netconf', `Wrong subsystem name: ${info.name}`);
- // XXX: hack to prevent success reply from being sent
- conn._protocol.channelSuccess = () => {};
- accept().close();
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- client.subsys('netconf', mustCall((err, stream) => {
- assert(err, 'Expected error');
- client.end();
- }));
- }));
- }
- {
- const { client, server } = setup_(
- 'Handshake errors are emitted',
- {
- client: {
- ...clientCfg,
- algorithms: { cipher: [ 'aes128-cbc' ] },
- },
- server: {
- ...serverCfg,
- algorithms: { cipher: [ 'aes128-ctr' ] },
- },
- noForceClientReady: true,
- noForceServerReady: true,
- },
- );
- client.removeAllListeners('error');
- function onError(err) {
- assert.strictEqual(err.level, 'handshake');
- assert(/handshake failed/i.test(err.message), 'Wrong error message');
- }
- server.on('connection', mustCall((conn) => {
- conn.removeAllListeners('error');
- conn.on('authentication', mustNotCall())
- .on('ready', mustNotCall())
- .on('handshake', mustNotCall())
- .on('error', mustCall(onError))
- .on('close', mustCall(() => {}));
- }));
- client.on('ready', mustNotCall())
- .on('error', mustCall(onError))
- .on('close', mustCall(() => {}));
- }
- {
- const { client, server } = setup_(
- 'Client signing errors are caught and emitted',
- {
- client: {
- username: 'foo',
- privateKey: KEY_RSA_BAD,
- },
- server: serverCfg,
- noForceClientReady: true,
- noForceServerReady: true,
- },
- );
- client.removeAllListeners('error');
- server.on('connection', mustCall((conn) => {
- let authAttempt = 0;
- conn.on('authentication', mustCall((ctx) => {
- assert(!ctx.signature, 'Unexpected signature');
- switch (++authAttempt) {
- case 1:
- assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
- return ctx.reject();
- case 2:
- assert(ctx.method === 'publickey',
- `Wrong auth method: ${ctx.method}`);
- ctx.accept();
- break;
- }
- }, 2)).on('ready', mustNotCall()).on('close', mustCall(() => {}));
- }));
- let cliError;
- client.on('ready', mustNotCall()).on('error', mustCall((err) => {
- if (cliError) {
- assert(/all configured/i.test(err.message), 'Wrong error message');
- } else {
- cliError = err;
- assert(/signing/i.test(err.message), 'Wrong error message');
- }
- }, 2)).on('close', mustCall(() => {}));
- }
- {
- const { client, server } = setup_(
- 'Server signing errors are caught and emitted',
- {
- client: clientCfg,
- server: { hostKeys: [KEY_RSA_BAD] },
- noForceClientReady: true,
- noForceServerReady: true,
- },
- );
- client.removeAllListeners('error');
- server.on('connection', mustCall((conn) => {
- conn.removeAllListeners('error');
- conn.on('error', mustCall((err) => {
- assert(/signature generation failed/i.test(err.message),
- 'Wrong error message');
- })).on('authentication', mustNotCall())
- .on('ready', mustNotCall())
- .on('close', mustCall(() => {}));
- }));
- client.on('ready', mustNotCall()).on('error', mustCall((err) => {
- assert(/KEY_EXCHANGE_FAILED/.test(err.message), 'Wrong error message');
- })).on('close', mustCall(() => {}));
- }
- {
- const { client, server } = setup_(
- 'Rekeying with AES-GCM',
- {
- client: {
- ...clientCfg,
- algorithms: { cipher: [ 'aes128-gcm@openssh.com' ] },
- },
- server: {
- ...serverCfg,
- algorithms: { cipher: [ 'aes128-gcm@openssh.com' ] },
- },
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- const reqs = [];
- conn.on('session', mustCall((accept, reject) => {
- if (reqs.length === 0) {
- conn.rekey(mustCall((err) => {
- assert(!err, `Unexpected rekey error: ${err}`);
- reqs.forEach((accept) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- });
- }));
- }
- reqs.push(accept);
- }, 3));
- }));
- }));
- client.on('ready', mustCall(() => {
- let calledBack = 0;
- function callback(err, stream) {
- assert(!err, `Unexpected error: ${err}`);
- stream.resume();
- if (++calledBack === 3)
- client.end();
- }
- client.exec('foo', mustCall(callback));
- client.exec('bar', mustCall(callback));
- client.exec('baz', mustCall(callback));
- }));
- }
- {
- const { client, server } = setup_(
- 'Switch from no compression to compression',
- {
- client: {
- ...clientCfg,
- algorithms: { compress: [ 'none' ] },
- },
- server: {
- ...serverCfg,
- algorithms: { compress: [ 'none', 'zlib@openssh.com' ] },
- },
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- const reqs = [];
- conn.on('session', mustCall((accept, reject) => {
- if (reqs.length === 0) {
- // XXX: hack to change algorithms after initial handshake
- client._protocol._offer = new KexInit({
- kex: [ 'ecdh-sha2-nistp256' ],
- serverHostKey: [ 'rsa-sha2-256' ],
- cs: {
- cipher: [ 'aes128-gcm@openssh.com' ],
- mac: [],
- compress: [ 'zlib@openssh.com' ],
- lang: [],
- },
- sc: {
- cipher: [ 'aes128-gcm@openssh.com' ],
- mac: [],
- compress: [ 'zlib@openssh.com' ],
- lang: [],
- },
- });
- conn.rekey(mustCall((err) => {
- assert(!err, `Unexpected rekey error: ${err}`);
- reqs.forEach((accept) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- });
- }));
- }
- reqs.push(accept);
- }, 3));
- }));
- }));
- let handshakes = 0;
- client.on('handshake', mustCall((info) => {
- switch (++handshakes) {
- case 1:
- assert(info.cs.compress === 'none', 'wrong compress value');
- assert(info.sc.compress === 'none', 'wrong compress value');
- break;
- case 2:
- assert(info.cs.compress === 'zlib@openssh.com',
- 'wrong compress value');
- assert(info.sc.compress === 'zlib@openssh.com',
- 'wrong compress value');
- break;
- }
- }, 2)).on('ready', mustCall(() => {
- let calledBack = 0;
- function callback(err, stream) {
- assert(!err, `Unexpected error: ${err}`);
- stream.resume();
- if (++calledBack === 3)
- client.end();
- }
- client.exec('foo', mustCall(callback));
- client.exec('bar', mustCall(callback));
- client.exec('baz', mustCall(callback));
- }));
- }
- {
- const { client, server } = setup_(
- 'Switch from compression to no compression',
- {
- client: {
- ...clientCfg,
- algorithms: { compress: [ 'zlib' ] },
- },
- server: {
- ...serverCfg,
- algorithms: { compress: [ 'zlib', 'none' ] },
- }
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- const reqs = [];
- conn.on('session', mustCall((accept, reject) => {
- if (reqs.length === 0) {
- // XXX: hack to change algorithms after initial handshake
- client._protocol._offer = new KexInit({
- kex: [ 'ecdh-sha2-nistp256' ],
- serverHostKey: [ 'rsa-sha2-256' ],
- cs: {
- cipher: [ 'aes128-gcm@openssh.com' ],
- mac: [],
- compress: [ 'none' ],
- lang: [],
- },
- sc: {
- cipher: [ 'aes128-gcm@openssh.com' ],
- mac: [],
- compress: [ 'none' ],
- lang: [],
- },
- });
- conn.rekey(mustCall((err) => {
- assert(!err, `Unexpected rekey error: ${err}`);
- reqs.forEach((accept) => {
- const session = accept();
- session.on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- stream.exit(0);
- stream.end();
- }));
- });
- }));
- }
- reqs.push(accept);
- }, 3));
- }));
- }));
- let handshakes = 0;
- client.on('handshake', mustCall((info) => {
- switch (++handshakes) {
- case 1:
- assert(info.cs.compress === 'zlib', 'wrong compress value');
- assert(info.sc.compress === 'zlib', 'wrong compress value');
- break;
- case 2:
- assert(info.cs.compress === 'none', 'wrong compress value');
- assert(info.sc.compress === 'none', 'wrong compress value');
- break;
- }
- }, 2)).on('ready', mustCall(() => {
- let calledBack = 0;
- function callback(err, stream) {
- assert(!err, `Unexpected error: ${err}`);
- stream.resume();
- if (++calledBack === 3)
- client.end();
- }
- client.exec('foo', mustCall(callback));
- client.exec('bar', mustCall(callback));
- client.exec('baz', mustCall(callback));
- }));
- }
- {
- const { client, server } = setup_(
- 'Large data compression',
- {
- client: {
- ...clientCfg,
- algorithms: { compress: [ 'zlib' ] },
- },
- server: {
- ...serverCfg,
- algorithms: { compress: [ 'zlib' ] },
- }
- },
- );
- const chunk = Buffer.alloc(1024 * 1024, 'a');
- const chunkCount = 10;
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.on('session', mustCall((accept, reject) => {
- accept().on('exec', mustCall((accept, reject, info) => {
- const stream = accept();
- for (let i = 0; i < chunkCount; ++i)
- stream.write(chunk);
- stream.exit(0);
- stream.end();
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- client.exec('foo', mustCall((err, stream) => {
- assert(!err, `Unexpected exec error: ${err}`);
- let nb = 0;
- stream.on('data', mustCallAtLeast((data) => {
- nb += data.length;
- })).on('end', mustCall(() => {
- assert(nb === (chunkCount * chunk.length),
- `Wrong stream byte count: ${nb}`);
- client.end();
- }));
- }));
- }));
- }
- {
- const { client, server } = setup_(
- 'Debug output',
- {
- client: {
- ...clientCfg,
- debug: mustCallAtLeast((msg) => {
- assert(typeof msg === 'string',
- `Wrong debug argument type: ${typeof msg}`);
- assert(msg.length > 0, 'Unexpected empty debug message');
- }),
- },
- server: {
- ...serverCfg,
- debug: mustCallAtLeast((msg) => {
- assert(typeof msg === 'string',
- `Wrong debug argument type: ${typeof msg}`);
- assert(msg.length > 0, 'Unexpected empty debug message');
- }),
- },
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.on('session', mustCall((accept, reject) => {
- accept().on('exec', mustCall((accept, reject, info) => {
- assert(info.command === 'foo --bar',
- `Wrong exec command: ${info.command}`);
- const stream = accept();
- stream.exit(100);
- stream.end();
- conn.end();
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- client.exec('foo --bar', mustCall((err, stream) => {
- assert(!err, `Unexpected exec error: ${err}`);
- stream.resume();
- }));
- }));
- }
- {
- const { server } = setup_(
- 'HTTP agent',
- {
- // No automatic client, the agent will create one
- server: serverCfg,
- debug,
- },
- );
- let httpServer;
- server.on('listening', () => {
- httpServer = http.createServer((req, res) => {
- httpServer.close();
- res.end('hello world!');
- });
- httpServer.listen(0, 'localhost', () => {
- const agent = new HTTPAgent({
- host: 'localhost',
- port: server.address().port,
- username: 'foo',
- password: 'bar',
- });
- http.get({
- host: 'localhost',
- port: httpServer.address().port,
- agent,
- headers: { Connection: 'close' },
- }, (res) => {
- assert(res.statusCode === 200,
- `Wrong http status code: ${res.statusCode}`);
- let buf = '';
- res.on('data', mustCallAtLeast((chunk) => {
- buf += chunk;
- })).on('end', mustCall(() => {
- assert(buf === 'hello world!',
- `Wrong http response body: ${inspect(buf)}`);
- }));
- });
- });
- });
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.on('tcpip', mustCall((accept, reject, info) => {
- assert(info.destIP === 'localhost', `Wrong destIP: ${info.destIP}`);
- assert(info.destPort === httpServer.address().port,
- `Wrong destPort: ${info.destPort}`);
- assert(info.srcIP === 'localhost', `Wrong srcIP: ${info.srcIP}`);
- const stream = accept();
- const tcp = new net.Socket();
- tcp.pipe(stream).pipe(tcp);
- tcp.connect(httpServer.address().port, 'localhost');
- }));
- }));
- }));
- }
- {
- const { server } = setup_(
- 'HTTPS agent',
- {
- // No automatic client, the agent will create one
- server: serverCfg,
- debug,
- },
- );
- let httpsServer;
- server.on('listening', () => {
- httpsServer = https.createServer({
- key: fixture('https_key.pem'),
- cert: fixture('https_cert.pem'),
- }, (req, res) => {
- httpsServer.close();
- res.end('hello world!');
- });
- httpsServer.listen(0, 'localhost', () => {
- const agent = new HTTPSAgent({
- host: 'localhost',
- port: server.address().port,
- username: 'foo',
- password: 'bar',
- });
- https.get({
- host: 'localhost',
- port: httpsServer.address().port,
- agent,
- headers: { Connection: 'close' },
- ca: fixture('https_cert.pem'),
- }, (res) => {
- assert(res.statusCode === 200,
- `Wrong http status code: ${res.statusCode}`);
- let buf = '';
- res.on('data', mustCallAtLeast((chunk) => {
- buf += chunk;
- })).on('end', mustCall(() => {
- assert(buf === 'hello world!',
- `Wrong http response body: ${inspect(buf)}`);
- }));
- }).on('error', (err) => {
- // This workaround is necessary for some reason on node < v14.x
- if (!/write after end/i.test(err.message))
- throw err;
- });
- });
- });
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.on('tcpip', mustCall((accept, reject, info) => {
- assert(info.destIP === 'localhost', `Wrong destIP: ${info.destIP}`);
- assert(info.destPort === httpsServer.address().port,
- `Wrong destPort: ${info.destPort}`);
- assert(info.srcIP === 'localhost', `Wrong srcIP: ${info.srcIP}`);
- const stream = accept();
- const tcp = new net.Socket();
- tcp.pipe(stream).pipe(tcp);
- tcp.connect(httpsServer.address().port, 'localhost');
- }));
- }));
- }));
- }
- [
- { desc: 'remove/append/prepend (regexps)',
- config: {
- remove: /.*/,
- append: /gcm/,
- prepend: /ctr/,
- },
- expected: [
- 'aes128-ctr',
- 'aes192-ctr',
- 'aes256-ctr',
- 'aes128-gcm',
- 'aes128-gcm@openssh.com',
- 'aes256-gcm',
- 'aes256-gcm@openssh.com',
- ],
- },
- { desc: 'remove/append/prepend (strings)',
- config: {
- remove: /.*/,
- append: 'aes256-ctr',
- prepend: [ 'aes256-gcm', 'aes128-gcm' ],
- },
- expected: [
- 'aes256-gcm',
- 'aes128-gcm',
- 'aes256-ctr',
- ],
- },
- ].forEach((info) => {
- const { client, server } = setup_(
- `Client algorithms option (${info.desc})`,
- {
- client: {
- ...clientCfg,
- algorithms: { cipher: info.config },
- },
- server: serverCfg,
- debug,
- },
- );
- server.on('connection', mustCall((conn) => {
- conn.on('authentication', mustCall((ctx) => {
- ctx.accept();
- })).on('ready', mustCall(() => {
- conn.end();
- }));
- }));
- client.on('ready', mustCall(() => {
- // XXX: hack to easily verify computed offer
- const offer = client._protocol._offer.lists;
- assert.deepStrictEqual(
- offer.cs.cipher.array,
- info.expected,
- `Wrong algorithm list: ${offer.cs.cipher.array}`
- );
- }));
- });
- {
- const { client } = setup_(
- `Safely end() from Client 'error' event handler`,
- {
- client: clientCfg,
- noClientError: true,
- noForceClientReady: true,
- },
- );
- const badServer = net.createServer((s) => {});
- badServer.listen(0, 'localhost', mustCall(() => {
- badServer.unref();
- client.on('error', mustCallAtLeast((err) => {
- client.end();
- })).on('ready', mustNotCall()).on('close', mustCall(() => {}));
- client.connect({
- host: 'localhost',
- port: badServer.address().port,
- user: 'foo',
- password: 'bar',
- readyTimeout: 1,
- });
- }));
- }
- {
- const { client } = setup_(
- 'Client error should be emitted on bad/nonexistent greeting',
- {
- client: clientCfg,
- noClientError: true,
- noForceClientReady: true,
- },
- );
- const badServer = net.createServer(mustCall((s) => {
- badServer.close();
- s.end();
- })).listen(0, 'localhost', mustCall(() => {
- client.on('error', mustCall((err) => {
- client.end();
- })).on('ready', mustNotCall()).on('close', mustCall(() => {}));
- client.connect({
- host: 'localhost',
- port: badServer.address().port,
- user: 'foo',
- password: 'bar',
- });
- }));
- }
- {
- const { client } = setup_(
- 'Only one client error on connection failure',
- {
- client: clientCfg,
- noClientError: true,
- noForceClientReady: true,
- },
- );
- client.on('error', mustCall((err) => {
- assert.strictEqual(err.syscall, 'getaddrinfo');
- }));
- client.connect({
- host: 'blerbblubblubblerb',
- port: 9999,
- user: 'foo',
- password: 'bar'
- });
- }
- {
- const { client, server } = setup(
- 'Client should remove reserved channels on incoming channel rejection'
- );
- const assignedPort = 31337;
- server.on('connection', mustCall((conn) => {
- conn.on('ready', mustCall(() => {
- conn.on('request', mustCall((accept, reject, name, info) => {
- assert(name === 'tcpip-forward', 'Wrong request name');
- assert.deepStrictEqual(
- info,
- { bindAddr: 'good', bindPort: 0 },
- 'Wrong request info'
- );
- accept(assignedPort);
- conn.forwardOut(info.bindAddr,
- assignedPort,
- 'remote',
- 12345,
- mustCall((err, ch) => {
- assert(err, 'Should receive error');
- client.end();
- }));
- }));
- }));
- }));
- client.on('ready', mustCall(() => {
- // request forwarding
- client.forwardIn('good', 0, mustCall((err, port) => {
- assert(!err, `Unexpected error: ${err}`);
- assert(port === assignedPort, 'Wrong assigned port');
- }));
- })).on('tcp connection', mustCall((details, accept, reject) => {
- assert.deepStrictEqual(
- details,
- { destIP: 'good',
- destPort: assignedPort,
- srcIP: 'remote',
- srcPort: 12345
- },
- 'Wrong connection details'
- );
- assert.strictEqual(Object.keys(client._chanMgr._channels).length, 1);
- assert.strictEqual(client._chanMgr._count, 1);
- reject();
- assert.strictEqual(Object.keys(client._chanMgr._channels).length, 0);
- assert.strictEqual(client._chanMgr._count, 0);
- }));
- }
- {
- // Allow injected sockets
- const socket = new Transform({
- emitClose: true,
- autoDestroy: true,
- transform: (chunk, encoding, cb) => {
- cb();
- },
- });
- socket.remoteAddress = '127.0.0.1';
- socket.remotePort = '12345';
- socket.remoteFamily = 'IPv4';
- socket.push(Buffer.from('SSH-2.0-foo\r\n'));
- const server = new Server(serverCfg);
- server.on('connection', mustCall((conn, info) => {
- assert.strictEqual(info.header.versions.software, 'foo');
- assert.strictEqual(info.ip, '127.0.0.1');
- assert.strictEqual(info.port, '12345');
- assert.strictEqual(info.family, 'IPv4');
- conn.on('ready', mustNotCall());
- conn.on('close', mustCall());
- socket.end();
- }));
- server.injectSocket(socket);
- }
|