Files
IronStorage/docs/android/presentation-security.md
hugo a06f39af23
Android ARM verification / rust (push) Failing after 47m6s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s
Enforce Android lifecycle and presentation security (#100)
2026-10-09 18:59:18 +02:00

113 lines
7.4 KiB
Markdown

# Android presentation security
The Android client presents Rust-owned authentication state. `crates/storage`
owns lease expiry, renewal, editor disposal, and clipboard content comparison.
Kotlin forwards OS events and applies native window, input, and clipboard controls.
## Lifecycle and immediate cleanup
| Event | Behavior |
| --- | --- |
| Foreground/resume | Report foreground to Rust; remain locked until explicit unlock. |
| Explicit interaction | Report user activity while unlocked; Rust decides lease renewal. |
| Pause/stop/task switch | Immediately cover native content and clear protected presentation, then revoke the Rust lease. |
| Loss of top-resumed status | Revoke for an inactive multi-window activity, except while its own OS biometric request is pending. |
| Native window focus lost/user leaves task | Revoke even if Recents keeps the activity resumed; owned dialog/menu focus transfers remain usable. |
| Screen off/device locked | Cancel authentication and revoke the lease. |
| Trim memory/low memory | Clear protected presentation and revoke regardless of trim level. |
| Process recreation | Start locked; never restore a lease or plaintext from saved state. |
| Manual lock/expiry | Clear protected presentation, editors, OTP state, and active clipboard copies. |
`ProtectedPresentation` invokes main-thread cleanup synchronously. Its callbacks
clear setup secrets, cancel staged setup/remote operations, drop decrypted facade
sections and editor/TOTP state, clear local dialog inputs, and cover the old native
surface with an opaque overlay. Accessibility descendants are suppressed while
covered. A generation change discards the old composition; the cleaned content is
rendered before the resumed activity is uncovered. This keeps the frame clock
running while preventing a stale frame from being visible between cleanup and
recomposition. Rust revocation jobs run in order and complete before a subsequent
unlock; stale results cannot republish a protected document.
Owned dialogs and menus register their window before opening and report their own
focus loss. The activity excludes those internal focus transfers and its active
OS biometric request from external focus-loss revocation. The native cover stays
in place until the cleaned root is both resumed and focused again.
TOTP ticks, recomposition, polling, animations, clipboard waiting/cleanup, and
foreground notifications never report user activity. Rotation clears input and
cancels setup operations rather than saving passphrases, armor, or tokens.
## Windows, inputs, and diagnostics
The main activity sets `FLAG_SECURE` before Compose renders. Sensitive dialogs
use secure windows; other dialogs inherit the activity flag. API 33+ also disables
recent-task screenshots. Picture-in-picture is disabled in the manifest. An
activity launched on a secondary display is revoked and finished. Task switching
and inactive multi-window views clear protected presentation; returning does not
automatically authenticate. Screen sharing/casting to nonsecure surfaces uses the
same window policy. See Android's [secure-window contract](https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_SECURE).
The activity and dialog roots exclude descendants from autofill and, on API 30+,
content capture. Input sessions request no personalized learning, fullscreen IME,
or extract UI. Password editors remain masked until an explicit reveal action.
Unlocked accessible fields remain available to assistive technology; locked
content is removed and covered. See the platform [view controls](https://developer.android.com/reference/android/view/View)
and [IME flags](https://developer.android.com/reference/android/view/inputmethod/EditorInfo).
The production Android modules have no secret logging, analytics, notification
payloads, or plaintext previews. Facade secret state and generated bridge records
have redacted diagnostic strings; errors use typed Rust messages or generic OS
failure text. Saved state contains navigation/setup choices, not protected input
or decrypted documents. Tests inspect serialized saved bundles and immediate
accessibility suppression. No memory-dump/export facility is provided.
The current manifest contains the main activity. Credential-provider components
are delivered by issue #109; they must use these protected presentation/window
controls when introduced. This issue does not install an empty credential service.
## Clipboard ownership and limits
Each explicit copy obtains a separate native clipboard capability. Rust supplies
the selected stored field and directs timeout/cancellation cleanup. Android marks
the clip sensitive, assigns a random owner identity, and records the OS timestamp.
Cleanup requires that identity, timestamp, and Rust's content comparison still
match. Another copy with identical text, unrelated user text, or a restored old
clip with a new timestamp is preserved. The adapter checks ownership again just
before clearing. A successful write publishes explicit copy feedback. Revocation
attempts cleanup synchronously while foreground clipboard access is still
available; no previous clipboard history is retained or restored.
Android's ClipboardManager does not offer an atomic compare-and-clear operation
across IPC calls. Another application can race the final check; this implementation
cannot promise atomicity that the OS API lacks. Android 10+ can deny background
clipboard reads, so cleanup cannot be guaranteed after the process has already
lost access or died. System/OEM clipboard histories may retain copies independently.
The sensitive flag suppresses supported clipboard previews; it is not encryption.
See [Android clipboard security](https://developer.android.com/privacy-and-security/risks/secure-clipboard-handling)
and [clipboard behavior](https://developer.android.com/develop/ui/views/touch-and-input/copy-paste).
`FLAG_SECURE`, autofill/content-capture exclusions, and IME requests rely on OS/OEM
and keyboard cooperation. They cannot protect against a rooted OS, debugger,
malicious keyboard, camera photographing the screen, or forensic memory access.
JVM/Compose strings cannot be reliably zeroized; cleanup drops references and
Rust zeroizes owned secret buffers, without claiming forensic JVM erasure.
## Validation
Rust tests exercise lifecycle revocation/editor disposal, foreground without
renewal, and immediate content-checked clipboard cleanup. Native facade tests
use real encrypted entries and verify synchronous clearing for background,
device lock, memory pressure, and process recreation. UI/OS tests cover native
clipboard ownership and restored metadata, sensitive clips, secure windows,
IME flags, saved bundles, rotation, and immediate accessibility masking.
The emulator matrix covers ARM64 API 26 (4 KB pages), API 37 (4 KB), and API 37
(strict 16 KB), including app/core/bridge instrumentation and five cold starts.
The existing embedded API 37 emulator additionally exercises real Keystore
biometric enrollment, unlock, cancellation, background cancellation, foreground
remaining locked, and memory-pressure revocation using disposable public fixture
credentials and its existing synthetic fingerprint. Computer use checks task
switching/recents and cleared input on return. ARMv7 receives build/package
validation; no suitable ARMv7 emulator image is available. Physical-device and
OEM-specific capture/casting coverage are excluded from this milestone's test
scope. Accessibility assertions do not claim spoken TalkBack verification.