Skip to content

known_hosts: split fields on any whitespace, not a single space - #619

Open
can2049 wants to merge 1 commit into
RustCrypto:masterfrom
can2049:fix/known-hosts-whitespace
Open

can2049 wants to merge 1 commit into
RustCrypto:masterfrom
can2049:fix/known-hosts-whitespace

Conversation

@can2049

@can2049 can2049 commented Nov 10, 2026

Copy link
Copy Markdown

What this fixes

Entry::from_str split each line with split_once(' '), so it only accepted a line whose fields are separated by exactly one ASCII space. The known_hosts format separates fields by any run of whitespace, so three shapes OpenSSH reads correctly were rejected outright:

line shape OpenSSH 8.2p1 (ssh-keygen -F) ssh-key 0.7.0-rc.12 (before)
host\tssh-ed25519\t<base64> matches Err(FormatEncoding)
host ssh-ed25519 <base64> matches Err(FormatEncoding)
host ssh-ed25519 <base64> matches Err(Encoding(Length))
@revoked\thost\t... marker recognised Err(FormatEncoding)

Because the error is per-line, KnownHosts::new(..) yields Err for such a line, and a caller that stops at the first error never reaches the rest of the file — so one hand-edited line can make an entire known_hosts unreadable.

This matters in practice because files in the wild are hand-edited and tabs/indentation are common; I hit it reading real known_hosts files with a port-forward manager that uses this crate.

The fix

Split on char::is_whitespace and treat the optional marker as a token, instead of assuming a single space follows it.

Two details are load-bearing:

  • The remainder is re-joined with single spaces before being handed to PublicKey's parser. decode_segment in public/ssh_format.rs terminates only on b' ', so ssh-ed25519\t<base64> is not parseable by PublicKey even though it is valid known_hosts. Normalising the separators at this boundary keeps PublicKey's stricter contract intact.
  • marker is rebuilt as format!("@{marker_str}") from the token after the @ has been stripped, so Marker::from_str still receives the spelling it expects.

Tests

Four unit tests added in known_hosts.rs (tab-separated, indented, runs of spaces, marker followed by a tab). Each fails before the change and passes after.

Verification

  • cargo test -p ssh-key — all suites pass, 0 failures
  • cargo clippy -p ssh-key --all-targets -- -D warnings — no errors
  • cargo fmt -p ssh-key --check — clean
  • cargo check -p ssh-key --no-default-features — clean (no alloc-only APIs used)

Note on scope

I have deliberately not changed the matching semantics in this crate — HostPatterns is still returned as raw patterns and matching remains the caller's job, as documented. This PR is limited to accepting the input shapes OpenSSH accepts.


AI usage

AI-assisted. The defect was found and reproduced by the author, the fix and tests were AI-drafted and then reviewed, compiled, and tested locally by the author. Reproductions were validated against a real OpenSSH 8.2p1 installation (ssh-keygen -F) rather than against assumptions about the format.

`Entry::from_str` split the line with `split_once(' ')`, which only
recognises a line whose fields are separated by exactly one ASCII space.
OpenSSH's known_hosts format separates fields by any run of whitespace, so
three shapes that OpenSSH reads correctly were rejected outright:

  * tab-separated:     `host\tssh-ed25519\t<base64>`  -> FormatEncoding
  * indented:          `   host ssh-ed25519 <base64>`   -> FormatEncoding
  * runs of spaces:    `host   ssh-ed25519   <base64>`  -> Encoding(Length)

Because the error is per-line, `KnownHosts::new(..)` silently yields an
`Err` for such a line and a caller that stops at the first error never
sees the rest of the file.

Reproduced against `ssh-key` 0.7.0-rc.12; OpenSSH 8.2p1 accepts all three
shapes (`ssh-keygen -F` matches each of them).

The fix splits on `char::is_whitespace` instead, taking the optional marker
as a token rather than assuming a single space follows it, and re-joins the
remainder with single spaces before handing it to `PublicKey`'s parser --
which does require exactly one space between `keytype` and the base64 body
(`decode_segment` in public/ssh_format.rs stops only on `b' '`).

`marker` is therefore now built as `format!("@{marker_str}")` from the
token with the `@` already stripped.

Tests added for all three shapes plus a marker followed by a tab.
@tarcieri

tarcieri commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

A couple suggestions:

Otherwise looks good, thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants