Files
keyrelay/tests/run_tests.sh
T
ducoterra e0780f58e0 phase: 02_tls_control_channel
**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`.
2026-11-09 05:51:57 -05:00

148 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Host tests for keyrelay's pure logic modules.
#
# tests/run_tests.sh build and run every suite
# tests/run_tests.sh clean remove the build directory
#
# The modules under test (src/keystate.h, src/proto.h, src/status.h, src/text.h,
# src/mouse.h, src/output.h, src/ws_codec.h, src/sha1.h, src/tls_pem.h, and the pure
# policy halves of src/tls_session.h and src/conn.h) have no Arduino or
# ESP-IDF
# dependency by design, so the exact code that runs on the board is compiled and
# exercised here before anything is flashed.
#
# A C++ compiler is not always installed on a workstation, so the runner picks one:
# $CXX if you set it, it wins
# g++ on PATH native, fastest
# a local container image podman + the first image that has g++ (see images
# below), with this repo bind-mounted read-write into
# /work -- no system packages installed for a test run
set -uo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
root="$(dirname "$here")"
out="$here/build"
if [[ "${1:-}" == "clean" ]]; then
rm -rf "$out"
echo "cleaned $out"
exit 0
fi
# Candidate images, tried in order, that already exist locally. Add your own here.
images=(
"localhost/ai-sandbox:ubuntu-9a08b1d500b0"
"docker.io/library/gcc:14"
)
suites=(test_keystate test_proto test_text test_mouse test_output test_ws_codec
test_tls_pem test_tls_session test_conn)
build_and_run() {
local cxx="$1"
local prefix=("$cxx")
local flags=(-std=gnu++17 -O1 -g -Wall -Wextra -Wno-unused-function -Isrc -Itests)
mkdir -p "$out"
if [[ -n "${SANITIZE:-}" ]]; then
flags+=(-fsanitize=address,undefined -fno-omit-frame-pointer)
echo "sanitizers: on"
fi
local failed=0 suite
for suite in "${suites[@]}"; do
if ! "${prefix[@]}" "${flags[@]}" "tests/$suite.cpp" -o "$out/$suite" \
2>"$out/$suite.log"; then
echo "BUILD FAILED: $suite"
cat "$out/$suite.log"
failed=1
continue
fi
[[ -s "$out/$suite.log" ]] && cat "$out/$suite.log"
"$out/$suite" || failed=1
done
# The Python side (the CLI client's WebSocket codec, and its key table against
# the one embedded in the controller page) needs only stdlib.
if command -v python3 >/dev/null 2>&1; then
python3 tests/test_relayctl.py || failed=1
else
echo "python3 not found: skipped tests/test_relayctl.py"
fi
return $failed
}
# The controller page is a C++ raw string, so no compiler looks at its JavaScript and
# a typo there fails in somebody's browser instead of in a build. Node runs it -- on
# the host, deliberately, because the container that supplies g++ need not have node.
run_page_suite() {
if command -v node >/dev/null 2>&1; then
node "$here/test_page.js" || return 1
else
echo "node not found: skipped tests/test_page.js"
fi
return 0
}
if [[ "${1:-}" == "--inside" ]]; then
cd "$root" || exit 2
# The container has g++ but not necessarily node; the page suite skips itself.
build_and_run "${CXX:-g++}"
rc=$?
if [[ -z "${KR_QUIET_RESULT:-}" ]]; then
if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi
fi
exit $rc
fi
if [[ -n "${CXX:-}" ]]; then
echo "compiler: \$CXX = $CXX"
(cd "$root" && build_and_run "$CXX")
rc=$?
(cd "$root" && run_page_suite) || rc=1
if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi
exit $rc
fi
if command -v g++ >/dev/null 2>&1; then
echo "compiler: $(command -v g++)"
(cd "$root" && build_and_run g++)
rc=$?
(cd "$root" && run_page_suite) || rc=1
if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi
exit $rc
fi
if command -v podman >/dev/null 2>&1; then
for img in "${images[@]}"; do
if ! timeout 60 podman image exists "$img" 2>/dev/null; then
continue
fi
if ! timeout 60 podman run --rm --entrypoint sh "$img" -c 'command -v g++' \
>/dev/null 2>&1; then
continue
fi
echo "compiler: g++ inside $img (repo mounted at /work)"
# :z relabels for SELinux; the build output lands in tests/build as usual.
# KR_QUIET_RESULT: the host prints the one summary, after its own page suite.
podman run --rm --entrypoint bash -v "$root:/work:z" \
-e "SANITIZE=${SANITIZE:-}" -e KR_QUIET_RESULT=1 \
"$img" /work/tests/run_tests.sh --inside
rc=$?
(cd "$root" && run_page_suite) || rc=1
if ((rc)); then echo "RESULT: FAILED"; exit 1; fi
echo "RESULT: all suites passed"
exit 0
done
fi
cat >&2 <<EOF
No C++ compiler found.
install one e.g. sudo dnf install gcc-c++ (then re-run)
or set \$CXX e.g. CXX=/path/to/g++ tests/run_tests.sh
or add an image add a local podman image with g++ to images[] above
EOF
exit 2