Этап 0 п.3 (доп): биометрическая привязка app password поверх шифрования
Auth-bound RSA-пара `f7_auth_master_bio` в AndroidKeystore (setUserAuthenticationRequired + setInvalidatedByBiometricEnrollment, per-use CryptoObject, префикс v2b:): ПУБЛИЧНЫЙ ключ шифрует без авторизации (в т.ч. в фоне — миграция/сохранение после входа), ПРИВАТНЫЙ расшифровывает ТОЛЬКО через BiometricPrompt+CryptoObject. Симметричный AES не подошёл бы: там auth требуется и на шифрование. Разрешение конфликта с фоновыми push/звонками: пароль расшифровывается ОДИН раз при разблокировке (AppLockGate) и живёт в памяти процесса (AppPasswordHolder, не на диске). AuthStore.load() в фоне возвращает null, пока «холодно» → существующие `?: return` в push/звонках деградируют сами; экраны Accept (CallIncomingActivity, TalkPushBridge) открывают приложение для разблокировки вместо тихого отказа. AppScaffold через loadMetadata() показывает экран блокировки, а не логин, и перечитывает сессию после разблокировки. ВЫКЛ по умолчанию (AppLockStore.isPasswordBindingEnabled, только при биометрии-без-PIN — единственный путь разблокировки прогревает holder). UI-тумблер и включение по умолчанию — ПОСЛЕ подтверждения владельцем поведения «после ребута до первой разблокировки звонки/push не работают» (как в Signal). v0.5.115 (123). Проверено сборкой assembleRelease на инфре B: APK 54.6 МБ, подпись v2+v3 OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+13
-2
@@ -14,9 +14,20 @@ object TalkPushBridge {
|
||||
private const val TAG = "TalkPushBridge"
|
||||
|
||||
fun launchAcceptedCall(context: Context, acceptUrl: String) {
|
||||
val session = AuthStore(context).load()
|
||||
val authStore = AuthStore(context)
|
||||
val session = authStore.load()
|
||||
if (session == null) {
|
||||
Log.w(TAG, "No session for accepted call")
|
||||
// Биопривязка + «холодно»: пароль ещё не разблокирован. Открываем приложение на экране
|
||||
// блокировки, чтобы пользователь прошёл биометрию, вместо тихого отказа.
|
||||
if (authStore.isPasswordLocked()) {
|
||||
Log.w(TAG, "Password locked, opening app to unlock before accepting call")
|
||||
context.packageManager.getLaunchIntentForPackage(context.packageName)?.let {
|
||||
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(it)
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No session for accepted call")
|
||||
}
|
||||
return
|
||||
}
|
||||
TalkNativeCallLauncher.launchIncomingCall(context, session, acceptUrl)
|
||||
|
||||
Reference in New Issue
Block a user