test-userauth.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. 'use strict';
  2. const assert = require('assert');
  3. const { inspect } = require('util');
  4. const {
  5. fixtureKey,
  6. mustCall,
  7. mustNotCall,
  8. setup,
  9. } = require('./common.js');
  10. const serverCfg = { hostKeys: [ fixtureKey('ssh_host_rsa_key').raw ] };
  11. const debug = false;
  12. // Keys ========================================================================
  13. [
  14. { desc: 'RSA (old OpenSSH)',
  15. clientKey: fixtureKey('id_rsa') },
  16. { desc: 'RSA (new OpenSSH)',
  17. clientKey: fixtureKey('openssh_new_rsa') },
  18. { desc: 'RSA (encrypted)',
  19. clientKey: fixtureKey('id_rsa_enc', 'foobarbaz'),
  20. passphrase: 'foobarbaz' },
  21. { desc: 'DSA',
  22. clientKey: fixtureKey('id_dsa') },
  23. { desc: 'ECDSA',
  24. clientKey: fixtureKey('id_ecdsa') },
  25. { desc: 'PPK',
  26. clientKey: fixtureKey('id_rsa.ppk') },
  27. ].forEach((test) => {
  28. const { desc, clientKey, passphrase } = test;
  29. const username = 'Key User';
  30. const { server } = setup(
  31. desc,
  32. {
  33. client: { username, privateKey: clientKey.raw, passphrase },
  34. server: serverCfg,
  35. debug,
  36. }
  37. );
  38. server.on('connection', mustCall((conn) => {
  39. let authAttempt = 0;
  40. conn.on('authentication', mustCall((ctx) => {
  41. assert(ctx.username === username,
  42. `Wrong username: ${ctx.username}`);
  43. switch (++authAttempt) {
  44. case 1:
  45. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  46. return ctx.reject();
  47. case 3:
  48. assert(ctx.signature, 'Missing publickey signature');
  49. // FALLTHROUGH
  50. case 2:
  51. assert(ctx.method === 'publickey',
  52. `Wrong auth method: ${ctx.method}`);
  53. assert(ctx.key.algo === clientKey.key.type,
  54. `Wrong key algo: ${ctx.key.algo}`);
  55. assert.deepStrictEqual(clientKey.key.getPublicSSH(),
  56. ctx.key.data,
  57. 'Public key mismatch');
  58. break;
  59. }
  60. if (ctx.signature) {
  61. assert(clientKey.key.verify(ctx.blob, ctx.signature) === true,
  62. 'Could not verify publickey signature');
  63. }
  64. ctx.accept();
  65. }, 3)).on('ready', mustCall(() => {
  66. conn.end();
  67. }));
  68. }));
  69. });
  70. // Password ====================================================================
  71. {
  72. const username = 'Password User';
  73. const password = 'hi mom';
  74. const { server } = setup(
  75. 'Password',
  76. {
  77. client: { username, password },
  78. server: serverCfg,
  79. debug,
  80. }
  81. );
  82. server.on('connection', mustCall((conn) => {
  83. let authAttempt = 0;
  84. conn.on('authentication', mustCall((ctx) => {
  85. assert(ctx.username === username,
  86. `Wrong username: ${ctx.username}`);
  87. if (++authAttempt === 1) {
  88. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  89. return ctx.reject();
  90. }
  91. assert(ctx.method === 'password',
  92. `Wrong auth method: ${ctx.method}`);
  93. assert(ctx.password === password,
  94. `Wrong password: ${ctx.password}`);
  95. ctx.accept();
  96. }, 2)).on('ready', mustCall(() => {
  97. conn.end();
  98. }));
  99. }));
  100. }
  101. {
  102. const username = '';
  103. const password = 'hi mom';
  104. const { server } = setup(
  105. 'Password (empty username)',
  106. {
  107. client: { username, password },
  108. server: serverCfg,
  109. debug,
  110. }
  111. );
  112. server.on('connection', mustCall((conn) => {
  113. let authAttempt = 0;
  114. conn.on('authentication', mustCall((ctx) => {
  115. assert(ctx.username === username,
  116. `Wrong username: ${ctx.username}`);
  117. if (++authAttempt === 1) {
  118. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  119. return ctx.reject();
  120. }
  121. assert(ctx.method === 'password',
  122. `Wrong auth method: ${ctx.method}`);
  123. assert(ctx.password === password,
  124. `Wrong password: ${ctx.password}`);
  125. ctx.accept();
  126. }, 2)).on('ready', mustCall(() => {
  127. conn.end();
  128. }));
  129. }));
  130. }
  131. {
  132. const username = 'foo';
  133. const oldPassword = 'bar';
  134. const newPassword = 'baz';
  135. const changePrompt = 'Prithee changeth thy password';
  136. const { client, server } = setup(
  137. 'Password (change requested)',
  138. {
  139. client: { username, password: oldPassword },
  140. server: serverCfg,
  141. debug,
  142. }
  143. );
  144. server.on('connection', mustCall((conn) => {
  145. let authAttempt = 0;
  146. conn.on('authentication', mustCall((ctx) => {
  147. assert(ctx.username === username,
  148. `Wrong username: ${ctx.username}`);
  149. if (++authAttempt === 1) {
  150. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  151. return ctx.reject();
  152. }
  153. assert(ctx.method === 'password',
  154. `Wrong auth method: ${ctx.method}`);
  155. assert(ctx.password === oldPassword,
  156. `Wrong old password: ${ctx.password}`);
  157. ctx.requestChange(changePrompt, mustCall((newPassword_) => {
  158. assert(newPassword_ === newPassword,
  159. `Wrong new password: ${newPassword_}`);
  160. ctx.accept();
  161. }));
  162. }, 2)).on('ready', mustCall(() => {
  163. conn.end();
  164. }));
  165. }));
  166. client.on('change password', mustCall((prompt, done) => {
  167. assert(prompt === changePrompt, `Wrong password change prompt: ${prompt}`);
  168. process.nextTick(done, newPassword);
  169. }));
  170. }
  171. // Hostbased ===================================================================
  172. {
  173. const localUsername = 'Local User Foo';
  174. const localHostname = 'Local Host Bar';
  175. const username = 'Hostbased User';
  176. const clientKey = fixtureKey('id_rsa');
  177. const { server } = setup(
  178. 'Hostbased',
  179. {
  180. client: {
  181. username,
  182. privateKey: clientKey.raw,
  183. localUsername,
  184. localHostname,
  185. },
  186. server: serverCfg,
  187. debug,
  188. }
  189. );
  190. server.on('connection', mustCall((conn) => {
  191. let authAttempt = 0;
  192. conn.on('authentication', mustCall((ctx) => {
  193. assert(ctx.username === username,
  194. `Wrong username: ${ctx.username}`);
  195. switch (++authAttempt) {
  196. case 1:
  197. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  198. return ctx.reject();
  199. case 2:
  200. assert(ctx.method === 'publickey',
  201. `Wrong auth method: ${ctx.method}`);
  202. return ctx.reject();
  203. case 3:
  204. assert(ctx.method === 'hostbased',
  205. `Wrong auth method: ${ctx.method}`);
  206. assert(ctx.key.algo === clientKey.key.type,
  207. `Wrong key algo: ${ctx.key.algo}`);
  208. assert.deepStrictEqual(clientKey.key.getPublicSSH(),
  209. ctx.key.data,
  210. 'Public key mismatch');
  211. assert(ctx.signature, 'Expected signature');
  212. assert(ctx.localHostname === localHostname, 'Wrong local hostname');
  213. assert(ctx.localUsername === localUsername, 'Wrong local username');
  214. assert(clientKey.key.verify(ctx.blob, ctx.signature) === true,
  215. 'Could not verify hostbased signature');
  216. break;
  217. }
  218. ctx.accept();
  219. }, 3)).on('ready', mustCall(() => {
  220. conn.end();
  221. }));
  222. }));
  223. }
  224. // keyboard-interactive ========================================================
  225. {
  226. const username = 'Keyboard-Interactive User';
  227. const request = {
  228. name: 'SSH2 Authentication',
  229. instructions: 'These are instructions',
  230. prompts: [
  231. { prompt: 'Password: ', echo: false },
  232. { prompt: 'Is the cake a lie? ', echo: true },
  233. ],
  234. };
  235. const responses = [
  236. 'foobarbaz',
  237. 'yes',
  238. ];
  239. const { client, server } = setup(
  240. 'Password (empty username)',
  241. {
  242. client: {
  243. username,
  244. tryKeyboard: true,
  245. },
  246. server: serverCfg,
  247. debug,
  248. }
  249. );
  250. server.on('connection', mustCall((conn) => {
  251. let authAttempt = 0;
  252. conn.on('authentication', mustCall((ctx) => {
  253. assert(ctx.username === username,
  254. `Wrong username: ${ctx.username}`);
  255. if (++authAttempt === 1) {
  256. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  257. return ctx.reject();
  258. }
  259. assert(ctx.method === 'keyboard-interactive',
  260. `Wrong auth method: ${ctx.method}`);
  261. ctx.prompt(request.prompts,
  262. request.name,
  263. request.instructions,
  264. mustCall((responses_) => {
  265. assert.deepStrictEqual(responses_, responses);
  266. ctx.accept();
  267. }));
  268. }, 2)).on('ready', mustCall(() => {
  269. conn.end();
  270. }));
  271. }));
  272. client.on('keyboard-interactive',
  273. mustCall((name, instructions, lang, prompts, finish) => {
  274. assert(name === request.name, `Wrong prompt name: ${name}`);
  275. assert(instructions === request.instructions,
  276. `Wrong prompt instructions: ${instructions}`);
  277. assert.deepStrictEqual(
  278. prompts,
  279. request.prompts,
  280. `Wrong prompts: ${inspect(prompts)}`
  281. );
  282. process.nextTick(finish, responses);
  283. }));
  284. }
  285. // authHandler() tests =========================================================
  286. {
  287. const username = 'foo';
  288. const password = '1234';
  289. const clientKey = fixtureKey('id_rsa');
  290. const { server } = setup(
  291. 'authHandler() (sync)',
  292. {
  293. client: {
  294. username,
  295. password,
  296. privateKey: clientKey.raw,
  297. authHandler: mustCall((methodsLeft, partial, cb) => {
  298. assert(methodsLeft === null, 'expected null methodsLeft');
  299. assert(partial === null, 'expected null partial');
  300. return 'none';
  301. }),
  302. },
  303. server: serverCfg,
  304. debug,
  305. }
  306. );
  307. server.on('connection', mustCall((conn) => {
  308. conn.on('authentication', mustCall((ctx) => {
  309. assert(ctx.username === username, `Wrong username: ${ctx.username}`);
  310. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  311. ctx.accept();
  312. })).on('ready', mustCall(() => {
  313. conn.end();
  314. }));
  315. }));
  316. }
  317. {
  318. const username = 'foo';
  319. const password = '1234';
  320. const clientKey = fixtureKey('id_rsa');
  321. const { server } = setup(
  322. 'authHandler() (async)',
  323. {
  324. client: {
  325. username,
  326. password,
  327. privateKey: clientKey.raw,
  328. authHandler: mustCall((methodsLeft, partial, cb) => {
  329. assert(methodsLeft === null, 'expected null methodsLeft');
  330. assert(partial === null, 'expected null partial');
  331. process.nextTick(mustCall(cb), 'none');
  332. }),
  333. },
  334. server: serverCfg,
  335. debug,
  336. }
  337. );
  338. server.on('connection', mustCall((conn) => {
  339. conn.on('authentication', mustCall((ctx) => {
  340. assert(ctx.username === username, `Wrong username: ${ctx.username}`);
  341. assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
  342. ctx.accept();
  343. })).on('ready', mustCall(() => {
  344. conn.end();
  345. }));
  346. }));
  347. }
  348. {
  349. const username = 'foo';
  350. const password = '1234';
  351. const clientKey = fixtureKey('id_rsa');
  352. const { client, server } = setup(
  353. 'authHandler() (no methods left -- sync)',
  354. {
  355. client: {
  356. username,
  357. password,
  358. privateKey: clientKey.raw,
  359. authHandler: mustCall((methodsLeft, partial, cb) => {
  360. assert(methodsLeft === null, 'expected null methodsLeft');
  361. assert(partial === null, 'expected null partial');
  362. return false;
  363. }),
  364. },
  365. server: serverCfg,
  366. debug,
  367. noForceClientReady: true,
  368. noForceServerReady: true,
  369. }
  370. );
  371. // Remove default client error handler added by `setup()` since we are
  372. // expecting an error in this case
  373. client.removeAllListeners('error');
  374. client.on('error', mustCall((err) => {
  375. assert.strictEqual(err.level, 'client-authentication');
  376. assert(/configured authentication methods failed/i.test(err.message),
  377. 'Wrong error message');
  378. }));
  379. server.on('connection', mustCall((conn) => {
  380. conn.on('authentication', mustNotCall())
  381. .on('ready', mustNotCall());
  382. }));
  383. }
  384. {
  385. const username = 'foo';
  386. const password = '1234';
  387. const clientKey = fixtureKey('id_rsa');
  388. const { client, server } = setup(
  389. 'authHandler() (no methods left -- async)',
  390. {
  391. client: {
  392. username,
  393. password,
  394. privateKey: clientKey.raw,
  395. authHandler: mustCall((methodsLeft, partial, cb) => {
  396. assert(methodsLeft === null, 'expected null methodsLeft');
  397. assert(partial === null, 'expected null partial');
  398. process.nextTick(mustCall(cb), false);
  399. }),
  400. },
  401. server: serverCfg,
  402. debug,
  403. noForceClientReady: true,
  404. noForceServerReady: true,
  405. }
  406. );
  407. // Remove default client error handler added by `setup()` since we are
  408. // expecting an error in this case
  409. client.removeAllListeners('error');
  410. client.on('error', mustCall((err) => {
  411. assert.strictEqual(err.level, 'client-authentication');
  412. assert(/configured authentication methods failed/i.test(err.message),
  413. 'Wrong error message');
  414. }));
  415. server.on('connection', mustCall((conn) => {
  416. conn.on('authentication', mustNotCall())
  417. .on('ready', mustNotCall());
  418. }));
  419. }
  420. {
  421. const username = 'foo';
  422. const password = '1234';
  423. const clientKey = fixtureKey('id_rsa');
  424. const events = [];
  425. const expectedEvents = [
  426. 'client', 'server', 'client', 'server'
  427. ];
  428. let clientCalls = 0;
  429. const { client, server } = setup(
  430. 'authHandler() (multi-step)',
  431. {
  432. client: {
  433. username,
  434. password,
  435. privateKey: clientKey.raw,
  436. authHandler: mustCall((methodsLeft, partial, cb) => {
  437. events.push('client');
  438. switch (++clientCalls) {
  439. case 1:
  440. assert(methodsLeft === null, 'expected null methodsLeft');
  441. assert(partial === null, 'expected null partial');
  442. return 'publickey';
  443. case 2:
  444. assert.deepStrictEqual(
  445. methodsLeft,
  446. ['password'],
  447. `expected 'password' method left, saw: ${methodsLeft}`
  448. );
  449. assert(partial === true, 'expected partial success');
  450. return 'password';
  451. }
  452. }, 2),
  453. },
  454. server: serverCfg,
  455. debug,
  456. }
  457. );
  458. server.on('connection', mustCall((conn) => {
  459. let attempts = 0;
  460. conn.on('authentication', mustCall((ctx) => {
  461. assert(++attempts === clientCalls, 'server<->client state mismatch');
  462. assert(ctx.username === username,
  463. `Unexpected username: ${ctx.username}`);
  464. events.push('server');
  465. switch (attempts) {
  466. case 1:
  467. assert(ctx.method === 'publickey',
  468. `Wrong auth method: ${ctx.method}`);
  469. assert(ctx.key.algo === clientKey.key.type,
  470. `Unexpected key algo: ${ctx.key.algo}`);
  471. assert.deepEqual(clientKey.key.getPublicSSH(),
  472. ctx.key.data,
  473. 'Public key mismatch');
  474. ctx.reject(['password'], true);
  475. break;
  476. case 2:
  477. assert(ctx.method === 'password',
  478. `Wrong auth method: ${ctx.method}`);
  479. assert(ctx.password === password,
  480. `Unexpected password: ${ctx.password}`);
  481. ctx.accept();
  482. break;
  483. }
  484. }, 2)).on('ready', mustCall(() => {
  485. conn.end();
  486. }));
  487. }));
  488. client.on('close', mustCall(() => {
  489. assert.deepStrictEqual(events, expectedEvents);
  490. }));
  491. }
  492. {
  493. const username = 'foo';
  494. const password = '1234';
  495. const { server } = setup(
  496. 'authHandler() (custom auth configuration)',
  497. {
  498. client: {
  499. username: 'bar',
  500. password: '5678',
  501. authHandler: mustCall((methodsLeft, partial, cb) => {
  502. assert(methodsLeft === null, 'expected null methodsLeft');
  503. assert(partial === null, 'expected null partial');
  504. return {
  505. type: 'password',
  506. username,
  507. password,
  508. };
  509. }),
  510. },
  511. server: serverCfg,
  512. debug,
  513. }
  514. );
  515. server.on('connection', mustCall((conn) => {
  516. conn.on('authentication', mustCall((ctx) => {
  517. assert(ctx.username === username, `Wrong username: ${ctx.username}`);
  518. assert(ctx.method === 'password', `Wrong auth method: ${ctx.method}`);
  519. assert(ctx.password === password, `Unexpected password: ${ctx.password}`);
  520. ctx.accept();
  521. })).on('ready', mustCall(() => {
  522. conn.end();
  523. }));
  524. }));
  525. }
  526. {
  527. const username = 'foo';
  528. const password = '1234';
  529. const { server } = setup(
  530. 'authHandler() (simple construction with custom auth configuration)',
  531. {
  532. client: {
  533. username: 'bar',
  534. password: '5678',
  535. authHandler: [{
  536. type: 'password',
  537. username,
  538. password,
  539. }],
  540. },
  541. server: serverCfg,
  542. debug,
  543. }
  544. );
  545. server.on('connection', mustCall((conn) => {
  546. conn.on('authentication', mustCall((ctx) => {
  547. assert(ctx.username === username, `Wrong username: ${ctx.username}`);
  548. assert(ctx.method === 'password', `Wrong auth method: ${ctx.method}`);
  549. assert(ctx.password === password, `Unexpected password: ${ctx.password}`);
  550. ctx.accept();
  551. })).on('ready', mustCall(() => {
  552. conn.end();
  553. }));
  554. }));
  555. }