Updated on 2026-08-14
This commit is contained in:
parent
31b1b2182b
commit
a79b38c2a4
7 changed files with 85 additions and 60 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue