k_card/CLAUDE.md

9.1 KiB
Raw Blame History

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

# 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_clientk_proxyk_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:9771k_proxy:8771
  • k_proxy localhost:9780k_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, 332 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.dartmakeCredential, 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 implemented. HTTPS tunnels to gated hosts are gated by hasAnyActiveSession(); the tunnel connects to the actual upstream target (not k_server).