78 lines
5.0 KiB
Markdown
78 lines
5.0 KiB
Markdown
# Android biometric authentication (#98)
|
|
|
|
Passwords and Preferences use one core facade over the private runtime's shared
|
|
Rust authentication owner. Rust validates the key, chooses protected operations,
|
|
owns the lease generation and inactivity deadline, and revokes native entry and
|
|
editor handles. Kotlin publishes that typed state and reports actual user
|
|
interaction; polling never extends the lease. Moving between tabs or browsing
|
|
folders retains a valid lease without requesting another biometric operation.
|
|
Backgrounding, manual Lock and Cancel immediately mask protected presentation
|
|
and interrupt a pending OS prompt before waiting for Rust to revoke its handles.
|
|
Obsolete requests cannot publish plaintext or overwrite newer authentication state.
|
|
|
|
Preferences can explicitly enroll biometric unlock even in an empty store. A
|
|
secure Compose dialog collects the GPG passphrase without saved state, disables
|
|
keyboard correction, masks input and clears it on submission, dismissal,
|
|
backgrounding or disposal. Rust proves the configured secret key's passphrase,
|
|
then persists the passphrase through the existing current-biometric-set secure
|
|
store and commits the setting only after the OS-authenticated cipher succeeds.
|
|
Disabling deletes the protected passphrases and revokes the shared lease.
|
|
|
|
AndroidX BiometricPrompt authenticates the Keystore cipher supplied by the
|
|
platform adapter with BIOMETRIC_STRONG. Current-set records never request device
|
|
credential fallback. The explicit Unlock action restores and validates the
|
|
configured key's protected passphrase; password-entry Unlock retains Rust's
|
|
recipient selection. Missing or unusable records offer the manual dialog.
|
|
Manual recovery while biometric unlock is enabled explicitly renews the protected
|
|
record; if strong biometrics are unavailable, disable biometric unlock in
|
|
Preferences before using manual password-entry unlock. Kotlin stores neither the
|
|
passphrase nor a duplicate authentication deadline in preferences, saved state,
|
|
files or WorkManager.
|
|
|
|
Capability/no-enrollment, denied access, cancellation, temporary/permanent
|
|
lockout, missing or invalidated keys and storage failures retain typed Rust
|
|
outcomes. Lockout maps to BiometryUnavailable with manual recovery; cancellation
|
|
remains Cancelled. Process recreation starts locked and does not resurrect a
|
|
lease. Protected fields are rendered only while the shared lease is unlocked,
|
|
even if an older page result is still present. Ordinary folders and cached
|
|
non-secret TOTP discovery do not invoke an authentication prompt.
|
|
|
|
## Validation
|
|
|
|
Rust tests use a fake monotonic clock and injected secure-store outcomes to check
|
|
wrong passphrases, failed/cancelled enrollment without setting publication,
|
|
immediate lease revocation after cancelled manual biometric renewal,
|
|
current-set protection, one lease without repeated prompts, typed lockout and
|
|
invalidation, renewed enrollment, process recreation, timeout/activity touches,
|
|
editor-handle revocation and interruption of a blocked platform prompt. An empty
|
|
store regression proves enrollment does not require an existing password entry.
|
|
|
|
Compose tests check explicit dialog submission, typed enrollment success,
|
|
manual recovery, lease masking, enable/disable/lock actions and clearing input
|
|
on cancellation, backgrounding and remount. The opt-in app instrumentation test
|
|
uses the production core facades, actual Rust and Android Keystore with isolated
|
|
Rust directories and namespaced OS aliases. It exercises authenticated cipher
|
|
enrollment/unlock, cancellation, background revocation, changed fingerprint
|
|
invalidation, removal of device protection, rejection of unsupported enrollment
|
|
and recovery after restoring a synthetic test PIN/fingerprint. Its arguments are
|
|
`-e class de.rfc1437.ironstorage.AuthenticationLeaseTest -e biometricLease true`.
|
|
The fixture stages are driven by emulator fingerprint controls/test automation;
|
|
only public disposable credentials are used. This test deliberately changes
|
|
emulator enrollment and lock-screen protection, so use a disposable test device
|
|
and restore its synthetic setup before subsequent tests.
|
|
|
|
Validation scope is ARM64 API 26/4 KB, API 37/4 KB and strict API 37/16 KB.
|
|
ARMv7 is compiled and package-audited; no ARMv7 system image is available locally.
|
|
Physical devices are excluded by the milestone's explicit emulator-only scope.
|
|
No Apple project or frontend changes are required.
|
|
|
|
Final verification passed all four required root Rust gates (477 workspace tests),
|
|
the complete Android debug/release/AAB, lint, JVM, style, architecture, ELF/ZIP
|
|
16 KB, manifest/backup, dependency/license and SBOM gates, and app plus native
|
|
instrumentation on all three configurations. Each configuration passed five
|
|
native cold loads and five application cold starts. The real biometric lifecycle
|
|
and capability/recovery test passed on API 37/4 KB with synthetic credentials.
|
|
Computer use verified the active OS prompt, production Preferences and secure
|
|
manual enrollment dialog in Android Studio's embedded emulator; approved CLI
|
|
and test automation drove fingerprint events and disposable fixture actions.
|