UNPKG

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