Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-25 14:17:26 +03:00
parent 31b1b2182b
commit a79b38c2a4
7 changed files with 85 additions and 60 deletions

View file

@ -5,10 +5,11 @@ import androidx.annotation.StringRes
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.common.*
import com.tangem.common.biometric.BiometricManager
import com.tangem.common.authentication.KeystoreManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.*
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.usersCode.UserCodeRepository
import com.tangem.core.analytics.Analytics
import com.tangem.crypto.bip39.DefaultMnemonic
@ -47,19 +48,22 @@ class TangemSdkManager(
private val userCodeRepository by lazy {
UserCodeRepository(
biometricManager = tangemSdk.biometricManager,
keystoreManager = tangemSdk.keystoreManager,
secureStorage = tangemSdk.secureStorage,
)
}
val canUseBiometry: Boolean
get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
val needEnrollBiometrics: Boolean
get() = tangemSdk.biometricManager.canEnrollBiometrics
get() = tangemSdk.authenticationManager.canEnrollBiometrics
val biometricManager: BiometricManager
get() = tangemSdk.biometricManager
val keystoreManager: KeystoreManager
get() = tangemSdk.keystoreManager
val secureStorage: SecureStorage
get() = tangemSdk.secureStorage
val userCodeRequestPolicy: UserCodeRequestPolicy
get() = tangemSdk.config.userCodeRequestPolicy

View file

@ -3,6 +3,7 @@ package com.tangem.tap.domain.userWalletList.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListManager
@ -11,6 +12,7 @@ import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
@ -43,10 +45,19 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation(
),
)
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorage = tangemSdkManager.secureStorage,
),
keystoreManager = tangemSdkManager.keystoreManager,
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
biometricManager = tangemSdkManager.biometricManager,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,

View file

