Implement SSH identity authentication and strict host-key verification #114

Closed
opened 2026-08-24 20:17:06 +00:00 by hugo · 1 comment
Owner

Goal

Provide secure, storage-owned SSH authentication and server identity verification for the optional SSH transport without invoking OpenSSH, a shell, or a credential helper.

Scope

  • Build the client on russh and keep its session, algorithm, authentication, and trust policy in crates/storage.
  • Support public-key authentication from explicitly configured OpenSSH private-key files, including encrypted Ed25519, ECDSA, and RSA keys supported by the selected Rust crates.
  • Support an already-running SSH agent through the pure-Rust agent protocol when explicitly enabled. IronStorage must never start, probe by executing, or configure an agent process.
  • Define deterministic, bounded identity selection. Do not spray unrelated keys indefinitely or silently fall back to password, keyboard-interactive, OpenSSH config, ProxyCommand, or another program.
  • Keep identity paths, public fingerprints, host/port, and algorithm names non-secret. Resolve private-key passphrases through the storage-owned protected secret store and SecretBytes; never place private key bytes or passphrases in TOML, Git config, logs, command history, or view-ready errors.
  • Verify every server key in the russh::client::Handler::check_server_key path against the configured/default OpenSSH known_hosts location using Rust parsing.
  • Return typed unknown-host, changed-host-key, unsupported-algorithm, missing-key, denied/cancelled passphrase, agent-unavailable, and authentication-rejected states.
  • Expose an explicit storage API that can persist one confirmed unknown host key. A changed key must always hard-fail and must never be replaced by trust-on-first-use.
  • Show SHA-256 host and identity fingerprints from canonical key bytes. Bound banners and diagnostics and redact all sensitive values.
  • Use modern algorithms by default and require an explicit future policy change for SHA-1 or other legacy algorithms.

Tests and verification

  • Use synthetic keys and a pure-Rust in-process SSH server/agent fixture.
  • Cover unencrypted and encrypted key files, agent selection, wrong user/key/passphrase, unavailable agent, unknown host confirmation, matching host, changed key, hashed known-host entries where supported, non-default ports, cancellation, and redacted Debug/error output.
  • Prove host verification occurs before authentication and the Git command channel.
  • Prove no production path calls std::process::Command or a helper.
  • Run all repository-required Rust gates.

Acceptance criteria

  • A trusted host can authenticate with a configured key file or explicitly enabled existing agent using Rust-only code.
  • Unknown hosts require an explicit fingerprint confirmation; changed keys fail closed.
  • Private keys and passphrases never enter ordinary configuration, Git data, logs, or frontend-owned state.
  • Cancellation and authentication failures leave repository, refs, index, worktree, known-hosts state, and secret storage unchanged.

Depends on the typed SSH remote and feature-wiring issue in this milestone.

## Goal Provide secure, storage-owned SSH authentication and server identity verification for the optional SSH transport without invoking OpenSSH, a shell, or a credential helper. ## Scope - Build the client on `russh` and keep its session, algorithm, authentication, and trust policy in `crates/storage`. - Support public-key authentication from explicitly configured OpenSSH private-key files, including encrypted Ed25519, ECDSA, and RSA keys supported by the selected Rust crates. - Support an already-running SSH agent through the pure-Rust agent protocol when explicitly enabled. IronStorage must never start, probe by executing, or configure an agent process. - Define deterministic, bounded identity selection. Do not spray unrelated keys indefinitely or silently fall back to password, keyboard-interactive, OpenSSH config, `ProxyCommand`, or another program. - Keep identity paths, public fingerprints, host/port, and algorithm names non-secret. Resolve private-key passphrases through the storage-owned protected secret store and `SecretBytes`; never place private key bytes or passphrases in TOML, Git config, logs, command history, or view-ready errors. - Verify every server key in the `russh::client::Handler::check_server_key` path against the configured/default OpenSSH `known_hosts` location using Rust parsing. - Return typed unknown-host, changed-host-key, unsupported-algorithm, missing-key, denied/cancelled passphrase, agent-unavailable, and authentication-rejected states. - Expose an explicit storage API that can persist one confirmed unknown host key. A changed key must always hard-fail and must never be replaced by trust-on-first-use. - Show SHA-256 host and identity fingerprints from canonical key bytes. Bound banners and diagnostics and redact all sensitive values. - Use modern algorithms by default and require an explicit future policy change for SHA-1 or other legacy algorithms. ## Tests and verification - Use synthetic keys and a pure-Rust in-process SSH server/agent fixture. - Cover unencrypted and encrypted key files, agent selection, wrong user/key/passphrase, unavailable agent, unknown host confirmation, matching host, changed key, hashed known-host entries where supported, non-default ports, cancellation, and redacted Debug/error output. - Prove host verification occurs before authentication and the Git command channel. - Prove no production path calls `std::process::Command` or a helper. - Run all repository-required Rust gates. ## Acceptance criteria - A trusted host can authenticate with a configured key file or explicitly enabled existing agent using Rust-only code. - Unknown hosts require an explicit fingerprint confirmation; changed keys fail closed. - Private keys and passphrases never enter ordinary configuration, Git data, logs, or frontend-owned state. - Cancellation and authentication failures leave repository, refs, index, worktree, known-hosts state, and secret storage unchanged. Depends on the typed SSH remote and feature-wiring issue in this milestone.
hugo added this to the 07 - Pure-Rust Git-over-SSH remotes milestone 2026-08-24 20:17:06 +00:00
hugo added the enhancement label 2026-08-24 20:17:06 +00:00
Author
Owner

