CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ssh2

SSH2 client and server modules written in pure JavaScript for node.js

Pending
Overview
Eval results
Files

ssh-server.mddocs/

SSH Server

Complete SSH server implementation for hosting SSH services with comprehensive authentication handling, session management, and client connection support.

Capabilities

Server Class

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');
});

IncomingClient Class

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;

Authentication Context Classes

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']);
  }
});

Session Management

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...
    });
  });
});

Events

Server Events

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;
}

Client Events

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;
}

Type Definitions

Server Channel Types

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;
}

Connection Request Types

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;

Keyboard Interactive Types

interface KeyboardPrompt {
  prompt: string;
  echo: boolean;
}

Install with Tessl CLI

npx tessl i tessl/npm-ssh2

docs

http-tunneling.md

index.md

key-management.md

port-forwarding.md

sftp-operations.md

ssh-agents.md

ssh-client.md

ssh-server.md

tile.json