Implement CameraX GPG key import and QR export on Android #106

Closed
opened 2026-08-17 19:15:15 +00:00 by hugo · 1 comment
Owner

Goal

Transfer ASCII-armored GPG public and private keys through single- or multipart QR codes using Android-native camera/display adapters.

Scope

  • Use CameraX Preview and ImageAnalysis with the runtime camera permission for scanning; pass normalized frame bytes/planes and orientation to Rust-owned QR decoding and multipart-envelope parsing.
  • Render Rust-provided QR matrices in Compose Canvas for export; Kotlin must not construct payloads or re-encode key armor.
  • Preserve exact armored bytes. Parsing, fingerprinting, public/secret matching, passphrase validation, duplicate handling, import transaction, and default-key selection remain in crates/storage.
  • Present progress for duplicate/out-of-order/missing frames and allow safe pause/cancel across rotation, fold, permission interruption, and app background.
  • Require explicit security confirmation plus storage-derived fingerprint/identity before importing or displaying private material.
  • Apply FLAG_SECURE, brightness/keep-screen-on only while needed, and immediate masking on background/lock; never copy armor to clipboard, save it to photos, cache camera frames, or log payloads.
  • Handle permission denial/permanent denial, unavailable camera, invalid/incomplete/mismatched armor, duplicate key, interrupted import, and process death safely.
  • Prove compatibility with existing ASCII-armored GPG fixtures and QR envelope versions.

Acceptance criteria

  • Single and multipart fixtures reconstruct byte-for-byte and import transactionally.
  • Invalid/incomplete/mismatched data cannot replace valid keys/config.
  • Camera analysis stays responsive and bounded on low-memory devices and changing fold orientations.
  • Private export/import is never visible in recents/screenshots and leaves no ordinary Android persistence.
  • Kotlin contains no QR payload, GPG parser, fingerprint, matching, or import policy.

Depends on Rust/UniFFI packaging, lifecycle security, biometric authentication, and onboarding. Part of #87.

Android support update (2026-10-08)

User-directed scope: Android release and CI targets are arm64-v8a and armeabi-v7a. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.

## Goal Transfer ASCII-armored GPG public and private keys through single- or multipart QR codes using Android-native camera/display adapters. ## Scope - Use CameraX Preview and ImageAnalysis with the runtime camera permission for scanning; pass normalized frame bytes/planes and orientation to Rust-owned QR decoding and multipart-envelope parsing. - Render Rust-provided QR matrices in Compose Canvas for export; Kotlin must not construct payloads or re-encode key armor. - Preserve exact armored bytes. Parsing, fingerprinting, public/secret matching, passphrase validation, duplicate handling, import transaction, and default-key selection remain in `crates/storage`. - Present progress for duplicate/out-of-order/missing frames and allow safe pause/cancel across rotation, fold, permission interruption, and app background. - Require explicit security confirmation plus storage-derived fingerprint/identity before importing or displaying private material. - Apply `FLAG_SECURE`, brightness/keep-screen-on only while needed, and immediate masking on background/lock; never copy armor to clipboard, save it to photos, cache camera frames, or log payloads. - Handle permission denial/permanent denial, unavailable camera, invalid/incomplete/mismatched armor, duplicate key, interrupted import, and process death safely. - Prove compatibility with existing ASCII-armored GPG fixtures and QR envelope versions. ## Acceptance criteria - Single and multipart fixtures reconstruct byte-for-byte and import transactionally. - Invalid/incomplete/mismatched data cannot replace valid keys/config. - Camera analysis stays responsive and bounded on low-memory devices and changing fold orientations. - Private export/import is never visible in recents/screenshots and leaves no ordinary Android persistence. - Kotlin contains no QR payload, GPG parser, fingerprint, matching, or import policy. Depends on Rust/UniFFI packaging, lifecycle security, biometric authentication, and onboarding. Part of #87. ## Android support update (2026-10-08) User-directed scope: Android release and CI targets are **arm64-v8a** and **armeabi-v7a**. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.
hugo added this to the 06 - Implement an Android version of the software milestone 2026-08-17 19:15:15 +00:00
hugo added the enhancement label 2026-08-17 19:15:15 +00:00
hugo added the critical label 2026-10-11 13:37:47 +00:00
Author
Owner

Implemented in d466b17.

  • Added lifecycle-bound CameraX Preview/ImageAnalysis with camera permission, pause/resume, denial/settings and unavailable-camera recovery. Bounded Y-plane bytes/strides/orientation go directly to Rust; Kotlin never decodes QR payloads or parses GPG material.
  • Reused the shared Rust camera/key-transfer contracts, added quarter-turn decoding, byte-exact multipart reassembly with zeroizing chunks, duplicate/out-of-order progress, and bounded export matrices. Imported ISKT1/ASCII armor retains the shared format.
  • Integrated the scanner into recovery and onboarding. Onboarding stages the reviewed key through its existing Rust transaction; it retains the separate local/HTTPS store setup choice and refreshes prepared-key selection correctly.
  • Added explicit fingerprint confirmation for private import/export, export identity labels, frame navigation/animation, scoped brightness/keep-screen-on, and a Rust-owned visibility deadline. Background/lock/dismissal clears matrices, inputs and transfer handles. Input camera bytes are cleared before publishing completion; no frame/armor persistence or clipboard path was added.
  • Reviewed and pinned CameraX/transitive checksums, locks and licenses. Audited its two native libraries and removed the unused transitive network-state permission.

