From 3d01e21ff8c333bd2d184d8b942951fbf4bbfded Mon Sep 17 00:00:00 2001 From: mimi89999 Date: Thu, 18 Dec 2025 17:18:45 +0100 Subject: [PATCH] Refactor authData parsing into AuthenticatorData structure --- app/src/main/java/pl/lebihan/authnkey/CTAP.kt | 73 ++++++++++++++++++ .../authnkey/CredentialProviderActivity.kt | 76 +++++-------------- 2 files changed, 90 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/pl/lebihan/authnkey/CTAP.kt b/app/src/main/java/pl/lebihan/authnkey/CTAP.kt index a0d4215..a2679c2 100644 --- a/app/src/main/java/pl/lebihan/authnkey/CTAP.kt +++ b/app/src/main/java/pl/lebihan/authnkey/CTAP.kt @@ -5,6 +5,79 @@ data class AlgorithmInfo( val alg: Int? ) +data class AttestedCredentialData( + val aaguid: ByteArray, + val credentialId: ByteArray, + val credentialPublicKey: Map<*, *> +) { + val publicKeyAlgorithm: Int? + get() = (credentialPublicKey[3L] as? Number)?.toInt() + + val keyType: Int? + get() = (credentialPublicKey[1L] as? Number)?.toInt() + + val curve: Int? + get() = (credentialPublicKey[-1L] as? Number)?.toInt() +} + +data class AuthenticatorData( + val rpIdHash: ByteArray, + val flags: Int, + val signCount: Long, + val attestedCredentialData: AttestedCredentialData? +) { + val userPresent: Boolean + get() = (flags and CTAP.AUTH_DATA_FLAG_UP) != 0 + + val userVerified: Boolean + get() = (flags and CTAP.AUTH_DATA_FLAG_UV) != 0 + + val hasAttestedCredentialData: Boolean + get() = (flags and CTAP.AUTH_DATA_FLAG_AT) != 0 + + val hasExtensions: Boolean + get() = (flags and CTAP.AUTH_DATA_FLAG_ED) != 0 + + companion object { + fun parse(data: ByteArray): AuthenticatorData? { + if (data.size < 37) return null + + val rpIdHash = data.sliceArray(0 until 32) + val flags = data[32].toInt() and 0xFF + val signCount = ((data[33].toLong() and 0xFF) shl 24) or + ((data[34].toLong() and 0xFF) shl 16) or + ((data[35].toLong() and 0xFF) shl 8) or + (data[36].toLong() and 0xFF) + + var offset = 37 + val hasAt = (flags and CTAP.AUTH_DATA_FLAG_AT) != 0 + + val attestedCredentialData = if (hasAt) { + if (data.size < offset + 18) return null + + val aaguid = data.sliceArray(offset until offset + 16) + offset += 16 + + val credIdLen = ((data[offset].toInt() and 0xFF) shl 8) or + (data[offset + 1].toInt() and 0xFF) + offset += 2 + + if (data.size < offset + credIdLen) return null + val credentialId = data.sliceArray(offset until offset + credIdLen) + offset += credIdLen + + val credentialPublicKey = CborDecoder.decode( + data.sliceArray(offset until data.size) + ) as? Map<*, *> ?: return null + + AttestedCredentialData(aaguid, credentialId, credentialPublicKey) + } else null + + return AuthenticatorData(rpIdHash, flags, signCount, attestedCredentialData) + } + } +} + data class DeviceInfo( val versions: List = emptyList(), val extensions: List = emptyList(), diff --git a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt index 47600c3..3f38c63 100644 --- a/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt +++ b/app/src/main/java/pl/lebihan/authnkey/CredentialProviderActivity.kt @@ -641,8 +641,9 @@ class CredentialProviderActivity : AppCompatActivity() { makeCredResult.authData ) - // Extract credential ID from authData - val credentialId = extractCredentialIdFromAuthData(makeCredResult.authData) + // Parse authData structure + val authData = AuthenticatorData.parse(makeCredResult.authData) + val credentialId = authData?.attestedCredentialData?.credentialId ?: ByteArray(0) // Check if credProps extension was requested val extensions = requestJson.optJSONObject("extensions") @@ -676,14 +677,16 @@ class CredentialProviderActivity : AppCompatActivity() { makeCredResult.authData, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP )) - extractPublicKeyAlgorithm(makeCredResult.authData)?.let { alg -> - put("publicKeyAlgorithm", alg) - } - extractPublicKeySpki(makeCredResult.authData)?.let { spki -> - put("publicKey", Base64.encodeToString( - spki, - Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP - )) + authData?.attestedCredentialData?.let { attCredData -> + attCredData.publicKeyAlgorithm?.let { alg -> + put("publicKeyAlgorithm", alg) + } + encodePublicKeySpki(attCredData)?.let { spki -> + put("publicKey", Base64.encodeToString( + spki, + Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP + )) + } } }) put("clientExtensionResults", JSONObject().apply { @@ -980,41 +983,10 @@ class CredentialProviderActivity : AppCompatActivity() { return output } - private fun extractCredentialIdFromAuthData(authData: ByteArray): ByteArray { - // authData structure: - // rpIdHash (32 bytes) + flags (1 byte) + signCount (4 bytes) + attestedCredentialData - // attestedCredentialData: aaguid (16 bytes) + credentialIdLength (2 bytes) + credentialId + publicKey - - if (authData.size < 55) { - return ByteArray(0) - } - - val flags = authData[32].toInt() and 0xFF - val hasAttestedCredData = (flags and CTAP.AUTH_DATA_FLAG_AT) != 0 - - if (!hasAttestedCredData) { - return ByteArray(0) - } - - // Skip rpIdHash (32) + flags (1) + signCount (4) + aaguid (16) - val credIdLengthOffset = 32 + 1 + 4 + 16 - val credIdLength = ((authData[credIdLengthOffset].toInt() and 0xFF) shl 8) or - (authData[credIdLengthOffset + 1].toInt() and 0xFF) - - val credIdOffset = credIdLengthOffset + 2 - return authData.sliceArray(credIdOffset until credIdOffset + credIdLength) - } - - private fun extractPublicKeyAlgorithm(authData: ByteArray): Int? { - val coseKey = extractCoseKeyFromAuthData(authData) ?: return null - return (coseKey[3L] as? Number)?.toInt() - } - - private fun extractPublicKeySpki(authData: ByteArray): ByteArray? { - val coseKey = extractCoseKeyFromAuthData(authData) ?: return null - - val kty = (coseKey[1L] as? Number)?.toInt() ?: return null - val crv = (coseKey[-1L] as? Number)?.toInt() ?: return null + private fun encodePublicKeySpki(attCredData: AttestedCredentialData): ByteArray? { + val kty = attCredData.keyType ?: return null + val crv = attCredData.curve ?: return null + val coseKey = attCredData.credentialPublicKey return when (kty) { CTAP.COSE_KTY_EC2 -> encodeEc2KeyAsSpki(coseKey, crv) @@ -1023,20 +995,6 @@ class CredentialProviderActivity : AppCompatActivity() { } } - private fun extractCoseKeyFromAuthData(authData: ByteArray): Map<*, *>? { - if (authData.size < 55) return null - - val flags = authData[32].toInt() and 0xFF - if ((flags and CTAP.AUTH_DATA_FLAG_AT) == 0) return null - - val credIdLenOffset = 32 + 1 + 4 + 16 - val credIdLen = ((authData[credIdLenOffset].toInt() and 0xFF) shl 8) or - (authData[credIdLenOffset + 1].toInt() and 0xFF) - - val coseKeyOffset = credIdLenOffset + 2 + credIdLen - return CborDecoder.decode(authData.sliceArray(coseKeyOffset until authData.size)) as? Map<*, *> - } - private fun encodeEc2KeyAsSpki(coseKey: Map<*, *>, crv: Int): ByteArray? { if (crv != CTAP.COSE_CRV_P256) return null