puressh
A pure-Rust SSH (Secure Shell)
protocol library and CLI suite, in the spirit of libssh,
built on purecrypto for every
cryptographic primitive. No C dependencies, no FFI in the dependency tree, and
no unsafe in the library itself (the optional ffi feature is the only place
unsafe appears, for the C ABI surface).
Status: functional, pre-1.0. Client, server, SFTP, SCP, port/agent/X11 and Unix-socket (StreamLocal) forwarding,
known_hosts, andssh_configparsing are all implemented and tested against OpenSSH. Blocking, async, tokio, and mio frontends share one sans-I/O core. The crate is0.0.x; the public API may still change before 1.0.
Install
Ubuntu (apt)
A signed apt repository is published for 20.04, 22.04 and 24.04 (amd64 and
arm64). puressh-server is a drop-in replacement for openssh-server;
puressh-client installs the client programs alongside OpenSSH's.
|
|
Installing puressh-server removes openssh-server but keeps
/etc/ssh/sshd_config and the host keys; sudo apt install openssh-server
switches back. See packaging/apt/README.md for how
the repository is built.
Other platforms
Prebuilt ssh / sshd (and the C library) for Linux, macOS and Windows are
attached to each GitHub release.
From source
Or add the library to a project:
What's in the box
- Library — a sans-I/O protocol core (the
driverstate machines) plus high-level blockingclient/serverAPIs built on top of it. - Async — optional frontends driving the same
ClientDriver/ServerDriveras the blocking APIs: runtime-agnostic overfutures_io(async), with native entry points for tokio (tokio) and a readiness/non-blocking mio-style client (mio). - CLI suite — drop-in
ssh,sftp,scp,sshd,ssh-keygen,ssh-agent,ssh-addandssh-keyscanbinaries built on the library. hazmat— the raw protocol layers the drivers are assembled from (wire format, packet codec, KEX, ciphers, MACs, channels). Public for people building their own frontends or tooling, but with no stability promise and plenty of ways to weaken security if misused; ordinary users never need it.- C ABI — optional
ffifeature exposing apcssh_*C interface (staticlib/cdylib), with bytes-path SFTP variants for non-UTF-8 paths.
Goals
- Pure Rust, no FFI deps. Crypto comes from
purecrypto; networking comes fromstd::net. Nothing links C. no_stdfriendly. The protocol core (packet codec, KEX, cipher/MAC adapters, key parsing) builds withoutstd; only the convenience I/O and the client/server/CLI layers require it.- Modern algorithms first, including the post-quantum hybrid KEXes
(
mlkem768x25519-sha256,sntrup761x25519-sha512). Legacy algorithms are present only where they remain useful for interop. - Auditable surface. Small modules, narrow public types, every algorithm identifier tracked back to its RFC.
Supported algorithms
| Category | Algorithm |
|---|---|
| KEX (PQ) | mlkem768x25519-sha256 (ML-KEM-768 + X25519 hybrid), sntrup761x25519-sha512 (+ its @openssh.com name; Streamlined NTRU Prime + X25519) |
| KEX | curve25519-sha256, curve25519-sha256@libssh.org |
| KEX | ecdh-sha2-nistp{256,384,521} |
| KEX | diffie-hellman-group{14,16,18}-sha{256,512} |
| KEX | diffie-hellman-group-exchange-sha256 |
| Host key | ssh-ed25519 |
| Host key | ecdsa-sha2-nistp{256,384,521} |
| Host key | rsa-sha2-256, rsa-sha2-512, ssh-rsa (with auto-upgrade via server-sig-algs) |
| Host key (PQ) | ssh-mldsa44-ed25519@openssh.com (ML-DSA-44 + Ed25519 composite; opt-in, as in OpenSSH) |
| Cipher | chacha20-poly1305@openssh.com |
| Cipher | aes{128,256}-gcm@openssh.com |
| Cipher | aes{128,192,256}-ctr |
| MAC | hmac-sha2-{256,512} (+ -etm@openssh.com variants) |
| Compression | none, zlib, zlib@openssh.com (delayed) |
| Auth | none, password, publickey, keyboard-interactive |
| Extensions | RFC 8308 ext-info / server-sig-algs |
Cargo features
| Feature | Default | Description |
|---|---|---|
std |
yes | I/O helpers, OS RNG, std::error::Error |
alloc |
yes | Heap-backed types (implied by std) |
client |
yes | High-level client API |
server |
no | High-level server API (incl. the SFTP server); pulls in nix on Unix |
compress |
yes | zlib compression via compcol |
pam |
no | PAM session integration for sshd (Linux only); implies server |
multichannel |
yes | Concurrent multi-channel client (SharedClient, SftpSession) |
knock |
yes | Stealth UDP knock rendezvous in front of SSH (puressh-only, see docs/knock.md) |
agent |
no | Agent daemon side (agent::AgentState, the ssh-agent binary); pulls in nix on Unix. The agent client is always available |
async |
no | Runtime-agnostic async frontends (AsyncClient, AsyncServerConnection) over futures_io |
tokio |
no | Native tokio entry points (connect_tokio / accept_tokio) accepting tokio::io streams; builds on async |
mio |
no | Native readiness frontend (MioClient) for a mio-style Poll loop; no mio dependency in the library |
ffi |
no | C ABI surface (pcssh_*); implies client + multichannel |
The default build is a client library and does not depend on nix: the
client is sans-IO underneath, and the blocking frontend sticks to std.
server and agent are opt-in and bring nix with them on Unix. A custom transport can let
SharedClient wait for input without holding its lock by implementing
Transport::split_reader.
Disable defaults for no_std:
= { = "0.1.4", = false, = ["alloc"] }
Quick start
use ;
For concurrent channels (several SFTP / exec / shell / tunnel handles on one
connection), use the multichannel layer's SharedClient and its
sftp() / exec_stream() / shell() / open_direct_tcpip() helpers.
With the async feature, the same flow is available without blocking on any
particular runtime — AsyncClient::connect takes any
futures_io::AsyncRead + AsyncWrite stream (tokio via tokio-util's Compat,
smol/async-std, etc.):
use Config;
use AsyncClient;
let mut c = connect.await?;
c.authenticate_password.await?;
let out = c.exec.await?;
Two further features expose native entry points on the same sans-I/O core, for callers who would rather not wire up the compat shim themselves:
tokio—AsyncClient::connect_tokio/AsyncServerConnection::accept_tokiotake tokio's ownAsyncRead/AsyncWritestreams (e.g.tokio::net::TcpStream) directly. The library links only tokio's io traits, never a runtime.mio—MioClientis a readiness/non-blocking client for amio-stylePollloop:pump_readable()/pump_writable()advance the connection on the matching readiness (WouldBlock = not ready) andpoll_event()surfacesMioEvents. It is generic overstd::io::Read + Write, so the library takes no dependency onmioitself.
Every frontend — blocking, futures, tokio, and mio — drives the same sans-I/O
ClientDriver / ServerDriver, so the handshake, auth, and channel logic is
written and tested once.
CLI binaries
The default features build the client programs (ssh, sftp, scp,
ssh-keygen, ssh-add, ssh-keyscan); sshd needs server (with pam
for PAM sessions on Linux) and ssh-agent needs agent:
cargo build --release --features server,pam,agent
| Binary | Purpose |
|---|---|
ssh |
Interactive shell / exec, port forwarding (-L/-R), agent & X11 forwarding |
sftp |
Interactive SFTP client |
scp |
File copy over SSH |
sshd |
SSH server daemon (PTY, PAM sessions on Linux) |
ssh-keygen |
Key generation and OpenSSH key-file management |
ssh-agent |
Key agent (OpenSSH-compatible protocol and command line) |
ssh-add |
Add / list / remove / lock agent keys |
ssh-keyscan |
Collect host keys in known_hosts format |
scripts/ssh-copy-id installs public keys in a remote authorized_keys.
All of them understand ssh_config (including Match blocks and Include),
known_hosts, and bracketed-IPv6 host syntax ([2001:db8::1]:22).
ssh and sshd also speak the puressh-only knock rendezvous
(docs/knock.md). With KnockListen set, sshd keeps no TCP port visibly open: an authorized key sends one signed UDP
datagram, and the server opens a one-shot TCP listener for that client only.
The SSH session that follows is ordinary, including full user
authentication. ssh knocks automatically when the TCP connect is refused or
slow (KnockMode auto).
Module layout
src/
├── lib.rs public re-exports
├── error.rs Error / Result
├── hazmat/ low-level sans-I/O protocol layers (no stability promise)
│ ├── format/ SSH wire format (Reader, Writer, mpint, name-list)
│ ├── transport/ binary packet protocol, version exchange, KEX runner
│ ├── kex/ curve25519, ecdh-nistp*, group-DH, GEX, mlkem768x25519, sntrup761x25519
│ ├── cipher/ aes-ctr, aes-gcm, chacha20-poly1305
│ ├── mac/ hmac-sha2-* (incl. -etm)
│ ├── channel/ RFC 4254 channels
│ └── compress/ zlib / zlib@openssh.com
├── hostkey/ ed25519, ecdsa-*, rsa-*, certificates
├── auth/ userauth (RFC 4252): Authenticator, ClientCredential, …
├── key/ private/public key files: OpenSSH, PKCS#1, SEC1, PKCS#8
├── known_hosts/ known_hosts store + verification
├── config/ ssh_config / sshd_config parsing (Match, Include)
├── forwarding/ direct-tcpip, reverse, agent, X11, StreamLocal
├── sftp/ SFTP client + server (with OpenSSH @openssh.com extensions)
├── scp/ SCP protocol
├── agent/ ssh-agent client protocol
├── driver/ sans-I/O ClientDriver / ServerDriver state machines
├── shared.rs SharedClient (multichannel layer)
├── knock/ stealth UDP knock rendezvous (feature `knock`)
├── ffi/ C ABI surface (feature `ffi`)
├── client.rs high-level blocking client API (feature `client`)
├── server.rs high-level blocking server API (feature `server`)
├── client_async.rs async client frontend (features `async`, `tokio`)
├── server_async.rs async server frontend (features `async`, `tokio`)
├── client_mio.rs readiness/non-blocking client frontend (feature `mio`)
└── bin/ ssh, sftp, scp, sshd, ssh-keygen
MSRV
puressh's MSRV is Rust 1.95, declared as rust-version in Cargo.toml
and enforced by a dedicated CI job. Older toolchains are not supported.
Non-goals
A few OpenSSH directives are intentionally unsupported and are rejected (strict
mode) rather than silently ignored: PermitTunnel (tun/tap device forwarding)
and external-command Subsystem entries. Two userauth methods are also not yet
implemented: hostbased and gssapi-with-mic.
Security
This is pre-1.0 software that has not had an independent third-party audit.
It is built on purecrypto and has been the subject of internal security-review
passes (host-key trust handling, forwarding default-deny policies, SFTP jail
hardening, secret zeroization, DoS caps). Use the strict known_hosts policy
(Config::with_known_hosts) in anything that matters, and review before
deploying in a security-sensitive context.
License
Dual-licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.