Implement biometric-protected GPG key unlock #47

Closed
opened 2026-08-09 20:00:30 +00:00 by hugo · 1 comment
Owner

Goal

Unlock GPG key material through an optional biometric-protected iPhone Keychain item and the shared authentication lease.

Scope

  • On the first decrypting action, request the GPG secret-key passphrase when no usable secure item exists, load the key through crates/storage, and establish the shared configurable authentication lease.
  • When the user enables biometric unlock in Preferences, store the passphrase through the Apple secure-storage adapter with device-only, passcode-required accessibility and current-biometric-set access control.
  • Retrieve the passphrase only through Keychain/LocalAuthentication so Face ID, Touch ID, or the device's supported biometric method gates access; Swift must not keep a second persistent copy.
  • When biometric unlock is disabled, remove the protected item and fall back to explicit passphrase entry.
  • Handle unavailable/unregistered biometrics, changed enrollment, cancellation, lockout, device-passcode removal, and Keychain errors without weakening policy; require manual passphrase re-entry and re-enrollment when needed.
  • Apply the shared inactivity timeout and manual lock behavior; expiry immediately removes decrypted presentation/edit state.
  • Keep platform FFI minimal, isolated, documented, and free of unsafe in project crates.

Acceptance criteria

  • Folder browsing does not authenticate; the first decrypt does.
  • Opted-in users can reestablish a lease using supported biometrics without the passphrase appearing in config or Swift persistence.
  • Biometric enrollment changes invalidate the stored credential and require manual recovery.
  • Disabling the preference deletes the protected passphrase, and expiry/lock masks all decrypted UI.
  • Deterministic tests cover lease timing while Apple integration tests cover success, cancellation, invalidation, and fallback.
## Goal Unlock GPG key material through an optional biometric-protected iPhone Keychain item and the shared authentication lease. ## Scope - On the first decrypting action, request the GPG secret-key passphrase when no usable secure item exists, load the key through `crates/storage`, and establish the shared configurable authentication lease. - When the user enables biometric unlock in Preferences, store the passphrase through the Apple secure-storage adapter with device-only, passcode-required accessibility and current-biometric-set access control. - Retrieve the passphrase only through Keychain/LocalAuthentication so Face ID, Touch ID, or the device's supported biometric method gates access; Swift must not keep a second persistent copy. - When biometric unlock is disabled, remove the protected item and fall back to explicit passphrase entry. - Handle unavailable/unregistered biometrics, changed enrollment, cancellation, lockout, device-passcode removal, and Keychain errors without weakening policy; require manual passphrase re-entry and re-enrollment when needed. - Apply the shared inactivity timeout and manual lock behavior; expiry immediately removes decrypted presentation/edit state. - Keep platform FFI minimal, isolated, documented, and free of `unsafe` in project crates. ## Acceptance criteria - Folder browsing does not authenticate; the first decrypt does. - Opted-in users can reestablish a lease using supported biometrics without the passphrase appearing in config or Swift persistence. - Biometric enrollment changes invalidate the stored credential and require manual recovery. - Disabling the preference deletes the protected passphrase, and expiry/lock masks all decrypted UI. - Deterministic tests cover lease timing while Apple integration tests cover success, cancellation, invalidation, and fallback.
hugo added this to the 04 - iPhone and Apple Watch apps milestone 2026-08-09 20:00:30 +00:00
hugo added the enhancement label 2026-08-09 20:00:30 +00:00
Author
Owner

Implemented in 873db91 and pushed to main.

  • Added a storage-owned mobile authentication service over the shared configurable lease. The first real entry decrypt verifies a manually entered GPG passphrase; only verified material can be enrolled.
  • Added Apple secure storage using AccessibleWhenPasscodeSetThisDeviceOnly plus BIOMETRY_CURRENT_SET, with device-only persistence, typed cancellation/unavailable/error handling, enrollment-change recovery, explicit deletion on opt-out, manual lock, and inactivity expiry. Project crates remain safe Rust and launch no helper processes.
  • Added the UniFFI boundary and native UIKit locked-entry and Preferences presentation. Swift keeps no persistent passphrase copy and folder browsing remains unauthenticated.
  • Added deterministic fixture-backed tests for manual recovery, biometric reuse, exact lease expiry, enrollment invalidation, re-enrollment, deletion fallback, and secret-free preference persistence.

Verification: all required cargo fmt, warning-denied check, clippy, and workspace test gates passed; xcodegen generate and the iPhone Simulator build passed; simulator visual checks covered browsing, Preferences, locked/unlocked presentation, manual entry, and fail-closed passcode absence. A development-signed build installed on the paired iPhone and verified manual decrypt, Face ID lease restoration after manual lock, and opt-out deletion restoring the GPG passphrase prompt. The app uses the existing AppIcon.

Implemented in `873db91` and pushed to `main`. - Added a storage-owned mobile authentication service over the shared configurable lease. The first real entry decrypt verifies a manually entered GPG passphrase; only verified material can be enrolled. - Added Apple secure storage using `AccessibleWhenPasscodeSetThisDeviceOnly` plus `BIOMETRY_CURRENT_SET`, with device-only persistence, typed cancellation/unavailable/error handling, enrollment-change recovery, explicit deletion on opt-out, manual lock, and inactivity expiry. Project crates remain safe Rust and launch no helper processes. - Added the UniFFI boundary and native UIKit locked-entry and Preferences presentation. Swift keeps no persistent passphrase copy and folder browsing remains unauthenticated. - Added deterministic fixture-backed tests for manual recovery, biometric reuse, exact lease expiry, enrollment invalidation, re-enrollment, deletion fallback, and secret-free preference persistence. Verification: all required `cargo fmt`, warning-denied check, clippy, and workspace test gates passed; `xcodegen generate` and the iPhone Simulator build passed; simulator visual checks covered browsing, Preferences, locked/unlocked presentation, manual entry, and fail-closed passcode absence. A development-signed build installed on the paired iPhone and verified manual decrypt, Face ID lease restoration after manual lock, and opt-out deletion restoring the GPG passphrase prompt. The app uses the existing AppIcon.
hugo closed this issue 2026-08-11 16:33:45 +00:00
Sign in to join this conversation.