@ -141,7 +141,7 @@ internal class BiometricUserWalletsListManager(
return sensitiveInformationRepository.delete(idsToRemove)
.flatMap { publicInformationRepository.delete(idsToRemove) }
.flatMap { keysRepository.delete(idsToRemove) }
.map { keysRepository.delete(idsToRemove) }
.map {
state.update { prevState ->
val newUserWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
@ -158,7 +158,7 @@ internal class BiometricUserWalletsListManager(
override suspend fun clear(): CompletionResult<Unit> {
return sensitiveInformationRepository.clear()
.flatMap { publicInformationRepository.clear() }
.flatMap { keysRepository.clear() }
.map { keysRepository.clear() }
.map {
selectedUserWalletRepository.set(null)
lock()

View file

@ -23,15 +23,13 @@ internal interface UserWalletsKeysRepository {
/**
* Delete encryption keys for user wallets. Biometric authentication not required
* @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted
* @return [CompletionResult] of operation
* */
suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit>
suspend fun delete(userWalletsIds: List<UserWalletId>)
/**
* Clear all encryption keys for user wallets. Biometric authentication not required
* @return [CompletionResult] of operation
* */
suspend fun clear(): CompletionResult<Unit>
suspend fun clear()
/**
* Determine if the user has saved user wallets

View file

@ -0,0 +1,31 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.services.secure.SecureStorage
/**
* A decorator for [SecureStorage] that facilitates data migration between two storages.
*
* @property featureStorage The primary storage, which will eventually contain all user data.
* @property cardSdkStorage The SDK's storage where user data might have been previously stored.
*/
internal class UserWalletsKeysStoreDecorator(
private val featureStorage: SecureStorage,
private val cardSdkStorage: SecureStorage,
) : SecureStorage by featureStorage {
override fun delete(account: String) {
featureStorage.delete(account)
cardSdkStorage.delete(account)
}
override fun get(account: String): ByteArray? {
var data = featureStorage.get(account)
if (data == null) {
data = cardSdkStorage.get(account) ?: return null
featureStorage.store(data, account)
}
return data
}
}

View file

@ -4,8 +4,7 @@ import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.common.*
import com.tangem.common.biometric.BiometricManager
import com.tangem.common.biometric.BiometricStorage
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListError
@ -18,13 +17,10 @@ import kotlinx.coroutines.withContext
internal class BiometricUserWalletsKeysRepository(
moshi: Moshi,
biometricManager: BiometricManager,
private val authenticatedStorage: AuthenticatedStorage,
private val secureStorage: SecureStorage,
) : UserWalletsKeysRepository {
private val biometricStorage = BiometricStorage(
biometricManager = biometricManager,
secureStorage = secureStorage,
)
private val encryptionKeyAdapter: JsonAdapter<UserWalletEncryptionKey> = moshi.adapter(
UserWalletEncryptionKey::class.java,
)
@ -37,13 +33,13 @@ internal class BiometricUserWalletsKeysRepository(
getAllInternal()
.mapFailure { error ->
when (error) {
is TangemSdkError.BiometricsAuthenticationLockout ->
is TangemSdkError.AuthenticationLockout ->
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = false)
is TangemSdkError.BiometricsAuthenticationPermanentLockout ->
is TangemSdkError.AuthenticationPermanentLockout ->
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
is TangemSdkError.BiometricCryptographyKeyInvalidated ->
is TangemSdkError.KeystoreInvalidated ->
UserWalletsListError.EncryptionKeyInvalidated
is TangemSdkError.BiometricsUnavailable ->
is TangemSdkError.AuthenticationUnavailable ->
UserWalletsListError.BiometricsAuthenticationDisabled
else -> error
}
@ -57,26 +53,24 @@ internal class BiometricUserWalletsKeysRepository(
}
}
override suspend fun delete(userWalletsIds: List<UserWalletId>): CompletionResult<Unit> {
override suspend fun delete(userWalletsIds: List<UserWalletId>) {
return withContext(Dispatchers.IO) {
userWalletsIds.map { userWalletId ->
userWalletsIds.forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
.fold()
.map { deleteUserWalletsIds(userWalletsIds) }
deleteUserWalletsIds(userWalletsIds)
}
}
override suspend fun clear(): CompletionResult<Unit> {
override suspend fun clear() {
return withContext(Dispatchers.IO) {
getUserWalletsIds()
.map { userWalletId ->
.forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
.fold()
.map {
clearUserWalletsIds()
}
clearUserWalletsIds()
}
}
@ -89,35 +83,20 @@ internal class BiometricUserWalletsKeysRepository(
private suspend fun getAllInternal(): CompletionResult<List<UserWalletEncryptionKey>> {
return getUserWalletsIds()
.map { userWalletId ->
// It is possible to request multiple user wallet keys from biometric storage because
// the biometric cryptography key has an expiration time.
// If this operation runs more than that expiration time, then the user will have to re-authorize
// to receive all user wallets encryption keys
getEncryptionKey(userWalletId)
.flatMapOnFailure { error ->
when (error) {
is TangemSdkError.InvalidBiometricCryptographyKey,
is TangemSdkError.BiometricCryptographyOperationFailed,
-> {
// These errors can be skipped as the user has the option to re-save their wallets
// in case they occur
CompletionResult.Success(data = null)
}
else -> CompletionResult.Failure(error)
}
}
.doOnFailure { error ->
when (error) {
is TangemSdkError.UserCanceledBiometricsAuthentication -> {
is TangemSdkError.UserCanceledAuthentication -> {
// If the user cancels biometric authentication, then cancel operation with error
return CompletionResult.Failure(error)
}
is TangemSdkError.BiometricCryptographyKeyInvalidated -> {
is TangemSdkError.KeystoreInvalidated -> {
// If the biometric cryptography key was invalidated,
// then delete all user wallets encryption keys and cancel operation with error
getUserWalletsIds().forEach { userWalletId ->
deleteEncryptionKey(userWalletId)
}
return CompletionResult.Failure(error)
}
}
@ -129,20 +108,22 @@ internal class BiometricUserWalletsKeysRepository(
}
private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult<UserWalletEncryptionKey?> {
return biometricStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name)
return catching { authenticatedStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name) }
.map { it.decodeToKey() }
}
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
return biometricStorage.store(
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
data = encryptionKey.encode(),
)
return catching {
authenticatedStorage.store(
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
data = encryptionKey.encode(),
)
}
.map { storeUserWalletId(encryptionKey.walletId) }
}
private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult<Unit> {
return biometricStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
private fun deleteEncryptionKey(userWalletId: UserWalletId) {
return authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
}
private suspend fun getUserWalletsIds(): List<UserWalletId> {