9.1 KiB
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.mdandWorkplan.mdare the canonical living docs. After each meaningful execution step, updateSetup.mdfor environment/runtime state andWorkplan.mdfor phase progress and next blocking action.
Running tests
# 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)
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
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:
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:
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:
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 <file> <host>:~ and run via ssh <host> <cmd>.
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:8771k_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-callingfido2_probe.py --jsonfido2-direct: performs real CTAP2makeCredential/getAssertionagainst the physical card viapython-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), resource/counter endpoints, and CONNECT tunnels. For CONNECT: checks hasAnyActiveSession(), connects to the actual upstream host:port, detaches the socket, and pipes bytes bidirectionally.
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
INITpackets get no reply after a card reattach, a full USB power cycle recovers the transport. CR_SDK_CK-mainis missing role directories (mvp,setup,components,samples) required for the firmware build/flash flow (./scripts/build_flash_mvp.sh).westandnrfjprogmust also be installed.- Phase 7 (firmware build): blocked on Chrome Roads (card vendor).
- Phase 9 (phone): Component 2 CONNECT handler implemented. HTTPS tunnels to gated hosts are gated by
hasAnyActiveSession(); the tunnel connects to the actual upstream target (not k_server).