Refactor authData parsing into AuthenticatorData structure

This commit is contained in:
mimi89999
2025-12-18 17:18:45 +01:00
parent 212dfa6046
commit 3d01e21ff8
2 changed files with 90 additions and 59 deletions

View File

@@ -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<String> = emptyList(),
val extensions: List<String> = emptyList(),

View File

@@ -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