**Verified / implemented (phase 02 final pass)** - All 6 tasks already in `complete/` → verification pass. Device measurement **found a real defect (README bug 11)**: the cert carried its SAN *value* under the OID written from the ASCII text `"2.5.29.17"` instead of DER content bytes `55 1D 11` (`openssl x509 -text` showed `1.10.46.53…`, hex `30 2f 06 09 32 2e 35…`), i.e. **no SAN** → hostname validation impossible while every host check stayed green (they walked the value, never the label). - Fixed in the pure module: `kSanOidTlv`/`sanOidDer()`, `sanExtensionFullDer()` (pinned to the 43 bytes OpenSSL writes for these names), `derCarriesSan()` (OID *and* value); `tls.cpp` uses them + 3 `static_assert`s; `cert` now warns and names the remedy. 33 new checks in `test_tls_pem.cpp`, incl. the pre-fix extension byte-for-byte (`derContains` yes / `derCarriesSan` no). - Corrected a false finding: the earlier "fingerprint rotated once" was an **OpenSSL `-ext` artifact** (`C8:76…` vs the real `61:54:19:30…`, same DER). Re-measured: serial `cert` == live handshake DER hash; page 51 626 B, `sha256 dd16e195…f04e9` == locally assembled page. **Tests / lint (100 % green)** - `bash .agents/validate.sh` → **GATE_EXIT=0**: `RESULT: all suites passed` in the plain *and* `SANITIZE=1` run, **4826 checks / 0 failures** each; `bridge`/`console`/`rollback` **SUCCESS**; `page bytes: 51626`; `README/AGENTS claims: OK`; `repo hygiene: OK (71 tracked files)`. - Footprint: bridge **RAM 30.4 % (99 616 B) / Flash 39.6 % (1 246 145 B)** vs baseline 26.7 %/87 592 & 36.1 %/1 135 293 → **+12 024 B RAM / +110 852 B flash**; TLS alone +6 160 B / +100 008 B (rollback image byte-identical); session heap 187 936 → ~145.5 k (1 session) → ~107.9 k (2). - `ruff check --select F,E9 tools/ tests/` → All checks passed; `bash -n` clean (gate, runner, probe); `-Wall -Wextra` warning-free. **Completion criteria**: 1 ✓ (gate green + RAM/Flash + session heap quoted); 2 ✓ as recorded (boot prints no fingerprint on the NVS-load path, `cert` prints on demand, identity survived reflashes, nothing about the key is printed anywhere — `nvs erase`→first-boot leg **not re-run**); 3 ✓ re-measured (51 626 == gate; `:80` → 301, no page bytes); 4 partial — 426/401/404 + pinning handshake measured, but arming/typing needs the operator's passphrase (`dflt:0`) and the target is this machine, so no input was sent; 5 ✓ (no `.pem/.key/.crt` tracked or on disk; hygiene block ran); 6 ✓ (`[env:rollback]` built every run + README section); 7 ✓. **Documentation**: README — check count 4793→4826 (intro + Quick start), `test_tls_pem` 829→862 with new pins, footprint/TLS-cost rows re-measured, "Ten bugs"→"Eleven bugs" + new entry + both anchors, `cert` row, Troubleshooting names-warning row, `tls_pem.h` architecture entry, nine/ten pure-module wording. AGENTS.md (staleness only): "Ten bugs"→"Eleven bugs" ×2, 4826, and the certificate-names row extended with the DER-OID rule. `.agents/HW_VERIFICATION.md` — 426 body/headers re-measured, fingerprint row correction + `-ext` rule, new SAN row (`failed → fixed in tree, device leg needs nvs erase`) with closing commands; phase report written to `.agents/reports/02_tls_control_channel/02_tls_control_channel__00_phase.a1.md`. **Deviations / live-infra**: made **no** reflash and **no** `nvs erase` (erase also destroys the token and Wi-Fi credentials; flashing puts a live HID keyboard into ROM download mode on this machine). Live steps were read-only `curl`/`openssl` plus one capped `console.py cert` (opening the CDC resets the board — permitted, recorded). No phase/PLAN files touched; everything left in the working tree for the harness to commit. **Next pending phase**: `03_session_takeover` — `01_pending_claim_socket.md`.
116 lines
5.8 KiB
INI
116 lines
5.8 KiB
INI
; keyrelay -- WiFi-controlled USB HID keyboard bridge for the M5Stack AtomS3 Lite.
|
|
;
|
|
; The board is an ESP32-S3FN8 (8 MB flash, no PSRAM) with NO USB-to-UART bridge:
|
|
; the USB-C connector is wired straight to the ESP32-S3 native USB (GPIO19/20), and
|
|
; those pads belong to ONE peripheral at a time:
|
|
;
|
|
; ARDUINO_USB_MODE=1 -> built-in USB-Serial/JTAG (console only, cannot type)
|
|
; ARDUINO_USB_MODE=0 -> USB-OTG + TinyUSB (HID keyboard + CDC console)
|
|
;
|
|
; That single-port fact is the whole reason the control path is WiFi: the board can
|
|
; be attached to exactly one machine over USB, so the controller on the laptop has to
|
|
; reach it over the network instead. See README "Why the control path is WiFi".
|
|
;
|
|
; Datasheet: https://docs.m5stack.com/en/core/AtomS3-Lite
|
|
|
|
[platformio]
|
|
default_envs = bridge
|
|
|
|
[usb]
|
|
; The core USB library compiles its MSC/FS sources together with the HID ones, and
|
|
; those include FS.h / vfs_api.h, which LDF will not resolve by itself (it tries the
|
|
; registry and fails). Only the include path is needed: nothing here references the
|
|
; MSC/FS symbols, so the linker drops those objects again. Carried over from the
|
|
; jiggle firmware on the same board.
|
|
fs_include = -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/FS/src
|
|
|
|
[env]
|
|
platform = espressif32@8.0.0
|
|
board = esp32-s3-devkitc-1
|
|
framework = arduino
|
|
|
|
; Nominal for USB CDC (the USB link ignores baud); 921600 is what this board takes.
|
|
upload_speed = 921600
|
|
monitor_speed = 115200
|
|
monitor_filters = esp32_exception_decoder, time
|
|
|
|
; WiFi + TinyUSB + the embedded controller page do not fit the default 1.3 MB app
|
|
; slot of the standard two-OTA layout. This board has no OTA path in this project --
|
|
; it is reflashed over USB, always -- so one 3 MB app partition is the right trade.
|
|
board_build.partitions = huge_app.csv
|
|
|
|
lib_deps =
|
|
adafruit/Adafruit NeoPixel@^1.15.0
|
|
|
|
; ---------------------------------------------------------------------------
|
|
; Default: the full bridge. The board is a composite USB HID keyboard + USB serial
|
|
; on the target, a web server and WebSocket endpoint on the network.
|
|
; ---------------------------------------------------------------------------
|
|
[env:bridge]
|
|
build_flags =
|
|
${usb.fs_include}
|
|
-DARDUINO_USB_MODE=0
|
|
; TLS on: the page and the WebSocket are served over HTTPS/wss with an identity the
|
|
; board generated itself (src/tls.h). The rollback value is KR_TLS=0 -- plain http://
|
|
; and ws:// only, exactly the pre-phase-02 behaviour, same standing as `mouse off`.
|
|
; It is a compile-time switch because a device that cannot be reached over TLS after a
|
|
; bad provisioning change needs a way back that is a reflash, not a repair.
|
|
-DKR_TLS=1
|
|
; CDC_ON_BOOT=0 is what makes the Logitech identity possible: with 1,
|
|
; cores/esp32/main.cpp calls USB.begin() from app_main() before setup() runs, the
|
|
; first tinyusb_init() fixes the VID/PID/strings as Espressif's, and every
|
|
; USB.VID()/productName() setter afterwards is a no-op. With 0 this environment
|
|
; starts the stack itself, after setting the identity (src/main.cpp: kUsb*). The
|
|
; console is unaffected: main.cpp owns a USBCDC instance, so /dev/ttyACM0 works.
|
|
-DARDUINO_USB_CDC_ON_BOOT=0
|
|
; Optional build-time station credentials, so a first bring-up can skip the serial
|
|
; console. Quoting is part of the macro: -D KR_STA_SSID='"MyNet"'.
|
|
; Prefer the `wifi <ssid> <pass>` console command: it stores into NVS and keeps
|
|
; the passphrase out of the image and out of shell history.
|
|
; -D KR_STA_SSID='" ssid "'
|
|
; -D KR_STA_PASS='" passphrase "'
|
|
build_unflags =
|
|
; esp32-s3-devkitc-1 pins ARDUINO_USB_MODE=1 in its board JSON; drop it so the
|
|
; value above is the only one on the command line.
|
|
-DARDUINO_USB_MODE=1
|
|
|
|
; ---------------------------------------------------------------------------
|
|
; Diagnostic: no USB-OTG, so no HID -- the board cannot type, but the whole control
|
|
; plane (WiFi, page, passphrase, WebSocket, LED, button) is live. Use it to separate
|
|
; "the network side is broken" from "the HID side is broken" without rebuilding.
|
|
; Arming refuses with E5 here, on purpose.
|
|
; ---------------------------------------------------------------------------
|
|
[env:console]
|
|
build_flags =
|
|
${usb.fs_include}
|
|
-DARDUINO_USB_MODE=1
|
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
|
; Same TLS default as bridge: the control plane is what this environment exists to
|
|
; diagnose, so it must behave identically. Roll back with -DKR_TLS=0 (see env:rollback).
|
|
-DKR_TLS=1
|
|
|
|
; ---------------------------------------------------------------------------
|
|
; Rollback: the bridge with TLS compiled out, i.e. the pre-phase-02 build -- plain
|
|
; http:// page and ws:// socket on :80, no :443, no identity in NVS. PLAN.md locks this
|
|
; in as the way back from a provisioning change that left a board unreachable over TLS:
|
|
; the fix is a reflash, not a repair, and a way back only counts if it still builds.
|
|
; That is why it is an environment and not a remembered command line: `pio run` has no
|
|
; --build-flags option in this PlatformIO, and a second -DKR_TLS on the command line
|
|
; beside the -DKR_TLS=1 above is a macro redefinition whose winner depends on flag order.
|
|
; Unflagging the default and setting 0 is the only spelling with no ambiguity -- and
|
|
; .agents/validate.sh builds this env on every run, so the rollback cannot quietly rot.
|
|
;
|
|
; Nothing about arming moves here: KR_TLS changes which port carries bytes, never who may
|
|
; type (src/config.h, README "The rollback"). The page this image serves is the KR_TLS=0
|
|
; branch of the marker in src/web_page.h; it is the only build where a plaintext ws://
|
|
; session is the normal case, and the only one the README permits it in.
|
|
; ---------------------------------------------------------------------------
|
|
[env:rollback]
|
|
extends = env:bridge
|
|
build_flags =
|
|
${env:bridge.build_flags}
|
|
-DKR_TLS=0
|
|
build_unflags =
|
|
${env:bridge.build_unflags}
|
|
-DKR_TLS=1
|