From e123c0d1563833ea191fa4de90f8b0a71d2fbaf0 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Wed, 29 Apr 2026 17:31:11 +0200 Subject: [PATCH 1/2] chore: Put passkeys in a dedicated folder on Android --- .../internal/KeyPairManagerImpl.android.kt | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt b/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt index 1d6fa101..4ecbc656 100644 --- a/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt +++ b/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt @@ -31,14 +31,26 @@ internal actual fun createKeyPairManager(): KeyPairManager = KeyPairManagerAndro private class KeyPairManagerAndroidImpl : KeyPairManager() { + private val keysDir by lazy { + appCtx.filesDir.resolve("passkeys").also { passkeysDir -> + passkeysDir.mkdir() + //TODO[ik-auth]: Remove the code below after the next pre-release. + appCtx.filesDir.listFiles { it.name.endsWith(".key") }!!.forEach { keyFile -> + keyFile.renameTo(passkeysDir.resolve(keyFile.name)) + } + } + } + @Throws(Exception::class) override suspend fun generateNewKey(userId: Long, keyId: String): Failure.KeyManagement.GenerationFailed? { val keyPair = generateEcKeyPair().getOrElse { return Failure.KeyManagement.GenerationFailed(it.toString()) } - saveFileToFilesDir("$userId-$keyId-private.key", keyPair.private.encoded) - saveFileToFilesDir("$userId-$keyId-public.key", keyPair.public.encoded) + Dispatchers.IO { + keyFile(userId = userId, keyId = keyId, isPublic = false).writeBytes(keyPair.private.encoded) + keyFile(userId = userId, keyId = keyId, isPublic = true).writeBytes(keyPair.public.encoded) + } return null } @@ -46,7 +58,7 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { userId: Long, keyId: String, ): Xor = Dispatchers.IO { - val file = File(appCtx.filesDir, "$userId-$keyId-public.key") + val file = keyFile(userId = userId, keyId = keyId, isPublic = true) runCatching { Xor.First(file.readBytes()) }.getOrElse { Xor.Second(Failure.KeyManagement.KeyExtractionFailed(it.toString())) } @@ -56,7 +68,7 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { userId: Long, keyId: String, ): Xor = Dispatchers.IO { - val file = File(appCtx.filesDir, "$userId-$keyId-private.key") + val file = keyFile(userId = userId, keyId = keyId, isPublic = false) runCatching { Xor.First(file.readBytes()) }.getOrElse { Xor.Second(Failure.KeyManagement.KeyExtractionFailed(it.toString())) } @@ -64,7 +76,7 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { override suspend fun getSortedKeyIds(matchOn: MatchOn): List { val files = withContext(Dispatchers.IO) { - appCtx.filesDir.listFiles() + keysDir.listFiles() } ?: return emptyList() return buildList { val predicate = matchOn.asFilterPredicate() @@ -91,19 +103,18 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { override suspend fun findKeyIdFor(matchOn: MatchOn): String? { val predicate = matchOn.asFilterPredicate() val userPassKey: File = withContext(Dispatchers.IO) { - appCtx.filesDir.listFiles() + keysDir.listFiles() }?.find { predicate(it.name) } ?: return null - //TODO 2: Put keys into a dedicated dir return extractKeyIdFromFileName(userPassKey.name) } override suspend fun deleteKeysMatching(matchOn: MatchOn): Xor { val predicate = matchOn.asFilterPredicate() val keys = withContext(Dispatchers.IO) { - appCtx.filesDir.listFiles() + keysDir.listFiles() }?.filter { predicate(it.name) } ?: return Xor.Second(Failure.KeyManagement.KeyNotFound("No keys")) @@ -120,8 +131,8 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { endIndex = name.indexOfLast { it == '-' } ) - private suspend fun saveFileToFilesDir(fileName: String, key: ByteArray) = Dispatchers.IO { - val file = File(appCtx.filesDir, fileName) - file.writeBytes(key) + private fun keyFile(userId: Long, keyId: String, isPublic: Boolean): File { + val visibility = if (isPublic) "public" else "private" + return keysDir.resolve("$userId-$keyId-$visibility.key") } } From a601d3ce4dea5ea901a737d33e5ddeb1c4130d54 Mon Sep 17 00:00:00 2001 From: Louis CAD Date: Thu, 30 Apr 2026 13:03:04 +0200 Subject: [PATCH 2/2] chore: Ensure key pairs are moved for test users --- .../androidMain/kotlin/internal/KeyPairManagerImpl.android.kt | 3 +++ multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt | 2 +- multiplatform-lib/src/commonMain/kotlin/internal/KeyManager.kt | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt b/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt index 4ecbc656..1a52fba8 100644 --- a/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt +++ b/multiplatform-lib/src/androidMain/kotlin/internal/KeyPairManagerImpl.android.kt @@ -41,6 +41,9 @@ private class KeyPairManagerAndroidImpl : KeyPairManager() { } } + override fun ensureKeyPairsAreMoved() { + val _ = keysDir //TODO[ik-auth]: Remove this code and the super method after the next pre-release. + } @Throws(Exception::class) override suspend fun generateNewKey(userId: Long, keyId: String): Failure.KeyManagement.GenerationFailed? { val keyPair = generateEcKeyPair().getOrElse { diff --git a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt index c52dcb1f..ca624a23 100644 --- a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt +++ b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt @@ -89,7 +89,7 @@ abstract class AuthenticatorFacade internal constructor() { val authenticatorManager = AuthenticatorManager( webAuthnRepository = webAuthnRepository, accountsRepository = accountsRepository - ) + ).also { it.keyPairManager.ensureKeyPairsAreMoved() } val migrationManager = MigrationManager( accountsDatabase = accountsDatabase, authenticatorManager = authenticatorManager, diff --git a/multiplatform-lib/src/commonMain/kotlin/internal/KeyManager.kt b/multiplatform-lib/src/commonMain/kotlin/internal/KeyManager.kt index 6ca729eb..ca7cebe4 100644 --- a/multiplatform-lib/src/commonMain/kotlin/internal/KeyManager.kt +++ b/multiplatform-lib/src/commonMain/kotlin/internal/KeyManager.kt @@ -30,6 +30,8 @@ internal abstract class KeyPairManager protected constructor() { operator fun invoke(): KeyPairManager = createKeyPairManager() } + open fun ensureKeyPairsAreMoved() = Unit + /** * Generates key pair for a new registration * (migrating from kAuth v1 or a backup, or a fresh new login)