client: verify certificate host keys - #748
Closed
biao29 wants to merge 2 commits into
Closed
Conversation
Five coupled pieces. The exchange hash now uses the blob the server actually sent: for a certificate the parsed form is only the key inside it, and re-encoding that hashes something the server never sent — a failure that looks like a bad signature and is computed entirely locally, so there is nothing on the wire to compare against. The key exchange is verified with the key the certificate contains; the certificate's own signature is a separate proof, left to the client and its trusted authorities. Collapsing them would accept a certificate nobody vouched for. check_server_certificate replaces check_server_key rather than adding to it — the key inside a certificate is not something a client was ever told to trust, and asking about it as well invites answering the wrong question. It defaults to refusing: a client that has not been taught which authorities it trusts cannot answer, and answering wrongly accepts any machine whose operator can get a certificate from anyone at all. Certificate algorithms are a parallel list because Algorithm cannot represent one — it maps ssh-ed25519-cert-v01@openssh.com back to Ed25519, so a certificate placed in `key` would be advertised under the plain name. Empty by default: advertising it makes servers present certificates to clients that may have no idea who is allowed to have signed them.
Two literals build it field-by-field, so a new field is a breaking change for them. Noted in the pull request: downstream code that does the same needs the one-line addition, which is why the field carries an empty default and nothing else changes.
This was referenced Aug 17, 2026
Owner
|
@all-contributors add @biao29 for code |
Contributor
|
I've put up a pull request to add @biao29! 🎉 |
Owner
|
Had to add a few fixes so it's getting merged via #752 |
|
Would this also add support for servers to authenticate users that provide a signed certificate. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lets a client accept a server that proves its identity with an OpenSSH host
certificate instead of a bare key, so a fleet can be trusted through its CA
rather than through a fingerprint on every machine and every device.
Off by default.
Preferred::host_key_certificatesis empty unless a callerfills it in, so no existing client changes what it advertises and no server
starts sending certificates to a client that has no idea who is allowed to have
signed them.
What changes
Preferred::host_key_certificates: the certificate algorithms to advertise.A parallel list because
Algorithmcannot represent one — it mapsssh-ed25519-cert-v01@openssh.comback toEd25519, so a certificate placedin
keywould be advertised under the plain name and never negotiated.client::Handler::check_server_certificate, called instead ofcheck_server_keywhen the server presented a certificate. It defaults torefusing: a client that has not been told which authorities it trusts cannot
answer, and answering wrongly accepts any host whose operator can obtain a
certificate from anyone at all.
the exchange signature with the key the certificate contains, and carries the
certificate to the handler.
The part worth reviewing closely
The exchange hash now uses the blob the server actually sent, rather than
re-encoding the parsed key:
For a plain key the two are identical. For a certificate the parsed form is
only the key inside it, so re-encoding hashes something the peer never sent —
and both sides of that comparison are computed locally, so the failure surfaces
as a bad signature with nothing on the wire to compare against.
The certificate's own signature is deliberately not checked here. That is a
question about trusted authorities, which only the handler can answer; verifying
the exchange with the contained key and vouching for the certificate are two
separate proofs, and collapsing them would accept a certificate nobody vouched
for.
Compatibility
Adding a field to
Preferredbreaks code that builds it literally rather thanwith
..Default::default(). Two such literals in this crate are updated in thesecond commit. Everything else is additive: a new defaulted trait method and a
new field.
Testing
cargo test -p russh --libpasses (152 tests). Exercised end to end againstOpenSSH
sshdwithHostCertificateconfigured andAuthorizedKeysFile none,including the refusal path when the presenting authority is not one the client
was told about.