# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Repository policy - `CR_SDK_CK-main/` is the firmware SDK source tree. Treat it as **read-only**. Do not add or edit files there. - All host-side scripts live at the workspace root (`/home/user/chromecard`). - `Setup.md` and `Workplan.md` are the canonical living docs. After each meaningful execution step, update `Setup.md` for environment/runtime state and `Workplan.md` for phase progress and next blocking action. ## Running tests ```bash # Python unit tests (no VMs or card required, 122 tests) python3 -m unittest tests/test_k_proxy.py # Playwright browser regression (requires services running and forwarded portal) PORTAL_BASE_URL=http://127.0.0.1:18766 npm run test:k-client # Split-VM end-to-end regression helper (runs from host via SSH into k_client) ./phase5_chain_regression.sh ./phase5_chain_regression.sh --interactive-card --expect-auth-mode fido2_assertion ``` ## Probing the card (on k_proxy) ```bash python3 /home/user/chromecard/fido2_probe.py --list python3 /home/user/chromecard/fido2_probe.py --json python3 /home/user/chromecard/raw_ctap_probe.py info python3 /home/user/chromecard/raw_ctap_probe.py make-credential --rp-id localhost python3 /home/user/chromecard/webauthn_local_demo.py # opens http://localhost:8765 ``` ## Generating TLS certificates ```bash python3 generate_phase2_certs.py # Writes tls/phase2/ca.crt, k_proxy.crt/.key, k_server.crt/.key ``` ## Starting services (split-VM shape) **k_server VM:** ```bash python3 /home/user/chromecard/k_server_app.py \ --host 127.0.0.1 --port 8780 \ --proxy-token dev-proxy-token \ --tls-certfile /home/user/chromecard/tls/phase2/k_server.crt \ --tls-keyfile /home/user/chromecard/tls/phase2/k_server.key ``` **k_proxy VM:** ```bash qvm-connect-tcp 9780:k_server:8780 python3 /home/user/chromecard/k_proxy_app.py \ --host 127.0.0.1 --port 8771 --session-ttl 300 \ --server-base-url https://127.0.0.1:9780 \ --server-ca-file /home/user/chromecard/tls/phase2/ca.crt \ --proxy-token dev-proxy-token \ --tls-certfile /home/user/chromecard/tls/phase2/k_proxy.crt \ --tls-keyfile /home/user/chromecard/tls/phase2/k_proxy.key # Add --auth-mode fido2-direct for real CTAP2 (default is probe mode) ``` **k_client VM:** ```bash qvm-connect-tcp 9771:k_proxy:8771 python3 /home/user/chromecard/k_client_portal.py \ --proxy-base-url https://127.0.0.1:9771 # Browser demo page at http://127.0.0.1:8766 ``` Files are deployed to VMs via `scp :~` and run via `ssh `. ## Architecture ### Target production architecture (4-device system) Four physical devices: optional client computer, phone, chromecard, server. **Devices:** - **Client (optional):** Computer with browser configured to use the phone as HTTP/HTTPS proxy. No knowledge of the auth system. - **Phone:** Central hub. Runs two components, hosts registration page, connects to chromecard via USB or WiFi. - **Chromecard:** FIDO2 hardware security module. All crypto happens on-card; private keys never leave. Two fingerprint types: *user* (login) and *admin* (registration/deletion). - **Server:** Accepts TLS only. Runs WebAuthn service that validates FIDO2 tokens before granting access to protected resources. **Components on the phone:** - **Component 1 — Proxy + gating filter:** Listens on a local port. Binary decision per request: host is gated → forward to Component 2 (TLS); host is not gated → forward directly to internet on port 80 (no TLS). - **Component 2 — FIDO2 client + URL recognition:** Receives all requests from Component 1. Detects registration-URL → triggers admin registration flow; other gated URLs → triggers FIDO2 assertion flow (contacts card, gets token, forwards to server via TLS). - **Registration page:** Local web app on phone. Requires admin fingerprint on the card for enrollment/deletion. **Three flows:** - **Flow A (authenticated proxy):** Browser → Component 1 → Component 2 → Card (user fingerprint, generates FIDO2 token) → Server (WebAuthn validates token) → resource returned. - **Flow B (registration):** Browser → Component 1 → Component 2 (detects registration URL) → Card (admin fingerprint) → user created/deleted on card. - **Flow C (unauthenticated):** Host not gated → Component 1 forwards directly to internet via port 80 (unencrypted, bypasses Component 2 and card). By design for normal web traffic. **Open architectural decisions:** - PIN on card (in addition to biometrics) — not yet decided - User database location: on-card only vs. external — not yet decided - Network-level access control on registration page — not yet decided ### Development topology (Qubes 3-VM) **Qubes 3-VM topology:** `k_client` → `k_proxy` → `k_server`, each a Debian 13 AppVM. Inter-VM transport uses `qvm-connect-tcp` localhost forwarding (not raw VM-IP routing). Validated chain: - `k_client localhost:9771` → `k_proxy:8771` - `k_proxy localhost:9780` → `k_server:8780` **k_proxy_app.py** — session gateway and FIDO2 auth bridge. Two auth modes: - `probe` (default): validates card presence by subprocess-calling `fido2_probe.py --json` - `fido2-direct`: performs real CTAP2 `makeCredential`/`getAssertion` against the physical card via `python-fido2`; auto-detects the FIDO hidraw device `ProxyState` holds all server-side state: in-memory session store (guarded by one lock), enrollment DB (JSON file), and an `UpstreamPool` of persistent TLS connections to k_server. Sessions are lost on restart. **k_server_app.py** — protected resource backend. Exposes a monotonic counter behind `X-Proxy-Token` auth. Counter state is in-memory only; resets on restart. Lock guards counter increments. **k_client_portal.py** — thin browser-facing portal in k_client. Delegates all auth and resource calls to k_proxy. Holds only a local preferred username; enrollment and session state live in k_proxy. **FIDO2 transport:** Card communicates over USB HID (CTAPHID) on `/dev/hidraw0` (FIDO interface, usage page `0xF1D0`). `/dev/hidraw1` is a separate vendor HID interface. If the card re-enumerates, k_proxy auto-detects the correct node. If CTAPHID stops responding, a full USB power cycle is the recovery path. **CardEmulator** (`tests/card_emulator.py`) — software emulator of the card for unit tests. Implements `make_credential`/`get_assertion` with real P-256 crypto; `user_confirms=False` simulates card rejection. Wire it into tests by patching `_with_direct_ctap2` and `_drop_direct_device` on `ProxyState`. See the module docstring for the exact patch pattern. **Key enrollment endpoints on k_proxy:** `POST /enroll/register`, `GET /enroll/status`, `POST /enroll/update`, `POST /enroll/delete`, `GET /enroll/list`. Usernames are normalized to lowercase, 3–32 chars `[a-z0-9._-]`. **Key session endpoints on k_proxy:** `POST /session/login`, `POST /session/status`, `POST /session/logout`, `POST /resource/counter`. ### k_phone Flutter app (Phase 9 — replaces k_proxy) **`k_phone/lib/filter_proxy.dart`** — Component 1. Raw-socket HTTP proxy with gating filter. Per-connection: gated host → CONNECT or plain-HTTP relay through Component 2; non-gated → direct to target. Gated hosts loaded from `gated_hosts.txt` in app documents dir; defaults to `httpbin.org`. Use `setGatedEntries()` in tests to inject entries directly. **`k_phone/lib/proxy_service.dart`** — Component 2. Background-service HTTP server (port 8771). Handles enrollment, session (login/status/logout), and resource/counter endpoints. **CONNECT handler not yet implemented** — gated HTTPS tunnels currently return 405. **`k_phone/lib/session_manager.dart`** — in-memory session store. `hasAnyActiveSession()` is the gate check for proxied traffic (personal-device model: one live session authorises all gated requests). **`k_phone/lib/fido2_ops.dart`** — `makeCredential`, `getAssertion`, ECDSA-P256 assertion verification against the card via CTAPHID. **`k_phone/lib/ctaphid_channel.dart`** — CTAPHID framing over USB (Kotlin platform channel) or emulator bridge TCP socket (`card_emulator_bridge.py` on `10.0.2.2:8772`). **`k_phone/lib/enrollment_db.dart`** — enrollment model + JSON persistence via path_provider. **Tests:** `flutter test test/filter_proxy_test.dart` and `flutter test test/enrollment_test.dart` (no device needed). ## Known limits and blockers - Concurrency ceiling on the browser-facing forwarded path is ~10 in-flight requests; higher fan-out triggers Qubes vchan failures (`xs_transaction_start: No space left on device`). - If CTAPHID `INIT` packets get no reply after a card reattach, a full USB power cycle recovers the transport. - `CR_SDK_CK-main` is missing role directories (`mvp`, `setup`, `components`, `samples`) required for the firmware build/flash flow (`./scripts/build_flash_mvp.sh`). `west` and `nrfjprog` must also be installed. - Phase 7 (firmware build): blocked on Chrome Roads (card vendor). - Phase 9 (phone): Component 2 CONNECT handler not yet implemented — HTTPS to gated hosts will fail until `_handleConnect` is added to `proxy_service.dart`.