Overview

SSH is the encrypted access method — the modern counterpart to telnet for reaching the gateway, and the way the gateway dials out to remote shells.

Where telnet is a cleartext NVT stream the gateway implements byte-for-byte, SSH is a full encrypted transport. The gateway does not hand-roll the SSH wire protocol — it builds on the russh crate (pure-Rust SSH) and supplies the policy around it: host keys, authentication, the bridge into a terminal session, and trust decisions when dialing out. Everything here lives in src/ssh.rs (the inbound server and key management) and the gateway driver in src/telnet.rs (the outbound proxy).

Telnet vs. SSH. Use telnet (default port 2323) for retro hardware that can't do crypto — Commodore 64, CP/M, AltairDuino. Use SSH (default port 2222) for encrypted access from modern terminals. Both land on the same menus and features, and both share one credential pair and one lockout map. See the Telnet Negotiation Reference for the cleartext side.

Two Roles

The gateway speaks SSH in both directions — keep the two straight, because they use different keys.

RoleDirectionWhat it is
SSH ServerInbound — a client connects to the gatewayAn encrypted front door onto the same menus telnet offers. The gateway proves its identity with its host key; the client authenticates with the unified username / password. Off by default.
SSH GatewayOutbound — the gateway dials out to a remote SSH serverA proxy: from the main menu you open an interactive shell on some other machine. The gateway authenticates with either a password you type or its own client key, and verifies the remote's host key trust-on-first-use.

Inbound SSH Server

An encrypted way in, on port 2222 by default (disabled until you enable it).

PropertyValue
Default statessh_enabled = false — opt-in.
Default port2222 (ssh_port).
Implementationrussh server (src/ssh.rs).
Host keyEd25519, auto-generated on first run, persisted to ethernet_ssh_host_key (OpenSSH PEM, 0o600 on Unix).
AuthenticationPassword only, against the unified username / password (shared with telnet and the web UI). Constant-time comparison.
Brute-force lockoutShared per-IP map with telnet: 3 failures → 5-minute ban. auth_rejection_time is 1 s.
CapacityBounded by max_sessions; connections over the cap are rejected at auth.
How a session is served

SSH clients expect a shell, not a raw socket, so the server bridges the SSH channel into the gateway's ordinary terminal session:

Connection lifecycle

  1. The client connects; the server presents the Ed25519 host key so the client can verify it (and pin it in their own ~/.ssh/known_hosts).
  2. auth_password runs the per-IP lockout check, then a constant-time compare of the username and password, and — only on a successful match — claims a session slot, rejecting if the server is already at max_sessions. Claiming the slot at successful login (rather than at connect) means an unauthenticated peer that opens many connections and stalls can't exhaust the session cap. The credentials are snapshotted at connect time, so saving a new password mid-session never invalidates an already-authenticated connection.
  3. On a shell_request the server opens one shell per connection and builds a duplex bridge to a TelnetSession (started in ANSI mode). SSH input is forwarded into the bridge; the session's output is read back out and sent to the client as channel data.
  4. The session writer joins the shared broadcast list, so a server-wide shutdown notice reaches SSH clients too.
  5. Client EOF (or disconnect) tears the bridge down and the session count is released.
Server auth is password-only. Public-key login into the gateway's SSH server is not offered — the server accepts the unified password. (Public keys are used in the other direction; see the gateway.) This is a deliberate simplicity choice for the gateway's trusted-user threat model — see Security.

The Three Key Files

SSH touches three on-disk files in the gateway's working directory. They are easy to confuse — here is exactly what each one is for.

FileUsed whenWhat it holds
ethernet_ssh_host_keyA client connects to our SSH serverThe gateway's own SSH server host key (Ed25519 private key). This is the identity remote clients pin.
ethernet_gateway_ssh_keyWe dial out to a remote SSH server in key modeThe gateway's outgoing client keypair (Ed25519). Put its public half into the remote's authorized_keys.
gateway_hostsWe dial out and verify the remoteTrusted remote-server fingerprints — the gateway's equivalent of OpenSSH's known_hosts. One host:port algorithm base64 entry per line.

All three are written atomically and chmod 0o600 on Unix. The private keys carry no passphrase — the gateway process must use them without interaction, so the file mode is the at-rest protection. gateway_hosts is locked down too: the stored public keys are not secret, but the file also reveals the gateway's dial history (which hosts the operator has connected to), which other local users shouldn't see.


Outbound SSH Gateway

Proxy out to a remote shell. Press S from the main menu.

Connecting

  1. Press S at the main menu. The header shows the active auth mode (password or gateway key).
  2. At the first prompt, press K to display the gateway's public key (for pasting into a remote's authorized_keys), or any other key to continue.
  3. Enter the remote hostname / IP, the port (default 22), and your username on the remote.
  4. The gateway connects and verifies the remote's host key against gateway_hosts (see Host-Key Verification).
  5. It authenticates using the one configured method — password or gateway key (see Authentication).
  6. On success you get a full interactive shell. ANSI from the remote is stripped for PETSCII / ASCII terminals (see ANSI Filtering).
  7. Press Esc twice (PETSCII: the back-arrow key twice) to disconnect and return to the menu.

The outbound client uses a 600-second inactivity timeout and a bounded connect timeout, so a dead or unreachable host fails cleanly rather than hanging the session.


Host-Key Verification (TOFU)

Before authenticating, the gateway checks the remote's host key against gateway_hosts — trust-on-first-use, just like OpenSSH.

On connect, the gateway captures the remote's public host key and looks up host:port in gateway_hosts. One of three things happens:

