- Scaffolds a new Rust crate `wasm/sss_recovery` for constant-time Shamir's Secret Sharing over GF(256) with strict Wasm `zeroize` - Implements purely typed BIP-39 fallback mapped via Deno WebCrypto in `ui/utils/bip39.ts` - Migrates `server/recovery.ts` logic mapping Device/Voucher + Server shares with Valkey rate-limiting - Applies mandatory in-memory JS zeroization on all reconstructed buffers Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
85 lines
4.3 KiB
Markdown
85 lines
4.3 KiB
Markdown
# TASK METADATA
|
|
|
|
- **Target Files:** `ui/components/RecoveryPage.tsx`, `server/main.ts`,
|
|
`server/recovery.ts` (new), `wasm/sss_recovery/` (new Rust module)
|
|
- **Core Objective:** Implement constant-time 2-of-3 Shamir's Secret Sharing
|
|
(SSS) key splitting and reconstruction in WebAssembly/Rust for the client-side
|
|
zero-downgrade recovery portal, with mandatory in-place memory zeroization.
|
|
- **Dependencies:** Deno WebCrypto API, SimpleWebAuthn (client & server),
|
|
Rust/Wasm toolchain (`wasm-pack`), IndexedDB.
|
|
- **Additional Important Notes:** Share choreography uses a Device Share
|
|
(IndexedDB via WebAuthn PRF), Hot Server Share (PostgreSQL via PIN), and Cold
|
|
Voucher (BIP-39 mnemonic). The execution sandbox must use Web Workers or
|
|
strict in-memory client modules with mandatory `Uint8Array.fill(0)`
|
|
zeroization; isolated iframes are rejected to prevent `postMessage` memory
|
|
leakage.
|
|
|
|
---
|
|
|
|
## Architectural Considerations & Risks
|
|
|
|
- **Risks:**
|
|
- **Garbage Collection Leaks:** Transferring ArrayBuffers between JavaScript
|
|
and Wasm can leave un-zeroed memory in V8. Strict lifecycle management and
|
|
immediate `Uint8Array.fill(0)` on all JS-side buffers is mandatory before
|
|
losing references.
|
|
- **Side-Channel Attacks:** Polynomial interpolation in Rust over GF(256) must
|
|
be constant-time to avoid timing attacks when processing recovery shares.
|
|
- **WebAuthn PRF Extension Support:** The Device Share in IndexedDB relies on
|
|
the WebAuthn PRF extension. A fallback or clear UX flow must be designed if
|
|
the user's authenticator lacks PRF support.
|
|
- **Brute-Forcing Server Share:** The Hot Server Share is gated by a recovery
|
|
PIN/code. Robust rate-limiting on the `/api/recovery/challenge` endpoint is
|
|
critical to prevent brute-forcing the server share.
|
|
- **Alternatives:**
|
|
- **Execution Context:** We explicitly rejected using an isolated sandbox
|
|
iframe. `postMessage` serializes data, creating uncontrollable memory copies
|
|
in the DOM that cannot be deterministically zeroed. We will use a Web Worker
|
|
or direct WebAssembly instantiation in the main thread with explicit
|
|
TypedArray zeroization.
|
|
- **Implementation Language:** Pure TypeScript SSS was rejected due to lack of
|
|
constant-time execution guarantees and poor low-level memory control
|
|
compared to Rust/Wasm.
|
|
|
|
## Proposed Implementation
|
|
|
|
### Phase 1: Wasm Core Engine (Rust)
|
|
|
|
1. Scaffold a new Rust crate (e.g., `wasm/sss_recovery`) compiling to
|
|
`wasm32-unknown-unknown`.
|
|
2. Implement a constant-time 2-of-3 Shamir's Secret Sharing reconstruction
|
|
algorithm over GF(256).
|
|
3. Expose FFI boundaries that accept two share buffers and output the
|
|
reconstructed master secret.
|
|
4. Utilize `zeroize` crate in Rust to ensure Wasm linear memory is purged of
|
|
intermediate polynomial data before returning control to JavaScript.
|
|
|
|
### Phase 2: Client-Side Choreography (`ui/components/RecoveryPage.tsx`)
|
|
|
|
1. Implement the UI flow for the two recovery scenarios:
|
|
- **Scenario A (Lost Key):** Fetch Device Share (IndexedDB + WebAuthn PRF) +
|
|
Server Share (via PIN).
|
|
- **Scenario B (Lost Device):** Prompt for Cold Voucher (12-word BIP-39) +
|
|
Server Share (via PIN).
|
|
2. Instantiate the Wasm SSS module.
|
|
3. Pass the two gathered shares to the Wasm module to reconstruct the master
|
|
secret.
|
|
4. Import the reconstructed master secret directly into WebCrypto as an
|
|
`extractable: false` `CryptoKey`.
|
|
5. **Memory Purge:** Immediately execute `Uint8Array.fill(0)` on the share
|
|
inputs, intermediate buffers, and the raw reconstructed byte array.
|
|
6. Use the WebCrypto key to derive the ephemeral recovery token and sign the
|
|
challenge for the new passkey registration.
|
|
|
|
### Phase 3: Server-Side Share Gating (`server/main.ts`, `server/recovery.ts`)
|
|
|
|
1. Implement backend storage for the Hot Server Share within the
|
|
`recovery_shares` table (or similar schema extension).
|
|
2. Update `/api/recovery/challenge` to validate the recovery PIN and release the
|
|
Hot Server Share only upon success, enforcing strict rate-limiting.
|
|
3. Update `/api/recovery/verify` to validate the ephemeral token signature
|
|
derived from the reconstructed master secret.
|
|
4. Complete the recovery cycle by binding the new WebAuthn passkey, revoking the
|
|
old credentials, and generating a new 2-of-3 share matrix for the new
|
|
passkey.
|