fix: fix biometric auth manager
This commit is contained in:
@@ -1,89 +1,129 @@
|
||||
package com.rosetta.messenger.biometric
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val Context.biometricDataStore: DataStore<Preferences> by preferencesDataStore(name = "biometric_prefs")
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Управление настройками и данными биометрической аутентификации
|
||||
* Безопасное хранилище настроек биометрической аутентификации
|
||||
* Использует EncryptedSharedPreferences с MasterKey из Android Keystore
|
||||
*
|
||||
* Уровни защиты:
|
||||
* - AES256_GCM для шифрования значений
|
||||
* - AES256_SIV для шифрования ключей
|
||||
* - MasterKey хранится в Android Keystore (TEE/StrongBox)
|
||||
*/
|
||||
class BiometricPreferences(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private val BIOMETRIC_ENABLED = booleanPreferencesKey("biometric_enabled")
|
||||
private val ENCRYPTED_PASSWORD_PREFIX = "encrypted_password_"
|
||||
private const val TAG = "BiometricPreferences"
|
||||
private const val PREFS_FILE_NAME = "rosetta_secure_biometric_prefs"
|
||||
private const val KEY_BIOMETRIC_ENABLED = "biometric_enabled"
|
||||
private const val ENCRYPTED_PASSWORD_PREFIX = "encrypted_password_"
|
||||
}
|
||||
|
||||
private val _isBiometricEnabled = MutableStateFlow(false)
|
||||
|
||||
private val encryptedPrefs: SharedPreferences by lazy {
|
||||
createEncryptedPreferences()
|
||||
}
|
||||
|
||||
init {
|
||||
// Загружаем начальное значение
|
||||
try {
|
||||
_isBiometricEnabled.value = encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED, false)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to read biometric enabled state", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает EncryptedSharedPreferences с максимальной защитой
|
||||
*/
|
||||
private fun createEncryptedPreferences(): SharedPreferences {
|
||||
try {
|
||||
// Создаем MasterKey с максимальной защитой
|
||||
val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.setUserAuthenticationRequired(false) // Биометрия проверяется отдельно в BiometricAuthManager
|
||||
.build()
|
||||
|
||||
return EncryptedSharedPreferences.create(
|
||||
context,
|
||||
PREFS_FILE_NAME,
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to create EncryptedSharedPreferences, falling back", e)
|
||||
// Fallback на обычные SharedPreferences в случае ошибки (не должно произойти)
|
||||
return context.getSharedPreferences(PREFS_FILE_NAME + "_fallback", Context.MODE_PRIVATE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Включена ли биометрическая аутентификация
|
||||
*/
|
||||
val isBiometricEnabled: Flow<Boolean> = context.biometricDataStore.data
|
||||
.map { preferences ->
|
||||
preferences[BIOMETRIC_ENABLED] ?: false
|
||||
}
|
||||
val isBiometricEnabled: Flow<Boolean> = _isBiometricEnabled.asStateFlow()
|
||||
|
||||
/**
|
||||
* Включить биометрическую аутентификацию
|
||||
*/
|
||||
suspend fun enableBiometric() {
|
||||
context.biometricDataStore.edit { preferences ->
|
||||
preferences[BIOMETRIC_ENABLED] = true
|
||||
}
|
||||
suspend fun enableBiometric() = withContext(Dispatchers.IO) {
|
||||
encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED, true).apply()
|
||||
_isBiometricEnabled.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключить биометрическую аутентификацию
|
||||
*/
|
||||
suspend fun disableBiometric() {
|
||||
context.biometricDataStore.edit { preferences ->
|
||||
preferences[BIOMETRIC_ENABLED] = false
|
||||
}
|
||||
suspend fun disableBiometric() = withContext(Dispatchers.IO) {
|
||||
encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED, false).apply()
|
||||
_isBiometricEnabled.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить зашифрованный пароль для аккаунта
|
||||
* Пароль уже зашифрован BiometricAuthManager, здесь добавляется второй слой шифрования
|
||||
*/
|
||||
suspend fun saveEncryptedPassword(publicKey: String, encryptedPassword: String) {
|
||||
val key = stringPreferencesKey("$ENCRYPTED_PASSWORD_PREFIX$publicKey")
|
||||
context.biometricDataStore.edit { preferences ->
|
||||
preferences[key] = encryptedPassword
|
||||
}
|
||||
suspend fun saveEncryptedPassword(publicKey: String, encryptedPassword: String) = withContext(Dispatchers.IO) {
|
||||
val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey"
|
||||
encryptedPrefs.edit().putString(key, encryptedPassword).apply()
|
||||
Log.d(TAG, "Encrypted password saved for account")
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить зашифрованный пароль для аккаунта
|
||||
*/
|
||||
suspend fun getEncryptedPassword(publicKey: String): String? {
|
||||
val key = stringPreferencesKey("$ENCRYPTED_PASSWORD_PREFIX$publicKey")
|
||||
return context.biometricDataStore.data.first()[key]
|
||||
suspend fun getEncryptedPassword(publicKey: String): String? = withContext(Dispatchers.IO) {
|
||||
val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey"
|
||||
encryptedPrefs.getString(key, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить зашифрованный пароль для аккаунта
|
||||
*/
|
||||
suspend fun removeEncryptedPassword(publicKey: String) {
|
||||
val key = stringPreferencesKey("$ENCRYPTED_PASSWORD_PREFIX$publicKey")
|
||||
context.biometricDataStore.edit { preferences ->
|
||||
preferences.remove(key)
|
||||
}
|
||||
suspend fun removeEncryptedPassword(publicKey: String) = withContext(Dispatchers.IO) {
|
||||
val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey"
|
||||
encryptedPrefs.edit().remove(key).apply()
|
||||
Log.d(TAG, "Encrypted password removed for account")
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить все биометрические данные
|
||||
*/
|
||||
suspend fun clearAll() {
|
||||
context.biometricDataStore.edit { preferences ->
|
||||
preferences.clear()
|
||||
}
|
||||
suspend fun clearAll() = withContext(Dispatchers.IO) {
|
||||
encryptedPrefs.edit().clear().apply()
|
||||
_isBiometricEnabled.value = false
|
||||
Log.d(TAG, "All biometric data cleared")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user