SSH client and server
- Shell 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The client under a talc terminal asks with the channel request forms@talc; sshd gives the session's child a forms pipe as TERMINAL_FORMS (inherited by that child alone), its forms sent back as extended data of code 2 and written on the client's forms pipe. A server that refuses the request is answered nothing more; -noforms asks nothing. regress-live checks both servers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| examples | ||
| lib/ssh | ||
| scripts | ||
| tools | ||
| .gitignore | ||
| README | ||
| sshd.talc | ||
| VERSION | ||
ssh: an SSH server and client for the talc system, so that a machine can
be reached from any other -- a shell, a command, files both ways -- and
can reach any other the same way. It speaks SSH 2 exactly, so the other
end may be OpenSSH; the implementation is talc's own. One set of
algorithms and nothing to negotiate but agreeing to it: curve25519-sha256,
ssh-ed25519 keys, chacha20-poly1305@openssh.com, strict key exchange.
Keys only, never a password; no forwarding, no agent, no compression.
The names and the files are SSH's own, because people reach this from
other systems.
From ../terminal, `ssh host` runs with no terminal on the other side --
this side's input is a pipe -- so the lines typed go to a shell that
reads them one by one, its output and its error stream come back told
apart, and its exit status is the command's: clean, and no prompt. For
prompts from a bash host, `ssh -t -noecho host "bash --noediting -i"`:
a terminal on the other side told not to echo (ECHO off, ECHONL on in
the pty-req's modes), and a bash that lets it do the reading; a shell
with a line editor of its own (readline, zle) echoes and paints
regardless, which a terminal that is not a VT100 cannot use.
From ../terminal to another talc machine, a program there that shows
a toolkit form (tirc, an application of the terminal's) is shown in
this terminal, as if it ran here. The client, finding TERMINAL_FORMS in
its environment, asks with the channel request forms@talc; sshd gives
what runs in that session a forms pipe of its own as TERMINAL_FORMS,
whose forms travel back as extended data of code 2 (1 is the error
stream) and are written on the client's forms pipe, and the events the
terminal sends come back on standard input as lines, as typed lines do.
The pipe reaches that session's child alone. A server that does not
know the request (OpenSSH) refuses it and the session goes on without;
-noforms asks nothing.
ssh -p 2222 root@127.0.0.1 '(run "/programs/tirc")'
sshd.talc the server: lets a user named in the machine's
users file (../users) in on a key in
<home>/.ssh/authorized_keys, and gives them a
shell, runs a command, or serves files
lib/ssh/wire.talc (ssh wire): SSH's data types and message numbers
lib/ssh/transport.talc (ssh transport): the version exchange, the
binary packet protocol, the key exchange and
rekeying -- a state machine of bytes in,
actions out, the random bytes handed in
lib/ssh/keys.talc (ssh keys): ed25519 keys in OpenSSH's files
(openssh-key-v1, the public line,
authorized_keys, known_hosts), blobs,
signatures, fingerprints
lib/ssh/authenticate.talc (ssh authenticate): the publickey exchange
both ways; the host-key verdict
lib/ssh/channels.talc (ssh channels): channels and their windows
lib/ssh/terminal.talc (ssh terminal): the server's pseudo-terminal
(after ../terminal), the client's raw mode
lib/ssh/client.talc (ssh client): what a program imports to reach
another machine -- connect, a session, a
command or a subsystem, read and write
lib/ssh/files.talc (ssh files): SFTP version 3, the packets and
the client, with reads and writes kept in
flight for whole files
lib/ssh/files/serve.talc (ssh files serve): SFTP's server side, a
request in, the answers out
lib/ssh/local-files.talc (ssh local-files): what SFTP needs of the
file system that (system files) has not:
reading and writing at an offset, a file's
status, times, size, owner, links
tools/ssh.talc `ssh [user@]host [command]`
tools/scp.talc `scp`: files and trees both ways, over SFTP
tools/sftp-server.talc `sftp-server`: SFTP on its input and output,
what sshd runs for the sftp subsystem
tools/ssh-keygen.talc `ssh-keygen`: an ed25519 key pair
tools/sftp.talc `sftp`, the interactive client [milestone 4]
examples/ the libraries' tests: wire, keys (byte for byte
against a key ssh-keygen made), the exchange in
memory, channels and authentication, files (the
SFTP client and server joined in one program)
scripts/build.sh builds everything under tools/ and sshd
scripts/regress.sh the tests under examples/
scripts/regress-live.sh on this host, no root, against OpenSSH both
ways: commands, shells, files, rekeying
scripts/vm-ssh.sh the vm reached from this host over a port
forward, and reaching back
Build and check:
scripts/build.sh
scripts/regress.sh
scripts/regress-live.sh (needs OpenSSH: ssh, sshd, sftp, sftp-server)
scripts/vm-ssh.sh (needs OpenSSH, qemu and ../vm buildable)
which need the installed toolchain (compiler on PATH) and ../network,
../users and ../services built beside this. The live tests run
unprivileged on high ports and touch nothing the system's own sshd owns.
RUNNING IT. The server is started by the supervisor (../services) from
definitions/sshd.service, after the network; it makes its host key at
first start under /settings/ssh if there is none and prints the fingerprint to
its log, reads the users at /settings/users and each user's keys from
<home>/.ssh/authorized_keys, and listens on port 22. It must be
privileged to give a session another user's numbers; unprivileged, it
serves its own user alone (which is how the live tests run).
sshd [-keys <dir>] [-users <path>] [-port n | -p n] [-address a]
[-run <dir>] [-sftp-server <path>] [-within seconds] [-seconds n] [-trace]
What a session gets: a shell on a pseudo-terminal when the client asks
for one, or on pipes when it does not; a command; or the sftp subsystem
(sftp-server, beside sshd unless -sftp-server says, started as the user,
so every file access is the kernel's decision about that user). A command
is written to a file under -run (/running/sshd) and the user's shell runs it
as a script, `shell <file>`, the form every shell understands: the talc
shell has no -c. Its language is the talc shell's, so from another
machine:
ssh -p 2222 root@localhost '(run "list" "-long")'
prints the listing and then the status, as the prompt would.
The clients find a machine's name through the network manager (NETWORK
in the environment), or take an address; they keep host keys in
~/.ssh/known_hosts and offer the key at ~/.ssh/id_ed25519 unless -i says
another.
ssh [-p port] [-i key] [-l user] [-t | -T] [-strict] [-known path] [-v] [user@]host [command ...]
scp [-P port] [-i key] [-r] [-p] [-q] [-v] [-strict] [-known path] source ... target
ssh-keygen [-t ed25519] [-f path] [-N ""] [-C comment] | -l -f path | -y -f path
sftp-server [-users <path>]
THE FILES ARE OPENSSH'S. A key made here is read by OpenSSH and the other
way round; `ssh-copy-id` from another machine appends to a user's
authorized_keys; a known_hosts or authorized_keys line pastes between
machines. The one thing not read is an encrypted private key: this system
has no passphrase ciphers, so `ssh-keygen -p -N ""` on the other side
takes the passphrase off first.
HOW FAST. About 255-345 MB a second for a 64 MB file on this host
against OpenSSH (talc scp to and from OpenSSH's sshd 255-345, OpenSSH's
scp to and from the talc sshd 255-290), and 280-295 talc to talc, where
OpenSSH to OpenSSH does 200-265 (the host varies).
scripts/regress-live.sh prints its rates too; they are lower, since it
rekeys every megabyte.
A NOTE ON THE WARNING. OpenSSH 10's client prints a line that the key
exchange is not post-quantum. It is not a refusal; the connection is made.
The cure, if it ever matters, is mlkem768x25519-sha256 (ML-KEM in talc),
which changes nothing else here.