FIDO2 / Security Key Authentication
May 22, 2026 ยท View on GitHub
The library supports authentication with sk-ssh-ed25519@openssh.com and
sk-ecdsa-sha2-nistp256@openssh.com keys.
Overview
This library does not include a CTAP2 transport (USB-HID, NFC, BLE). Callers are responsible for providing their own FIDO2 stack and surfacing the resulting assertion through AuthHandler.onSignatureRequest.
The helpers in org.connectbot.sshlib.sk cover the SSH wire-format bits so
callers don't have to implement OpenSSH's PROTOCOL.u2f themselves.
Implementation Example
import org.connectbot.sshlib.AuthHandler
import org.connectbot.sshlib.AuthPublicKey
import org.connectbot.sshlib.sk.SkAlgorithm
import org.connectbot.sshlib.sk.SkAuthHelpers
import org.connectbot.sshlib.sk.SkSignatureBlob
class SkAuthHandler(
private val rawPublicKey: ByteArray, // 32-byte Ed25519 pubkey from your stored SK
private val application: String, // RP id, e.g. "ssh:"
private val credentialId: ByteArray, // CTAP2 credential id
private val ctap2: MyCtap2Stack, // your CTAP2 transport
) : AuthHandler {
override suspend fun onPublicKeysNeeded() = listOf(
SkAuthHelpers.buildAuthPublicKey(SkAlgorithm.ED25519, rawPublicKey, application),
)
override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray {
// The CTAP2 device hashes (clientDataHash) for us โ just pass dataToSign through SHA-256.
val assertion = ctap2.getAssertion(
rpId = application,
credentialId = credentialId,
clientDataHash = sha256(dataToSign),
)
return SkSignatureBlob.pack(
algorithm = SkAlgorithm.ED25519,
rawSignature = assertion.signature, // 64-byte raw Ed25519, or DER for ECDSA-P256
flags = assertion.flags, // 0x01 = UP, |0x04 if UV was tested
counter = assertion.counter,
)
}
override suspend fun onPasswordNeeded() = null
override suspend fun onKeyboardInteractivePrompt(...) = null
}
val client = SshClient("server.example.com", hostKeyVerifier = myVerifier)
client.connect()
val result = client.authenticate("user", SkAuthHandler(...))
ECDSA P-256 Keys
For ECDSA-P256 keys, pass SkAlgorithm.ECDSA_P256 and supply the DER-encoded SEQUENCE { INTEGER r, INTEGER s } signature that CTAP2 returns. SkSignatureBlob.pack() converts it to OpenSSH's mpint r || mpint s form internally.
Public Key Decoding
Use SkPublicKeyDecoder to parse public-key blobs that arrive in OpenSSH-format SK files.
Out of Scope
The following are not handled by this library:
- CTAP2 transport (USB-HID, NFC, BLE).
- Credential creation (
ssh-keygen -t *-skequivalent). - Parsing
ssh-keygen's OpenSSH-format SK private-key files. (The library decodes the SK public-key blob; the surrounding OpenSSH private-key envelope is the caller's responsibility.)