Updated on 2026-08-14
This commit is contained in:
parent
c29e4a974b
commit
24e0fb015b
2 changed files with 56 additions and 54 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue