feat: download resident keys from FIDO2 security keys - #162
Open
rschaffar wants to merge 1 commit into
Open
Conversation
Adds an ssh-keygen -K equivalent to the Add hardware key sheet: a new "From key" action reads resident (discoverable) OpenSSH credentials straight off a FIDO2 authenticator over USB or NFC and turns them into the usual *_sk stubs, so a lost stub no longer requires a desktop to recover access. - New FidoResidentKeyDownloader speaks CTAP2 authenticatorCredential- Management, including the pre-2.1 credentialMgmtPreview command byte that most deployed YubiKeys expose, enumerates RPs filtered to the ssh: application prefix, and rebuilds ed25519-sk / ecdsa-sk stubs (mirroring OpenSSH flag semantics, incl. credProtect=3 -> UV flag). - PIN collected up front so iOS NFC sessions survive; incorrect PINs re-prompt with remaining retries, mirroring the signing flow. - Sheet shows live status while talking to the key and offers a picker when several resident SSH keys are found; label is pre-filled from the application suffix. Closes gwitko#154
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.
Closes #154.
I will gladly take and apply any change requests — including splitting
or trimming this if it goes beyond what you want to maintain.
ssh-keygen -Kfor the phone: the Add hardware key sheet gets a thirdsource, From key, which reads resident (discoverable) OpenSSH
credentials off a FIDO2 authenticator over USB or NFC and turns them into
the usual
*_skstubs. Motivation is the recovery case from #154: if thephone is lost, the authenticator alone is enough to restore access — no
desktop with OpenSSH 8.3+ needed.
The private part never leaves the authenticator. A stub contains only the
public key, application string, and credential handle. Downloading
requires the key's PIN; CTAP credential management always does.
Decisions
Own credential-management client instead of the fido2 package's.
The package's
CredentialManagementhardcodes the CTAP 2.1 command byte(0x0A) and its constructor rejects authenticators that only advertise
credentialMgmtPreview— which is what most deployed YubiKeys expose.The preview command (0x41) shares the wire format, so
_CredentialManagementClientreuses the package's request encoder,response decoder, and data types, and only issues the command byte
itself. It is also more tolerant where pre-2.1 firmware is quirky:
missing
totalRPs/credProtectfields get spec defaults instead of thepackage's null-assertion crashes, and
CTAP2_ERR_NO_CREDENTIALSis anempty result, not an error. OpenSSH's
sk-usbhid.cdoes the same previewfallback. The class can be deleted once the package supports the preview
command.
pinUvAuthParamlength depends on the PIN protocol. The package'sauthenticate()returns the full 32-byte HMAC for both protocols, butCTAP requires
LEFT(HMAC, 16)for PIN protocol 1 and the full HMAC forprotocol 2. Sending 32 bytes to a protocol-1 key — every
credentialMgmtPreview-era YubiKey — fails withPIN_AUTH_INVALID.Protocol 2 is preferred when the key advertises it.
PIN is collected before the CTAP session opens. On iOS an NFC session
does not survive a PIN dialog. Reading resident keys always requires the
PIN, so it is collected up front on all platforms — the same pattern as
the existing signing flow, including re-prompts with the remaining-retries
count after a rejection, bounded at 3 attempts.
Stub flags mirror OpenSSH's
sk-usbhid.c. Downloaded stubs getSSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY(0x21), plusSSH_SK_USER_VERIFICATION_REQDwhen the credential was created withcredProtect
uvRequired(0x03) — so ssh prompts for UV exactly when thekey will demand it.
Only
ssh:applications are enumerated. RPs are filtered to thessh:prefix before any credentials are requested, so WebAuthn logins onthe same key are never read. Credentials with unsupported algorithms are
skipped; ed25519-sk and ecdsa-sk (P-256) are supported.
The picker shows SHA256 fingerprints. Two resident keys can share an
application (both bare
ssh:), making picker rows otherwise identical.The fingerprint is computed exactly like
ssh-keygen -l(SHA256 over thepublic key blob, unpadded base64), so it can be checked against
authorized_keys. The label is pre-filled from the application suffix;ssh-keygen's fixed user name "openssh" is never suggested as a label.
Testing
flutter analyze: 0 issues.dart format: no diff.flutter test: 264/264. 11 new tests drive the downloader against ascripted fake authenticator: byte-exact stubs (re-parsed with
dartssh2), ecdsa point padding, preview command fallback,
credProtect→UV mapping, non-ssh RP filtering, empty key, PIN re-prompt
and cancel, and
pinUvAuthParamlength under both PIN protocols(
FakeCtapDevicegained a configurable token length for theprotocol-2 case).
5.7.1 (CTAP 2.1
credMgmt) and firmware 5.4.3(
credentialMgmtPreview), so both command bytes are verified on realhardware — each with multiple resident SSH keys and the picker, the
5.4.3 run including
verify-requiredcredentials (credProtect → UVflag). Everything works as expected. Both firmwares advertise PIN
protocol 2, which is preferred when available; the protocol-1 length
handling is covered by the unit tests.
Development was assisted by GPT 5.6-sol and Claude Fable 5.