Files
hugo d466b170ab
Android ARM verification / rust (push) Failing after 47m42s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s
Implement CameraX GPG QR transfer on Android (#106)
2026-10-12 09:49:38 +02:00

97 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.