Integrate Android BiometricPrompt with the shared authentication lease #98

Closed
opened 2026-08-17 19:12:20 +00:00 by hugo · 1 comment
Owner

Goal

Reestablish the Rust-owned authentication lease through Android biometrics without duplicating authentication policy in Kotlin.

Scope

  • Use AndroidX Biometric BiometricPrompt with the Keystore cipher operation supplied by the secure-secret adapter.
  • When no enrolled protected passphrase is usable, collect the GPG passphrase in a secure Compose dialog and pass it directly to storage for key validation; do not persist it unless storage completes explicit enrollment.
  • Let crates/storage own authentication generation, inactivity timeout, activity touches, manual lock, cancellation, and which operations require decryption.
  • Map strong biometric/device capability, no enrollment, cancellation, negative action, temporary/permanent lockout, key invalidation, process death, and app backgrounding to typed outcomes.
  • Enabling biometric unlock must perform capability checks, prove the passphrase, create the authentication-bound Keystore record, and only then commit the setting. Disabling deletes it.
  • Never allow device credential fallback when it would violate the current-biometric-set policy; if a different policy is desired it requires an explicit storage-level decision.
  • Ensure ordinary folder browsing and cached non-secret TOTP paths do not prompt.

Acceptance criteria

  • First protected access requests manual passphrase or a valid enrolled biometric operation and establishes one shared lease across screens.
  • Valid biometric unlock works without the passphrase entering ordinary Kotlin persistence.
  • Enrollment change, lock-screen removal, invalid key, cancellation, and lockout fail closed and offer manual passphrase recovery.
  • Timeout/manual lock immediately invalidate native handles and mask every protected screen.
  • Unit tests use fake clocks/auth results; device tests cover success, cancellation, invalidation, background, and recovery.

Depends on the Keystore adapter, platform-neutral contract, and Compose shell issues. Part of #87.

Android support update (2026-10-08)

User-directed scope: Android release and CI targets are arm64-v8a and armeabi-v7a. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.

## Goal Reestablish the Rust-owned authentication lease through Android biometrics without duplicating authentication policy in Kotlin. ## Scope - Use AndroidX Biometric `BiometricPrompt` with the Keystore cipher operation supplied by the secure-secret adapter. - When no enrolled protected passphrase is usable, collect the GPG passphrase in a secure Compose dialog and pass it directly to storage for key validation; do not persist it unless storage completes explicit enrollment. - Let `crates/storage` own authentication generation, inactivity timeout, activity touches, manual lock, cancellation, and which operations require decryption. - Map strong biometric/device capability, no enrollment, cancellation, negative action, temporary/permanent lockout, key invalidation, process death, and app backgrounding to typed outcomes. - Enabling biometric unlock must perform capability checks, prove the passphrase, create the authentication-bound Keystore record, and only then commit the setting. Disabling deletes it. - Never allow device credential fallback when it would violate the current-biometric-set policy; if a different policy is desired it requires an explicit storage-level decision. - Ensure ordinary folder browsing and cached non-secret TOTP paths do not prompt. ## Acceptance criteria - First protected access requests manual passphrase or a valid enrolled biometric operation and establishes one shared lease across screens. - Valid biometric unlock works without the passphrase entering ordinary Kotlin persistence. - Enrollment change, lock-screen removal, invalid key, cancellation, and lockout fail closed and offer manual passphrase recovery. - Timeout/manual lock immediately invalidate native handles and mask every protected screen. - Unit tests use fake clocks/auth results; device tests cover success, cancellation, invalidation, background, and recovery. Depends on the Keystore adapter, platform-neutral contract, and Compose shell issues. Part of #87. ## Android support update (2026-10-08) User-directed scope: Android release and CI targets are **arm64-v8a** and **armeabi-v7a**. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.
hugo added this to the 06 - Implement an Android version of the software milestone 2026-08-17 19:12:20 +00:00
hugo added the enhancement label 2026-08-17 19:12:20 +00:00
Author
Owner

Implemented in commit 647dc1bb4b (pushed to main).

Passwords and Preferences now share the private runtime's Rust authentication lease through one injected core facade. AndroidX BiometricPrompt authenticates the Keystore cipher with strong biometrics and the existing current-set policy; device credentials never substitute for that policy. Explicit enrollment validates the configured GPG key even in an empty store, creates the protected record, then commits the setting. Disabling removes protected passphrases and locks the lease.

The manual passphrase dialog is secure, masked, unsaved and cleared on submission/cancellation/background/disposal. Typed missing/invalidation/capability/lockout/cancellation outcomes offer manual recovery. Rust owns deadlines, activity touches, generation and native-handle revocation. Tabs and ordinary folders retain the shared lease without another prompt; cached non-secret TOTP paths remain non-interactive. Lock/background/cancel mask protected views and interrupt pending OS authentication; stale results cannot restore plaintext or overwrite newer state.

