Repository navigation
fix(openssh): decide FIDO host-key coverage for the hostkey-agent sk certificate phases #328
Description
Problem / Background
Follow-up to #314. The pinned OpenSSH V_10_3_P1 regress test hostkey-agent passes every plain host-key phase, every non-FIDO host-certificate phase, and multiple hostkeys. The two remaining failures are cert type sk-ssh-ed25519-cert-v01@openssh.com and cert type sk-ecdsa-sha2-nistp256-cert-v01@openssh.com.
The test builds its server host keys from ${SSH} -Q key-plain of the client under test (test-exec.sh:731-732), but takes its certificate list from the reference ssh -Q key-sig, which includes the sk-* certificate types when the regress tree is built with the sk-dummy provider (both CI runners and this host). bssh does not list sk-* in -Q key-plain (src/app/query.rs:20), so the server never holds an sk-* host key and a client restricted to -oHostKeyAlgorithms=sk-*-cert-v01@openssh.com cannot negotiate one.
Listing sk-* in -Q key-plain also makes every test's setup add sk-* user keys as IdentityFile and HostKey (test-exec.sh:734-760), and keytype.sh logs in with ed25519-sk and ecdsa-sk user keys, so bssh must sign user authentication through the security-key provider. Host-side support alone cannot pass the suite.
Scope decision (2026-10-22)
FIDO security keys are in scope, matching OpenSSH 10.3p1. This reverses the FIDO half of the #275 exclusion; PKCS#11 stays excluded. OpenSSH behavior, from the pinned source:
KEX_DEFAULT_PK_ALG(myproposal.h) listssk-ssh-ed25519,sk-ecdsa-sha2-nistp256, andwebauthn-sk-ecdsa-sha2-nistp256(plain and-cert-v01@openssh.com) after the ECDSA entries and before RSA in each group; the referencessh -Gprints the same order forhostkeyalgorithmsandpubkeyacceptedalgorithms.-Q key-plainlistssk-ssh-ed25519@openssh.comafterssh-ed25519andsk-ecdsa-sha2-nistp256@openssh.comafterecdsa-sha2-nistp521;-Q key-sigadds thewebauthn-sk-ecdsa-sha2-nistp256forms.SecurityKeyProviderdefaults tointernal(built-in USB HID) when built with it and to$SSH_SK_PROVIDERotherwise (readconf.c:3014-3018); a leading$names an environment variable. Signing goes throughsshsk_sign(ssh-sk.c) in thessh-sk-helpersubprocess, which loads the provider library (sk-api,SSH_SK_VERSION_MAJOR 0x000a0000).
The built-in internal provider is split into #331, which builds on the provider interface added here.
Proposed Solution
- Advertise the
sk-*types in-Q key-plain,-Q key,-Q key-cert,-Q key-sig, and the defaultHostKeyAlgorithmsandPubkeyAcceptedAlgorithmsin OpenSSH's order (diff each against the referencessh -Qandssh -Goutput). Advertise awebauthn-sk-*type only if bssh verifies it; otherwise document the gap. - Verify
sk-*host-key KEX signatures andsk-*host certificates through the existing known_hosts andhost_certificate.rspaths (russh 0.64.1ALL_KEY_TYPESand ssh-key 0.7 already know both algorithms). Follow OpenSSH's flag checks for host-key signatures. - Implement
SecurityKeyProviderfor user authentication: parse the option (currentlyUnimplementedinsrc/ssh/ssh_config/parser/options/support.rs:245), expand$VAR, load an external sk-api library, and signsk-*identity files (key handle, application, flags) through it, with OpenSSH's user-presence and PIN prompts. Decide in-process loading versus anssh-sk-helper-style subprocess and record why. Keys held in an agent keep signing through the agent. Define the provider interface so feat(ssh): implement the built-in internal SecurityKeyProvider for USB HID authenticators #331 plugsinternalin; until theninternal(the default) returns an explicit diagnostic and authentication moves on to the next identity. - Static musl builds cannot
dlopen: they must still build and report an explicit diagnostic for an external provider. - Update
docs/openssh-compatibility-status.md, the epic: OpenSSH drop-in compatibility, measured against the OpenSSH regress suite #275 exclusion text, and the PKCS#11 skip reasons intests/openssh-regress/selection.tsvso they no longer claim FIDO is excluded.
Acceptance Criteria
-
hostkey-agentpasses on Linux and macOS CI, including bothsk-*certificate phases. (PR feat(ssh): support FIDO security keys through SecurityKeyProvider #333, CI runs 39715706746: Ubuntu 69/70, macOS 66/67) -
keytype,limit-keytype, andagentpass with thesk-*key types active, and the regress run shows no new failures elsewhere. (PR feat(ssh): support FIDO security keys through SecurityKeyProvider #333; the only remaining regress failure isagent-restrict, feat(agent): bind destination-constrained agent keys with session-bind@openssh.com #327) -
-Qoutput and the-Gdefaults match the referencesshfor every algorithm bssh supports; each deviation is documented. (PR feat(ssh): support FIDO security keys through SecurityKeyProvider #333) - An explicit
HostKeyAlgorithmsrestriction is never widened: a regression test covers a certificate-only list. (PR feat(ssh): support FIDO security keys through SecurityKeyProvider #333) - Unit tests cover provider loading failures,
$VARexpansion, theinternaldiagnostic, and sk signature encoding; each fails when its behavior is reverted. (PR feat(ssh): support FIDO security keys through SecurityKeyProvider #333) - Every
release.ymltarget still builds, including both musl targets. (review only:cargo check --target aarch64-unknown-linux-musl -p libloadingpasses; a full musl build needs a musl cross compiler foraws-lc-sys, first checked by the next release workflow run)
Reproduction
python3 tests/openssh-regress/run.py --test hostkey-agent --results /tmp/hostkey-agent.json, then inspect target/openssh-regress/logs/hostkey-agent-candidate.log: only the two sk-*-cert-v01@openssh.com phases report failed / bad SSH_CONNECTION key type.
Activity
feat(ssh): add Unix-domain forwarding and validated host certificates
fix: close residual OpenSSH regress gaps in rekey, multiplex, and hos…
inureyes commentedon Oct 22, 2026
Finding from #330 item 4 (certificate-only
HostKeyAlgorithmsnegotiation), recorded here for the implementer of this issue. No code changed for it in #330.bssh does not advertise anything outside a certificate-only
HostKeyAlgorithmslist.resolve_host_keys(src/ssh/tokio_client/algorithms.rs) putssk-ssh-ed25519-cert-v01@openssh.comintocertificatesand leaveskeysas an explicit empty list, the parser stores both asSome(..), andto_russh_config(src/ssh/tokio_client/connection.rs) passes them toPreferred::keyandPreferred::host_key_certificateswithout falling back to the defaults. russh 0.64.1 writes the client KEXINIT host-key name-list ashost_key_certificates(as certificate names) followed bykey(negotiation.rs,write_kex), andread_kexselects from the same combined list, so a server cannot negotiate a type the client did not list.prefer_known_host_algorithmsonly reorders, and ordering is disabled for an explicit list anyway. A probe through the real-o HostKeyAlgorithms=...path on main 5b20ce5 advertised exactly["sk-ssh-ed25519-cert-v01@openssh.com"], and the same forssh-ed25519-cert-v01@openssh.com.Why
hostkey-agentfails its twosk-*cert phases:test-exec.shbuildsSSH_KEYTYPESfrom$SSH -Q key-plain, which for the candidate run is bssh and lists nosk-*types, so the script never createssk-*host keys or host certificates for sshd.SSH_CERTTYPEScomes from the referencessh -Q key-sigonPATH, which does list thesk-*cert types, so the script still runs those phases. sshd then has nosk-*certificate to offer, there is no common host-key algorithm, and bssh fails the connection before key exchange completes (nokex: host key algorithmline for those invocations in the latest run). That is the correct fail-closed result. Thekex: host key algorithm: ssh-ed25519lines near those phases infailed-ssh.logbelong to other invocations: that file concatenates every failing test's client log. Older runs in the same file fail even earlier withinvalid label: 'sk-ssh-ed25519', from a binary that predates thecertificate_algorithmparser.Consequence for this issue: once bssh reports the
sk-*types in-Q key-plain(and-Q key), the regress script will generatesk-*host keys and certificates throughSSH_SK_PROVIDER, and the two phases should pass only if bssh can verify ansk-*host certificate signature. They are the acceptance signal for the host-key half of this issue.fix(ssh): close host-certificate and Unix-socket hardening gaps (#332)
test: keep unimplemented-option diagnostic test off SecurityKeyProvider
fix(ssh): continue after security-key signing failures
fix(ssh): queue security-key signers on an async lock
fix(ssh): refuse host keys that do not match the negotiated type
fix(ssh): harden security-key provider loading and signing
docs: describe security-key auth and host-key type check