Implemented in 5dbda4bbd2ac60937b67eeb683cfb7fca2d4b788 and pushed to main.

Implementation:

  • Added a storage-owned Russh client with explicit OpenSSH Ed25519, ECDSA, and RSA key-file authentication, encrypted-key passphrases retrieved as SecretBytes, and exact-fingerprint pure-Rust SSH-agent selection with bounded identity enumeration.
  • Added strict storage-owned known_hosts verification for exact, comma-list, negated, hashed, non-default-port, revoked, and certificate-authority records. Unknown hosts return a typed confirmation payload; only that payload can be atomically persisted. Changed and revoked keys are never replaced.
  • Disabled RSA/SHA-1 host-key and authentication paths, added SHA-256 fingerprints, cancellation polling, bounded files/diagnostics/timeouts, and typed missing/denied/cancelled/agent/authentication/host-key failures.
  • Added protected SSH passphrase storage references and documented the configuration, trust, and secret-storage contracts.
  • Added in-process pure-Rust SSH server and agent tests covering all three key families, encrypted keys, wrong credentials, exact agent selection/no spraying, unavailable agents, unknown/confirmed/changed/hashed/revoked hosts, cancellation, and verification-before-authentication.

Verification:

  • cargo fmt --all -- --check
  • RUSTFLAGS="-D warnings" cargo check --workspace --all-targets
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace
  • cargo clippy --manifest-path crates/storage/Cargo.toml --all-targets --features ssh -- -D warnings
  • SSH feature tests passed with cargo test --manifest-path crates/storage/Cargo.toml --features ssh --no-fail-fast plus the final targeted SSH suite after hardening.
  • cargo audit ran with no suppressions. It reports RUSTSEC-2023-0071 for the existing rsa 0.9.10 OpenPGP path and Russh RSA support through rsa 0.10.0-rc.18; neither has a fixed release. The exposure and signing/decryption distinction are recorded in DEPENDENCIES.md.
  • Computer Use on the freshly bundled release desktop app loaded the existing password store and showed its HTTPS origin at 0 ahead / 0 behind with a clean worktree. SSH frontend exposure is intentionally part of milestone issue #117; no Apple source changed in this issue.
Implemented in `5dbda4bbd2ac60937b67eeb683cfb7fca2d4b788` and pushed to `main`. Implementation: - Added a storage-owned Russh client with explicit OpenSSH Ed25519, ECDSA, and RSA key-file authentication, encrypted-key passphrases retrieved as `SecretBytes`, and exact-fingerprint pure-Rust SSH-agent selection with bounded identity enumeration. - Added strict storage-owned `known_hosts` verification for exact, comma-list, negated, hashed, non-default-port, revoked, and certificate-authority records. Unknown hosts return a typed confirmation payload; only that payload can be atomically persisted. Changed and revoked keys are never replaced. - Disabled RSA/SHA-1 host-key and authentication paths, added SHA-256 fingerprints, cancellation polling, bounded files/diagnostics/timeouts, and typed missing/denied/cancelled/agent/authentication/host-key failures. - Added protected SSH passphrase storage references and documented the configuration, trust, and secret-storage contracts. - Added in-process pure-Rust SSH server and agent tests covering all three key families, encrypted keys, wrong credentials, exact agent selection/no spraying, unavailable agents, unknown/confirmed/changed/hashed/revoked hosts, cancellation, and verification-before-authentication. Verification: - `cargo fmt --all -- --check` - `RUSTFLAGS="-D warnings" cargo check --workspace --all-targets` - `cargo clippy --workspace --all-targets -- -D warnings` - `cargo test --workspace` - `cargo clippy --manifest-path crates/storage/Cargo.toml --all-targets --features ssh -- -D warnings` - SSH feature tests passed with `cargo test --manifest-path crates/storage/Cargo.toml --features ssh --no-fail-fast` plus the final targeted SSH suite after hardening. - `cargo audit` ran with no suppressions. It reports RUSTSEC-2023-0071 for the existing `rsa 0.9.10` OpenPGP path and Russh RSA support through `rsa 0.10.0-rc.18`; neither has a fixed release. The exposure and signing/decryption distinction are recorded in `DEPENDENCIES.md`. - Computer Use on the freshly bundled release desktop app loaded the existing password store and showed its HTTPS origin at 0 ahead / 0 behind with a clean worktree. SSH frontend exposure is intentionally part of milestone issue #117; no Apple source changed in this issue.
hugo closed this issue 2026-08-25 17:53:20 +00:00
Sign in to join this conversation.