Skip to content

feat(sk): FIDO2 / Security Key auth helpers#146

Merged
kruton merged 4 commits into
connectbot:mainfrom
GlassOnTin:sk-auth-helpers
May 19, 2026
Merged

feat(sk): FIDO2 / Security Key auth helpers#146
kruton merged 4 commits into
connectbot:mainfrom
GlassOnTin:sk-auth-helpers

Conversation

@GlassOnTin
Copy link
Copy Markdown
Contributor

Implements the helpers + tests + docs scoped in #145.

What this adds

org.connectbot.sshlib.sk package — caller-side helpers for SK authentication via AuthHandler. The library remains FIDO-stack-agnostic; callers bring their own CTAP2 transport.

Symbol Purpose
enum class SkAlgorithm ED25519 / ECDSA_P256 with sshName constants and fromSshName(name) / isSkAlgorithm(name).
SkPublicKeyEncoder.encode(algo, rawKey, application) Builds the OpenSSH SK public-key wire blob.
SkPublicKeyDecoder.decode(blob)SkPublicKey Inverse, strict (rejects trailing bytes / wrong curve / size).
SkSignatureBlob.pack(algo, rawSignature, flags, counter) Algorithm-aware per your review comment. Ed25519 takes a 64-byte raw sig; ECDSA-P256 takes the DER signature from CTAP2 and converts to mpint r || mpint s internally. Returns the full SSH SK signature wire blob.
SkAuthHelpers.buildAuthPublicKey(...) One-call AuthPublicKey for onPublicKeysNeeded().

Plus the AuthHandler kdoc update and sk-* early errors in SshSigning.sign() / signWithKeyPair() you OK'd for the same PR.

Tests

  • Unit 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 ECDSA r has the high bit set, and DER-error cases (empty / wrong tag / truncated).
  • Integration test (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, signs SHA256(application) || flags || counter || SHA256(dataToSign) per PROTOCOL.u2f §3.2 with the local key, packages it through SkSignatureBlob.pack(), and runs through the standard SshClient.authenticate(username, AuthHandler) flow. Both algorithms pass against the real sshd, which confirms the wire format matches what OpenSSH accepts.

What's not here (per #145)

  • No CTAP2 transport — out of scope.
  • No on-device credential creation.
  • No parsing of OpenSSH-format SK private-key files (callers like Haven and ConnectBot already have this in their app layer).
  • Agent-forwarding behaviour for SK is unchanged in this PR (separate follow-up if needed).

Verification

  • ./gradlew :sshlib:check is green locally (spotless, Metalava, unit tests, kover verify, integration tests including SK end-to-end).
  • Metalava: only the intentional new public API surfaces; no other API changes.

Closes #145

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
Comment thread sshlib/src/main/kotlin/org/connectbot/sshlib/sk/SkSignatureBlob.kt
Comment thread sshlib/src/main/kotlin/org/connectbot/sshlib/sk/SkPublicKeyDecoder.kt Outdated
kruton added 3 commits May 17, 2026 17:06
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 kruton force-pushed the sk-auth-helpers branch from fcc3173 to 2ff6bc7 Compare May 19, 2026 19:20
@kruton kruton merged commit 55c7366 into connectbot:main May 19, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FIDO2 / SK auth support — architecture proposal

2 participants