Files
mobile-android/app/src/main/java/com/rosetta/messenger/biometric/BiometricPreferences.kt

146 lines
5.9 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
*
* Биометрия привязана к конкретному аккаунту (per-account), не глобальная.
*/
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_PREFIX = "biometric_enabled_"
private const val ENCRYPTED_PASSWORD_PREFIX = "encrypted_password_"
// Legacy key (global) — for migration
private const val KEY_BIOMETRIC_ENABLED_LEGACY = "biometric_enabled"
// Shared state for reactive UI updates
private val biometricEnabledState = MutableStateFlow(false)
}
private val appContext = context.applicationContext
private val _isBiometricEnabled = biometricEnabledState
private val encryptedPrefs: SharedPreferences by lazy {
createEncryptedPreferences()
}
private fun createEncryptedPreferences(): SharedPreferences {
try {
val masterKey = MasterKey.Builder(appContext)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.setUserAuthenticationRequired(false)
.build()
return EncryptedSharedPreferences.create(
appContext,
PREFS_FILE_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} catch (e: Exception) {
return appContext.getSharedPreferences(PREFS_FILE_NAME + "_fallback", Context.MODE_PRIVATE)
}
}
val isBiometricEnabled: Flow<Boolean> = _isBiometricEnabled.asStateFlow()
/**
* Загрузить состояние биометрии для конкретного аккаунта
*/
fun loadForAccount(publicKey: String) {
try {
val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey"
val perAccount = encryptedPrefs.getBoolean(key, false)
// Migration: если per-account нет, проверяем legacy глобальный ключ
if (!perAccount && encryptedPrefs.getBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, false)) {
// Мигрируем: копируем глобальное значение в per-account
encryptedPrefs.edit().putBoolean(key, true).apply()
_isBiometricEnabled.value = true
} else {
_isBiometricEnabled.value = perAccount
}
} catch (e: Exception) {
_isBiometricEnabled.value = false
}
}
/**
* Включить биометрическую аутентификацию для аккаунта
*/
suspend fun enableBiometric(publicKey: String) = withContext(Dispatchers.IO) {
val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey"
encryptedPrefs.edit().putBoolean(key, true).commit()
_isBiometricEnabled.value = true
}
/**
* Отключить биометрическую аутентификацию для аккаунта
*/
suspend fun disableBiometric(publicKey: String) = withContext(Dispatchers.IO) {
val key = "$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey"
encryptedPrefs.edit().putBoolean(key, false).commit()
_isBiometricEnabled.value = false
}
/**
* Проверить включена ли биометрия для аккаунта (синхронно)
*/
fun isBiometricEnabledForAccount(publicKey: String): Boolean {
return try {
encryptedPrefs.getBoolean("$KEY_BIOMETRIC_ENABLED_PREFIX$publicKey", false)
} catch (_: Exception) { false }
}
// --- Legacy compat: old callers without publicKey ---
@Deprecated("Use enableBiometric(publicKey) instead")
suspend fun enableBiometric() = withContext(Dispatchers.IO) {
encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, true).commit()
_isBiometricEnabled.value = true
}
@Deprecated("Use disableBiometric(publicKey) instead")
suspend fun disableBiometric() = withContext(Dispatchers.IO) {
encryptedPrefs.edit().putBoolean(KEY_BIOMETRIC_ENABLED_LEGACY, false).commit()
_isBiometricEnabled.value = false
}
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) {
encryptedPrefs.edit().clear().commit()
_isBiometricEnabled.value = false
}
suspend fun hasEncryptedPassword(publicKey: String): Boolean {
return getEncryptedPassword(publicKey) != null
}
}