Этап 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:
b-dev-mobile
2026-07-07 20:49:45 +00:00
parent 6abb1b15c3
commit 321f81bffe
11 changed files with 335 additions and 37 deletions
+10 -1
View File
@@ -62,10 +62,19 @@ legacy WebView-оболочки `android-webview`.
## Секреты (где, НЕ значения)
- В git секретов нет. Учётные данные пользователя — только на устройстве
(session storage `core/auth`).
- App password шифруется в покое ключом AndroidKeystore AES-256-GCM (`KeystoreCrypto`,
ключ `f7_auth_master`, префикс `v1:`). Опционально — **биометрическая привязка**
(`AppLockStore.isPasswordBindingEnabled`, ВЫКЛ по умолчанию): пароль шифруется
auth-bound RSA-парой `f7_auth_master_bio` (`setUserAuthenticationRequired`, префикс
`v2b:`), расшифровывается ТОЛЬКО через BiometricPrompt+CryptoObject один раз при
разблокировке и живёт в памяти процесса (`AppPasswordHolder`, не на диске). Фоновые
сценарии (push/входящие Talk-звонки) до первой разблокировки после ребута деградируют
корректно («Разблокируйте приложение…»). Включение по умолчанию/UI-тумблер — ПОСЛЕ
подтверждения владельцем допустимости этого поведения (как в Signal).
- `google-services.json` (Firebase) — вне git; путь сборки документируется в
PROJECT-STATUS.md.
- Release-keystore для подписи — вне git (пока не настроен, сборки debug).
## Версия / changelog
- Текущее: v0.5.113 (code 121) — модуль «Задачи». История — CHANGELOG.md
- Текущее: v0.5.115 (code 123) — биопривязка app password (Этап 0 п.3 доп). История — CHANGELOG.md
и git-история `app/build.gradle`.