Repository URL to install this package:
|
Version:
0.6.0 ▾
|
'use strict';
const assert = require('assert');
const { inspect } = require('util');
const {
fixtureKey,
mustCall,
mustNotCall,
setup,
} = require('./common.js');
const serverCfg = { hostKeys: [ fixtureKey('ssh_host_rsa_key').raw ] };
const debug = false;
// Keys ========================================================================
[
{ desc: 'RSA (old OpenSSH)',
clientKey: fixtureKey('id_rsa') },
{ desc: 'RSA (new OpenSSH)',
clientKey: fixtureKey('openssh_new_rsa') },
{ desc: 'RSA (encrypted)',
clientKey: fixtureKey('id_rsa_enc', 'foobarbaz'),
passphrase: 'foobarbaz' },
{ desc: 'DSA',
clientKey: fixtureKey('id_dsa') },
{ desc: 'ECDSA',
clientKey: fixtureKey('id_ecdsa') },
{ desc: 'PPK',
clientKey: fixtureKey('id_rsa.ppk') },
].forEach((test) => {
const { desc, clientKey, passphrase } = test;
const username = 'Key User';
const { server } = setup(
desc,
{
client: { username, privateKey: clientKey.raw, passphrase },
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
switch (++authAttempt) {
case 1:
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
return ctx.reject();
case 3:
assert(ctx.signature, 'Missing publickey signature');
// FALLTHROUGH
case 2:
assert(ctx.method === 'publickey',
`Wrong auth method: ${ctx.method}`);
assert(ctx.key.algo === clientKey.key.type,
`Wrong key algo: ${ctx.key.algo}`);
assert.deepStrictEqual(clientKey.key.getPublicSSH(),
ctx.key.data,
'Public key mismatch');
break;
}
if (ctx.signature) {
assert(clientKey.key.verify(ctx.blob, ctx.signature) === true,
'Could not verify publickey signature');
}
ctx.accept();
}, 3)).on('ready', mustCall(() => {
conn.end();
}));
}));
});
// Password ====================================================================
{
const username = 'Password User';
const password = 'hi mom';
const { server } = setup(
'Password',
{
client: { username, password },
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
if (++authAttempt === 1) {
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
return ctx.reject();
}
assert(ctx.method === 'password',
`Wrong auth method: ${ctx.method}`);
assert(ctx.password === password,
`Wrong password: ${ctx.password}`);
ctx.accept();
}, 2)).on('ready', mustCall(() => {
conn.end();
}));
}));
}
{
const username = '';
const password = 'hi mom';
const { server } = setup(
'Password (empty username)',
{
client: { username, password },
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
if (++authAttempt === 1) {
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
return ctx.reject();
}
assert(ctx.method === 'password',
`Wrong auth method: ${ctx.method}`);
assert(ctx.password === password,
`Wrong password: ${ctx.password}`);
ctx.accept();
}, 2)).on('ready', mustCall(() => {
conn.end();
}));
}));
}
{
const username = 'foo';
const oldPassword = 'bar';
const newPassword = 'baz';
const changePrompt = 'Prithee changeth thy password';
const { client, server } = setup(
'Password (change requested)',
{
client: { username, password: oldPassword },
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
if (++authAttempt === 1) {
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
return ctx.reject();
}
assert(ctx.method === 'password',
`Wrong auth method: ${ctx.method}`);
assert(ctx.password === oldPassword,
`Wrong old password: ${ctx.password}`);
ctx.requestChange(changePrompt, mustCall((newPassword_) => {
assert(newPassword_ === newPassword,
`Wrong new password: ${newPassword_}`);
ctx.accept();
}));
}, 2)).on('ready', mustCall(() => {
conn.end();
}));
}));
client.on('change password', mustCall((prompt, done) => {
assert(prompt === changePrompt, `Wrong password change prompt: ${prompt}`);
process.nextTick(done, newPassword);
}));
}
// Hostbased ===================================================================
{
const localUsername = 'Local User Foo';
const localHostname = 'Local Host Bar';
const username = 'Hostbased User';
const clientKey = fixtureKey('id_rsa');
const { server } = setup(
'Hostbased',
{
client: {
username,
privateKey: clientKey.raw,
localUsername,
localHostname,
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
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}`);
return ctx.reject();
case 3:
assert(ctx.method === 'hostbased',
`Wrong auth method: ${ctx.method}`);
assert(ctx.key.algo === clientKey.key.type,
`Wrong key algo: ${ctx.key.algo}`);
assert.deepStrictEqual(clientKey.key.getPublicSSH(),
ctx.key.data,
'Public key mismatch');
assert(ctx.signature, 'Expected signature');
assert(ctx.localHostname === localHostname, 'Wrong local hostname');
assert(ctx.localUsername === localUsername, 'Wrong local username');
assert(clientKey.key.verify(ctx.blob, ctx.signature) === true,
'Could not verify hostbased signature');
break;
}
ctx.accept();
}, 3)).on('ready', mustCall(() => {
conn.end();
}));
}));
}
// keyboard-interactive ========================================================
{
const username = 'Keyboard-Interactive User';
const request = {
name: 'SSH2 Authentication',
instructions: 'These are instructions',
prompts: [
{ prompt: 'Password: ', echo: false },
{ prompt: 'Is the cake a lie? ', echo: true },
],
};
const responses = [
'foobarbaz',
'yes',
];
const { client, server } = setup(
'Password (empty username)',
{
client: {
username,
tryKeyboard: true,
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let authAttempt = 0;
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username,
`Wrong username: ${ctx.username}`);
if (++authAttempt === 1) {
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
return ctx.reject();
}
assert(ctx.method === 'keyboard-interactive',
`Wrong auth method: ${ctx.method}`);
ctx.prompt(request.prompts,
request.name,
request.instructions,
mustCall((responses_) => {
assert.deepStrictEqual(responses_, responses);
ctx.accept();
}));
}, 2)).on('ready', mustCall(() => {
conn.end();
}));
}));
client.on('keyboard-interactive',
mustCall((name, instructions, lang, prompts, finish) => {
assert(name === request.name, `Wrong prompt name: ${name}`);
assert(instructions === request.instructions,
`Wrong prompt instructions: ${instructions}`);
assert.deepStrictEqual(
prompts,
request.prompts,
`Wrong prompts: ${inspect(prompts)}`
);
process.nextTick(finish, responses);
}));
}
// authHandler() tests =========================================================
{
const username = 'foo';
const password = '1234';
const clientKey = fixtureKey('id_rsa');
const { server } = setup(
'authHandler() (sync)',
{
client: {
username,
password,
privateKey: clientKey.raw,
authHandler: mustCall((methodsLeft, partial, cb) => {
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
return 'none';
}),
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username, `Wrong username: ${ctx.username}`);
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
ctx.accept();
})).on('ready', mustCall(() => {
conn.end();
}));
}));
}
{
const username = 'foo';
const password = '1234';
const clientKey = fixtureKey('id_rsa');
const { server } = setup(
'authHandler() (async)',
{
client: {
username,
password,
privateKey: clientKey.raw,
authHandler: mustCall((methodsLeft, partial, cb) => {
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
process.nextTick(mustCall(cb), 'none');
}),
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username, `Wrong username: ${ctx.username}`);
assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`);
ctx.accept();
})).on('ready', mustCall(() => {
conn.end();
}));
}));
}
{
const username = 'foo';
const password = '1234';
const clientKey = fixtureKey('id_rsa');
const { client, server } = setup(
'authHandler() (no methods left -- sync)',
{
client: {
username,
password,
privateKey: clientKey.raw,
authHandler: mustCall((methodsLeft, partial, cb) => {
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
return false;
}),
},
server: serverCfg,
debug,
noForceClientReady: true,
noForceServerReady: true,
}
);
// Remove default client error handler added by `setup()` since we are
// expecting an error in this case
client.removeAllListeners('error');
client.on('error', mustCall((err) => {
assert.strictEqual(err.level, 'client-authentication');
assert(/configured authentication methods failed/i.test(err.message),
'Wrong error message');
}));
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustNotCall())
.on('ready', mustNotCall());
}));
}
{
const username = 'foo';
const password = '1234';
const clientKey = fixtureKey('id_rsa');
const { client, server } = setup(
'authHandler() (no methods left -- async)',
{
client: {
username,
password,
privateKey: clientKey.raw,
authHandler: mustCall((methodsLeft, partial, cb) => {
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
process.nextTick(mustCall(cb), false);
}),
},
server: serverCfg,
debug,
noForceClientReady: true,
noForceServerReady: true,
}
);
// Remove default client error handler added by `setup()` since we are
// expecting an error in this case
client.removeAllListeners('error');
client.on('error', mustCall((err) => {
assert.strictEqual(err.level, 'client-authentication');
assert(/configured authentication methods failed/i.test(err.message),
'Wrong error message');
}));
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustNotCall())
.on('ready', mustNotCall());
}));
}
{
const username = 'foo';
const password = '1234';
const clientKey = fixtureKey('id_rsa');
const events = [];
const expectedEvents = [
'client', 'server', 'client', 'server'
];
let clientCalls = 0;
const { client, server } = setup(
'authHandler() (multi-step)',
{
client: {
username,
password,
privateKey: clientKey.raw,
authHandler: mustCall((methodsLeft, partial, cb) => {
events.push('client');
switch (++clientCalls) {
case 1:
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
return 'publickey';
case 2:
assert.deepStrictEqual(
methodsLeft,
['password'],
`expected 'password' method left, saw: ${methodsLeft}`
);
assert(partial === true, 'expected partial success');
return 'password';
}
}, 2),
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
let attempts = 0;
conn.on('authentication', mustCall((ctx) => {
assert(++attempts === clientCalls, 'server<->client state mismatch');
assert(ctx.username === username,
`Unexpected username: ${ctx.username}`);
events.push('server');
switch (attempts) {
case 1:
assert(ctx.method === 'publickey',
`Wrong auth method: ${ctx.method}`);
assert(ctx.key.algo === clientKey.key.type,
`Unexpected key algo: ${ctx.key.algo}`);
assert.deepEqual(clientKey.key.getPublicSSH(),
ctx.key.data,
'Public key mismatch');
ctx.reject(['password'], true);
break;
case 2:
assert(ctx.method === 'password',
`Wrong auth method: ${ctx.method}`);
assert(ctx.password === password,
`Unexpected password: ${ctx.password}`);
ctx.accept();
break;
}
}, 2)).on('ready', mustCall(() => {
conn.end();
}));
}));
client.on('close', mustCall(() => {
assert.deepStrictEqual(events, expectedEvents);
}));
}
{
const username = 'foo';
const password = '1234';
const { server } = setup(
'authHandler() (custom auth configuration)',
{
client: {
username: 'bar',
password: '5678',
authHandler: mustCall((methodsLeft, partial, cb) => {
assert(methodsLeft === null, 'expected null methodsLeft');
assert(partial === null, 'expected null partial');
return {
type: 'password',
username,
password,
};
}),
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username, `Wrong username: ${ctx.username}`);
assert(ctx.method === 'password', `Wrong auth method: ${ctx.method}`);
assert(ctx.password === password, `Unexpected password: ${ctx.password}`);
ctx.accept();
})).on('ready', mustCall(() => {
conn.end();
}));
}));
}
{
const username = 'foo';
const password = '1234';
const { server } = setup(
'authHandler() (simple construction with custom auth configuration)',
{
client: {
username: 'bar',
password: '5678',
authHandler: [{
type: 'password',
username,
password,
}],
},
server: serverCfg,
debug,
}
);
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustCall((ctx) => {
assert(ctx.username === username, `Wrong username: ${ctx.username}`);
assert(ctx.method === 'password', `Wrong auth method: ${ctx.method}`);
assert(ctx.password === password, `Unexpected password: ${ctx.password}`);
ctx.accept();
})).on('ready', mustCall(() => {
conn.end();
}));
}));
}