((section 2 "Outdated egg!" (p "This is an egg for CHICKEN 4, the unsupported old release.  You're almost certainly looking for " (int-link "/eggref/5/minissh" "the CHICKEN 5 version of this egg") ", if it exists.") (p "If it does not exist, there may be equivalent functionality provided by another egg; have a look at the " (link "https://wiki.call-cc.org/chicken-projects/egg-index-5.html" "egg index") ". Otherwise, please consider porting this egg to the current version of CHICKEN.")) (section 2 "SSH for CHICKEN" (toc) (p "A SSH-2 server and client implementation for " (link "http://call-cc.org" "CHICKEN") " Scheme. It supports a limited suite of ciphers. Not enough to be standards compliant, but enough to work with " (link "https://www.openssh.com/" "OpenSSH") " versions " (link "https://www.openssh.com/txt/release-6.5" "6.5 and above") " from 2013.") (p (tt "minissh") " is intended to be compliant with " (link "https://www.openssh.com/" "OpenSSH") " and itself. It runs on " (link "http://call-cc.org" "CHICKEN") " versions 4 and 5.") (section 3 "Compatibility" (p (tt "minissh") " servers will only accept " (link "https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-00" "ssh-ed25519") " user keys, so " (link "https://www.openssh.com/" "OpenSSH") " clients will have to do " (tt "ssh-keygen -t ed25519") ".") (p (tt "minissh") " clients will only work with servers which have " (link "https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-00" "ssh-ed25519") " host-keys. These are generated on recent versions of " (link "https://www.openssh.com/" "OpenSSH") " by default. If you run into trouble, check for something like " (tt "/etc/ssh/ssh_host_ed25519_key.pub") ".") (section 4 (link "https://tools.ietf.org/html/rfc4253" "SSH Transport Layer") (p "The SSH-2 transport layer provides a packet-based channel over which packets (and their length) is encrypted and where the server (accepting tcp connections) is authenticated. Clients (initiating tcp connections) are authenticated in a separate layer (" (tt "userauth-publickey") " and " (tt "userauth-password") "). The other SSH layers sit on top of the transport layer.") (p "The SSH-2 procotol supports a negotiating a large veriety of ciphers. " (tt "minissh") " only supports a single selection of these:") (ul (li "kex algorithms:                  " (link "https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD" "curve25519-sha256@libssh.org")) (li "user authentication:             " (link "https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-00" "ssh-ed25519")) (li "server host key algorithms:      " (link "https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-ed448-00" "ssh-ed25519")) (li "encryption algorithms:           " (link "https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD" "chacha20-poly1305@openssh.com")) (li "mac algorithms:                  only implicitly through chacha20-poly1305") (li "compression:                     none")) (p "Note that " (tt "minissh") " is missing support for a lot of \"REQUIRED\" ciphers and may not work on many SSH implementations.")))) (section 2 "API" (toc) (p "All calls uses blocking semantics and should be thread-safe.") (section 3 "Key API" (def (sig (procedure " (ssh-keygen type)" (id ssh-keygen))) (p "Mimics OpenSSH's " (tt "ssh-keygen -t ed25519") ". " (tt "type") " must be " (tt "'ed25519") ". Returns two values: public key as a base64 encoded string and a secret key as a blob. Users of this egg is responsible for handling the secret key with the right amount of precaution.") (p "The public key is encoded the same way as " (link "https://www.openssh.com/" "OpenSSH") "'s public keys. This should make it simple to move things around between " (tt "minissh") ", " (tt "~/.ssh/known_hosts") " and " (tt "~/.ssh/authorized_keys") ". See " (tt "examples/client-publickey.scm") "."))) (section 3 "Client API" (def (sig (procedure " (ssh-connect host port verifier)" (id ssh-connect))) (p "Connects to a SSH server on " (tt "host:port") ". " (tt "verifier") " is called with the the server's public key and must return " (tt "#f") " if the host is not recognized.") (p (tt "ssh-connect") " returns an " (tt "ssh") " client session which provides an encrypted, packet-based transport layer to an authenticated server.") (p "Following SSH-2 procedures, the client must initiate user authentication next using the procedures below.")) (def (sig (procedure " (userauth-publickey ssh user pk sk)" (id userauth-publickey))) (p "Tries to log in to " (tt "ssh") " using the public key (base64 string) and secret key (blob) provided. Returns " (tt "#t") " on successful login, " (tt "#f") " otherwise.") (p "It is an error to call this when " (tt "(ssh-user ssh)") " is already set.")) (def (sig (procedure " (userauth-password ssh user password)" (id userauth-password))) (p "Tries to log in to " (tt "ssh") " using the username and password provided. The password is not sent in cleartext. It is the user's responsibility to treat " (tt "password") " with the right amount of precaution.") (p "It is an error to call this when " (tt "(ssh-user ssh)") " is already set."))) (section 3 "Server API" (def (sig (procedure " (ssh-server public-key secret-key handler #!key (port 22022))" (id ssh-server))) (p "Listens on tcp port " (tt "port") " and, for each incoming connection, establishes an SSH session by authenticating itself using " (tt "public-key") " (blob) and " (tt "secret-key") " (blob) then calls " (tt "(handler ssh)") " in a new srfi-18 thread, where " (tt "ssh") " is an encrypted SSH server session.") (p "Following SSH-2 procedures, the server awaits user authentication. Therefore, the first thing " (tt "handler") " does is typically to call " (tt "userauth-accept") ".")) (def (sig (procedure " (userauth-accept ssh #!key publickey password banner)" (id userauth-accept))) (p "Authenticate the user incoming authentication request. The callbacks are as follows.") (ul (li (tt "publickey: (lambda (user type pk signed?) ...)") " Allow public key logins and deny access to users where this procedure returns " (tt "#f") ". Grant access otherwise. To save CPU power, servers may ask if " (tt "pk") " would be allowed before generating the actual signature. So this procedure may be called where " (tt "signed?") " is " (tt "#f") " before being called again where " (tt "signed?") " is " (tt "#t") ".") (li (tt "password: (lambda (user password) ...)") " Allow password login and deny access to users where this procedure returns " (tt "#f") ". Grant access otherwise. " (tt "users") " is string. " (tt "password") " is the plaintext password string.") (li (tt "banner: (lambda (user granted? pk) ...)") " Called when granting or denying " (tt "user") " access as " (tt "granted?") " indicates with " (tt "#t") " or " (tt "#f") ". Must returns a string or " (tt "#f") " for no banner. Note that clients may not display banners in the terminal. " (tt "pk") " is the public key of the user for publickey login attempts or " (tt "#f") " for password login attempts. The banner string should return a trailing newline.")) (p "Each callback may be called multiple times. Either " (tt "publickey") ", " (tt "password") " or both must be supplied."))) (section 3 "Channel API" (section 4 "Creating channels" (def (sig (procedure " (channel-accept ssh)" (id channel-accept))) (p "Typically run by SSH servers. Blocks until the remote side requests to open a session channel to run a command. Returns a ssh channel object for the new channel.")) (def (sig (procedure " (channel-exec ssh cmd)" (id channel-exec))) (p "Typically run by SSH clients. Requests to open a session channel and run command " (tt "cmd") ". If remote side replies with success, returns a ssh " (tt "channel") " object. If remote side replies with failure, throws an error."))) (section 4 "Working with channels" (def (sig (procedure " (channel-command channel)" (id channel-command))) (p "Return the command string for " (tt "channel") ". As in " (tt "ssh -p 22022 localhost \"command string\"") " or " (tt "(channel-exec ssh \"command string\")") ". For interactive shell sessions, this returns " (tt "#f") ".")) (def (sig (procedure " (channel-read channel)" (id channel-read))) (p "Read the next data packet from " (tt "channel") ". Returns two values:") (ul (li "the data as a string") (li "the " (link "https://tools.ietf.org/html/rfc4254#section-5.2" "data type code") " which is " (tt "#f") " for normal data and a fixnum for extended data packets where 1 represents stderr.")) (p "The remote window size size is adjusted to stay between 1-2 MiB.")) (def (sig (procedure " (channel-write channel str #!optional extended)" (id channel-write))) (p "Sends a SSH data packet with " (tt "str") " to " (tt "channel") ". This respects the SSH-2 channel window size limitations and may therefore block waiting for window size adjustments. " (tt "extended") " may be supplied as " (tt "'stderr") " or a fixnum for extended data packets.")) (def (sig (procedure " (channel-eof channel)" (id channel-eof))) (p "Sends an SSH eof packet to " (tt "channel") ". This indicates that no more data will be sent, often resulting in the remote end initiating to close. Incoming data is unaffected.")) (def (sig (procedure " (channel-close channel)" (id channel-close))) (p "Closes " (tt "channel") " and also sends an SSH close packet unless " (tt "channel") " is already closed. It is an error to call " (tt "channel-write") " on a channel which is closed.")) (def (sig (procedure " (channel-input-port channel)" (id channel-input-port)) (procedure " (channel-output-port channel)" (id channel-output-port)) (procedure " (channel-error-port channel)" (id channel-error-port)) (procedure " (with-channel-ports channel thunk)" (id with-channel-ports)) (procedure " (with-channel-ports* channel thunk)" (id with-channel-ports*))) (p "Wrap channel calls into ports. " (tt "channel-input-port") " does " (tt "(channel-read channel)") " and ignores the extended data index, so it cannot distinguish between " (tt "stdout") " and " (tt "stderr") ". " (tt "channel-output-port") " does " (tt "(channel-write channel str)") " and " (tt "channel-error-port") " does " (tt "(channel-write ch 'stderr)") ".") (p (tt "with-channel-ports") " calls " (tt "thunk") " with " (tt "current-input-port") " and " (tt "current-output-port") " bound to " (tt "channels") "'s ports. " (tt "with-channel-ports*") " also wraps " (tt "current-error-port") ". This may sometimes cause problems as runtime errors are printed onto " (tt "channels") "'s stderr.")))) (section 3 "Key exchange API" (def (sig (procedure " (kexinit-start ssh)" (id kexinit-start))) (p "Explicitly demand renegotiation of keys. This blocks other senders until the key exchange process is complete. " (link "https://www.openssh.com/" "OpenSSH") " clients will initiate this after 1GiB of data."))) (section 3 "Logging API" (def (sig (parameter " (ssh-log? #t)" (id ssh-log?)) (parameter " (ssh-log-payload? #f)" (id ssh-log-payload?))) (p "Tune logging verbosity with these parameters. Default values are shown above. " (tt "(ssh-log? #f)") " shuts off logging completely. " (tt "(ssh-log-payload? #t)") " turns on logging on parsed packet content which may be useful during SSH debugging.")))) (section 2 "Notes" (toc) (section 3 ("Configuring " (link "https://www.openssh.com/" "OpenSSH") " with " (tt "ControlMaster")) (p "The SSH-2 protocol allows multiplexing multiple channels over a single TCP connection. This means multiple programs may be started with a single login. See the " (link "https://man.openbsd.org/ssh_config#ControlMaster" ((tt "ControlMaster"))) " ssh config option for how to apply this in your OpenSSH client.")) (section 3 "Server vs Client channels" (p "The SSH-2 protocol does not dictate that only servers should accept new channels. However, " (link "https://tools.ietf.org/html/rfc4254#section-6.1" "RFC4254") " says:") (p "blockquoteClient implementations SHOULD reject any session channel open requests to make it more difficult for a corrupt server to attack the client.") (p (tt "minissh") " supports client that call " (tt "channel-accept") " and servers that call " (tt "channel-exec") ", though this is unconventional."))) (section 2 "TODO" (toc) (section 3 "fix known bug: will never initiate key negitiation" (p (link "https://tools.ietf.org/html/rfc4253#section-9" "RFC4253s9") " says:") (p "blockquoteIt is RECOMMENDED that the keys be changed after each gigabyte of transmitted data or after each hour of connection time, whichever comes sooner.  However, since the re-exchange is a public key operation, it requires a fair amount of processing power and should not be performed too often.") (p "minissh will currently never initiate a key exchange (but will respond correctly to when the remote side initiates). You can call " (tt "kexinit-start") " to explicitly renegotiate keys.")) (section 3 "plus these things" (ul (li "benchmark: faster read-string! based channel-input-port") (li "transport: allow querying current encryption level") (li "channels: pty handling?") (li "channels: do some buffering (don't send 1-byte SSH packets)") (li "find a faster current-entropy-port") (li "reply with unimplemented when receiving unhandled messages")))))