Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/architecture/ssh-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@
- Host key verification with three modes:
- `StrictHostKeyChecking::Yes` - Strict verification using known_hosts; a missing file behaves as an empty one, so unknown hosts are rejected
- `StrictHostKeyChecking::No` - Skip all verification
- `StrictHostKeyChecking::AcceptNew` - TOFU mode: a key matching any of a host's recorded known_hosts entries is accepted (not just the first one, matching OpenSSH rather than short-circuiting on the first same-algorithm mismatch), unknown hosts are recorded and accepted, and a key is rejected only when the host has recorded keys and none of them match
- `StrictHostKeyChecking::AcceptNew` - TOFU mode: a key matching any of a host's recorded known_hosts entries is accepted (not just the first one, matching OpenSSH rather than short-circuiting on the first same-algorithm mismatch), unknown hosts are recorded and accepted, and a key is rejected only when the host has recorded keys and none of them match. If no default known_hosts path can be determined, bssh pins the first key for each host:port in memory for the lifetime of the process instead of disabling verification.
- CLI flag `--strict-host-key-checking` with default "accept-new"
- Uses system known_hosts file (~/.ssh/known_hosts); accept-new creates the directory (0700) and file (0600) with their final permissions up front rather than tightening the mode after creation, and serializes concurrent first-time recordings behind a process-wide lock so parallel connects to the same new host produce a single entry
- Uses system known_hosts file (~/.ssh/known_hosts); accept-new creates the directory (0700) and file (0600) with their final permissions up front rather than tightening the mode after creation, validates that an existing known_hosts path is a readable regular file before treating it as trust state, and serializes concurrent first-time recordings behind both a process-wide mutex and a sibling advisory lock file so parallel connects to the same new host produce a single entry across cooperating bssh processes
- Changed keys surface as a dedicated `HostKeyChanged` error (host, port, offending line) instead of a generic check failure, in strict and accept-new modes alike; a key matching a known_hosts `@revoked` marker line is rejected separately as `HostKeyRevoked`, since russh's parser otherwise reads the marker itself as the literal host field and never matches these lines against the real host, silently letting a revoked key through TOFU
- A `@cert-authority` marker line is not validated (bssh has no CA signature verification path); it only prints a warning and falls through to ordinary TOFU for the offered key, rather than failing closed
- A `@cert-authority` marker line is not validated (bssh has no CA signature verification path); by default it prints a warning and falls through to ordinary TOFU for the offered key, and security-sensitive deployments can set `BSSH_CERT_AUTHORITY_POLICY=reject` to fail closed instead
- The hostname is lowercased once before both the lookup and the recorded entry, matching OpenSSH's case-insensitive known_hosts comparison, and rejected outright if it cannot round-trip through a known_hosts line (contains whitespace, a control character, or a known_hosts/glob metacharacter), since an unrecordable hostname can never be verified on a later connection either
- SSH agent authentication with auto-detection

Expand Down
2 changes: 1 addition & 1 deletion src/cli/bssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ pub struct Cli {
#[arg(
long,
default_value = "accept-new",
help = "Host key checking mode (yes/no/accept-new) [default: accept-new]\n yes - Strict checking against known_hosts (most secure)\n no - Accept all host keys (insecure, testing only)\n accept-new - Accept new hosts, reject changed keys (recommended)"
help = "Host key checking mode (yes/no/accept-new) [default: accept-new]\n yes - Strict checking against known_hosts (most secure)\n no - Accept all host keys (insecure, testing only)\n accept-new - Accept new hosts, reject changed keys; falls back to process-only pinning when no known_hosts path is available. @cert-authority lines warn and use TOFU unless BSSH_CERT_AUTHORITY_POLICY=reject"
)]
pub strict_host_key_checking: String,

Expand Down
13 changes: 7 additions & 6 deletions src/ssh/known_hosts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,17 @@ pub fn get_check_method(strict_mode: StrictHostKeyChecking) -> ServerCheckMethod
}
None => {
// Without a home directory there is no persistent trust state,
// so first-use recording and change detection are impossible:
// every host is "new" and accept-new semantics accept it. Warn
// loudly that nothing can be recorded or verified.
// but accept-new must still avoid becoming unconditional
// NoCheck. The client will pin host keys in memory for this
// process so parallel fan-out can detect a changed key during
// the same run.
tracing::warn!(
"Could not determine known_hosts path; host keys cannot be recorded or verified"
"Could not determine known_hosts path; host keys will be pinned only for this bssh process"
);
eprintln!(
"Warning: could not determine the known_hosts path; host keys cannot be recorded or verified in accept-new mode"
"Warning: could not determine the known_hosts path; host keys will be pinned only for this bssh process"
);
ServerCheckMethod::NoCheck
ServerCheckMethod::AcceptNewInMemory
}
},
}
Expand Down
7 changes: 7 additions & 0 deletions src/ssh/tokio_client/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ pub enum ServerCheckMethod {
/// matching keys are accepted, unknown hosts are recorded and accepted,
/// changed keys are rejected (OpenSSH `StrictHostKeyChecking=accept-new`)
AcceptNewKnownHostsFile(String),
/// Trust On First Use for the lifetime of this process only.
///
/// Used when `StrictHostKeyChecking=accept-new` is requested but no
/// default known_hosts path can be determined. This keeps the default from
/// becoming unconditional `NoCheck`: the first key seen for a host:port is
/// accepted, and a different key later in the same run is rejected.
AcceptNewInMemory,
}

impl ServerCheckMethod {
Expand Down
8 changes: 8 additions & 0 deletions src/ssh/tokio_client/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,14 @@ impl Handler for ClientHandler {
)
.await
}
ServerCheckMethod::AcceptNewInMemory => {
super::host_verification::verify_accept_new_in_memory(
&self.hostname,
self.host.port(),
server_public_key,
)
.await
}
}
}
}
Loading