Reviewed the full scope and acceptance criteria. The review also corrected cancelled manual biometric renewal retaining an internal lease, and stale asynchronous error publication. Regression checks prove immediate lease revocation and masking of an uncleared plaintext page.

Validation passed:

  • All four required root Rust gates, with 477 workspace tests; fake clocks/auth results cover expiry, activity, old native handles, wrong passphrases, enrollment failure, current-set protection, cancellation, lockout, invalidation, recovery and process recreation.
  • Full Android debug/release/AAB, JVM, lint, style, boundary, native ARM64/ARMv7, 16 KB ELF/ZIP, manifest/backup, dependency/license and SBOM gates.
  • App and native instrumentation on ARM64 API 26/4 KB, API 37/4 KB and strict API 37/16 KB, including five native cold loads and five application cold starts per configuration.
  • Real Rust/core/Android Keystore integration on API 37: enrollment, cipher unlock without a second prompt, cancellation, background revocation, changed-fingerprint invalidation, manual renewal, lock-screen/fingerprint removal, failed enrollment while protection is unavailable, and successful recovery after restoring synthetic protection. Only isolated directories, namespaced aliases and public disposable credentials were used.
  • Compose enrollment/manual recovery, dialog clearing, enable/disable/lock and stale-page masking regressions. Computer use verified the active OS prompt, production Preferences and secure manual enrollment dialog in Android Studio's embedded emulator; explicitly authorized CLI/test automation drove fixture actions and fingerprints.

ARMv7 is compiled/package-audited because no ARMv7 emulator image is available locally. Physical devices are excluded by milestone scope. API 37 biometric tests deliberately changed disposable emulator protection and restored its synthetic baseline. No Apple project or frontend changes were required. Details: docs/android/authentication.md.

Implemented in commit 647dc1bb4bf2864fd9f2a4980f29601c8cf51940 (pushed to main). Passwords and Preferences now share the private runtime's Rust authentication lease through one injected core facade. AndroidX BiometricPrompt authenticates the Keystore cipher with strong biometrics and the existing current-set policy; device credentials never substitute for that policy. Explicit enrollment validates the configured GPG key even in an empty store, creates the protected record, then commits the setting. Disabling removes protected passphrases and locks the lease. The manual passphrase dialog is secure, masked, unsaved and cleared on submission/cancellation/background/disposal. Typed missing/invalidation/capability/lockout/cancellation outcomes offer manual recovery. Rust owns deadlines, activity touches, generation and native-handle revocation. Tabs and ordinary folders retain the shared lease without another prompt; cached non-secret TOTP paths remain non-interactive. Lock/background/cancel mask protected views and interrupt pending OS authentication; stale results cannot restore plaintext or overwrite newer state. Reviewed the full scope and acceptance criteria. The review also corrected cancelled manual biometric renewal retaining an internal lease, and stale asynchronous error publication. Regression checks prove immediate lease revocation and masking of an uncleared plaintext page. Validation passed: - All four required root Rust gates, with 477 workspace tests; fake clocks/auth results cover expiry, activity, old native handles, wrong passphrases, enrollment failure, current-set protection, cancellation, lockout, invalidation, recovery and process recreation. - Full Android debug/release/AAB, JVM, lint, style, boundary, native ARM64/ARMv7, 16 KB ELF/ZIP, manifest/backup, dependency/license and SBOM gates. - App and native instrumentation on ARM64 API 26/4 KB, API 37/4 KB and strict API 37/16 KB, including five native cold loads and five application cold starts per configuration. - Real Rust/core/Android Keystore integration on API 37: enrollment, cipher unlock without a second prompt, cancellation, background revocation, changed-fingerprint invalidation, manual renewal, lock-screen/fingerprint removal, failed enrollment while protection is unavailable, and successful recovery after restoring synthetic protection. Only isolated directories, namespaced aliases and public disposable credentials were used. - Compose enrollment/manual recovery, dialog clearing, enable/disable/lock and stale-page masking regressions. Computer use verified the active OS prompt, production Preferences and secure manual enrollment dialog in Android Studio's embedded emulator; explicitly authorized CLI/test automation drove fixture actions and fingerprints. ARMv7 is compiled/package-audited because no ARMv7 emulator image is available locally. Physical devices are excluded by milestone scope. API 37 biometric tests deliberately changed disposable emulator protection and restored its synthetic baseline. No Apple project or frontend changes were required. Details: docs/android/authentication.md.
hugo closed this issue 2026-10-09 14:33:49 +00:00
Sign in to join this conversation.