feat(sk): FIDO2 / Security Key auth helpers#146
Merged
Conversation
Adds org.connectbot.sshlib.sk: helper API for callers that drive SK authentication via AuthHandler. The library remains FIDO-stack-agnostic (no CTAP2 transport) — callers bring their own USB/NFC/BLE FIDO2 stack and surface the resulting signature through onSignatureRequest. Public surface: - SkAlgorithm enum (sk-ssh-ed25519@openssh.com, sk-ecdsa-sha2-nistp256@openssh.com) - SkPublicKeyEncoder / SkPublicKeyDecoder for the OpenSSH SK public-key blob - SkSignatureBlob.pack(algorithm, rawSignature, flags, counter) for the full signature wire blob per PROTOCOL.u2f §3.2; ECDSA path takes DER from CTAP2 and converts to mpint r || mpint s internally - SkAuthHelpers.buildAuthPublicKey() for the common onPublicKeysNeeded() case - SshSigning.sign() / signWithKeyPair() now reject sk-* names early with a message pointing to the AuthHandler API - AuthHandler.onSignatureRequest kdoc explains the external-signer pattern Tests cover both algorithms end-to-end against an OpenSSH 9.9p2 container: the test acts as a software SK authenticator (Ed25519 / SHA256withECDSA over SHA256(rpId) || flags || counter || SHA256(challenge) per PROTOCOL.u2f §3.2) and OpenSSH accepts the resulting auth. Byte-exact unit-test vectors cover both the public-key blob and the full signature blob (including the mpint zero-padding case for ECDSA r/s with the high bit set). Closes connectbot#145
kruton
requested changes
May 13, 2026
This removes the hand-written parsing code for FIDO2/SK keys. Also includes some negative tests to make sure the parsing rejects invalid keys.
kruton
approved these changes
May 19, 2026
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.
Implements the helpers + tests + docs scoped in #145.
What this adds
org.connectbot.sshlib.skpackage — caller-side helpers for SK authentication viaAuthHandler. The library remains FIDO-stack-agnostic; callers bring their own CTAP2 transport.enum class SkAlgorithmED25519/ECDSA_P256withsshNameconstants andfromSshName(name)/isSkAlgorithm(name).SkPublicKeyEncoder.encode(algo, rawKey, application)SkPublicKeyDecoder.decode(blob)→SkPublicKeySkSignatureBlob.pack(algo, rawSignature, flags, counter)mpint r || mpint sinternally. Returns the full SSH SK signature wire blob.SkAuthHelpers.buildAuthPublicKey(...)AuthPublicKeyforonPublicKeysNeeded().Plus the AuthHandler kdoc update and
sk-*early errors inSshSigning.sign()/signWithKeyPair()you OK'd for the same PR.Tests
sshlib/src/test/kotlin/org/connectbot/sshlib/sk/) — byte-exact vectors for public-key encoding and the full signature wire blob, both algorithms. Includes the mpint zero-padding case where ECDSArhas the high bit set, and DER-error cases (empty / wrong tag / truncated).SkAuthIntegrationTest) — uses the existing OpenSSH 9.9p2 Testcontainers image. The test acts as a software SK authenticator: generates an Ed25519 (or ECDSA-P256) keypair at startup, signsSHA256(application) || flags || counter || SHA256(dataToSign)perPROTOCOL.u2f§3.2 with the local key, packages it throughSkSignatureBlob.pack(), and runs through the standardSshClient.authenticate(username, AuthHandler)flow. Both algorithms pass against the realsshd, which confirms the wire format matches what OpenSSH accepts.What's not here (per #145)
Verification
./gradlew :sshlib:checkis green locally (spotless, Metalava, unit tests, kover verify, integration tests including SK end-to-end).Closes #145