package com.rosetta.messenger.biometric import android.content.Context 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.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 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_" // Shared between all BiometricPreferences instances so UI in different screens // receives updates immediately (ProfileScreen <-> BiometricEnableScreen). private val biometricEnabledState = MutableStateFlow(false) } private val appContext = context.applicationContext private val _isBiometricEnabled = biometricEnabledState private val encryptedPrefs: SharedPreferences by lazy { createEncryptedPreferences() } init { // Загружаем начальное значение try { _isBiometricEnabled.value = encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED, false) } catch (e: Exception) { } } /** * Создает EncryptedSharedPreferences с максимальной защитой */ private fun createEncryptedPreferences(): SharedPreferences { try { // Создаем MasterKey с максимальной защитой val masterKey = MasterKey.Builder(appContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .setUserAuthenticationRequired(false) // Биометрия проверяется отдельно в BiometricAuthManager .build() return EncryptedSharedPreferences.create( appContext, PREFS_FILE_NAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } catch (e: Exception) { // Fallback на обычные SharedPreferences в случае ошибки (не должно произойти) return appContext.getSharedPreferences(PREFS_FILE_NAME + "_fallback", Context.MODE_PRIVATE) } } /** * Включена ли биометрическая аутентификация */ val isBiometricEnabled: Flow = _isBiometricEnabled.asStateFlow() /** * Включить биометрическую аутентификацию */ suspend fun enableBiometric() = withContext(Dispatchers.IO) { val success = encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED, true).commit() if (!success) { Log.w(TAG, "Failed to persist biometric enabled state") } _isBiometricEnabled.value = encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED, false) } /** * Отключить биометрическую аутентификацию */ suspend fun disableBiometric() = withContext(Dispatchers.IO) { val success = encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED, false).commit() if (!success) { Log.w(TAG, "Failed to persist biometric disabled state") } _isBiometricEnabled.value = encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED, false) } /** * Сохранить зашифрованный пароль для аккаунта * Пароль уже зашифрован BiometricAuthManager, здесь добавляется второй слой шифрования */ suspend fun saveEncryptedPassword(publicKey: String, encryptedPassword: String) = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.edit().putString(key, encryptedPassword).apply() } /** * Получить зашифрованный пароль для аккаунта */ suspend fun getEncryptedPassword(publicKey: String): String? = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.getString(key, null) } /** * Удалить зашифрованный пароль для аккаунта */ suspend fun removeEncryptedPassword(publicKey: String) = withContext(Dispatchers.IO) { val key = "$ENCRYPTED_PASSWORD_PREFIX$publicKey" encryptedPrefs.edit().remove(key).apply() } /** * Удалить все биометрические данные */ suspend fun clearAll() = withContext(Dispatchers.IO) { val success = encryptedPrefs.edit().clear().commit() if (!success) { Log.w(TAG, "Failed to clear biometric preferences") } _isBiometricEnabled.value = encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED, false) } /** * Проверить, есть ли сохраненный зашифрованный пароль для аккаунта */ suspend fun hasEncryptedPassword(publicKey: String): Boolean { return getEncryptedPassword(publicKey) != null } }