Skip to content

feat(ios): fix QR enrollment + encryption + deep links#76

Merged
Jesssullivan merged 4 commits intomainfrom
feat/bootstrap-qr-encryption
Mar 10, 2026
Merged

feat(ios): fix QR enrollment + encryption + deep links#76
Jesssullivan merged 4 commits intomainfrom
feat/bootstrap-qr-encryption

Conversation

@Jesssullivan
Copy link
Copy Markdown
Owner

Summary

  • Fix "invalid symbol 123" QR parsing bug: QRScannerView now detects payload type (raw JSON vs base64 vs deep link) BEFORE attempting base64 decode. Previously, raw JSON {...} was routed to EnrollmentInvite::decode() which expected base64.
  • Add encryption passphrase + salt to bootstrap QR: gen-bootstrap-qr.sh reads TCFS_ENCRYPTION_KEY_FILE from sops-nix and includes encryption credentials in the QR payload (optional — omitted for plaintext mode).
  • Register tcfs:// URL scheme: Added CFBundleURLTypes to project.yml and .onOpenURL handler in TCFSApp for tcfs://bootstrap and tcfs://enroll deep links.

Test plan

  • Generate bootstrap QR on fleet host with encryption enabled, scan on iOS — no "invalid symbol" error
  • Verify encryption passphrase/salt appear in Keychain after QR scan
  • Test tcfs://bootstrap?data=<base64> deep link opens app and configures
  • Test tcfs://enroll?data=<base64> deep link opens app and processes invite
  • Verify plaintext mode (no TCFS_ENCRYPTION_KEY_FILE) still generates valid QR

🤖 Generated with Claude Code

QRScannerView.handleScan() was passing all scanned strings directly to
processEnrollmentInvite(), which calls the Rust FFI base64 decoder.
Raw JSON payloads (starting with '{') caused "invalid symbol 123,
offset 0" because ASCII 123 = '{' is not valid base64.

Add explicit type detection before any parsing:
- Raw JSON (starts with '{') -> BootstrapConfig.parse() first
- tcfs://bootstrap deep links -> BootstrapConfig.parse()
- tcfs://enroll deep links -> enrollment invite processing
- Opaque strings -> try bootstrap, then enrollment invite

Thread TCFSViewModel through AuthView -> QRScannerView so bootstrap
configs scanned from the Auth section can be saved to keychain.
gen-bootstrap-qr.sh now reads TCFS_ENCRYPTION_KEY_FILE from sops-nix
and optionally includes encryption_passphrase and encryption_salt in
the QR JSON payload. Fields are omitted when no passphrase is set
(plaintext mode). iOS app already handles these optional fields.
Add CFBundleURLTypes to project.yml so iOS opens the TCFS app when
handling tcfs://bootstrap and tcfs://enroll deep links. The onOpenURL
handler was added in the previous commit.
- gen-bootstrap-qr.sh now signs the JSON payload with BLAKE3 keyed-MAC
  using the device master key or derived key from encryption passphrase.
  Adds created_at and expires_at (1h TTL) timestamps.
- New UniFFI export: verify_bootstrap_signature() for iOS-side
  BLAKE3 signature verification with constant-time comparison.
- BootstrapConfig struct gains created_at, expires_at, signature fields.
  Expired QR codes are rejected. Unsigned configs accepted with warning.
- blake3 crate added to tcfs-file-provider (uniffi feature only).
@Jesssullivan Jesssullivan merged commit 6c15bf5 into main Mar 10, 2026
8 of 10 checks passed
@Jesssullivan Jesssullivan deleted the feat/bootstrap-qr-encryption branch April 5, 2026 20:49
Jesssullivan added a commit to tinyland-inc/tummycrypt that referenced this pull request Apr 8, 2026
* fix(ios): route bootstrap QR payloads before base64 decode

QRScannerView.handleScan() was passing all scanned strings directly to
processEnrollmentInvite(), which calls the Rust FFI base64 decoder.
Raw JSON payloads (starting with '{') caused "invalid symbol 123,
offset 0" because ASCII 123 = '{' is not valid base64.

Add explicit type detection before any parsing:
- Raw JSON (starts with '{') -> BootstrapConfig.parse() first
- tcfs://bootstrap deep links -> BootstrapConfig.parse()
- tcfs://enroll deep links -> enrollment invite processing
- Opaque strings -> try bootstrap, then enrollment invite

Thread TCFSViewModel through AuthView -> QRScannerView so bootstrap
configs scanned from the Auth section can be saved to keychain.

* feat(ios): add encryption passphrase + salt to bootstrap QR

gen-bootstrap-qr.sh now reads TCFS_ENCRYPTION_KEY_FILE from sops-nix
and optionally includes encryption_passphrase and encryption_salt in
the QR JSON payload. Fields are omitted when no passphrase is set
(plaintext mode). iOS app already handles these optional fields.

* feat(ios): register tcfs:// URL scheme for deep links

Add CFBundleURLTypes to project.yml so iOS opens the TCFS app when
handling tcfs://bootstrap and tcfs://enroll deep links. The onOpenURL
handler was added in the previous commit.

* feat(ios): add BLAKE3-HMAC signature + expiry to bootstrap QR

- gen-bootstrap-qr.sh now signs the JSON payload with BLAKE3 keyed-MAC
  using the device master key or derived key from encryption passphrase.
  Adds created_at and expires_at (1h TTL) timestamps.
- New UniFFI export: verify_bootstrap_signature() for iOS-side
  BLAKE3 signature verification with constant-time comparison.
- BootstrapConfig struct gains created_at, expires_at, signature fields.
  Expired QR codes are rejected. Unsigned configs accepted with warning.
- blake3 crate added to tcfs-file-provider (uniffi feature only).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant