Files
IronStorage/docs/android/credentials.md
T
hugo 0c8366d34f
Android ARM verification / rust (push) Failing after 49m12s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Failing after 21s
Implement Android Credential Manager and Autofill providers (#109)
2026-10-12 19:18:10 +02:00

131 lines
8.6 KiB
Markdown

# Android password providers
IronStorage supplies password credentials through AndroidX Credential Provider on
Android 14 and newer and through framework Autofill on Android 8 and newer. Both
use the same `NativeCredentialRepository` facade and storage-owned request,
matching, search, authentication, credential-field and save APIs. Services and
selection run inside the private application UID and share one Rust owner;
requesting applications run outside that boundary. There is no second secret
cache or service-specific authentication lease.
Enable IronStorage in its Preferences → Autofill and Credential Manager providers
section. The two status rows open Android's provider settings. For current Chrome,
select the provider in Android settings, then open Chrome Settings → Autofill and
passwords → Autofill settings, choose **Autofill using another service**, and
restart Chrome. See [Chrome's instructions](https://support.google.com/chrome/answer/142893?co=GENIE.Platform%3DAndroid&hl=en).
## Requests and identity
Candidate responses contain entry paths only. Rust ranks exact path components
matching the verified application package or website host. Manual search is also
Rust-owned and returns up to 100 matching paths; refine the search to find another
entry. Listing candidates never decrypts an entry or restores a passphrase.
The OS adapter supplies installed package signing fingerprints.
The Android 11+ package-visibility permission is required to recheck the current
signers of arbitrary requesting apps, including clients with no launcher intent.
IronStorage queries only the package supplied by the OS request and never
enumerates installed apps. See [Android's package visibility contract](https://developer.android.com/training/package-visibility/declaring).
The manifest suppresses only the lint recommendation to replace this permission
with static queries: arbitrary OS-mediated clients have no common discoverable
intent filter. A Google Play release must declare and justify this use under
[Play's package visibility policy](https://support.google.com/googleplay/android-developer/answer/10158779).
Credential Manager's supplied signing identity must agree with PackageManager. Rust's
reviewed release-signature list is passed to AndroidX `CallingAppInfo.getOrigin`;
Rust then validates and canonicalizes the verified HTTPS origin. The list covers
release Chrome and Firefox. Unsupported or untrusted origin claims remain bound
to the verified application identity. Framework Autofill never treats raw
AssistStructure `webDomain` assertions as a website permission.
Kotlin translates AssistStructure identifiers, hints, HTML attributes and input
facts. Rust chooses exactly one username/email and one password role and rejects
ambiguous roles, duplicate identifiers and oversized facts. It uses the lossless
pass document's typed username/email/password fields after explicit selection;
it rejects ambiguous entries and filters any client-specified account constraint.
Requests have random 256-bit, process-local identifiers, a two-minute monotonic
deadline, an exact caller binding and the repository mutation revision. At most
16 Rust requests are outstanding. The final secret read checks identity, revision,
deadline and the shared authentication lease again and consumes the request.
Cancellation, relock, device lock, memory pressure and process recreation revoke
requests. Secret reads and metadata refreshes do not renew the lease.
The selection activity is not exported. PendingIntents have an explicit component,
a fixed action and unique immutable base data; mutability exists only so Android
can attach its complete request. The activity verifies that request against its
original caller and form. It uses secure-window and recents protections, rejects
obscured touches and clears its passphrase when focus or foreground ownership is
lost. It revokes the lease after successful handoff as well as cancellation.
## Filling and saving
Users select an entry, explicitly consent to the named recipient receiving its
username and password, and authenticate with a valid lease, biometrics or the GPG
passphrase. Only then does Rust release the selected fields to the OS response.
Autofill supplies an authentication suggestion, including an inline presentation
when the IME advertises the supported AndroidX style. Its attribution PendingIntent
is immutable and opens the ordinary app; the private authentication PendingIntent
is not exposed as IME attribution. Responses contain no other
entry fields. Credentials are never written to logs, saved activity state,
notifications or an application dataset cache. Android 12+ authentication results
use the framework's ephemeral-dataset flag. Older Android versions retain the
handed-off values in their current framework autofill session; IronStorage does
not keep a dataset cache.
Save offers use the same request binding. Submitted values are immediately sealed
with the installed encryption-capable private-key identities and retained in bounded process memory only.
They expire with the request. Users choose an entry path, confirm saving and
separately authorize updating an existing destination. Rust authenticates the chosen destination's recipient policy, including a different key than the default, and validates the
repository and authentication again, preserves unrelated fields, rejects an open
editor of the target and uses the recoverable structured-editor/Git transaction.
Its commit guard refuses cancelled, expired or relocked requests. There is no
silent capture and no competing password-store format.
An explicitly confirmed update containing identical fields succeeds without
rewriting the ciphertext, after rechecking the original destination and request.
## Verification
Rust tests use real encrypted stores to cover selection, missing consent, missing
lease, account constraints, one-use consumption, cancellation, changed signatures,
untrusted and invalid origins, stale repositories, expiry, ambiguous forms and
explicit create/update preservation. Native instrumentation exercises the same
production facade with real GPG and repository transactions on API 26 ARM64 and
strict 16-KB API 37 ARM64.
`android/provider-client` builds a standalone fixture APK with synthetic fields and actual
Credential Manager client calls. Its public HTML asset can be served on host loopback port 8765 and reached with emulator `adb reverse tcp:8765 tcp:8765`; the form verifies synthetic values locally and submits nothing.
The opt-in `ProviderFixtureSetupTest` seeds an
unconfigured retained emulator through production onboarding and refuses to
replace existing keys or a configured store. Its debug-only Hilt access hook is
absent from release builds. The fixture passphrase and credentials are public
synthetic values in the test source, not user credentials.
Computer-use validation on the retained strict 16-KB API 37 ARM64 emulator
confirmed real Credential Manager and native framework Autofill fills, plus a
Chrome fill whose local form checked both exact synthetic values. Chrome
145.0.7632.218 was used as explicitly requested by the user; no browser update
was installed. On this fixture Chrome offered Autofill when its password field
was focused. Before authentication, only the unlock/search suggestion and entry
paths were shown. Every handoff relocked the provider, and interrupted requests
expired without filling.
Credential Manager also saved `fixture-bob` at an explicitly selected new
synthetic destination after consent and authentication. The opt-in
`providerSavedFixtureValidation=true` instrumentation read the resulting username
and password through the production Rust credential facade and verified both
submitted fields.
Framework Autofill's actual system save offer also created a second explicitly
selected destination; the same readback test verified both submitted fields there.
Overwrite without its separate confirmation was rejected; a confirmed identical
update completed successfully through the actual system dialog.
The separate `providerFixtureValidation=true` regression checks
installed caller visibility and verifies that normal UI/cache trim callbacks
preserve locked metadata requests while genuine memory pressure revokes them.
The integration follows the official [Credential Provider guide](https://developer.android.com/identity/sign-in/credential-provider),
[Autofill service contract](https://developer.android.com/reference/android/service/autofill/AutofillService)
and [inline suggestion contract](https://developer.android.com/identity/autofill/ime-autofill).
AndroidX Credentials 1.6.0 and Autofill 1.3.0 are pinned with locks, checksums and
Apache-2.0 license inventory entries. Emulator evidence is ARM-only; no physical
or x86_64 runtime coverage is claimed.