Publish threat model and refresh native v1 docs
Add docs/THREAT_MODEL.md deriving a publishable threat model from NATIVE_V1_SPEC.md sections 4-6: assets, in/out-of-scope attackers, security properties vs SSH (including where Dosh aims to exceed it), cryptographic building blocks as implemented, and an honest accepted-residual-risks / known-gaps section. Refresh docs/PUBLIC_READINESS.md: feature matrix now reflects native auth, host-key trust, authorized_keys policy, doctor, and -L/-R/-D forwarding; add a per-item "Native v1 verification checklist status" table mapping spec section 16 to done/in-progress/pending from code. Update README.md status with a native v1 section and honest claim posture pointing to THREAT_MODEL.md. Annotate NATIVE_V1_SPEC.md with a v1 status block summarizing milestone progress. Point SPEC.md security model and header at native auth and the threat model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6c14d669b8
commit
828206f757
@@ -0,0 +1,232 @@
|
||||
# Dosh Threat Model
|
||||
|
||||
This is the published threat model for Dosh native v1, derived from
|
||||
`docs/NATIVE_V1_SPEC.md` sections 4-6. It states the assets Dosh protects, the
|
||||
attackers it does and does not defend against, the security properties Dosh claims
|
||||
relative to SSH, the cryptographic building blocks actually in use, and an honest
|
||||
list of accepted residual risks and known gaps.
|
||||
|
||||
Dosh is a remote-login transport for Dosh-installed servers. It is intended to
|
||||
replace the day-to-day `ssh host` workflow for terminals and TCP forwarding while
|
||||
keeping OpenSSH as a recovery and bootstrap fallback. Dosh is **not** an
|
||||
RFC-compatible SSH implementation and does not claim SSH's entire protocol security
|
||||
surface. It claims security that is **equivalent to, and in some respects stronger
|
||||
than, SSH for the Dosh terminal and forwarding use case** on hosts running
|
||||
`dosh-server`.
|
||||
|
||||
## 1. Assets
|
||||
|
||||
Dosh protects:
|
||||
|
||||
- **Terminal session contents.** Keystrokes, command output, and the authoritative
|
||||
screen state for every named or generated session.
|
||||
- **Forwarded TCP streams.** Bytes carried over `-L`, `-R`, and `-D` channels.
|
||||
- **User authentication credentials.** The user's SSH/Dosh private keys and any
|
||||
ssh-agent identities. Dosh never sees private key material in plaintext on the
|
||||
wire; signatures are produced locally or by the agent.
|
||||
- **Server-issued credentials.** Session keys, `ClientId` association state,
|
||||
server-sealed attach tickets, and the client-held attach-ticket PSK.
|
||||
- **Server identity.** The persistent Dosh host key (`~/.config/dosh/host_key`) and
|
||||
the server secret used to seal attach tickets and derive bootstrap material.
|
||||
- **Host-trust state.** The client's pinned known-hosts file
|
||||
(`~/.config/dosh/known_hosts`).
|
||||
- **Authorization policy.** `~/.ssh/authorized_keys` /
|
||||
`~/.config/dosh/authorized_keys` and the forwarding policy they encode.
|
||||
|
||||
## 2. Attackers
|
||||
|
||||
### In scope (Dosh must defend against these)
|
||||
|
||||
- **Passive network observer.** May record all UDP traffic between client and
|
||||
server.
|
||||
- **Active network attacker.** May spoof, drop, replay, reorder, or modify any
|
||||
packet, and may attempt to inject forged packets in either direction.
|
||||
- **NAT rebinding / roaming.** Client source IP and port may change mid-session,
|
||||
including across sleep, network switch, and NAT timeout.
|
||||
- **Stolen attach-ticket cache without the user's private key.** An attacker who
|
||||
reads a client cache copies a server-sealed ticket plus its PSK but does not have
|
||||
the user's SSH private key.
|
||||
- **Server restart and key rotation.** Stale session keys, tickets, and replay
|
||||
state must not be usable after the server rotates its secret or host key.
|
||||
- **Malicious unauthenticated client flooding auth attempts.** A peer that has no
|
||||
authorized key tries to exhaust server resources or guess credentials.
|
||||
- **Compromised low-privilege local user on a shared client.** A different local
|
||||
user attempts to read Dosh credential caches on the same machine.
|
||||
|
||||
### Out of scope (explicitly not defended against)
|
||||
|
||||
- **Compromised client machine.** If the endpoint running `dosh-client` is owned by
|
||||
the attacker, the attacker has the user's keys and terminal.
|
||||
- **Compromised server account.** If the login account on the server is owned, the
|
||||
attacker already has the shell Dosh would have given them.
|
||||
- **Malicious kernel, terminal emulator, or PTY implementation** on either side.
|
||||
- **A server that was legitimately authorized and later turns malicious.** Host-key
|
||||
pinning detects a *substituted* server, not a trusted server that decides to
|
||||
misbehave.
|
||||
|
||||
These exclusions match SSH's own boundaries: SSH likewise cannot protect a
|
||||
compromised endpoint or a malicious authorized peer.
|
||||
|
||||
## 3. Security Properties Claimed vs SSH
|
||||
|
||||
| Property | SSH | Dosh native v1 | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Server authentication before trusting session data | yes | yes | Host key signs the handshake transcript; client verifies before sending user auth or accepting terminal bytes. |
|
||||
| User authentication by private-key possession | yes | yes | Ed25519 via ssh-agent or encrypted OpenSSH key; signature binds the full transcript. |
|
||||
| Forward secrecy | yes | yes | Ephemeral X25519 per connection; long-term host/user keys never derive the traffic key. |
|
||||
| AEAD on every post-handshake packet | yes | yes | ChaCha20-Poly1305 with per-direction, per-sequence nonces. |
|
||||
| Replay protection | yes | yes | Sliding replay window over the AEAD packet counter, plus transcript-bound handshake. |
|
||||
| Host-key pinning with explicit first use | TOFU, weakly tied to transport | yes, with explicit policy | Default refuses unknown host keys; TOFU only when `trust_on_first_use` is set; mismatch hard-fails and never auto-replaces. |
|
||||
| No plaintext terminal bytes after handshake | yes | yes | All `Frame`/`Input`/stream packets are AEAD-sealed. |
|
||||
| No custom cryptographic primitives | yes | yes | Standard X25519/HKDF-SHA256/ChaCha20-Poly1305/Ed25519 crates only. |
|
||||
| Fail-closed downgrade behavior | yes | yes | Native auth failure surfaces an explicit error and SSH fallback is explicit; it never silently drops to an unauthenticated mode. |
|
||||
| Fast resumption without re-auth | ControlMaster only | yes, native | Cached session/ticket attach skips a fresh round of public-key proof; this is a deliberate speed/security trade discussed in section 6. |
|
||||
|
||||
### Where Dosh aims to *exceed* SSH for this use case
|
||||
|
||||
- **Tighter transcript binding.** A user-auth signature binds both ephemeral keys,
|
||||
both randoms, the server host key, requested user/session/mode/terminal size,
|
||||
selected algorithms, and protocol version into one transcript. This forecloses
|
||||
cross-protocol and partial-replay confusion classes for the narrow Dosh surface.
|
||||
- **Smaller attack surface.** Dosh deliberately omits the full SSH transport/channel
|
||||
machinery, arbitrary subsystems, X11, SFTP/SCP, and forced-command subsystems.
|
||||
Fewer features means fewer parsers and fewer reachable states.
|
||||
- **Explicit, file-pinned host trust.** Host trust is a first-class, inspectable
|
||||
known-hosts entry with source provenance (`tofu`/`ssh`/`manual`) and a hard-fail
|
||||
mismatch path, rather than the looser default TOFU behavior most SSH clients ship.
|
||||
- **Modern primitives only.** Ed25519 and X25519 by default; no DSA, no SHA-1
|
||||
signatures, no CBC-and-MAC constructions.
|
||||
|
||||
Dosh does **not** claim generic SSH compatibility and must not be described as an
|
||||
SSH-protocol implementation.
|
||||
|
||||
## 4. Cryptographic Building Blocks (as implemented)
|
||||
|
||||
These reflect the code in `src/crypto.rs`, `src/native.rs`, `src/auth.rs`, and
|
||||
`src/protocol.rs`, not just the spec.
|
||||
|
||||
- **Key exchange:** X25519 ephemeral-ephemeral (`x25519-dalek`). The shared secret
|
||||
is checked for contributory behaviour; a non-contributory result is rejected.
|
||||
- **Handshake/transport KDF:** HKDF-SHA256 (`hkdf`), salted with the SHA-256 of the
|
||||
serialized `ClientHello` and `ServerHello`, binding traffic keys to the transcript.
|
||||
- **AEAD:** ChaCha20-Poly1305 (`chacha20poly1305`) for every encrypted packet and
|
||||
for sealed attach tickets. Nonces are derived as `direction || sequence`, giving a
|
||||
unique nonce per `(key, direction, sequence)`. AES-GCM is reserved for later and is
|
||||
not selectable today.
|
||||
- **Host-key signatures:** Ed25519 (`ed25519-dalek`) over the handshake transcript.
|
||||
- **User-auth signatures:** Ed25519, produced either by ssh-agent over a Unix socket
|
||||
(`src/ssh_agent.rs`) or from an encrypted/plaintext OpenSSH private key
|
||||
(`ssh-key`). The signature covers the user-auth transcript described above.
|
||||
- **Bootstrap auth (SSH fallback path):** HMAC-SHA256 attach tokens and HKDF-SHA256
|
||||
derived session keys, with attach tickets sealed under an HKDF-derived
|
||||
ticket key. Token comparison is constant-time.
|
||||
- **Hashing/transcript:** SHA-256 (`sha2`).
|
||||
- **Randomness:** OS CSPRNG via `rand::thread_rng()` / `OsRng`.
|
||||
|
||||
No homegrown ciphers, MACs, padding schemes, or key derivation are used. No nonce or
|
||||
key pair is reused within a direction.
|
||||
|
||||
## 5. How the Properties Are Enforced (handshake and transport)
|
||||
|
||||
- **Server authentication.** `ServerHello` carries the host public key and an Ed25519
|
||||
signature over `dosh/native/server-hello/v1 || ClientHello || ServerHello(unsigned)`.
|
||||
The client verifies this signature *and* checks the host key against its pinned
|
||||
known-hosts entry before sending user auth or accepting terminal bytes. Unknown
|
||||
keys are refused unless TOFU is enabled; mismatches hard-fail with both
|
||||
fingerprints and the file path.
|
||||
- **User authentication.** `UserAuth` carries an Ed25519 signature over
|
||||
`dosh/native/user-auth/v1 || ClientHello || ServerHello || UserAuth(unsigned)`. The
|
||||
server looks the public key up in `authorized_keys`, enforces authorized-key
|
||||
options, then verifies the signature. A removed key can no longer authenticate.
|
||||
- **Authorization options.** `from=` (with CIDR, glob, and negation),
|
||||
`no-port-forwarding`, and `permitopen=` are enforced. `command=` is rejected for
|
||||
native terminal login. Any unrecognized restrictive option fails closed rather than
|
||||
being silently ignored.
|
||||
- **Forwarding policy.** The server enforces `allow_tcp_forwarding`,
|
||||
`allow_remote_forwarding`, and a loopback-only default for remote binds
|
||||
(`allow_remote_non_loopback_bind`), in addition to per-key `permitopen=` /
|
||||
`no-port-forwarding`.
|
||||
- **Replay protection.** A 128-wide sliding window over the per-direction packet
|
||||
counter rejects duplicates and stale sequences. Sequence 0 is never accepted.
|
||||
- **Stale-key tolerance.** Each encrypted packet carries a `session_key_id`; packets
|
||||
under an old key are dropped as "stale or wrong session key id" rather than treated
|
||||
as fatal decrypt failures, so reconnect after rotation is non-destructive.
|
||||
- **Roaming.** The server keys clients by `ClientId` and session key id, not by
|
||||
source address, and updates the endpoint after any valid encrypted packet from a
|
||||
new address — without weakening authentication, because the packet must still
|
||||
decrypt and verify.
|
||||
|
||||
## 6. Accepted Residual Risks and Known Gaps
|
||||
|
||||
These are stated openly so the public claim gate (`NATIVE_V1_SPEC.md` section 17) can
|
||||
be evaluated honestly. Items here are *not* yet "green".
|
||||
|
||||
### Accepted residual risks (by design)
|
||||
|
||||
- **Attach tickets prove recent server-issued possession, not fresh private-key
|
||||
possession.** A stolen client cache containing both the sealed ticket and its PSK
|
||||
can attach until the ticket expires (default TTL 24h, configurable down to zero) or
|
||||
until the server rotates its secret/host key or the user key is removed. This is the
|
||||
deliberate speed trade. It is bounded by TTL, scoped to host/user/session/mode, and
|
||||
can be disabled. SSH ControlMaster has an analogous live-socket exposure.
|
||||
- **Local cache confidentiality relies on filesystem permissions.** Host keys,
|
||||
server secret, known-hosts, and credential caches are written `0600`. A
|
||||
same-machine attacker who can already read another user's `0600` files (e.g. via
|
||||
root) is out of scope, as with SSH's `~/.ssh`.
|
||||
- **A trusted server that turns malicious is not detected.** Host-key pinning
|
||||
detects substitution, not betrayal by an already-authorized server. This matches
|
||||
SSH.
|
||||
|
||||
### Known gaps / work in progress (must close before the public claim)
|
||||
|
||||
- **Per-IP rate limiting is partial.** The server evicts half-finished native
|
||||
handshakes on a TTL so a flood of `ClientHello` packets cannot grow the pending map
|
||||
without bound, and it reports a static `rate_limit_remaining` hint in `ServerHello`.
|
||||
A full per-source token-bucket limiter is **in progress on another track** and is
|
||||
not yet enforced. Until it lands, sustained auth flooding is mitigated only by the
|
||||
handshake-eviction TTL and OS-level limits.
|
||||
- **Protocol VERSION negotiation is being hardened.** The wire format pins a single
|
||||
protocol version: the packet header rejects any non-matching `VERSION` byte and the
|
||||
native handshake rejects any non-matching `protocol_version`. There is no
|
||||
multi-version negotiation yet, so cross-version interop and downgrade-resistance for
|
||||
future versions are still being designed. Today's behavior is fail-closed (reject),
|
||||
not silent downgrade.
|
||||
- **Fuzzing is not yet wired into CI.** CI currently runs format, tests, release
|
||||
build, and the Docker SSH benchmark gate. Fuzz targets for packet parsing,
|
||||
authorized-key parsing, known-host parsing, and handshake state (spec milestone 5)
|
||||
are **being wired in** and are not yet running in CI.
|
||||
- **User-key algorithm coverage is Ed25519-only today.** The spec permits ECDSA
|
||||
P-256 and (compatibility-only, SHA-2) RSA, but native auth currently accepts and
|
||||
produces `ssh-ed25519` only. ECDSA/RSA support is pending. This is a parity gap, not
|
||||
a weakening of what *is* supported.
|
||||
- **Hostile-network and long-soak integration tests are partial.** Roaming,
|
||||
retransmit, resize, and multi-client tests exist; a dedicated adversarial
|
||||
drop/reorder/replay suite and 30-minute-sleep soak (spec section 16) are still being
|
||||
expanded.
|
||||
- **No external security review yet.** The spec's milestone 5 requires an external
|
||||
review checklist before public security claims. That review has not happened.
|
||||
|
||||
### Auth posture
|
||||
|
||||
Native auth is **opt-in alongside SSH fallback**. `auth_preference` defaults to
|
||||
`native,ssh`: Dosh tries the native authenticated path first and falls back to SSH
|
||||
bootstrap explicitly and visibly when native auth is disabled, unavailable, or
|
||||
rejected. Native auth failure never silently degrades to an unauthenticated mode.
|
||||
Forwarding (`-L`/`-R`/`-D`) requires the native authenticated path and refuses to run
|
||||
under `--local-auth`.
|
||||
|
||||
## 7. Verification and Public-Claim Status
|
||||
|
||||
Dosh may claim "native SSH replacement for Dosh-installed servers" only after the
|
||||
conditions in `NATIVE_V1_SPEC.md` section 17 are met: native auth default on a real
|
||||
host, SSH fallback available, the section 16 checklist green, this threat model
|
||||
published, and benchmarks with raw samples for SSH cold, Dosh native cold, Dosh
|
||||
cached attach, and Mosh startup.
|
||||
|
||||
Current status: this threat model is published (this document). The verification
|
||||
checklist is **not yet fully green** — see the item-by-item status table in
|
||||
`docs/PUBLIC_READINESS.md` ("Native v1 verification checklist status") and the known
|
||||
gaps in section 6 above. Until the gaps close and an external review is complete,
|
||||
Dosh's defensible public claim remains **fast, encrypted native attach/reconnect with
|
||||
SSH-equivalent transport security and SSH bootstrap fallback** — not a fully verified,
|
||||
externally reviewed SSH replacement.
|
||||
Reference in New Issue
Block a user