Implement CameraX GPG QR transfer on Android (#106)
This commit is contained in:
1 parent
6669b77f4e
commit
d466b170ab
33 files changed
+1616
-162
No files matched your search
@@ -0,0 +1,96 @@
|
||||
# Android GPG QR transfer
|
||||
|
||||
Issue [#106](https://git.rfc1437.de/hugo/IronStorage/issues/106) uses the shared
|
||||
ASCII-armored OpenPGP and `ISKT1` multipart format. `crates/storage` owns frame
|
||||
bounds/orientation, QR decoding, checksum/reassembly, identity, passphrase
|
||||
validation, duplicate handling and transactional key installation. Kotlin only
|
||||
supplies camera facts and renders storage-provided QR matrices.
|
||||
|
||||
## Import and export
|
||||
|
||||
Open Preferences → Import or export GPG QR codes. Scanning requests camera
|
||||
permission, binds CameraX Preview and ImageAnalysis to the Activity lifecycle,
|
||||
and uses only the latest Y plane. Pause closes the camera while retaining the
|
||||
in-memory multipart session; resume opens a fresh camera. Frames can arrive in
|
||||
any order. Duplicate frames leave the received count unchanged. The complete
|
||||
key's Rust-derived identity and fingerprint appear before import. Private import
|
||||
requires a separate fingerprint/security confirmation. Default-key selection
|
||||
and replacement are explicit controls; incorrect passphrases, incomplete,
|
||||
conflicting or mismatched transfers cannot replace valid keys/configuration.
|
||||
|
||||
Onboarding → Import existing GPG key → Scan GPG QR codes uses the same scanner
|
||||
and review, then stages the key through Android's existing onboarding transaction.
|
||||
It does not create a store or bypass the later local/HTTPS setup choice. Public
|
||||
keys cannot become onboarding/default decryption keys; Rust rejects them.
|
||||
|
||||
Export selects a storage-provided key. Private export requires a GPG passphrase
|
||||
and a separate fingerprint/security confirmation. Every displayed export remains
|
||||
labeled with the identity that produced it, even if selection changes. Previous
|
||||
matrices are erased before a new export. Multipart exports support previous/next
|
||||
and optional automatic frame advancement. Brightness and keep-screen-on apply
|
||||
only while QR display is composed and are restored on disposal. Frame ticks do
|
||||
not renew the storage-owned visibility deadline (the configured authentication
|
||||
timeout).
|
||||
|
||||
## Privacy and interruption
|
||||
|
||||
The Activity and transfer dialog use `FLAG_SECURE`. Lock/background/focus loss,
|
||||
process recreation and dismissal remove transfer state, clear Kotlin matrices
|
||||
and inputs, and drop the Rust importer/exporter. Rust multipart chunks and
|
||||
reassembly buffers zeroize on drop. No camera frame, armor or QR payload is
|
||||
written to Android persistence, photos, clipboard, saved instance state or logs.
|
||||
Only an explicitly confirmed successful import persists GPG-compatible keys.
|
||||
|
||||
Camera permission prompts, an external image picker, device rotation and app
|
||||
backgrounding follow the existing fail-closed lifecycle policy: an interrupted
|
||||
transfer is discarded; reopen and rescan rather than retaining private bytes
|
||||
across an OS interruption. A camera pause within the active dialog retains
|
||||
progress. Denial (including permanent denial) presents a settings link and image/
|
||||
manual alternatives. Missing/busy camera reports an actionable recovery message.
|
||||
|
||||
Camera decoding accepts at most 2048 × 2048 pixels and a 4 MiB Y plane, validates
|
||||
row/pixel strides and the four quarter-turn rotations before accessing pixels,
|
||||
and allows one native decode in flight. Kotlin clears the input plane before
|
||||
publishing a completed key to state observers, with a final cleanup on every
|
||||
error/cancellation path. CameraX drops obsolete frames rather
|
||||
than queueing them. Imported `ISKT1` envelopes retain the existing 11,000-frame/
|
||||
17.6 MB armor bound and 1,600-byte chunk bound. Export rejects more than 256
|
||||
frames before allocating matrices, keeping the matrix set below 8 MiB.
|
||||
|
||||
## Verification
|
||||
|
||||
Rust tests use upstream-compatible Alice public/private armor to prove byte-exact
|
||||
single/multipart reconstruction, rotated/padded camera planes, out-of-order and
|
||||
duplicate delivery, incomplete/corrupt/mismatched rejection, wrong-passphrase
|
||||
retention, import rollback, export size bounds and deadline polling without renewal.
|
||||
Native Android settings tests also exercise camera planes through generated UniFFI
|
||||
and verify Kotlin frame bytes and QR matrices are cleared. Compose tests check
|
||||
private import/export confirmation and hidden-input saved-state exclusions.
|
||||
`AndroidQrCameraTest` exercises actual emulator CameraX frame delivery and repeated
|
||||
close/reopen; the preview is clipped in both Android View and Compose layout.
|
||||
|
||||
Build gates include both ARM ABIs, debug/release/AAB, JVM tests, lint/ktlint,
|
||||
architecture/source checks, exact dependency locks/checksums, the license/SBOM
|
||||
gate, every native ELF/APK 16 KB alignment check, and matching FULL release symbols.
|
||||
CameraX 1.6.2 and its exact transitives are reviewed against official Maven POM
|
||||
licenses; camera-core includes Apache-2.0 and BSD-3-Clause portions. The merged
|
||||
manifest removes Media3's unused ACCESS_NETWORK_STATE permission; only CAMERA
|
||||
is added for this feature.
|
||||
|
||||
Validation is emulator-only under the milestone's ARM support policy. The
|
||||
Keystore enrollment/invalidation tests require separate interactive device setup;
|
||||
their setup failures are not camera test results and do not establish biometric
|
||||
coverage for this change.
|
||||
|
||||
Recorded for #106 on 2026-10-12:
|
||||
|
||||
| Environment | Evidence |
|
||||
| --- | --- |
|
||||
| API 26 ARM, 4 KB | Native settings/viewer fixture including private export and relock erasure; 14 bridge tests; app runner OK (32 reported, including four opt-in/biometric methods not enabled); final onboarding/settings regression group 4 passed; CameraX close/reopen passed |
|
||||
| API 37 ARM, strict 16 KB | Native fixture including private export and relock erasure; 14 bridge tests; CameraX close/reopen passed; original app run included both passing QR settings tests |
|
||||
| API 37 ARM, 4 KB, computer use | Update retained existing store/preferences; camera permission interruption discarded transfer; live CameraX preview and clipped scrolling controls inspected; private export stayed disabled until fingerprint confirmation |
|
||||
| Rust 1.97.1 and Android packages | Required workspace gates, androidVerification and release lint, license/SBOM, debug/release manifests/backup exclusions, ARM native alignment and release FULL-symbol checks passed |
|
||||
|
||||
Private export from the retained simulator profile awaits optional user input.
|
||||
Successful private export/import and erasure evidence above uses isolated,
|
||||
encrypted compatibility fixtures rather than a user's passphrase.
|
||||
@@ -46,9 +46,9 @@ shared core's recipient/signing rules, pass/pass-otp format, HOTP counter safety
|
||||
Git failure semantics and secret-redaction requirements remain binding wherever
|
||||
an Android workflow invokes them; platform presentation may not replace them.
|
||||
|
||||
Current iPhone camera adapters pass OS-decoded QR bytes. Android #106/#91 must
|
||||
also supply bounded image/frame facts for Rust decoding as required by the
|
||||
Android contract; that additional adapter is pending, not an existing API.
|
||||
Current iPhone camera adapters pass OS-decoded QR bytes. Android #106 supplies bounded Y-plane/stride/orientation facts through the
|
||||
shared native contract; storage decodes and reassembles the transfer. See
|
||||
[key-transfer.md](key-transfer.md) for interruption/security behavior and evidence.
|
||||
The external-credential matching/grant API required by #109 must be implemented
|
||||
in storage, rather than copying frontend selection assumptions from AutoFill.
|
||||
|
||||
|
||||
Reference in new issue
Block a user