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

6.2 KiB
Raw Permalink Blame History

Android GPG QR transfer

Issue #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.