- Introduced a maximum bitmap decode dimension to prevent excessive memory usage. - Enhanced base64 to bitmap conversion by extracting payload and applying EXIF orientation. - Improved error handling for image downloads and decoding processes. - Simplified media picker and chat input components to manage keyboard visibility more effectively. - Updated color selection grid to adaptively adjust based on available width. - Added safety checks for notifications and call actions in profile screens. - Optimized bitmap decoding in uriToBase64Image to handle large images more efficiently.
144 lines
5.9 KiB
Kotlin
144 lines
5.9 KiB
Kotlin
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<Boolean> = _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
|
||
}
|
||
}
|