diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index c285a15c80..a27e52d22e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -75,9 +75,9 @@ internal class BiometricUserWalletsListManager( } val selectedUserWallet = selectedUserWalletSync - if (selectedUserWallet == null || selectedUserWallet.isLocked) { - findAndSetUnlockedUserWallet(userWallets) - ?: throw UserWalletsListError.NoUserWalletSelected + if (selectedUserWallet == null) { + Timber.e("Unable to find selected user wallet") + throw UserWalletsListError.NoUserWalletSelected } else { selectedUserWallet } @@ -257,10 +257,7 @@ internal class BiometricUserWalletsListManager( prevState.copy( userWallets = wallets, - selectedUserWalletId = findOrSetSelectedUserWalletId( - prevSelectedWalletId = prevState.selectedUserWalletId, - userWallets = wallets, - ), + selectedUserWalletId = findOrSetSelectedWalletId(prevState.selectedUserWalletId, wallets), ) } } @@ -278,18 +275,22 @@ internal class BiometricUserWalletsListManager( } } - private fun findOrSetSelectedUserWalletId( + private fun findOrSetSelectedWalletId( prevSelectedWalletId: UserWalletId?, userWallets: List, ): UserWalletId? { - return prevSelectedWalletId - ?: (selectedUserWalletRepository.get() ?: findAndSetUnlockedUserWallet(userWallets)?.walletId) - } + val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get() + var possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId) - private fun findAndSetUnlockedUserWallet(userWallets: List): UserWallet? { - return userWallets - .firstOrNull { !it.isLocked } - ?.also { selectedUserWalletRepository.set(it.walletId) } + if (possibleSelectedUserWallet == null || possibleSelectedUserWallet.isLocked) { + possibleSelectedUserWallet = userWallets.firstOrNull { !it.isLocked } ?: userWallets.firstOrNull() + + if (possibleSelectedUserWallet != null) { + selectedUserWalletRepository.set(possibleSelectedUserWallet.walletId) + } + } + + return possibleSelectedUserWallet?.walletId } private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { @@ -318,10 +319,11 @@ internal class BiometricUserWalletsListManager( } } - private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? { - return userWallets.firstOrNull { - it.walletId == state.value.selectedUserWalletId - } + private fun findSelectedUserWallet( + userWallets: List = state.value.userWallets, + selectedUserWalletId: UserWalletId? = state.value.selectedUserWalletId, + ): UserWallet? { + return userWallets.firstOrNull { it.walletId == selectedUserWalletId } } private data class State( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 8c67c90889..13c56f4f22 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -1,12 +1,12 @@ package com.tangem.tap.domain.userWalletList.repository.implementation -import android.security.keystore.KeyProperties import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.catching import com.tangem.common.services.secure.SecureStorage +import com.tangem.crypto.operations.AESCipherOperations import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.common.extensions.filterNotNull @@ -16,14 +16,14 @@ import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInfor import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec +import timber.log.Timber import javax.crypto.spec.SecretKeySpec internal class DefaultUserWalletsSensitiveInformationRepository( moshi: Moshi, private val secureStorage: SecureStorage, ) : UserWalletsSensitiveInformationRepository { + private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( UserWalletSensitiveInformation::class.java, ) @@ -31,12 +31,11 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), ) - private val cipher: Cipher by lazy { - Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") - } - override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { - if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing + if (encryptionKey == null) { + return CompletionResult.Success(Unit) // Encryption key is null, do nothing + } + return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() @@ -63,7 +62,7 @@ internal class DefaultUserWalletsSensitiveInformationRepository( .mapValues { (userWalletId, encryptionKey) -> encryptedSensitiveInformation[userWalletId.stringValue] ?.getIvAndDecrypt(userWalletId.stringValue, encryptionKey.encryptionKey) - .decodeToSensitiveInformation() + ?.decodeToSensitiveInformation() } .filterNotNull() } @@ -80,7 +79,8 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private suspend fun getAllEncrypted(): Map { return withContext(Dispatchers.IO) { secureStorage.get(StorageKey.UserWalletsSensitiveInformation.name) - .decodeToEncryptedSensitiveInformation() + ?.decodeToEncryptedSensitiveInformation() + .orEmpty() } } @@ -105,20 +105,25 @@ internal class DefaultUserWalletsSensitiveInformationRepository( } } - private suspend fun ByteArray?.decodeToEncryptedSensitiveInformation(): Map { + private suspend fun ByteArray.decodeToEncryptedSensitiveInformation(): Map? { return withContext(Dispatchers.Default) { this@decodeToEncryptedSensitiveInformation - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(encryptedSensitiveInformationMapAdapter::fromJson) - .orEmpty() + .decodeToString(throwOnInvalidSequence = true) + .let(encryptedSensitiveInformationMapAdapter::fromJson) } } - private suspend fun ByteArray?.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { + private suspend fun ByteArray.decodeToSensitiveInformation(): UserWalletSensitiveInformation? { return withContext(Dispatchers.Default) { - this@decodeToSensitiveInformation - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(sensitiveInformationAdapter::fromJson) + try { + this@decodeToSensitiveInformation + .decodeToString(throwOnInvalidSequence = true) + .let(sensitiveInformationAdapter::fromJson) + } catch (e: CharacterCodingException) { + Timber.e(e, "Unable to decode sensitive information") + + null + } } } @@ -140,23 +145,24 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private suspend fun ByteArray.encryptAndStoreIv(userWalletId: String, encryptionKey: ByteArray): ByteArray { return withContext(Dispatchers.Default) { - val secretKey = SecretKeySpec(encryptionKey, algorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - val encryptedData = cipher.doFinal(this@encryptAndStoreIv) - secureStorage.store(data = cipher.iv, account = StorageKey.SensitiveInformationIv(userWalletId).name) + val secretKey = SecretKeySpec(encryptionKey, AESCipherOperations.KEY_ALGORITHM) + val cipher = AESCipherOperations.initEncryptionCipher(secretKey) + val encryptedData = AESCipherOperations.encrypt(cipher, decryptedData = this@encryptAndStoreIv) + + secureStorage.store(cipher.iv, StorageKey.SensitiveInformationIv(userWalletId).name) + encryptedData } } - private suspend fun ByteArray.getIvAndDecrypt(userWalletId: String, encryptionKey: ByteArray): ByteArray? { + private suspend fun ByteArray.getIvAndDecrypt(userWalletId: String, encryptionKey: ByteArray): ByteArray { return withContext(Dispatchers.Default) { + val secretKeySpec = SecretKeySpec(encryptionKey, AESCipherOperations.KEY_ALGORITHM) val iv = secureStorage.get(StorageKey.SensitiveInformationIv(userWalletId).name) - ?: error("IV not found") - val ivParam = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(encryptionKey, algorithm) - cipher - .also { it.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParam) } - .doFinal(this@getIvAndDecrypt) + ?: error("IV for user wallet $userWalletId not found") + val cipher = AESCipherOperations.initDecryptionCipher(secretKeySpec, iv) + + AESCipherOperations.decrypt(cipher, encryptedData = this@getIvAndDecrypt) } } @@ -171,10 +177,4 @@ internal class DefaultUserWalletsSensitiveInformationRepository( override val name: String = "user_wallet_sensitive_information_iv_$userWalletId" } } - - companion object { - private const val algorithm = KeyProperties.KEY_ALGORITHM_AES - private const val blockMode = KeyProperties.BLOCK_MODE_CBC - private const val encryptionPadding = KeyProperties.ENCRYPTION_PADDING_PKCS7 - } } \ No newline at end of file