Skip to content

feat(vault): Dart client-side crypto core (ADR-052 D-4, mobile first)#300

Merged
physercoe merged 1 commit into
mainfrom
feat/vault-client-crypto-dart
Jul 5, 2026
Merged

feat(vault): Dart client-side crypto core (ADR-052 D-4, mobile first)#300
physercoe merged 1 commit into
mainfrom
feat/vault-client-crypto-dart

Conversation

@physercoe

Copy link
Copy Markdown
Owner

First slice of the client side of the zero-knowledge key vault (ADR-052 D-4), Dart/mobile first. All crypto happens on-device; only opaque ciphertext leaves it (the hub sync store from #299 is a blind blob store).

VaultCrypto (lib/services/vault/vault_crypto.dart)

  • Bundle seal/open — one AES-256-GCM vault key seals the whole bundle (connections + keys + passphrases + passwords, D-3).
  • Device wrap/unwrap — X25519 sealed box (ephemeral X25519 → ECDH → HKDF-SHA256 → AES-GCM) so the hub relays a per-device wrapped key it can't read; deviceKeyPairFromSeed reconstructs a device keypair from its persisted seed.
  • RecoverygenerateRecoveryCode (160-bit, dash-grouped base32) + wrapForRecovery/unwrapRecovery (vault key wrapped under a director-escrowed code via Argon2id, formatting-insensitive). Argon2 cost is injectable so tests stay fast.

Wire formats are base64 of packed bytes matching the hub's opaque TEXT columns:
sealed bundle = nonce‖ct‖mac, device envelope = ephPub(32)‖box, recovery envelope = salt(16)‖box.

Tests

Bundle round-trip + wrong-key auth failure + fresh-nonce; device wrap/unwrap + intruder rejection + seed-reconstruction round-trip; recovery round-trip + wrong-code failure + format tolerance; recovery-code shape.

Note on verification

The Flutter/Dart SDK isn't available in this environment (per project setup), so I could not run flutter analyze/test locally — CI is the validation gate. The API is written against package:cryptography 2.9.0, matching the existing usage in lib/services/keychain/ssh_key_service.dart. If CI flags an API signature (e.g. an Argon2id param name), it's a quick fix.

Follow-ups (not in this PR)

Depends on #299 (hub vault store) being merged for end-to-end, but this crypto core is independent and mergeable on its own.

🤖 Generated with Claude Code

…vault

First slice of the client side of ADR-052 D-4 (Dart/mobile first). Pure on-device
crypto over package:cryptography — only opaque ciphertext ever leaves the device.

VaultCrypto:
- generateVaultKey / sealBundle / openBundle — one AES-256-GCM vault key seals the
  whole bundle (connections + keys + passphrases + passwords, D-3).
- device keypair (X25519) + wrapForDevice / unwrapForDevice — sealed-box (ephemeral
  X25519 -> ECDH -> HKDF-SHA256 -> AES-GCM) so the hub relays a per-device wrapped
  key it can't read; deviceKeyPairFromSeed reconstructs from the persisted seed.
- generateRecoveryCode (160-bit base32) + wrapForRecovery / unwrapRecovery — the
  vault key wrapped under a director-escrowed recovery code (Argon2id), formatting-
  insensitive. Argon2 cost is injectable so tests stay fast.

Wire formats are base64 of packed bytes matching the hub's opaque columns. Tests
cover bundle round-trip + wrong-key auth failure + fresh-nonce, device wrap/unwrap +
intruder rejection + seed-reconstruction, recovery round-trip + wrong-code + format
tolerance, and code shape. Flutter SDK isn't available locally (per project setup),
so CI analyze+test is the validation gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@physercoe
physercoe merged commit 8fba837 into main Jul 5, 2026
4 checks passed
physercoe pushed a commit that referenced this pull request Jul 5, 2026
Surfaces the zero-knowledge SSH key-vault sync (ADR-052 D-4) in the mobile
app end-to-end: hub blind blob store (#299), Dart crypto core (#300), VaultApi
sync client, and the Settings › Data › Key vault sync flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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