package com.rosetta.messenger.biometric import android.content.Context import android.os.Build import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyPermanentlyInvalidatedException import android.security.keystore.KeyProperties import android.util.Base64 import android.util.Log import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import java.security.KeyStore import java.security.cert.Certificate import java.util.Arrays import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec /** * Безопасный менеджер биометрической аутентификации * * Использует Android Keystore для хранения ключей шифрования: * - Ключи генерируются и хранятся в аппаратном модуле безопасности (TEE/StrongBox) * - Ключи привязаны к биометрии через setUserAuthenticationRequired * - Ключи инвалидируются при изменении биометрических данных * - Используется AES-GCM для аутентифицированного шифрования * - CryptoObject привязывает криптографические операции к биометрии */ class BiometricAuthManager(private val context: Context) { companion object { private const val TAG = "BiometricAuthManager" private const val KEYSTORE_PROVIDER = "AndroidKeyStore" private const val KEY_ALIAS = "rosetta_biometric_key" private const val TRANSFORMATION = "AES/GCM/NoPadding" private const val GCM_TAG_LENGTH = 128 private const val IV_SEPARATOR = "|" // Google Hardware Attestation Root Certificate private const val GOOGLE_ROOT_CA_FINGERPRINT = "98:BD:5B:98:E9:D3:29:F4:CF:10:45:76:A5:00:7D:A8:08:7A:70:50:4D:FF:4D:C5:95:3A:7F:C3:73:79:0F:ED" } private val keyStore: KeyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) } /** * Проверяет доступность STRONG биометрической аутентификации * BIOMETRIC_STRONG требует Class 3 биометрию (отпечаток/лицо с криптографической привязкой) */ fun isBiometricAvailable(): BiometricAvailability { val biometricManager = BiometricManager.from(context) return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) { BiometricManager.BIOMETRIC_SUCCESS -> BiometricAvailability.Available BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> BiometricAvailability.NotAvailable("Биометрическое оборудование недоступно") BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricAvailability.NotAvailable("Биометрическое оборудование временно недоступно") BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricAvailability.NotEnrolled("Биометрические данные не настроены") BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> BiometricAvailability.NotAvailable("Требуется обновление безопасности") BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> BiometricAvailability.NotAvailable("Биометрическая аутентификация не поддерживается") BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> BiometricAvailability.NotAvailable("Неизвестный статус") else -> BiometricAvailability.NotAvailable("Неизвестная ошибка") } } /** * Шифрует пароль с использованием ключа из Android Keystore * Шифрование привязано к биометрии через CryptoObject */ fun encryptPassword( activity: FragmentActivity, password: String, onSuccess: (String) -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { try { // Генерируем или получаем ключ val secretKey = getOrCreateSecretKey() // Создаем Cipher для шифрования val cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, secretKey) // Показываем биометрический промпт с CryptoObject showBiometricPromptWithCrypto( activity = activity, cipher = cipher, title = "Сохранить пароль", subtitle = "Подтвердите биометрией для защиты пароля", onSuccess = { authenticatedCipher -> try { val passwordBytes = password.toByteArray(Charsets.UTF_8) try { val encrypted = authenticatedCipher.doFinal(passwordBytes) val iv = authenticatedCipher.iv // Сохраняем IV и зашифрованные данные val ivString = Base64.encodeToString(iv, Base64.NO_WRAP) val encryptedString = Base64.encodeToString(encrypted, Base64.NO_WRAP) onSuccess("$ivString$IV_SEPARATOR$encryptedString") } finally { // Secure memory wipe - обнуляем пароль в памяти Arrays.fill(passwordBytes, 0.toByte()) } } catch (e: Exception) { onError("Ошибка шифрования: ${e.message}") } }, onError = onError, onCancel = onCancel ) } catch (e: KeyPermanentlyInvalidatedException) { removeBiometricData() onError("Биометрические данные изменились. Пожалуйста, настройте заново.") } catch (e: Exception) { onError("Ошибка инициализации: ${formatInitializationError(e)}") } } /** * Расшифровывает пароль с использованием ключа из Android Keystore * Расшифровка возможна только после успешной биометрической аутентификации */ fun decryptPassword( activity: FragmentActivity, encryptedData: String, onSuccess: (String) -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { try { val parts = encryptedData.split(IV_SEPARATOR) if (parts.size != 2) { onError("Неверный формат зашифрованных данных") return } val iv = Base64.decode(parts[0], Base64.NO_WRAP) val encrypted = Base64.decode(parts[1], Base64.NO_WRAP) // Получаем ключ из Keystore val secretKey = getSecretKey() if (secretKey == null) { onError("Ключ не найден. Пожалуйста, настройте биометрию заново.") return } // Создаем Cipher для расшифровки с тем же IV val cipher = Cipher.getInstance(TRANSFORMATION) val spec = GCMParameterSpec(GCM_TAG_LENGTH, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) // Показываем биометрический промпт с CryptoObject showBiometricPromptWithCrypto( activity = activity, cipher = cipher, title = "Разблокировать", subtitle = "Подтвердите биометрией для входа", onSuccess = { authenticatedCipher -> var decrypted: ByteArray? = null try { decrypted = authenticatedCipher.doFinal(encrypted) onSuccess(String(decrypted, Charsets.UTF_8)) } catch (e: Exception) { onError("Ошибка расшифровки: ${e.message}") } finally { // Secure memory wipe - обнуляем расшифрованные данные decrypted?.let { Arrays.fill(it, 0.toByte()) } } }, onError = onError, onCancel = onCancel ) } catch (e: KeyPermanentlyInvalidatedException) { removeBiometricData() onError("Биометрические данные изменились. Пожалуйста, войдите с паролем и настройте биометрию заново.") } catch (e: Exception) { onError("Ошибка инициализации: ${formatInitializationError(e)}") } } /** * Показывает биометрический промпт с криптографической привязкой (CryptoObject) */ private fun showBiometricPromptWithCrypto( activity: FragmentActivity, cipher: Cipher, title: String, subtitle: String, onSuccess: (Cipher) -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { val executor = ContextCompat.getMainExecutor(context) val callback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) when (errorCode) { BiometricPrompt.ERROR_USER_CANCELED, BiometricPrompt.ERROR_NEGATIVE_BUTTON, BiometricPrompt.ERROR_CANCELED -> onCancel() BiometricPrompt.ERROR_LOCKOUT, BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> onError("Слишком много попыток. Попробуйте позже.") else -> onError(errString.toString()) } } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) // Получаем аутентифицированный Cipher из CryptoObject val authenticatedCipher = result.cryptoObject?.cipher if (authenticatedCipher != null) { onSuccess(authenticatedCipher) } else { onError("Ошибка: криптографический объект не получен") } } override fun onAuthenticationFailed() { super.onAuthenticationFailed() // Не вызываем onError - пользователь может попробовать снова } } val biometricPrompt = BiometricPrompt(activity, executor, callback) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle(title) .setSubtitle(subtitle) .setNegativeButtonText("Отмена") .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG) .setConfirmationRequired(false) .build() // Аутентификация с CryptoObject - криптографическая операция привязана к биометрии biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher)) } /** * Генерирует новый ключ или возвращает существующий из Android Keystore */ private fun getOrCreateSecretKey(): SecretKey { // Проверяем, есть ли уже ключ getSecretKey()?.let { return it } val attempts = buildKeyGenerationAttempts() var lastError: Exception? = null for ((index, config) in attempts.withIndex()) { try { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER ) val spec = buildKeyGenSpec(config) keyGenerator.init(spec) return keyGenerator.generateKey() } catch (e: Exception) { lastError = e Log.w( TAG, "Key generation attempt ${index + 1}/${attempts.size} failed " + "(strongBox=${config.useStrongBox}, attestation=${config.useAttestation}, " + "unlockedRequired=${config.requireUnlockedDevice})", e ) removeKeyAliasSilently() } } throw IllegalStateException("Failed to generate key", lastError) } private fun buildKeyGenerationAttempts(): List { val attempts = mutableListOf() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { attempts += KeyGenerationConfig( useStrongBox = true, useAttestation = true, requireUnlockedDevice = true ) } attempts += KeyGenerationConfig( useStrongBox = false, useAttestation = true, requireUnlockedDevice = true ) attempts += KeyGenerationConfig( useStrongBox = false, useAttestation = false, requireUnlockedDevice = true ) attempts += KeyGenerationConfig( useStrongBox = false, useAttestation = false, requireUnlockedDevice = false ) return attempts } private fun buildKeyGenSpec(config: KeyGenerationConfig): KeyGenParameterSpec { val builder = KeyGenParameterSpec.Builder( KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(256) .setUserAuthenticationRequired(true) .setInvalidatedByBiometricEnrollment(true) if (config.requireUnlockedDevice && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { builder.setUnlockedDeviceRequired(true) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { builder.setUserAuthenticationParameters( 0, KeyProperties.AUTH_BIOMETRIC_STRONG ) } if (config.useAttestation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { builder.setAttestationChallenge(generateAttestationChallenge()) } if (config.useStrongBox && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { builder.setIsStrongBoxBacked(true) } return builder.build() } private fun removeKeyAliasSilently() { try { if (keyStore.containsAlias(KEY_ALIAS)) { keyStore.deleteEntry(KEY_ALIAS) } } catch (_: Exception) { } } private fun formatInitializationError(error: Throwable): String { val rootCause = generateSequence(error) { it.cause }.lastOrNull() ?: error val message = rootCause.message ?: error.message ?: "Unknown error" return if (message.contains("Failed to generate key", ignoreCase = true)) { "Не удалось создать ключ. Проверьте, что на устройстве включена блокировка экрана и настроена биометрия." } else { message } } /** * Получает существующий ключ из Keystore */ private fun getSecretKey(): SecretKey? { return try { keyStore.getKey(KEY_ALIAS, null) as? SecretKey } catch (e: Exception) { null } } /** * Удаляет ключ из Android Keystore * Это делает все ранее зашифрованные данные недоступными */ fun removeBiometricData() { try { if (keyStore.containsAlias(KEY_ALIAS)) { keyStore.deleteEntry(KEY_ALIAS) } } catch (e: Exception) { } } /** * Проверяет, есть ли сохраненный ключ */ fun hasStoredKey(): Boolean { return try { keyStore.containsAlias(KEY_ALIAS) } catch (e: Exception) { false } } /** * Генерирует случайный challenge для Key Attestation */ private fun generateAttestationChallenge(): ByteArray { val challenge = ByteArray(32) java.security.SecureRandom().nextBytes(challenge) return challenge } /** * Проверяет Key Attestation - убеждается что ключ реально в TEE/StrongBox * Возвращает true если attestation валидный или недоступен (fallback) */ fun verifyKeyAttestation(): KeyAttestationResult { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { return KeyAttestationResult.NotSupported } return try { val certificateChain = keyStore.getCertificateChain(KEY_ALIAS) if (certificateChain.isNullOrEmpty()) { return KeyAttestationResult.NotSupported } // Проверяем что цепочка валидна for (i in 0 until certificateChain.size - 1) { try { certificateChain[i].verify(certificateChain[i + 1].publicKey) } catch (e: Exception) { return KeyAttestationResult.Invalid("Цепочка сертификатов недействительна") } } // Проверяем attestation extension в leaf сертификате val leafCert = certificateChain[0] as? java.security.cert.X509Certificate if (leafCert != null) { val attestationExtension = leafCert.getExtensionValue("1.3.6.1.4.1.11129.2.1.17") if (attestationExtension != null) { return KeyAttestationResult.Valid(isStrongBox = isKeyInStrongBox()) } } KeyAttestationResult.NotSupported } catch (e: Exception) { KeyAttestationResult.Invalid(e.message ?: "Unknown error") } } /** * Проверяет находится ли ключ в StrongBox */ private fun isKeyInStrongBox(): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return false return try { val key = keyStore.getKey(KEY_ALIAS, null) val keyInfo = java.security.KeyFactory.getInstance( key.algorithm, KEYSTORE_PROVIDER ).getKeySpec(key, android.security.keystore.KeyInfo::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { keyInfo.securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX } else { @Suppress("DEPRECATION") keyInfo.isInsideSecureHardware } } catch (e: Exception) { false } } /** * Проверяет целостность устройства (root detection) */ fun checkDeviceIntegrity(): DeviceIntegrityResult { val issues = mutableListOf() // Проверка на root val rootIndicators = listOf( "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su", "/system/xbin/daemonsu", "/system/bin/.ext/.su", "/system/etc/init.d/99SuperSUDaemon", "/dev/com.koushikdutta.superuser.daemon/", "/system/app/Superuser", "/system/app/SuperSU", "/system/app/Magisk" ) for (path in rootIndicators) { if (java.io.File(path).exists()) { issues.add("Root indicator found: $path") break } } // Проверка Build tags val buildTags = Build.TAGS if (buildTags != null && buildTags.contains("test-keys")) { issues.add("Device has test-keys") } // Проверка dangerous props try { val process = Runtime.getRuntime().exec(arrayOf("/system/bin/getprop", "ro.debuggable")) val reader = java.io.BufferedReader(java.io.InputStreamReader(process.inputStream)) val debuggable = reader.readLine() reader.close() if (debuggable == "1") { issues.add("Device is debuggable") } } catch (e: Exception) { // Ignore } return if (issues.isEmpty()) { DeviceIntegrityResult.Secure } else { DeviceIntegrityResult.Compromised(issues) } } } private data class KeyGenerationConfig( val useStrongBox: Boolean, val useAttestation: Boolean, val requireUnlockedDevice: Boolean ) /** * Результат проверки Key Attestation */ sealed class KeyAttestationResult { data class Valid(val isStrongBox: Boolean) : KeyAttestationResult() data class Invalid(val reason: String) : KeyAttestationResult() object NotSupported : KeyAttestationResult() } /** * Результат проверки целостности устройства */ sealed class DeviceIntegrityResult { object Secure : DeviceIntegrityResult() data class Compromised(val issues: List) : DeviceIntegrityResult() } /** * Результат проверки доступности биометрии */ sealed class BiometricAvailability { object Available : BiometricAvailability() data class NotAvailable(val reason: String) : BiometricAvailability() data class NotEnrolled(val reason: String) : BiometricAvailability() }