Files

4.2 KiB

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.