Skip to content

update: upgrade russh to 0.63.1 and refuse host certificates - #270

Merged
inureyes merged 1 commit into
mainfrom
update/russh-0.63-upgrade
Aug 24, 2026
Merged

update: upgrade russh to 0.63.1 and refuse host certificates#270
inureyes merged 1 commit into
mainfrom
update/russh-0.63-upgrade

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

Upgrades russh from 0.62.1 (lock 0.62.7) to 0.63.1, handles the one breaking API change, and documents why bssh refuses OpenSSH host certificates.

0.63.1 also carries two security fixes worth taking: client-side Handler callbacks were reachable with never-opened channel IDs (GHSA-47hw-gvq5-r2gm), and a MAC-requiring block cipher could negotiate mac=none and panic the session task (GHSA-p8qx-h547-fjw9).

Breaking change handled

russh 0.63.0 widened client::Handler::check_server_key from &PublicKey to &PublicKeyOrCertificate, so the callback can now receive an OpenSSH host certificate. This was the only compile break in the tree: one implementation plus nine test call sites.

ClientHandler::check_server_key now fails closed on a certificate:

  • bssh never advertises certificate host key algorithms. Both Preferred overrides (src/ssh/tokio_client/connection.rs for the client, src/server/mod.rs for the server) change only compression and inherit host_key_certificates from Preferred::DEFAULT, which is empty, so a server cannot negotiate one.
  • If a peer sends one regardless, bssh has no CA signature verification (the @cert-authority scan in host_verification.rs only warns and falls back to TOFU). The key inside a certificate has never been vouched for by anything bssh trusts, and matching it against known_hosts would answer a different question than the one the certificate poses.
  • ServerCheckMethod::NoCheck still accepts, because there the operator turned host verification off outright.

server::Config also gained a certificates field; the existing ..Default::default() leaves it empty, so the server keeps presenting a plain host key.

Compatibility audit

Reviewed the full 0.62.7 to 0.63.1 source diff for behavior changes that compile silently.

Change Effect on bssh
MAC-requiring cipher rejects mac=none None. SAFE_HMAC_ORDER has no none and bssh does not override mac.
Channel-ID validation on client callbacks None. A correct server confirms a channel open before sending data.
Stricter strict-kex checks during initial KEX None. Applies only when both peers negotiate strict kex.
PKCS#8 parse panic, constant-time agent unlock, Curve25519 param sanitizing Pure hardening.
Handler trait method set Unchanged: 27 client, 34 server.
russh's own dependency requirements Identical between the two versions, so no new transitive crates and no new duplicates.

Validation

  • cargo check --all-targets, cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --all -- --check: clean.
  • cargo +1.96 check --workspace --locked on the declared MSRV: passes.
  • Full test suite with --no-fail-fast: green except the three integration_test cases that CI already skips, which fail on a stale localhost entry in the developer's ~/.ssh/known_hosts and reproduce identically on main.
  • Real handshakes against bssh-server: exec, ping, and an SFTP upload/download round trip with matching SHA-256.
  • Real handshakes against OpenSSH 10.3p1: exec plus a 300 KB SFTP download with matching SHA-256.

Tests added

Three regression tests in src/ssh/tokio_client/host_verification.rs pin the certificate behavior: every verifying mode refuses a host certificate even when the key inside it is pinned in known_hosts, NoCheck accepts it, and host_key_certificates is asserted empty in both Preferred::DEFAULT and the client config so the rejection path cannot be reached by accident.

Note on Cargo.lock

Beyond russh itself, Cargo.lock carries a routine cargo update refresh of 70 other packages that was already pending in the working tree before this change. No Cargo.toml requirement other than russh was touched.

Out of scope

An unrelated pre-existing bug surfaced during the end-to-end smoke tests: bssh-to-bssh SFTP downloads fail for files larger than 255 KiB because bssh-server does not advertise the limits@openssh.com extension. It reproduces identically on main with russh 0.62.5, so it is not a regression from this upgrade and is filed separately.

russh 0.63.0 widened `client::Handler::check_server_key` from `&PublicKey` to `&PublicKeyOrCertificate` so a server can present an OpenSSH host certificate, and 0.63.1 adds two security fixes on top: client-side `Handler` callbacks were reachable with never-opened channel IDs (GHSA-47hw-gvq5-r2gm), and a MAC-requiring block cipher could negotiate `mac=none` and panic the session task (GHSA-p8qx-h547-fjw9).

`ClientHandler::check_server_key` now fails closed on a certificate. bssh never advertises certificate host key algorithms: both `Preferred` overrides change only `compression` and inherit `host_key_certificates` from `Preferred::DEFAULT`, which is empty, so a server cannot negotiate one. If a peer sends one regardless, bssh has no CA signature verification (the `@cert-authority` scan only warns and falls back to TOFU), so the key inside the certificate has never been vouched for by anything bssh trusts and matching it against known_hosts would answer a different question. `ServerCheckMethod::NoCheck` still accepts, because there the operator turned verification off outright. `server::Config` gained a `certificates` field that the existing `..Default::default()` leaves empty.

Reviewed the full 0.62.7 to 0.63.1 source diff for behavior changes that compile silently. The MAC fix cannot reach bssh: `SAFE_HMAC_ORDER` contains no `none` and bssh does not override `mac`. The channel-ID gating is invisible to a correct server, which confirms a channel open before sending data. The `Handler` trait method set is unchanged on both sides (27 client, 34 server), and russh's own dependency requirements are identical between the two versions, so no new transitive crates or duplicates appear.

Cargo.lock also carries a routine `cargo update` refresh of 70 other packages that was already pending in the working tree.

Validation: `cargo check --all-targets`, `clippy -D warnings`, and `cargo fmt --check` all clean; `cargo +1.96 check --workspace --locked` passes on the declared MSRV. Full test suite green except the three `integration_test` cases that CI already skips, which fail on a stale localhost entry in the developer's `~/.ssh/known_hosts`. End-to-end handshakes verified against bssh-server (exec, ping, SFTP round trip with matching SHA-256) and against OpenSSH 10.3p1 (exec plus a 300 KB SFTP download with matching SHA-256). Three regression tests pin the certificate behavior: every verifying mode refuses one, `NoCheck` accepts, and `host_key_certificates` is asserted empty.
@inureyes inureyes added type:dependency Dependency updates type:security Security vulnerability or fix status:review Under review priority:high High priority issue and removed type:security Security vulnerability or fix labels Aug 24, 2026
@inureyes inureyes self-assigned this Aug 24, 2026
@inureyes
inureyes merged commit a817847 into main Aug 24, 2026
3 checks passed
@inureyes
inureyes deleted the update/russh-0.63-upgrade branch August 24, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:high High priority issue status:review Under review type:dependency Dependency updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant