diff --git a/app/src/main/java/app/passwordstore/passkeys/AppPasskeyProviderActivity.kt b/app/src/main/java/app/passwordstore/passkeys/AppPasskeyProviderActivity.kt index e74237742..06b5c97d0 100644 --- a/app/src/main/java/app/passwordstore/passkeys/AppPasskeyProviderActivity.kt +++ b/app/src/main/java/app/passwordstore/passkeys/AppPasskeyProviderActivity.kt @@ -573,23 +573,16 @@ class AppPasskeyProviderActivity : BaseGitActivity() { try { val publicCredential = createdCredential.credential - val credentialWithBinding = - publicCredential.copy( - createdByCallerType = verifiedContext.callerType, - createdByPackage = verifiedContext.callingPackage, - createdByCertificateDigest = verifiedContext.signingCertificateDigests.firstOrNull(), - verifiedOrigin = verifiedContext.origin, - ) val responseJson = PasskeyProviderUtils.buildAttestationResponse( - credentialWithBinding, + publicCredential, createRequest.requestJson, verifiedContext, ) val saveResult = createdCredential.usePrivateKeySuspend { privateKey -> - passkeyStorage.saveCredential(credentialWithBinding, privateKey) + passkeyStorage.saveCredential(publicCredential, privateKey) } if (saveResult.isErr) { val error: Throwable? = @@ -611,12 +604,12 @@ class AppPasskeyProviderActivity : BaseGitActivity() { passkeyRepositoryState.onCredentialSaved() generationProvider.bumpWorktreeGeneration() metadataIndex.put( - credentialWithBinding.credentialId, + publicCredential.credentialId, MetadataEntry( - userName = credentialWithBinding.user.name, - userDisplayName = credentialWithBinding.user.displayName, - rpId = credentialWithBinding.rpId, - createdAt = credentialWithBinding.createdAt.toEpochMilliseconds(), + userName = publicCredential.user.name, + userDisplayName = publicCredential.user.displayName, + rpId = publicCredential.rpId, + createdAt = publicCredential.createdAt.toEpochMilliseconds(), ), ) diff --git a/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/PasskeyCredential.kt b/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/PasskeyCredential.kt index 8a0a11f9b..ac0dbf7ba 100644 --- a/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/PasskeyCredential.kt +++ b/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/PasskeyCredential.kt @@ -7,7 +7,6 @@ package app.passwordstore.passkeys.model -import app.passwordstore.passkeys.crypto.CallerType import kotlin.time.Instant import kotlinx.serialization.Serializable @@ -23,10 +22,6 @@ public data class PasskeyCredential( public val uvInitialized: Boolean = true, public val backupEligible: Boolean = true, public val backupState: Boolean = false, - public val createdByCallerType: CallerType? = null, - public val createdByPackage: String? = null, - public val createdByCertificateDigest: String? = null, - public val verifiedOrigin: String? = null, ) { init { @@ -51,10 +46,6 @@ public data class PasskeyCredential( if (uvInitialized != other.uvInitialized) return false if (backupEligible != other.backupEligible) return false if (backupState != other.backupState) return false - if (createdByCallerType != other.createdByCallerType) return false - if (createdByPackage != other.createdByPackage) return false - if (createdByCertificateDigest != other.createdByCertificateDigest) return false - if (verifiedOrigin != other.verifiedOrigin) return false return true } @@ -69,10 +60,6 @@ public data class PasskeyCredential( result = 31 * result + uvInitialized.hashCode() result = 31 * result + backupEligible.hashCode() result = 31 * result + backupState.hashCode() - result = 31 * result + (createdByCallerType?.hashCode() ?: 0) - result = 31 * result + (createdByPackage?.hashCode() ?: 0) - result = 31 * result + (createdByCertificateDigest?.hashCode() ?: 0) - result = 31 * result + (verifiedOrigin?.hashCode() ?: 0) return result } diff --git a/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/StoredCredential.kt b/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/StoredCredential.kt index 76d5a05e3..1e897c7d7 100644 --- a/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/StoredCredential.kt +++ b/passkeys/core/src/main/kotlin/app/passwordstore/passkeys/model/StoredCredential.kt @@ -11,7 +11,6 @@ import app.passwordstore.passkeys.cbor.Cbor import app.passwordstore.passkeys.cbor.CborMap import app.passwordstore.passkeys.cbor.CborValue import app.passwordstore.passkeys.cbor.toCborIntegerArray -import app.passwordstore.passkeys.crypto.CallerType import java.math.BigInteger import kotlin.time.Instant import logcat.LogPriority @@ -31,10 +30,6 @@ public data class StoredCredential( val extensions: Extensions = Extensions(), val backupEligible: Boolean = true, val backupState: Boolean = false, - val createdByCallerType: CallerType? = null, - val createdByPackage: String? = null, - val createdByCertificateDigest: String? = null, - val verifiedOrigin: String? = null, ) : AutoCloseable { @Volatile private var wiped = false @@ -70,18 +65,6 @@ public data class StoredCredential( map["extensions"] = CborValue.Map(extensions.toCborMap()) map["backup_eligible"] = if (backupEligible) CborValue.True else CborValue.False map["backup_state"] = if (backupState) CborValue.True else CborValue.False - createdByCallerType?.let { - map["created_by_caller_type"] = CborValue.TextString(it.name) - } ?: run { map["created_by_caller_type"] = CborValue.Null } - createdByPackage?.let { - map["created_by_package"] = CborValue.TextString(it) - } ?: run { map["created_by_package"] = CborValue.Null } - createdByCertificateDigest?.let { - map["created_by_cert_digest"] = CborValue.TextString(it) - } ?: run { map["created_by_cert_digest"] = CborValue.Null } - verifiedOrigin?.let { - map["verified_origin"] = CborValue.TextString(it) - } ?: run { map["verified_origin"] = CborValue.Null } return Cbor.fromMap(CborMap.from(map)).toBytes() } @@ -97,10 +80,6 @@ public data class StoredCredential( uvInitialized = true, backupEligible = backupEligible, backupState = backupState, - createdByCallerType = createdByCallerType, - createdByPackage = createdByPackage, - createdByCertificateDigest = createdByCertificateDigest, - verifiedOrigin = verifiedOrigin, ) } @@ -126,10 +105,6 @@ public data class StoredCredential( if (extensions != other.extensions) return false if (backupEligible != other.backupEligible) return false if (backupState != other.backupState) return false - if (createdByCallerType != other.createdByCallerType) return false - if (createdByPackage != other.createdByPackage) return false - if (createdByCertificateDigest != other.createdByCertificateDigest) return false - if (verifiedOrigin != other.verifiedOrigin) return false return true } @@ -146,10 +121,6 @@ public data class StoredCredential( result = 31 * result + extensions.hashCode() result = 31 * result + backupEligible.hashCode() result = 31 * result + backupState.hashCode() - result = 31 * result + (createdByCallerType?.hashCode() ?: 0) - result = 31 * result + (createdByPackage?.hashCode() ?: 0) - result = 31 * result + (createdByCertificateDigest?.hashCode() ?: 0) - result = 31 * result + (verifiedOrigin?.hashCode() ?: 0) return result } @@ -211,22 +182,6 @@ public data class StoredCredential( val extensionsMap = map.getMap("extensions") val backupEligible = map.getBoolean("backup_eligible") ?: true val backupState = map.getBoolean("backup_state") ?: false - val createdByCallerType = - if (map.isNull("created_by_caller_type")) null - else - map.getString("created_by_caller_type")?.let { - try { - CallerType.valueOf(it) - } catch (_: Exception) { - null - } - } - val createdByPackage = - if (map.isNull("created_by_package")) null else map.getString("created_by_package") - val createdByCertificateDigest = - if (map.isNull("created_by_cert_digest")) null else map.getString("created_by_cert_digest") - val verifiedOrigin = - if (map.isNull("verified_origin")) null else map.getString("verified_origin") return StoredCredential( id = id, @@ -241,10 +196,6 @@ public data class StoredCredential( extensions = extensionsMap?.let { Extensions.fromCborMap(it) } ?: Extensions(), backupEligible = backupEligible, backupState = backupState, - createdByCallerType = createdByCallerType, - createdByPackage = createdByPackage, - createdByCertificateDigest = createdByCertificateDigest, - verifiedOrigin = verifiedOrigin, ) } @@ -301,10 +252,6 @@ public data class StoredCredential( extensions = Extensions(), backupEligible = credential.backupEligible, backupState = credential.backupState, - createdByCallerType = credential.createdByCallerType, - createdByPackage = credential.createdByPackage, - createdByCertificateDigest = credential.createdByCertificateDigest, - verifiedOrigin = credential.verifiedOrigin, ) } } diff --git a/passkeys/core/src/test/kotlin/app/passwordstore/passkeys/model/StoredCredentialBindingMetadataTest.kt b/passkeys/core/src/test/kotlin/app/passwordstore/passkeys/model/StoredCredentialBindingMetadataTest.kt deleted file mode 100644 index 2756e63cb..000000000 --- a/passkeys/core/src/test/kotlin/app/passwordstore/passkeys/model/StoredCredentialBindingMetadataTest.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright © 2014-2026 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -@file:OptIn(kotlin.time.ExperimentalTime::class) - -package app.passwordstore.passkeys.model - -import app.passwordstore.passkeys.crypto.CallerType -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Test - -class StoredCredentialBindingMetadataTest { - - private fun baseCredential(): StoredCredential = - StoredCredential( - id = ByteArray(32) { it.toByte() }, - rp = RelyingParty(id = "example.com", name = "Example"), - user = User(id = "user1".toByteArray(), name = "testuser", displayName = "Test User"), - signCount = 0u, - alg = StoredCredential.ALG_ES256, - privateKey = ByteArray(32) { (it + 1).toByte() }, - publicKey = ByteArray(65).also { it[0] = 0x04 }, - created = 1700000000L, - ) - - @Test - fun `binding metadata round-trips through CBOR`() { - val original = - baseCredential() - .copy( - createdByCallerType = CallerType.NATIVE_APP, - createdByPackage = "com.example.app", - createdByCertificateDigest = "abc123digest", - verifiedOrigin = "android:apk-key-hash:xyz", - ) - - val cbor = original.toCbor() - val restored = StoredCredential.fromCbor(cbor) - - assertEquals(CallerType.NATIVE_APP, restored.createdByCallerType) - assertEquals("com.example.app", restored.createdByPackage) - assertEquals("abc123digest", restored.createdByCertificateDigest) - assertEquals("android:apk-key-hash:xyz", restored.verifiedOrigin) - } - - @Test - fun `null binding metadata round-trips through CBOR`() { - val original = baseCredential() - val cbor = original.toCbor() - val restored = StoredCredential.fromCbor(cbor) - - assertNull(restored.createdByCallerType) - assertNull(restored.createdByPackage) - assertNull(restored.createdByCertificateDigest) - assertNull(restored.verifiedOrigin) - } - - @Test - fun `browser caller type round-trips`() { - val original = - baseCredential() - .copy( - createdByCallerType = CallerType.PRIVILEGED_BROWSER, - createdByPackage = "com.android.chrome", - createdByCertificateDigest = "browserdigest", - verifiedOrigin = "https://example.com", - ) - - val cbor = original.toCbor() - val restored = StoredCredential.fromCbor(cbor) - - assertEquals(CallerType.PRIVILEGED_BROWSER, restored.createdByCallerType) - assertEquals("com.android.chrome", restored.createdByPackage) - assertEquals("https://example.com", restored.verifiedOrigin) - } - - @Test - fun `toPasskeyCredential preserves binding metadata`() { - val stored = - baseCredential() - .copy( - createdByCallerType = CallerType.NATIVE_APP, - createdByPackage = "com.example", - createdByCertificateDigest = "digest", - verifiedOrigin = "android:apk-key-hash:abc", - ) - - val passkey = stored.toPasskeyCredential() - - assertEquals(CallerType.NATIVE_APP, passkey.createdByCallerType) - assertEquals("com.example", passkey.createdByPackage) - assertEquals("digest", passkey.createdByCertificateDigest) - assertEquals("android:apk-key-hash:abc", passkey.verifiedOrigin) - } - - @Test - fun `fromPasskeyCredential preserves binding metadata`() { - val privateKey = ByteArray(32) - val passkey = - app.passwordstore.passkeys.model.PasskeyCredential( - credentialId = ByteArray(32), - publicKey = ByteArray(65).also { it[0] = 0x04 }, - rpId = "example.com", - user = FidoUser(id = ByteArray(4), name = "user", displayName = "User"), - createdAt = kotlin.time.Instant.fromEpochSeconds(1700000000L), - createdByCallerType = CallerType.PRIVILEGED_BROWSER, - createdByPackage = "com.brave.browser", - createdByCertificateDigest = "bravedigest", - verifiedOrigin = "https://example.com", - ) - - val stored = StoredCredential.fromPasskeyCredential(passkey, privateKey) - - assertEquals(CallerType.PRIVILEGED_BROWSER, stored.createdByCallerType) - assertEquals("com.brave.browser", stored.createdByPackage) - assertEquals("bravedigest", stored.createdByCertificateDigest) - assertEquals("https://example.com", stored.verifiedOrigin) - } - - @Test - fun `fromPasskeyCredential owns arrays it wipes`() { - val credentialId = ByteArray(32) { (it + 1).toByte() } - val publicKey = ByteArray(65) { if (it == 0) 0x04.toByte() else it.toByte() } - val userId = ByteArray(16) { (it + 2).toByte() } - val privateKey = ByteArray(32) { (it + 3).toByte() } - val passkey = - PasskeyCredential( - credentialId = credentialId, - publicKey = publicKey, - rpId = "example.com", - user = FidoUser(id = userId, name = "user", displayName = "User"), - createdAt = kotlin.time.Instant.fromEpochSeconds(1700000000L), - ) - val expectedCredentialId = credentialId.copyOf() - val expectedPublicKey = publicKey.copyOf() - val expectedUserId = userId.copyOf() - val expectedPrivateKey = privateKey.copyOf() - - StoredCredential.fromPasskeyCredential(passkey, privateKey).close() - - assertArrayEquals(expectedCredentialId, credentialId) - assertArrayEquals(expectedPublicKey, publicKey) - assertArrayEquals(expectedUserId, userId) - assertArrayEquals(expectedPrivateKey, privateKey) - } - - @Test - fun `old credential without binding fields can be parsed`() { - val original = baseCredential() - val cbor = original.toCbor() - val restored = StoredCredential.fromCbor(cbor) - - assertNull(restored.createdByCallerType) - assertNull(restored.createdByPackage) - assertNull(restored.createdByCertificateDigest) - assertNull(restored.verifiedOrigin) - assertEquals("example.com", restored.rp.id) - assertArrayEquals(original.id, restored.id) - } -}