From ae46268907112c02a8d045f076f0dd0ce121a994 Mon Sep 17 00:00:00 2001 From: Anton Tkachev Date: Tue, 5 Mar 2024 18:50:29 +0400 Subject: [PATCH] AND-6313 [Biometrics] Added migrations to work with different master key configurations --- .../{ => storage}/AuthenticatedStorage.kt | 151 +++++++++++------- 1 file changed, 94 insertions(+), 57 deletions(-) rename tangem-sdk-core/src/main/java/com/tangem/common/authentication/{ => storage}/AuthenticatedStorage.kt (60%) diff --git a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticatedStorage.kt b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/storage/AuthenticatedStorage.kt similarity index 60% rename from tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticatedStorage.kt rename to tangem-sdk-core/src/main/java/com/tangem/common/authentication/storage/AuthenticatedStorage.kt index cbb08b6e..899cb034 100644 --- a/tangem-sdk-core/src/main/java/com/tangem/common/authentication/AuthenticatedStorage.kt +++ b/tangem-sdk-core/src/main/java/com/tangem/common/authentication/storage/AuthenticatedStorage.kt @@ -1,6 +1,8 @@ -package com.tangem.common.authentication +package com.tangem.common.authentication.storage import com.tangem.Log +import com.tangem.common.authentication.keystore.KeystoreManager +import com.tangem.common.authentication.keystore.MasterKeyConfigs import com.tangem.common.services.secure.SecureStorage import com.tangem.crypto.operations.AESCipherOperations import kotlinx.coroutines.Dispatchers @@ -28,19 +30,49 @@ class AuthenticatedStorage( suspend fun get(keyAlias: String): ByteArray? = withContext(Dispatchers.IO) { val encryptedData = secureStorage.get(keyAlias) ?.takeIf(ByteArray::isNotEmpty) + ?: run { + Log.warning { + """ + $TAG - Data not found in storage + |- Key: $keyAlias + """.trimIndent() + } + + return@withContext null + } - if (encryptedData == null) { + val (config, key) = getSecretKey(keyAlias) ?: run { Log.warning { """ - $TAG - Data not found in storage - |- Key: $keyAlias + $TAG - The secret key is not stored + |- Key alias: $keyAlias """.trimIndent() } return@withContext null } - decrypt(keyAlias, encryptedData) + val decryptedData = getDecryptedData(keyAlias, key, encryptedData) + + migrateIfNeeded(keyAlias, decryptedData, config) + + decryptedData + } + + private suspend fun getSecretKey(keyAlias: String): Pair? { + return MasterKeyConfigs.all.reversed() + .firstNotNullOfOrNull { masterKeyConfig -> + keystoreManager.get(masterKeyConfig, keyAlias)?.let { secretKey -> + masterKeyConfig to secretKey + } + } + } + + private suspend fun migrateIfNeeded(keyAlias: String, decryptedData: ByteArray, config: MasterKeyConfigs) { + if (!config.isDeprecated) return + + val encryptedData = encrypt(keyAlias, decryptedData) + secureStorage.store(encryptedData, keyAlias) } /** @@ -59,20 +91,71 @@ class AuthenticatedStorage( keyAlias to data } - .toMap() + .takeIf { it.isNotEmpty() } + ?.toMap() + ?: run { + Log.warning { + """ + $TAG - Data not found in storage + |- Keys: $keysAliases + """.trimIndent() + } + + return@withContext emptyMap() + } - if (encryptedData.isEmpty()) { + val (config, keys) = getSecretKeys(keysAliases) ?: run { Log.warning { """ - $TAG - Data not found in storage - |- Keys: $keysAliases + $TAG - The secret keys are not stored + |- Key aliases: $keysAliases """.trimIndent() } return@withContext emptyMap() } - decrypt(encryptedData) + val decryptedData = encryptedData + .mapNotNull { (keyAlias, encryptedData) -> + val key = keys[keyAlias] ?: return@mapNotNull null + val decryptedData = getDecryptedData(keyAlias, key, encryptedData) + + keyAlias to decryptedData + } + .toMap() + + migrateIfNeeded(decryptedData, config) + + decryptedData + } + + private fun getDecryptedData(keyAlias: String, key: SecretKey, encryptedData: ByteArray): ByteArray { + val iv = getDataIv(keyAlias) + val decryptionCipher = AESCipherOperations.initDecryptionCipher(key, iv) + + return AESCipherOperations.decrypt(decryptionCipher, encryptedData) + } + + private suspend fun getSecretKeys( + keysAliases: Collection, + ): Pair>? { + return MasterKeyConfigs.all.reversed() + .firstNotNullOfOrNull { masterKeyConfig -> + val keys = keystoreManager.get(masterKeyConfig, keysAliases) + .takeIf { it.isNotEmpty() } + ?: return@firstNotNullOfOrNull null + + masterKeyConfig to keys + } + } + + private suspend fun migrateIfNeeded(decryptedData: Map, config: MasterKeyConfigs) { + if (!config.isDeprecated) return + + decryptedData.forEach { (keyAlias, data) -> + val encryptedData = encrypt(keyAlias, data) + secureStorage.store(encryptedData, keyAlias) + } } /** @@ -106,56 +189,10 @@ class AuthenticatedStorage( return encryptedData } - private suspend fun decrypt(keyAlias: String, encryptedData: ByteArray): ByteArray? { - val key = keystoreManager.get(keyAlias) - - if (key == null) { - Log.warning { - """ - $TAG - The data key is not stored - |- Key alias: $keyAlias - """.trimIndent() - } - - return null - } - - val iv = getDataIv(keyAlias) - val decryptionCipher = AESCipherOperations.initDecryptionCipher(key, iv) - - return AESCipherOperations.decrypt(decryptionCipher, encryptedData) - } - - private suspend fun decrypt(keyAliasToEncryptedData: Map): Map { - val keys = keystoreManager.get(keyAliasToEncryptedData.keys) - - if (keys.isEmpty()) { - Log.warning { - """ - $TAG - The data keys are not stored - |- Key aliases: ${keyAliasToEncryptedData.keys} - """.trimIndent() - } - - return emptyMap() - } - - return keyAliasToEncryptedData - .mapNotNull { (keyAlias, encryptedData) -> - val key = keys[keyAlias] ?: return@mapNotNull null - val iv = getDataIv(keyAlias) - val decryptionCipher = AESCipherOperations.initDecryptionCipher(key, iv) - val decryptedData = AESCipherOperations.decrypt(decryptionCipher, encryptedData) - - keyAlias to decryptedData - } - .toMap() - } - private suspend fun generateAndStoreDataKey(keyAlias: String): SecretKey { val dataKey = AESCipherOperations.generateKey() - keystoreManager.store(keyAlias, dataKey) + keystoreManager.store(MasterKeyConfigs.current, keyAlias, dataKey) return dataKey }