to-do: rewrite & convert to html for docs pages # SSH MontaukOS ships an SSH-2 server, `sshd`, that gives a remote client a shell on the machine. It speaks enough of the protocol to interoperate with stock OpenSSH clients: ``` ssh dan@montauk-box interactive shell ssh dan@montauk-box 'ls 0:/' run one command ``` Remote access is **off by default**. Turn it on from Settings > SSH Server, which flips `services.ssh.enabled` in `0:/config/init.toml`; `init` starts the daemon on the next boot. Two gates stand in front of a login: the account must exist in the MontaukOS user database with a valid password, and it must be listed in the `[allow]` table of `0:/config/ssh.toml`. A user absent from that table is refused even with the right password. The Settings applet maintains the table; `sshd` never writes it. ## Layout ``` programs/src/sshd/ main.cpp transport framing, key exchange, auth, session/channel loop crypto.hpp host key storage, DH group 14 arithmetic, cipher/MAC wrappers programs/src/sshserver/ main.cpp Settings applet: enable the service, edit the allow table programs/include/montauk/ssh.h shared policy helpers used by both ``` The crypto primitives (SHA-256, HMAC, AES-CTR, RSA signing, the DRBG) come from BearSSL. The modular exponentiation for Diffie-Hellman is in `crypto.hpp` because BearSSL exposes no public bignum API. ## What it implements | | | |---|---| | Key exchange | `diffie-hellman-group14-sha256` | | Host key | `rsa-sha2-256`, 2048-bit, generated on first start | | Cipher | `aes128-ctr` | | MAC | `hmac-sha2-256` | | Compression | none | | Auth | password only | Client-initiated rekeying works, so long sessions survive OpenSSH's 1 GiB / one-hour rekey threshold. Channel windows are tracked in both directions, so sessions that move more than a window's worth of data keep flowing. ## What it does not No public key authentication, no port or X11 forwarding, no SFTP or SCP. One session channel per connection, and connections are served one at a time -- there is no `fork`, so a second client waits for the first to finish. A pre-authentication timeout keeps an idle peer from holding the daemon shut. `exec` requests are run by typing the command into an interactive shell rather than executing it directly, so the prompt and the echoed command come back mixed into the output. This is why `scp` and friends will not work as-is. ## The host key Generated on first start and written to `0:/config/ssh_hostkey.toml`, separate from the policy file so the unprivileged Settings applet -- which rewrites `ssh.toml` whenever the allow table changes -- never round-trips private key material. That separation is hygiene, not protection: **the filesystem has no permission model**, so any local process can read the host key. Closing that hole needs FS-level access control, at which point the key file should be restricted to the account `sshd` runs as. A key found in an older `ssh.toml` is migrated to the new file on first start so the host key does not change under existing clients. ## Debugging `sshd` calls `montauk::print`, which reaches the boot console only. **It has no usable diagnostics once the desktop is up**, pending the unified syslog. Worth understanding, because it catches out anything written as a daemon: `montauk::print` is `SYS_PRINT`, which writes the kernel *terminal*, not the kernel *log*. Only in-kernel `KernelLogStream` writes raise `g_kernelLogDepth`, and only those append to the ring buffer `SYS_LOG` reads -- so daemon output never shows up in `klog`. On top of that, `Sys_Print` returns early once `g_suppressKernelLog` is set, which the desktop does at startup, so the output is discarded outright from then on. `init` spawns services with `spawn` rather than `spawn_redir`, so there is no stream to capture either. Until there is somewhere for it to go, debugging a handshake means watching the boot console, or temporarily pointing the print calls at a file. Failures print a numeric code -- `key exchange failed (N)`, `authentication failed (N)` -- where N identifies the step; see the `kex_error` assignments in `main.cpp`. Running the client with `ssh -vvv` and lining its trace up against those codes is usually the fastest way to find where a handshake diverged.