Verification:

  • All required Rust 1.97.1 workspace gates passed: fmt, warning-denied check, Clippy, and cargo test --workspace. Fixture tests cover real encrypted import/export, wrong passphrases, rollback, exact/multipart camera reconstruction, orientations/strides, stale/cancel cleanup, export bounds and non-renewing expiry.
  • androidVerification and release lint passed, including both ARM ABI builds, debug/release/AAB, JVM tests, ktlint, architecture checks, ELF/APK 16 KB audits. Exact license/SBOM, packaged manifest/backup exclusions and matching FULL release-symbol audits passed.
  • API 26 arm64: native settings/viewer test with successful private export and relock erasure; 14 bridge tests; app runner OK (32 reported, including four opt-in/biometric methods not enabled); final onboarding/settings group 4 passed; actual CameraX close/reopen passed.
  • API 37 arm64, 16 KB with compatibility disabled: final native private-export/erasure test, 14 bridge tests and actual CameraX close/reopen passed. armeabi-v7a has build/package evidence; runtime checks used arm64 emulators.
  • Computer use on the persistent API 37 emulator verified update-state retention, permission interruption, actual live preview, scrolling/clipping, and disabled private export before explicit confirmation. A preview overlap found in this pass was fixed and rechecked.

The retained-profile private-export prompt remains an optional user check; isolated encrypted fixtures provide the successful-private-operation evidence. Seven broader Keystore tests requiring separate enrolled-biometric setup failed their prerequisites and are not counted as camera coverage. Physical devices remain excluded by the milestone support policy.

Implementation/security details and per-environment evidence: Android key transfer. Completion review found and fixed the onboarding refresh and byte-erasure ordering defects; no remaining #106 implementation gap was found.

Implemented in [d466b17](https://git.rfc1437.de/hugo/IronStorage/commit/d466b170ab407ea68452975dbb46486dc7b1d1ba). - Added lifecycle-bound CameraX Preview/ImageAnalysis with camera permission, pause/resume, denial/settings and unavailable-camera recovery. Bounded Y-plane bytes/strides/orientation go directly to Rust; Kotlin never decodes QR payloads or parses GPG material. - Reused the shared Rust camera/key-transfer contracts, added quarter-turn decoding, byte-exact multipart reassembly with zeroizing chunks, duplicate/out-of-order progress, and bounded export matrices. Imported ISKT1/ASCII armor retains the shared format. - Integrated the scanner into recovery and onboarding. Onboarding stages the reviewed key through its existing Rust transaction; it retains the separate local/HTTPS store setup choice and refreshes prepared-key selection correctly. - Added explicit fingerprint confirmation for private import/export, export identity labels, frame navigation/animation, scoped brightness/keep-screen-on, and a Rust-owned visibility deadline. Background/lock/dismissal clears matrices, inputs and transfer handles. Input camera bytes are cleared before publishing completion; no frame/armor persistence or clipboard path was added. - Reviewed and pinned CameraX/transitive checksums, locks and licenses. Audited its two native libraries and removed the unused transitive network-state permission. Verification: - All required Rust 1.97.1 workspace gates passed: fmt, warning-denied check, Clippy, and cargo test --workspace. Fixture tests cover real encrypted import/export, wrong passphrases, rollback, exact/multipart camera reconstruction, orientations/strides, stale/cancel cleanup, export bounds and non-renewing expiry. - androidVerification and release lint passed, including both ARM ABI builds, debug/release/AAB, JVM tests, ktlint, architecture checks, ELF/APK 16 KB audits. Exact license/SBOM, packaged manifest/backup exclusions and matching FULL release-symbol audits passed. - API 26 arm64: native settings/viewer test with successful private export and relock erasure; 14 bridge tests; app runner OK (32 reported, including four opt-in/biometric methods not enabled); final onboarding/settings group 4 passed; actual CameraX close/reopen passed. - API 37 arm64, 16 KB with compatibility disabled: final native private-export/erasure test, 14 bridge tests and actual CameraX close/reopen passed. armeabi-v7a has build/package evidence; runtime checks used arm64 emulators. - Computer use on the persistent API 37 emulator verified update-state retention, permission interruption, actual live preview, scrolling/clipping, and disabled private export before explicit confirmation. A preview overlap found in this pass was fixed and rechecked. The retained-profile private-export prompt remains an optional user check; isolated encrypted fixtures provide the successful-private-operation evidence. Seven broader Keystore tests requiring separate enrolled-biometric setup failed their prerequisites and are not counted as camera coverage. Physical devices remain excluded by the milestone support policy. Implementation/security details and per-environment evidence: [Android key transfer](https://git.rfc1437.de/hugo/IronStorage/src/branch/main/docs/android/key-transfer.md). Completion review found and fixed the onboarding refresh and byte-erasure ordering defects; no remaining #106 implementation gap was found.
hugo closed this issue 2026-10-12 07:52:54 +00:00
Sign in to join this conversation.