25 KiB
Setup
Last updated: 2026-04-25
This is a living setup/status file for the local ChromeCard workspace at /home/user/chromecard.
Update this file whenever environment status or verified behavior changes.
Repository Policy
- Treat
/home/user/chromecard/CR_SDK_CK-mainas read-only in this workflow. - Do not add or modify helper/test scripts inside
CR_SDK_CK-main. - Keep host-side helper scripts at workspace root (
/home/user/chromecard).
Documentation Maintenance
- Canonical living status docs for this workspace are:
/home/user/chromecard/Setup.md/home/user/chromecard/Workplan.md
- After each meaningful execution step, update at least:
Setup.mdfor observed environment/runtime stateWorkplan.mdfor phase progress and next blocking action
- Keep helper script paths consistent in docs:
/home/user/chromecard/fido2_probe.py/home/user/chromecard/webauthn_local_demo.py
- Treat
CR_SDK_CK-main/README_HOST.mdas historical reference unless its script paths are aligned with this workspace policy.
Scope
- Experimental ChromeCard connected over USB.
- Firmware source tree:
/home/user/chromecard/CR_SDK_CK-main. - Host-side FIDO2 demo tools:
/home/user/chromecard/fido2_probe.py/home/user/chromecard/webauthn_local_demo.py
- Target runtime platform: Qubes OS with 3 AppVMs:
k_client(browser + enrollment process)k_proxy(card-connected proxy/auth client)k_server(protected resource/backend)
Planned Transport Evolution
- Current phase assumption: card is connected directly to
k_proxy(USB). - Future target: card is connected to a phone, and
k_proxyperforms validation through a wireless link to that phone. - Design implication: keep authenticator transport behind an abstraction in
k_proxyso USB-direct and phone-wireless backends can be swapped without changing client/server API contracts.
Target Qubes Topology
- Base template for all AppVMs:
debian-13-xfce. - Allowed network paths:
k_client->k_proxyover TLSk_proxy->k_serverover TLS- Response traffic returns on those established connections.
- Disallowed direct path:
k_client->k_server(direct access should be blocked).
Functional roles:
k_client:- Browser-only traffic client.
- Runs a user enrollment process.
k_proxy:- Current: connected to the ChromeCard over USB.
- Future: connects wirelessly to phone-attached card for validation.
- Accepts TLS requests from
k_client. - Uses card-backed FIDO2/WebAuthn operations to authenticate user/session.
- Calls
k_serverover TLS after successful authorization. - Returns proxied data and session information to
k_client.
k_server:- Hosts resource(s) requiring login via the proxy-mediated flow.
- Provides a dummy protected resource for early integration testing (monotonic increasing number/counter).
- May hold user/session state logic needed for authorization decisions.
UI baseline for each AppVM (start-menu visible apps):
- Firefox
- XFCE Terminal
- File Manager
Target Request Flow
k_clientsends HTTPS request tok_proxy.k_proxyvalidates/authenticates user via card-backed flow.- If allowed,
k_proxyopens HTTPS request tok_serverresource. k_serverresponds tok_proxy.k_proxyreturns response payload tok_clientplus session state.- Subsequent requests reuse session state so card auth is not required every request.
Implementation note:
k_proxydoes not need a full web server stack; a minimal TLS API service is sufficient.- Session state should be integrity-protected (signed/encrypted token or server-side session ID) with TTL and revocation behavior defined.
k_proxyandk_servermust be safe under concurrent access (thread-safe state handling).
Minimum Service Behavior (Current Target)
k_server:- Expose protected endpoint returning an increasing integer value (dummy resource).
- Increment behavior must remain correct under concurrent requests.
- Optionally expose/maintain user/session validation logic.
k_proxy:- Accept concurrent HTTPS requests from one or more
k_clientinstances. - Perform card-backed auth when no valid session is present.
- Cache and validate session state so repeated requests avoid card access until expiry.
- Forward authorized requests to
k_serverand return upstream data plus session info.
- Accept concurrent HTTPS requests from one or more
Thread-safety expectation:
- Shared mutable state (counter, session store, user state) must be protected against races.
- Parallel requests must not corrupt session records or return duplicate/skipped counter values caused by unsafe updates.
Test Topology Requirement
- Support concurrency testing from multiple simultaneous clients:
- multiple browser tabs/processes in one
k_client, and/or - multiple
k_clientAppVM instances if available.
- multiple browser tabs/processes in one
- Validate both correctness and stability under load:
- session reuse works as intended
- unauthorized access stays blocked
- protected counter/resource remains consistent.
Current Status Snapshot (2026-04-24)
- AppVM OS version is confirmed: Debian
13.4(k_server, and same onk_client/k_proxy). - Python in AppVMs is available:
Python 3.13.5. python3 /home/user/chromecard/fido2_probe.py --listink_proxynow detects ChromeCard on/dev/hidraw0(vid:pid=4617:5).- HID raw device nodes are now visible in
k_proxy:/dev/hidraw0->crw-rw----+/dev/hidraw1->crw-------
python3 /home/user/chromecard/fido2_probe.py --jsonsucceeds and returns CTAP2getInfo:- versions:
["FIDO_2_0"] - aaguid:
1234567890abcdef0123456789abcdef - options:
rk=false,up=true,uv=true - max_msg_size:
1024
- versions:
- Local WebAuthn demo (
http://localhost:8765ink_proxy) succeeded:- register:
ok=true,username=alice,credential_count=1 - login/auth:
ok=true,username=alice,authenticated=true
- register:
- Phase 5 prototype services are now available:
/home/user/chromecard/k_proxy_app.py/home/user/chromecard/k_server_app.py/home/user/chromecard/PHASE5_RUNBOOK.md
- Remote VM access is now available via SSH/SCP aliases:
- command execution:
ssh <host> <cmd> - file copy to VM home:
scp <file> <host>:~ - validated hosts:
k_client,k_proxy,k_server
- command execution:
westis not currently installed/in PATH:west not found.- The checked-out
CR_SDK_CK-maintree appears incomplete for documented sysbuild role layout:- missing:
mvp,setup,components,samples
- missing:
CR_SDK_CK-main/scripts/build_flash_mvp.shexists, but it expects the above role directories.- Python helper scripts were intentionally moved out of
CR_SDK_CK-main/scriptsand are now maintained at workspace root. - Qubes AppVM baseline is now up:
k_client,k_proxy,k_servercan start and have terminals running.
Implication:
- Live FIDO2 connectivity from
k_proxyto ChromeCard is confirmed over USB HID/CTAPHID. - Local browser WebAuthn register/login flow is confirmed working in
k_proxy. - We cannot currently run the documented firmware build/flash flow.
Session note (2026-04-24):
- Markdown tracking was reviewed and normalized around
Setup.md+Workplan.mdas the active, continuously updated execution record. - AppVM template decision recorded: use
debian-13-xfcefork_client,k_proxy, andk_server. - VM start attempt failed with Xen toolstack error:
libxenlight have failed to create new domain 'k_client'. - VM start blocker was resolved by reducing VM memory to
400MiB; all three AppVMs now start. - Runtime check from VMs: Debian
13.4and Python3.13.5;k_proxystill showsno hidraw devices. - After USB assignment to
k_proxy,/dev/hidraw0and/dev/hidraw1appeared. - CTAP probe re-run succeeded with detected ChromeCard device and valid CTAP2
getInforesponse. - Local WebAuthn demo completed successfully for user
alice(register + login). - Phase 5 starter implementation added with session TTL, logout/invalidation, and proxy->server protected counter forwarding.
Session note (2026-04-24, doc maintenance):
- Top-level Markdown files were re-scanned:
PHASE5_RUNBOOK.md,Setup.md,Workplan.md. PHASE5_RUNBOOK.mdremains consistent with the current Phase 5 prototype paths and flow.- No plan/setup drift was found requiring behavioral changes; docs remain aligned.
- SSH-based VM operation was validated for
k_client,k_proxy,k_server(Debian13.4confirmed remotely). - SCP file transfer to
k_proxyhome directory was validated with read-back.
Session note (2026-04-24, remote flow diagnostics):
- VM script staging gap found:
/home/user/chromecard/k_proxy_app.py,k_server_app.py, and helper files were missing on AppVMs and were copied viascp. - Services were started in VMs and verified locally:
k_proxylocal health OK on127.0.0.1:8770and127.0.0.1:8771k_serverlocal health OK on127.0.0.1:8780
- Verified VM IPs during this run:
k_proxy:10.137.0.12k_server:10.137.0.13k_client:10.137.0.16
- Current chain failure is network pathing/firewall:
k_client -> k_proxy(10.137.0.12:8771) times out.k_proxy -> k_server(10.137.0.13:8780) times out.- Proxy returns upstream error payload:
server unavailable: timed out.
Session note (2026-04-24, markdown re-scan):
- Re-read top-level workspace Markdown files:
Setup.md,Workplan.md,PHASE5_RUNBOOK.md. - Re-skimmed source-tree reference docs in
CR_SDK_CK-main, includingBUILD.md,README.md,README_HOST.md,RELEASE.md, anddistribute_bundle.md. - Current workspace docs remain aligned with the verified execution record.
- Source-tree doc drift remains unchanged:
README_HOST.mdstill points to./scripts/fido2_probe.pyand./scripts/webauthn_local_demo.py.- Active workspace policy continues to treat those paths as historical; maintained helper paths remain
/home/user/chromecard/fido2_probe.pyand/home/user/chromecard/webauthn_local_demo.py.
- Source-tree build docs continue to describe a full SDK layout with
mvp,setup,components, andsamples, which is still not present in the current local checkout snapshot.
Session note (2026-04-24, policy retry):
- Markdown re-scan was retried after local policy changes.
- Re-running the workspace doc scan with a non-login shell completed cleanly, without the earlier SSH/socat startup noise in command output.
Session note (2026-04-24, chain probe retry):
- Re-probed the Qubes access path for
k_client -> k_proxy -> k_server. - Local forwarded SSH listener ports still exist on the host:
0.0.0.0:2222->qrexec-client-vm 'k_client' qubes.ConnectTCP+220.0.0.0:2223->qrexec-client-vm 'k_proxy' qubes.ConnectTCP+220.0.0.0:2224->qrexec-client-vm 'k_server' qubes.ConnectTCP+22
- These forwarded SSH ports currently fail immediately:
ssh k_client/ssh k_proxy/ssh k_serverclose immediately on localhost forwarded ports.- Direct
qrexec-client-vm <target> qubes.ConnectTCP+22returnsRequest refused.
- Chain ports are currently blocked at the same qrexec layer:
qrexec-client-vm k_proxy qubes.ConnectTCP+8770->Request refusedqrexec-client-vm k_server qubes.ConnectTCP+8780->Request refused
- This means the current blocker is active qrexec policy/service refusal for
qubes.ConnectTCP, not the Python service code ink_proxy_app.pyork_server_app.py. - Separate SSH config issue remains on the host:
/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.confis still ownedroot:rootbut mode777, which causes OpenSSH to reject it as insecure on the normal login-shell path.
Session note (2026-04-25, post-restart probe):
- Correct client-facing proxy port is
8771for the current split-VM chain checks. - SSH to
k_proxyis working again. k_proxycard visibility is restored after VM restart and card reconnect:/dev/hidraw0and/dev/hidraw1are present ink_proxy
- Current service state after restart:
k_proxyhas no listener on127.0.0.1:8771k_serverhas no listener on127.0.0.1:8780
- Current qrexec chain state after restart:
qrexec-client-vm k_proxy qubes.ConnectTCP+8771->Request refusedqrexec-client-vm k_server qubes.ConnectTCP+8780->Request refused
- Practical meaning:
- SSH and card attachment recovered
- phase-5 app services are not currently running in the VMs
- qrexec forwarding for the chain ports is still being refused
Session note (2026-04-25, service restart):
k_server_app.pywas restarted successfully ink_server:- PID
1320 - listening on
127.0.0.1:8780 /healthreturns{"ok": true, "service": "k_server", ...}
- PID
k_proxy_app.pywas restarted successfully ink_proxy:- PID
2774 - listening on
127.0.0.1:8771 /healthreturns{"ok": true, "service": "k_proxy", "active_sessions": 0, ...}
- PID
- Despite local service recovery, qrexec forwarding is still denied:
qrexec-client-vm k_proxy qubes.ConnectTCP+8771->Request refusedqrexec-client-vm k_server qubes.ConnectTCP+8780->Request refused
Session note (2026-04-25, markdown refresh):
- Re-read the active workspace markdown files:
Setup.mdWorkplan.mdPHASE5_RUNBOOK.md
- Corrected the Phase 5 runbook to distinguish the old same-VM quickstart from the current split-VM chain usage.
- Current documented client-facing proxy port for split-VM tests is
8771. - Current documented blocker remains unchanged:
- local service health inside
k_proxyandk_serveris good - inter-VM forwarding via
qubes.ConnectTCPis still refused
- local service health inside
Session note (2026-04-25, Phase 2 HTTPS bring-up):
- Added direct TLS support to:
/home/user/chromecard/k_proxy_app.py/home/user/chromecard/k_server_app.py
- Added local certificate generator:
/home/user/chromecard/generate_phase2_certs.py
- Generated local CA and service certs at:
/home/user/chromecard/tls/phase2/ca.crt/home/user/chromecard/tls/phase2/k_proxy.crt/home/user/chromecard/tls/phase2/k_server.crt
- Certificate generation was corrected to include subject key identifier and authority key identifier so Python TLS verification succeeds.
- Current validated HTTPS shape is Qubes-localhost forwarding, not raw VM-IP routing:
- in
k_client:qvm-connect-tcp 9771:k_proxy:8771 - in
k_proxy:qvm-connect-tcp 9780:k_server:8780 k_proxylistens onhttps://127.0.0.1:8771k_serverlistens onhttps://127.0.0.1:8780k_proxyupstream ishttps://127.0.0.1:9780
- in
- Verified HTTPS checks:
k_client -> k_proxy/healthover TLS succeeds with--cacert /home/user/chromecard/tls/phase2/ca.crtk_proxy -> k_server/healthand/resource/counterover TLS succeed through the9780forwarder- end-to-end
k_client -> k_proxy -> k_serverlogin + session reuse succeeded over HTTPS
- End-to-end verified results:
- login returned
ok=trueforalice - first protected counter call returned value
1 - second protected counter call returned value
2 - session status remained valid after reuse
- login returned
Session note (2026-04-25, Phase 2.5 ownership and concurrency):
- Current prototype state ownership is now explicit:
k_proxyis authoritative for session statek_serveris authoritative for protected resource statek_clientis not authoritative for either session validity or counter/resource state
- Current session model in
k_proxy:- server-side in-memory session store only
- opaque bearer token generated by
secrets.token_urlsafe(32) - per-session fields are
usernameandexpires_at - expiry is enforced in
k_proxy;k_serverdoes not validate client sessions directly
- Current resource model in
k_server:- in-memory monotonic counter guarded by a lock
- access allowed only when request arrives from
k_proxywith the expectedX-Proxy-Token
- Current concurrency model in code:
- both services use
ThreadingHTTPServer k_proxyprotects session-map mutations and garbage collection with a single lockk_serverprotects counter increments with a single lock- TLS verification and upstream fetches happen outside the session lock in
k_proxy
- both services use
- Current runtime assumptions and limits:
- Qubes localhost forwarders are treated as transport plumbing, not as state authorities
- if
k_proxyrestarts, in-memory sessions are lost - if
k_serverrestarts, the in-memory counter resets - the current shared
X-Proxy-Tokenis a prototype trust mechanism, not a final authorization design
- Practical meaning:
- race-free behavior is currently defined for session CRUD and counter increments inside one process per VM
- persistence, distributed session authority, and multi-proxy/multi-server coordination are not implemented yet
Session note (2026-04-25, Phase 6 client portal prototype):
- Added browser-facing client process:
/home/user/chromecard/k_client_portal.py
- Current Phase 6 prototype shape:
- portal runs in
k_clientonhttp://127.0.0.1:8766 - portal keeps local enrolled username state in
k_client - portal calls
k_proxyover the validated TLS forwardhttps://127.0.0.1:9771
- portal runs in
- Current local enrollment model:
- enrollment is a client-local username selection stored by the portal
- no dedicated server-side enrollment API exists yet
- Verified portal API flow in
k_client:GET /healthreturnsok=truePOST /api/enrollwithalicesucceedsPOST /api/loginsucceeds and returns a proxy session tokenPOST /api/statussucceedsPOST /api/resource/countersucceeds twice with upstream values3and4POST /api/logoutsucceeds
- Current implication:
k_clientnow has a concrete client-side process instead of only runbook curls- browser-facing flow is now available through the local portal
- next hardening step is to replace client-local enrollment with the intended enrollment contract and decide whether browser traffic should eventually talk to
k_proxydirectly or continue through a local client portal
Session note (2026-04-25, Phase 6 enrollment contract):
- Added proxy-side enrollment API and storage:
POST /enroll/registerGET /enroll/status?username=<name>- persisted prototype store at
/home/user/chromecard/k_proxy_enrollments.jsonink_proxy
- Current enrollment authority is now
k_proxy, not thek_clientportal. - Current portal behavior:
- portal enrollment calls
k_proxyover TLS - portal keeps only a preferred local username for convenience
- portal login now depends on proxy-side enrollment existing
- portal enrollment calls
- Verified behavior:
- direct proxy login for unenrolled
bobreturns{"ok": false, "error": "user not enrolled", ...} - portal enrollment of
alicesucceeds and persists in proxy-side enrollment storage - proxy enrollment status for
alicereturnsok=true - portal login and protected counter access still succeed after enrollment
- direct proxy login for unenrolled
- Practical meaning:
- Phase 6 now has a real
k_client -> k_proxyenrollment request path - the remaining gap is not basic routing; it is deciding the final enrollment semantics and whether the browser should stay behind a local portal or talk to
k_proxydirectly
- Phase 6 now has a real
Session note (2026-04-25, in-VM forwarding test):
- Tested the intended in-VM forwarding path with
qvm-connect-tcpinstead of host-sideqrexec-client-vm. - Forwarders start and bind locally:
- in
k_client:qvm-connect-tcp 8771:k_proxy:8771bindslocalhost:8771 - in
k_proxy:qvm-connect-tcp 8780:k_server:8780bindslocalhost:8780
- in
- But the actual client->proxy connection is still refused when used:
k_clientforward log showsRequest refusedsocatreports child exit status126andConnection reset by peer
- Local login on
k_proxyreaches the app but fails on the auth dependency:POST /session/logintohttp://127.0.0.1:8771returns401- details:
Missing dependency: python-fido2 ... No module named 'fido2'
k_serverwas not reached during this login test; currentk_server.logonly shows/health.
Session note (2026-04-25, after python3-fido2 install):
k_proxywas restarted afterpython3-fido2installation and now listens again on127.0.0.1:8771.- The previous Python import blocker is resolved; local login now reaches the CTAP probe path.
- Current local login result on
k_proxy:{"ok": false, "error": "card auth failed", "details": "No CTAP HID devices found."}
- Current forwarded login result from
k_clientis still not completing:curl http://127.0.0.1:8771/session/login->Empty reply from serverqvm_connect_8771.logstill shows repeatedRequest refusedand child exit status126
- Practical meaning:
- Python dependency issue in
k_proxyis fixed - card access inside
k_proxyis currently missing again at CTAP/HID level k_client -> k_proxyqrexec forwarding is still effectively denied/refused
- Python dependency issue in
Session note (2026-04-25, card reattached):
- Card visibility in
k_proxyis restored again:/dev/hidraw0and/dev/hidraw1presentfido2_probe.py --listdetects ChromeCard on/dev/hidraw0
- Local login on
k_proxynow succeeds again:POST /session/loginon127.0.0.1:8771returns200- session creation for user
alicesucceeded
- Remaining failure is isolated to the client-facing qrexec path:
k_client->localhost:8771throughqvm-connect-tcpstill returnsEmpty reply from serverqvm_connect_8771.logstill showsRequest refused
Session note (2026-04-25, clean forward retest):
- Re-ran both forwards and exercised each hop immediately after local bind.
k_proxy -> k_server:qvm-connect-tcp 8780:k_server:8780bindslocalhost:8780ink_proxy- first real
POST /resource/counterthrough that forward returnsEmpty reply from server qvm_connect_8780.logthen recordsRequest refusedwith child exit status126
k_client -> k_proxy:qvm-connect-tcp 8771:k_proxy:8771bindslocalhost:8771ink_client- first real
POST /session/loginthrough that forward returnsEmpty reply from server qvm_connect_8771.logrecordsRequest refusedwith child exit status126
- Conclusion from this retest:
- both forwards fail in the same way
- local bind succeeds, but the actual qrexec
qubes.ConnectTCPrequest is refused when the first connection is attempted
Session note (2026-04-25, dom0 policy fix validated):
- After changing dom0 policy to use explicit destination VMs instead of
@defaultforqubes.ConnectTCP, both forwards now work. - Verified hop 1:
- in
k_proxy,POST http://127.0.0.1:8780/resource/counterwithX-Proxy-Token: dev-proxy-tokensucceeds - response included counter value
1
- in
- Verified hop 2:
- in
k_client,POST http://127.0.0.1:8771/session/loginsucceeds - session token is returned through the
k_client -> k_proxyforward
- in
- Verified full end-to-end flow from
k_client:- login succeeded and returned session token
POST /session/statussucceededPOST /resource/countersucceeded twice with upstream values2and3POST /session/logoutsucceeded- post-logout
POST /resource/countercorrectly returned401 invalid or expired session
- Current conclusion:
k_client -> k_proxy -> k_serverchain is operational- session reuse and logout behavior are working in the current prototype
Known FIDO2 Transport Boundary
- FIDO2 on this firmware is handled via USB HID (CTAPHID), not Wi-Fi/BLE/MQTT.
- Key code points in
CR_SDK_CK-main:mgr_fido2.c:mgr_fido2_init()registersfido2_ctaphid_handle_packet.ctaphid.c:fido2_ctaphid_handle_packet(...).cr_config.h: FIDO2 HID report descriptor definitions.
Host Bring-Up Steps (How To Get To A Working FIDO2 Check)
- Confirm USB enumeration and HID visibility.
- Replug card with a known data-capable cable.
- Check:
ls -l /dev/hidraw*
- If needed, grant Linux HID access for this device.
- Add rule at
/etc/udev/rules.d/70-chromecard-fido.rules:
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="0005", MODE="0660", TAG+="uaccess"
- Reload/apply rules and replug the device.
- Verify CTAP HID presence.
python3 /home/user/chromecard/fido2_probe.py --list- Then:
python3 /home/user/chromecard/fido2_probe.py --json
- Run local WebAuthn bring-up demo.
python3 /home/user/chromecard/webauthn_local_demo.py- Open
http://localhost:8765(uselocalhost, not127.0.0.1).
- Execute register/login test.
- Register a user.
- Login with the same user.
- Confirm no origin/challenge mismatch errors.
Build/Flash Prerequisites (How To Get To Firmware Build)
- Ensure full SDK checkout layout exists under
CR_SDK_CK-main:
mvpsetupcomponentssamples
- Ensure toolchain is available in shell:
west --versionnrfjprog --version
- Once layout/tooling are in place, run:
cd /home/user/chromecard/CR_SDK_CK-main./scripts/build_flash_mvp.sh
Open Gaps To Resolve
- Whether a full
CR_SDK_CK-maincheckout (with role directories) is available locally. - Whether server-side code should be pulled now for broader CIP/WebAuthn integration testing.
- Exact Qubes firewall and service binding rules to enforce the
k_client -> k_proxy -> k_serverchain. - Exact enrollment process interface running in
k_clientand how it reachesk_proxy. - Upgrade Phase 5 auth gate from card-presence probe to full WebAuthn assertion verification for session creation.
- Precise ownership split of session/user state between
k_proxyandk_server. - Concrete concurrency limits and acceptance criteria (requests/sec, parallel clients, latency/error thresholds).