fix: fix biometric auth manager

This commit is contained in:
2026-02-02 17:51:50 +05:00
parent 6cb4a80666
commit dc23ba9d36
3 changed files with 486 additions and 178 deletions

View File

@@ -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")
}
/**