Files
tty7/crates
l0ng-aiandl0ng-ai efe345174b fix(ssh): stop a new host-key algorithm from reading as a compromise (#516)
* fix(ssh): stop a new host-key algorithm from reading as a compromise

A host that grows an ed25519 key beside the ssh-rsa one it has always had
raised the full man-in-the-middle sheet — red border, fingerprint diff, a
"type yes" field — because `check_in_str` folded "known by another
algorithm" into `HostKeyStatus::Changed`. OpenSSH treats a key of an
algorithm the host has no entry for as simply unknown, and saves the alarm
for a key that contradicts one on file.

`ChangedAlgorithm` splits the two apart, with `Changed` keeping precedence
so a same-algorithm mismatch still screams however many other-algorithm
lines sit beside it.

The dialog was only half of it. Negotiation started from russh's default
order, which leads with ed25519, so a host known only by ssh-rsa was
*asked about on every single connection* — and an attacker could pick an
algorithm the user had no entry for to trade the alarm for the mild
confirmation. `build_preferred` now orders the host-key list the way
OpenSSH's `order_hostkeyalgs()` does: what is already on file goes first,
nothing is dropped, and a pinned `HostKeyAlgorithms` is left alone. It
matches on key type, so all three RSA spellings travel together rather
than pinning the host to SHA-1 signatures.

The prompt reuses `AuthPromptKind::HostKeyUnknown` with an added optional
field rather than gaining a variant: the enum is externally tagged and
crosses both the daemon/GUI and the GUI/tty7-server boundaries, where a
new variant is a hard decode failure on an older peer and a new field is
not.

Also fixes a defect the issue did not mention: overriding a genuinely
changed key appended the new line without removing the old one, and since
any same-algorithm match answers `Known`, the superseded — possibly
attacker's — key stayed trusted forever, silently. The superseded line is
now dropped first, and only lines naming this one host are touched, so a
wildcard or `@revoked` entry is never collateral.

* fix(ssh): make the Override button on a changed host key actually override

`host_key_changed_decision` returns `accept: false` for anything but
"yes", which is byte-for-byte what Abort sends — and the button had no
disabled state and closed the sheet unconditionally. So clicking Override
with an empty field rejected the key and dismissed the prompt, indistinguishable
from having aborted, with nothing said. Enter on the input had the same trap.

Override is now dead until the word is there, which is what the line above
the field has been claiming all along, and Enter on a half-typed answer
leaves the sheet up instead of quietly deciding. `changed_confirmed` is the
single predicate behind both, so the button and the decision cannot
disagree about what "yes" means. `host_key_changed_decision`'s `false`
branch stays as defence in depth.

Both input subscriptions also notify on `Change`, or the enabled flag would
go stale between keystrokes, and a hint appears once the field holds
something that is not "yes". Abort is untouched: still primary, still last.

---------

Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
2026-08-11 21:22:03 +08:00
..