Compare commits

..

No commits in common. "b34475b4fb1dd7a012bf7fd872fa4bb097399939" and "4e7d6c8add7cef8d301d06bd8fc189e9ba696eee" have entirely different histories.

3 changed files with 0 additions and 205 deletions

View File

@ -1,117 +0,0 @@
# TASK METADATA
- **Target Files:** `server/audit.ts`, `server/audit_merkle.ts`,
`server/valkey.ts`, `server/db.ts`, and database schema/migration files.
- **Core Objective:** Implement an append-only RFC 6962 Merkle Tree audit ledger
in PostgreSQL with an in-process Deno micro-batcher for STH computation and
Valkey pub/sub broadcast.
- **Dependencies:** Deno 2.x WebCrypto (Ed25519), Valkey connection, PostgreSQL
schema updates, and SPIFFE workload identity key material.
- **Additional Important Notes:** Must use Deno's native WebCrypto (no external
crypto npm packages). Valkey session keys should use raw strings without
'session:' prefix (not fully applicable here, but good standard). Ensure tests
mock Valkey, PostgreSQL, and SPIFFE cleanly.
---
## Architectural Considerations & Risks
Before implementing the RFC 6962 Merkle Tree Ledger, we must evaluate the
current architecture and explicitly identify risks, regressions, and necessary
constraints.
**Risks & Tradeoffs:**
1. **In-Process Micro-Batcher Memory & State:** The micro-batcher runs
in-process inside Deno using asynchronous intervals (e.g., `setInterval` or
explicit flush). If the Deno process crashes abruptly, unflushed audit logs
in memory could be lost before the batch is committed to PostgreSQL and the
Merkle tree head is updated.
- _Mitigation:_ Ensure `auditLog` still performs an immediate async insert of
the _record_ to the database (as it currently does via `server/audit.ts`),
but the `leaf_hash` and tree updates are computed dynamically or buffered
for the batched STH computation. The database remains the source of truth;
the batcher just finalizes the cryptographical proof.
2. **Database Schema Locking:** Adding a `leaf_hash` column to the
`audit_records` table and creating the `audit_sths` table might cause locking
if the table is large.
- _Mitigation:_ The `leaf_hash` can be computed prior to insert.
3. **SPIFFE Identity Dependency:** STH generation requires the Deno process to
sign using the SPIFFE workload identity/server signing key via WebCrypto. If
SPIFFE/FFI fails or the keys are rotating during a batch, the STH generation
might fail.
- _Mitigation:_ Graceful error handling and retry mechanism for STH signing.
4. **No Third-Party Dependencies:** Adhering to the memory context, we must
exclusively use Deno 2.x native `crypto.subtle` API for SHA-256 and
Ed25519/ECDSA, rather than any Node.js polyfills or npm packages.
**Alternatives Evaluated:**
- _External Worker:_ Using a dedicated worker (e.g., a separate service or CRON)
for computing the Merkle root. _Decision:_ We rejected this to keep the
application self-contained and zero-dependency, per user guidance.
- _Synchronous Tree Updates:_ Recomputing the tree on every single write.
_Decision:_ Rejected. This would severely block relational write throughput. A
micro-batcher (e.g., 30-60s or size threshold) is the optimal path for
decoupling heavy crypto logic from the request hot-path.
## Proposed Implementation
The execution of this feature will be broken down into the following structured
phases:
### Phase 1: Database Schema Expansion
1. Update PostgreSQL schema to alter `audit_records` and add a `leaf_hash TEXT`
column.
2. Create the new `audit_sths` table:
```sql
CREATE TABLE audit_sths (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tree_size BIGINT NOT NULL,
root_hash TEXT NOT NULL,
signature TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
### Phase 2: RFC 6962 Cryptographic Primitives (`server/audit_merkle.ts`)
1. Create `server/audit_merkle.ts`.
2. Implement RFC 6962 leaf hashing: `SHA-256(0x00 || entry_bytes)`.
3. Implement internal tree hashing:
`SHA-256(0x01 || left_child || right_child)`.
4. Implement the logic to construct the Merkle Tree from an array of leaves and
generate an inclusion proof (`verifyInclusionProof`).
5. Ensure all crypto utilizes `crypto.subtle.digest('SHA-256', ...)`.
### Phase 3: The Micro-Batcher & STH Signing (`server/audit.ts`)
1. Refactor `auditLog` to compute the `leaf_hash` synchronously before the async
database insert.
2. Implement a background micro-batcher in `server/audit.ts` (or
`server/audit_merkle.ts` if better decoupled) using `setInterval` (e.g.,
every 1000ms, or on explicit flush).
3. The batcher will:
- Query new `leaf_hash` entries from the database since the last STH
`tree_size`.
- Compute the new Merkle Root.
- Access the SPIFFE server signing key.
- Sign the STH (tree size, root hash) using `crypto.subtle.sign` (Ed25519).
- Insert the new STH into the `audit_sths` table.
### Phase 4: Valkey STH Broadcast (`server/valkey.ts`)
1. Once the STH is successfully saved to PostgreSQL, update the cache.
2. Use `valkey.set('auth:audit:latest_sth', json_payload)`.
3. Broadcast the update to independent witness nodes via pub/sub:
`valkey.publish('auth:audit:sth', json_payload)`.
### Phase 5: Testing & Quality Gates
1. Write hermetic tests in `server/audit_merkle.test.ts`.
2. Use `@std/testing/mock` to mock `valkey`, `sqlWrapper`, and SPIFFE keys.
3. Validate leaf hashing correctness against RFC 6962 test vectors.
4. Verify the Valkey `PUBLISH` payload formatting.
5. Execute standard quality gates: `deno fmt`, `deno task lint`,
`deno task check`, and `deno task test`.

View File

@ -1,45 +0,0 @@
# TASK METADATA
- **Target Files:** `server/main.ts`, `server/db.ts`, `ui/public/auth-client.js`, `ui/components/RegisterPage.tsx`, `ui/components/LoginPage.tsx`
- **Core Objective:** Implement WebAuthn PRF extension support for progressive feature detection and Key Encryption Key (KEK) derivation during registration and login, with graceful fallback.
- **Dependencies:** WebCrypto API natively in Deno/browser, SimpleWebAuthn v13 for passing PRF extension options.
- **Additional Important Notes:** This task establishes the PRF derivation pipeline. SSS multi-share reconstruction will integrate in a future story (3.3). If PRF is unsupported, registration/login must proceed normally without breaking standard WebAuthn flows.
---
## Architectural Considerations & Risks
- **Risks:**
- **Authenticator Compatibility:** Not all authenticators support the WebAuthn PRF extension. A hard failure when PRF is missing would lock users out. The progressive fallback design is critical to prevent regressions in standard authentication.
- **Extension Types & SDK Mapping:** Passing the exact extension payloads for PRF (`eval.first`, `eval.second`) in `generateRegistrationOptions` and `generateAuthenticationOptions` might require careful type mapping if `SimpleWebAuthn` types are strict.
- **Database Migrations:** Modifying the `passkeys` table to include `prf_enabled` and `prf_salt` must maintain compatibility with existing passkey rows (which will default to false/null).
- **Alternatives:**
- Traditional server-side wrapping (HSM/KMS) or user passwords could be used for key derivation. However, the WebAuthn PRF extension natively binds the encryption key material to the hardware authenticator itself, preserving Auth-Yes's passwordless UX and zero-trust properties without transmitting raw secrets.
## Proposed Implementation
### 1. Database Schema Updates (`server/db.ts`)
- Modify the `passkeys` table schema to include a `prf_enabled BOOLEAN DEFAULT FALSE` column.
- Add a `prf_salt` column (binary or hex string) to store the 32-byte cryptographic salt generated during registration.
### 2. Registration Flow (Server & Client)
- **Server (`server/main.ts`)**: In the `/api/register/challenge` endpoint, ensure the `prf: {}` extension is requested via `generateRegistrationOptions`.
- **Client (`ui/public/auth-client.js`)**: Execute `navigator.credentials.create()` through the client SDK. Extract `getClientExtensionResults()?.prf`.
- **Server (`server/main.ts`)**: In the `/api/register/verify` endpoint, inspect the extension results to check if PRF is enabled (`prf.enabled === true`). If supported, generate a 32-byte secure random salt (`prf_salt`). Store `prf_enabled: true` and the `prf_salt` alongside the new passkey record.
### 3. Login Flow (Server & Client)
- **Server (`server/main.ts`)**: In the `/api/login/challenge` endpoint, retrieve the user's `prf_salt` if their passkey has `prf_enabled`. Include the `prf: { eval: { first: <prf_salt> } }` extension payload in `generateAuthenticationOptions`.
- **Client (`ui/public/auth-client.js`)**:
- Execute `navigator.credentials.get()` with the provided PRF evaluation salt.
- Check `getClientExtensionResults()?.prf?.results?.first` for the PRF output.
- **Client-Side KEK Derivation**:
- If PRF output exists, use it as Input Keying Material (IKM) for WebCrypto HKDF to derive a 256-bit AES-GCM Key Encryption Key (KEK).
- **HKDF Parameters**:
- Hash: `SHA-256` (RFC 5869)
- Salt: 32-byte cryptographic salt (stored with passkey record)
- Info: `new TextEncoder().encode("auth-yes:prf:device-share:v1")`
- **Progressive Fallback**:
- If `getClientExtensionResults()?.prf` is missing or fails, gracefully bypass the KEK derivation step and continue standard signature-only WebAuthn login.
### 4. UI Integration (`ui/components/RegisterPage.tsx`, `ui/components/LoginPage.tsx`)
- (Optional but recommended) Include minor, non-blocking UI indicators or debug logs to signify when advanced hardware encryption (PRF) is successfully negotiated, aiding in development and progressive feature adoption.

View File

@ -1,43 +0,0 @@
# 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.