Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-18 18:46:09 +04:00
parent c29e4a974b
commit 24e0fb015b
2 changed files with 56 additions and 54 deletions

View file

@ -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<UserWallet>,
): UserWalletId? {
return prevSelectedWalletId
?: (selectedUserWalletRepository.get() ?: findAndSetUnlockedUserWallet(userWallets)?.walletId)
}
val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get()
var possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId)
private fun findAndSetUnlockedUserWallet(userWallets: List<UserWallet>): 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<UserWalletId>) {
@ -318,10 +319,11 @@ internal class BiometricUserWalletsListManager(
}
}
private fun findSelectedUserWallet(userWallets: List<UserWallet> = state.value.userWallets): UserWallet? {
return userWallets.firstOrNull {
it.walletId == state.value.selectedUserWalletId
}
private fun findSelectedUserWallet(
userWallets: List<UserWallet> = state.value.userWallets,
selectedUserWalletId: UserWalletId? = state.value.selectedUserWalletId,
): UserWallet? {
return userWallets.firstOrNull { it.walletId == selectedUserWalletId }
}
private data class State(

View file

@ -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<UserWalletSensitiveInformation> = 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<Unit> {
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<String, ByteArray> {
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<String, ByteArray> {
private suspend fun ByteArray.decodeToEncryptedSensitiveInformation(): Map<String, ByteArray>? {
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
}
}