StatusMeaningWhat the gateway does
KnownStored key matches the presented key.Proceeds silently to authentication.
UnknownNo entry for this host:port.Shows the key type and SHA-256 fingerprint and asks Trust this host? (Y/N). On Y it saves the key (a TOFU accept, logged) and continues; on N it disconnects.
ChangedStored key for this host does not match.Prints WARNING: HOST KEY HAS CHANGED! with the new fingerprint and makes you review before accepting — a possible man-in-the-middle.
# gateway_hosts — one entry per host:port
example.com:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
192.168.1.10:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
# blank lines and # comments are ignored
Independent of your OpenSSH known_hosts. Trust here is stored only in the gateway's gateway_hosts; it does not read or write any ~/.ssh/known_hosts. Every trust decision (first-time accept, change, rejection) is written to the server log for auditability.

Authentication

Inbound and outbound authenticate differently — and the outbound gateway uses exactly one method, chosen by config.

Into the server (inbound)

Password only, against the unified username / password shared across telnet, SSH, and the web UI, compared in constant time, behind the shared per-IP lockout.

Out through the gateway (outbound)

The ssh_gateway_auth key selects one method — there is no silent fallback, so the remote sees exactly one auth attempt and a failure is unambiguous:

ssh_gateway_authMethodBehavior
password (default)Keyboard passwordThe gateway prompts you for the remote password each dial and authenticates with it. Nothing is stored.
keyGateway client keyThe gateway authenticates with its own Ed25519 key (ethernet_gateway_ssh_key, auto-generated on first use). No password is requested and there is no fallback — the remote must already trust the key.

Setting up gateway-key (passwordless) dial-out

  1. Set ssh_gateway_auth = key in config (telnet, web, or GUI).
  2. At the SSH Gateway prompt, press K to print the gateway's public key in one-line OpenSSH form (ssh-ed25519 AAAA…, with no stray comment field).
  3. Append that single line to the remote account's ~/.ssh/authorized_keys.
  4. Future dials to that host authenticate with the key and skip the password prompt entirely.
This is not pubkey-then-password. In key mode the gateway never prompts for a password, and in password mode it never offers the key. If a key-mode dial fails, fix the remote's authorized_keys (or switch the mode back to password) — the gateway will not quietly try the other method.

ANSI Filtering for Vintage Terminals

A remote shell assumes a VT-style terminal. A Commodore 64 isn't one.

When you reach the SSH gateway from a PETSCII or ASCII terminal, the gateway strips ANSI escape sequences from the remote's output — CSI, OSC, DCS, PM, APC, and SOS sequences — so raw colour and cursor-control codes don't splatter across a screen that can't interpret them. The escape-state machine is carried across reads so sequences split over packet boundaries are still caught. ANSI terminals get the stream through unchanged.

See also: the ANSI Escape Sequences appendix for the exact codes being filtered, and the Telnet Reference for the same PETSCII / ASCII / ANSI terminal-type model on the cleartext side.

Security Model

SSH here is built for a trusted-user / LAN deployment, and the design reflects that.

  • Encrypted transport. russh provides the encrypted channel; the gateway never sends credentials or session data in clear over SSH.
  • Unified credentials. One username / password covers telnet, SSH, and the web UI — fewer secrets to manage. Compared in constant time to avoid timing leaks.
  • Shared lockout. The per-IP brute-force map is shared with telnet, so bouncing between protocols doesn't reset an attacker's failure count: 3 failures → 5-minute ban.
  • Host-key TOFU outbound. A changed remote host key is surfaced loudly rather than silently accepted.
  • Owner-only key files. Private keys and the dial-history file are 0o600, written atomically so they are never briefly world-readable.
Known limitations (by design). The inbound server is password-only (no client-certificate / pubkey login into the gateway), and private keys are stored without a passphrase because the process runs unattended. Both are deliberate trade-offs for the trusted-user / LAN threat model. If you expose the gateway to a hostile network, front it with a firewall or VPN.

Configuration Keys

Editable from the telnet Configuration menu, the GUI, the web UI, or egateway.conf directly.

KeyDefaultMeaning
ssh_enabledfalseEnable the inbound SSH server.
ssh_port2222Port the SSH server listens on.
ssh_gateway_authpasswordOutbound auth method: password (prompt each dial) or key (gateway client key, no fallback).
username / passwordadmin / changemeUnified credentials for telnet, SSH, and the web UI. Change the password before exposing the gateway.
max_sessions50Concurrent session cap, applied independently to telnet and SSH (each protocol allows up to this many; only the per-IP lockout map is shared between them).
File reminder: ssh_* config keys control behavior; the actual keys live in the ethernet_ssh_host_key, ethernet_gateway_ssh_key, and gateway_hosts files described under Key Files. The legacy separate ssh_username / ssh_password keys were removed — SSH now shares the one credential pair.

References

Deep-dive reference pages for every protocol and interface, plus the character-set tables and ANSI escape-sequence reference.

XMODEM

128-byte blocks; CRC-16 / checksum negotiation.

YMODEM

Block-0 metadata, batch, exact size truncation.

ZMODEM

Streaming; ZDLE, CRC-32, autostart, resume.

Kermit

Send-Init negotiation; F / A / D / Z / B transfer.

Punter

C1 dual checksum, two-phase, GOO/BAD/ACK.

AT Commands

Hayes command set, S-registers, +++ escape.

Telnet

IAC negotiation, every option, the NVT data phase.

SSH (this page)

Server, gateway, host keys, TOFU, auth modes.

Character Code Tables

Hex tables for every encoding: ASCII, ANSI, PETSCII, ATASCII, Baudot/ITA2, ZX Spectrum, TRS-80.

ANSI Escape Sequences

Cursor, colour/SGR, erase, and screen-mode escape codes, with the raw hex bytes.