SSH2 client and server modules written in pure JavaScript for node.js
—
Complete SSH server implementation for hosting SSH services with comprehensive authentication handling, session management, and client connection support.
Creates and manages SSH server instances with full protocol support.
/**
* SSH server for hosting SSH services
* Extends net.Server for network connection handling
*/
class Server extends net.Server {
constructor(config: ServerConfig, connectionListener?: ConnectionListener);
maxConnections: number;
}
interface ServerConfig {
// Required: Host keys for server identity
hostKeys: Array<Buffer | string | ParsedKey>;
// Protocol settings
algorithms?: AlgorithmList;
ident?: string;
banner?: string;
greeting?: string;
keepaliveInterval?: number;
keepaliveCountMax?: number;
// Debugging
debug?: DebugFunction;
}
type ConnectionListener = (client: IncomingClient, info: ClientInfo) => void;
interface ClientInfo {
ip: string;
header: {
identRaw: string;
versions: {
protocol: string;
software: string;
};
comments?: string;
};
}Usage Examples:
const { Server } = require('ssh2');
const fs = require('fs');
// Basic server setup
const hostKey = fs.readFileSync('host.key');
const server = new Server({
hostKeys: [hostKey]
}, (client) => {
console.log('Client connected!');
client.on('authentication', (ctx) => {
console.log(`Auth attempt: ${ctx.method} for ${ctx.username}`);
if (ctx.method === 'password') {
if (ctx.username === 'admin' && ctx.password === 'secret') {
ctx.accept();
} else {
ctx.reject();
}
} else {
ctx.reject(['password']);
}
});
});
server.listen(2222, '0.0.0.0', () => {
console.log('SSH server listening on port 2222');
});Represents a connected SSH client with authentication and session management.
/**
* Server-side representation of connected SSH client
* Extends EventEmitter for client lifecycle events
*/
class IncomingClient extends EventEmitter {
// Connection properties
authenticated: boolean;
noMoreSessions: boolean;
// Connection management
end(): boolean;
setNoDelay(noDelay?: boolean): boolean;
rekey(callback?: StatusCallback): boolean;
// Port forwarding
forwardOut(
boundAddr: string,
boundPort: number,
remoteAddr: string,
remotePort: number,
callback: ForwardOutCallback
): boolean;
// X11 forwarding
x11(
originAddr: string,
originPort: number,
callback: X11ForwardCallback
): boolean;
// OpenSSH extensions
openssh_forwardOutStreamLocal(
socketPath: string,
callback: ForwardOutCallback
): boolean;
}
type ForwardOutCallback = (err: Error | null, stream?: ServerChannel) => void;
type X11ForwardCallback = (err: Error | null, stream?: ServerChannel) => void;
type StatusCallback = (err?: Error | null) => void;Comprehensive authentication handling for different authentication methods.
/**
* Base authentication context for all authentication methods
*/
class AuthContext {
username: string;
user: string; // Alias for username
service: string;
method: string;
/**
* Accept the authentication attempt
*/
accept(): void;
/**
* Reject the authentication attempt
* @param methodsLeft - Optional array of allowed methods for continuation
* @param isPartial - Whether this is partial authentication success
*/
reject(methodsLeft?: string[], isPartial?: boolean): void;
}
/**
* Password authentication context
*/
class PwdAuthContext extends AuthContext {
password: string;
/**
* Request password change from client
* @param prompt - Password change prompt message
* @param callback - Callback receiving new password
*/
requestChange(prompt: string, callback: PasswordChangeCallback): void;
}
/**
* Public key authentication context
*/
class PKAuthContext extends AuthContext {
key: ParsedKey;
signature?: Buffer;
blob: Buffer;
hashAlgo?: string;
}
/**
* Host-based authentication context
*/
class HostbasedAuthContext extends PKAuthContext {
localHostname: string;
localUsername: string;
}
/**
* Keyboard-interactive authentication context
*/
class KeyboardAuthContext extends AuthContext {
submethods: string[];
/**
* Send prompts to client for keyboard-interactive authentication
* @param prompts - Array of prompts to send
* @param title - Optional title for prompt dialog
* @param instructions - Optional instructions
* @param callback - Callback receiving user responses
*/
prompt(
prompts: KeyboardPrompt[],
title?: string,
instructions?: string,
callback: KeyboardResponseCallback
): void;
}
type PasswordChangeCallback = (newPassword: string) => void;
type KeyboardResponseCallback = (responses: string[]) => void;Authentication Examples:
client.on('authentication', (ctx) => {
console.log(`Authentication attempt: ${ctx.method} for user ${ctx.username}`);
switch (ctx.method) {
case 'password':
if (ctx.username === 'admin' && ctx.password === 'secret') {
ctx.accept();
} else {
ctx.reject();
}
break;
case 'publickey':
// Verify public key against authorized keys
const authorizedKey = getAuthorizedKey(ctx.username);
if (ctx.key.type === authorizedKey.type &&
ctx.key.public.equals(authorizedKey.public)) {
if (ctx.signature) {
// Verify signature if provided
const sigOK = ctx.key.verify(getAuthData(ctx), ctx.signature, ctx.hashAlgo);
if (sigOK) {
ctx.accept();
} else {
ctx.reject();
}
} else {
ctx.accept(); // Key is acceptable, signature will be checked on next attempt
}
} else {
ctx.reject();
}
break;
case 'keyboard-interactive':
ctx.prompt([
{ prompt: 'Password: ', echo: false },
{ prompt: 'Token: ', echo: false }
], 'Two-Factor Authentication', 'Please provide password and token', (responses) => {
if (responses[0] === 'secret' && responses[1] === '123456') {
ctx.accept();
} else {
ctx.reject();
}
});
break;
default:
ctx.reject(['password', 'publickey']);
}
});Handle client session requests for shells, commands, and subsystems.
/**
* Session represents a client session request
*/
interface Session extends EventEmitter {
// Session type events
on(event: 'shell', listener: (accept: AcceptSession, reject: RejectSession) => void): this;
on(event: 'exec', listener: (accept: AcceptSession, reject: RejectSession, info: ExecInfo) => void): this;
on(event: 'subsystem', listener: (accept: AcceptSession, reject: RejectSession, info: SubsystemInfo) => void): this;
on(event: 'sftp', listener: (accept: AcceptSession, reject: RejectSession) => void): this;
// Session control events
on(event: 'pty', listener: (accept: AcceptPty, reject: RejectPty, info: PtyInfo) => void): this;
on(event: 'window-change', listener: (accept: AcceptWindowChange, reject: RejectWindowChange, info: WindowChangeInfo) => void): this;
on(event: 'x11', listener: (accept: AcceptX11, reject: RejectX11, info: X11RequestInfo) => void): this;
on(event: 'env', listener: (accept: AcceptEnv, reject: RejectEnv, info: EnvInfo) => void): this;
on(event: 'signal', listener: (accept: AcceptSignal, reject: RejectSignal, info: SignalInfo) => void): this;
on(event: 'auth-agent', listener: (accept: AcceptAgent, reject: RejectAgent) => void): this;
// Session lifecycle
on(event: 'end', listener: () => void): this;
on(event: 'close', listener: () => void): this;
}
interface ExecInfo {
command: string;
}
interface SubsystemInfo {
name: string;
}
interface PtyInfo {
cols: number;
rows: number;
width: number;
height: number;
term: string;
modes: { [mode: string]: number };
}
interface WindowChangeInfo {
cols: number;
rows: number;
width: number;
height: number;
}
interface X11RequestInfo {
single: boolean;
protocol: string;
cookie: string;
screen: number;
}
interface EnvInfo {
key: string;
val: string;
}
interface SignalInfo {
name: string;
}
type AcceptSession = () => ServerChannel;
type RejectSession = () => boolean;
type AcceptPty = () => void;
type RejectPty = () => boolean;
type AcceptWindowChange = () => void;
type RejectWindowChange = () => boolean;
type AcceptX11 = () => void;
type RejectX11 = () => boolean;
type AcceptEnv = () => void;
type RejectEnv = () => boolean;
type AcceptSignal = () => void;
type RejectSignal = () => boolean;
type AcceptAgent = () => void;
type RejectAgent = () => boolean;Session Handling Examples:
client.on('ready', () => {
console.log('Client authenticated!');
client.on('session', (accept, reject) => {
const session = accept();
session.on('shell', (accept, reject) => {
const stream = accept();
console.log('Client requested shell');
// Set up shell interaction
stream.write('Welcome to SSH server!\n$ ');
stream.on('data', (data) => {
const command = data.toString().trim();
console.log('Command received:', command);
if (command === 'exit') {
stream.exit(0);
stream.end();
} else {
stream.write(`Command output for: ${command}\n$ `);
}
});
});
session.on('exec', (accept, reject, info) => {
const stream = accept();
console.log('Client wants to execute:', info.command);
// Execute command and send output
stream.write(`Output of ${info.command}\n`);
stream.exit(0);
stream.end();
});
session.on('sftp', (accept, reject) => {
console.log('Client requested SFTP subsystem');
const sftpStream = accept();
// Set up SFTP handling...
});
});
});interface ServerEvents {
/**
* Emitted when a new client connects
*/
'connection': (client: IncomingClient, info: ClientInfo) => void;
/**
* Emitted when server error occurs
*/
'error': (err: Error) => void;
/**
* Emitted when server starts listening
*/
'listening': () => void;
/**
* Emitted when server is closed
*/
'close': () => void;
}interface IncomingClientEvents {
/**
* Emitted for each authentication attempt
*/
'authentication': (ctx: AuthContext) => void;
/**
* Emitted when client is successfully authenticated
*/
'ready': () => void;
/**
* Emitted when client requests a new session
*/
'session': (accept: AcceptSession, reject: RejectSession) => void;
/**
* Emitted for direct TCP/IP connection requests (from local forwarding)
*/
'tcpip': (
accept: AcceptTcpip,
reject: RejectTcpip,
info: TcpipInfo
) => void;
/**
* Emitted for OpenSSH streamlocal connection requests
*/
'openssh.streamlocal': (
accept: AcceptStreamLocal,
reject: RejectStreamLocal,
info: StreamLocalInfo
) => void;
/**
* Emitted for global requests
*/
'request': (
accept: AcceptRequest,
reject: RejectRequest,
name: string,
info: RequestInfo
) => void;
/**
* Emitted when key re-exchange is initiated
*/
'rekey': () => void;
/**
* Emitted when client connection ends
*/
'end': () => void;
/**
* Emitted when client connection is closed
*/
'close': () => void;
/**
* Emitted when client connection error occurs
*/
'error': (err: Error) => void;
}interface ServerChannel extends Duplex {
stderr: Writable;
/**
* Send exit status to client
*/
exit(status: number): boolean;
/**
* Send signal to client
*/
signal(signal: string): boolean;
/**
* Close the channel
*/
close(): boolean;
/**
* Set window size
*/
setWindow(rows: number, cols: number, height?: number, width?: number): boolean;
}interface TcpipInfo {
srcIP: string;
srcPort: number;
destIP: string;
destPort: number;
}
interface StreamLocalInfo {
socketPath: string;
}
interface RequestInfo {
bindAddr?: string;
bindPort?: number;
socketPath?: string;
[key: string]: any;
}
type AcceptTcpip = () => ServerChannel;
type RejectTcpip = () => boolean;
type AcceptStreamLocal = () => ServerChannel;
type RejectStreamLocal = () => boolean;
type AcceptRequest = () => void;
type RejectRequest = () => boolean;interface KeyboardPrompt {
prompt: string;
echo: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-ssh2