From fdf97711c612cb486827a1077ce392a8bfc7e619 Mon Sep 17 00:00:00 2001 From: JC-000 <3798556+JC-000@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:18:49 -0500 Subject: [PATCH] docs: audit README against the tree and correct every stale claim Every factual claim in README.md re-verified against the source, the build output, or a measured run. Corrections, with how each was checked: - Binary sizes / label counts: 38 KB + 1738/1816 labels -> 47,105 B / 2292 (ip65) and 62,977 B / 2400 (uci), from `make clean && make [BACKEND=uci]` plus `grep -c '^al ' build/labels.txt`. - Architecture diagram named `.asm` files the ca65 conversion removed (`http.asm`, `tls13.asm`, `tls_record.asm`, `tls_handshake.asm`, `hkdf.asm`). Only `src/constants.asm` and `src/entropy.asm` survive, and neither is built. - Diagram said "ECDHE P-256"; the ClientHello carries exactly one group, 0x001D x25519. - `src/net_abi.inc` was described as the interface TLS/HTTP consume networking through. Nothing `.include`s it; declared and used surfaces overlap in 6 of 17 symbols; ip65 exports none of `net_dhcp_acquire`, `net_tcp_set_recv_cb`, `net_local_ip`, `net_resolved_ip`, `net_last_error`, `net_tcp_state`. - Memory map predated the W1 hot/cold split and presented one layout as universal. Replaced with per-backend maps read from `build/c64-https.map`; the quarter-square table is at $BC00, not $7800. - max_fragment_length "512 or 1024" -> always value 1 = 512. - X25519 "~3.6 min/op" / "12,782 jiffies" -> measured 12,635 jiffies = 211 s (3.5 min), 13.9 s wall under warp, via tools/bench_x25519.py. - "253 tests across 11 suites" -> 266 across 11, measured twice. x25519 71 -> 73; `--slow` is now a no-op. `--skip-slow` "~5s" -> ~45 s; full run "~5 min" -> ~2 min. - ECDSA verify wall-clock ("~85 s/op", "~60-70 min at 1 MHz") and the ip65 e2e "~2-3 h" replaced with the measured figures from CLAUDE.md, each tagged with the libs/nistcurves v0.6.0 pin they were taken at. - "The Makefile automatically builds ip65 ... into a blob" contradicted the prerequisites block and is false: `.incbin` is invisible to make. - Added the `make clean`-between-flags trap, the two currently broken build flags (USE_X25519_SIBLING, P-384) with their exact ld65 errors, and the v0.2.0 release link. - Relative sibling-repo links (`../c64-wireguard` etc.) never resolved on GitHub; replaced with real URLs and split into vendored vs. not. The "I just want to run it" and "Before you build or test" blocks are unchanged apart from the release link. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 262 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 196 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 31cf286..648a2e6 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,15 @@ An HTTPS client for the Commodore 64 in 6502 assembly. Implements TLS 1.3 over TCP/IP, with two interchangeable networking backends: - **ip65** (default) — RR-Net (CS8900a) ethernet adapter via the [ip65](https://github.com/cc65/ip65) networking stack. -- **uci** — Ultimate 64 Elite (U64E) onboard ethernet via the UCI command interface. +- **uci** — Ultimate 64 Elite (U64E) and C64 Ultimate onboard ethernet via the UCI command interface. **For demonstration and educational purposes only — not cryptographically secure.** ## I just want to run it -Grab a release: every build is prebuilt, as a `.prg` and as a bootable `.d64`. +Grab a release — latest is +[**v0.2.0**](https://github.com/JC-000/c64-https/releases/tag/v0.2.0). +Every build is prebuilt, as a `.prg` and as a bootable `.d64`. No assembler, no cc65, no Python packages, no build step. Two questions pick your image, and `MANIFEST.txt` in the release walks through them: @@ -61,20 +63,20 @@ a venv mismatch reproduces #90 exactly after an install that appeared to succeed ``` ┌─────────────────────────────────────────┐ - │ HTTP/1.1 Client │ http.asm + │ HTTP/1.1 Client │ http.s ├─────────────────────────────────────────┤ - │ TLS 1.3 Engine │ tls13.asm (state machine) + │ TLS 1.3 Engine │ tls13.s (state machine) │ ┌──────────────┬──────────────────┐ │ - │ │ Record Layer │ Handshake Proto │ │ tls_record.asm, tls_handshake.asm + │ │ Record Layer │ Handshake Proto │ │ tls_record.s, tls_handshake.s │ └──────┬───────┴────────┬─────────┘ │ │ │ │ │ │ ┌──────┴───────┐ ┌──────┴─────────┐ │ │ │ AEAD │ │ Key Schedule │ │ (crypto modules) - │ │ ChaCha20- │ │ HKDF-SHA256 │ │ hkdf.asm - │ │ Poly1305 │ │ ECDHE P-256 │ │ + │ │ ChaCha20- │ │ HKDF-SHA256 │ │ hkdf.s + │ │ Poly1305 │ │ ECDHE X25519 │ │ │ └──────────────┘ └────────────────┘ │ ├─────────────────────────────────────────┤ - │ Network ABI (src/net_abi.inc) │ net_init / net_tcp_* / net_dns_* + │ Network backend boundary │ net_init / net_poll / net_tcp_* ├──────────────────────┬──────────────────┤ │ ip65 backend │ UCI backend │ src/net/ip65/ | src/net/uci/ │ (TCP/UDP/DNS/ │ (firmware-level │ @@ -86,20 +88,32 @@ a venv mismatch reproduces #90 exactly after an install that appeared to succeed make BACKEND=ip65 make BACKEND=uci ``` -The TLS, HTTP, and crypto layers are backend-agnostic; they consume -networking only through `src/net_abi.inc`. Switching backend is a -link-line change (different cfg + different `src/net//*.o`), +The TLS, HTTP, and crypto layers are backend-agnostic: switching backend +is a link-line change (different cfg + different `src/net//*.o`), not a call-site change. +`src/net_abi.inc` is **documentation, not an enforced interface**. No +translation unit `.include`s it (`grep -rn net_abi src/ tools/ cfg/ Makefile` +returns only comments), so none of its twelve `.import`s is checked by +the assembler or the linker. The symbols it declares and the +symbols TLS/HTTP/boot actually import overlap in 6 of 17, and the ip65 +adapter exports neither `net_dhcp_acquire`, `net_tcp_set_recv_cb`, +`net_local_ip`, `net_resolved_ip`, `net_last_error` nor `net_tcp_state` +(`net_last_error` exists only under UCI, so ip65 has no error channel). +Making the header real, or deleting it, is item P1 of issue #70. + ### TLS 1.3 Cipher Suite -Target: **TLS_CHACHA20_POLY1305_SHA256** (0x1303) +**TLS_CHACHA20_POLY1305_SHA256** (0x1303) — the only suite the +ClientHello offers (`src/tls_handshake.s:85`), and the ServerHello echo +is checked against it (`:380`). There is no AES anywhere in `src/crypto/`. -- **AEAD:** ChaCha20-Poly1305 (from [c64-wireguard](../c64-wireguard)) -- **Hash:** SHA-256 (from [c64-aes256-ecdsa](../c64-aes256-ecdsa)) -- **Key exchange:** ECDHE with X25519 (optimized: REU DMA multiply, self-mod code, ~3.6 min/op) -- **Key derivation:** HKDF-SHA256 (new, built from HMAC-SHA256) -- **PRNG:** HMAC-DRBG seeded from SID+CIA entropy (from c64-aes256-ecdsa) +- **AEAD:** ChaCha20-Poly1305 — in-tree `src/crypto/{chacha20,poly1305,aead}.s`, originally from [c64-wireguard](https://github.com/JC-000/c64-wireguard) +- **Hash:** SHA-256 — in-tree `src/crypto/sha256.s`, originally from [c64-aes256-ecdsa](https://github.com/JC-000/c64-aes256-ecdsa) +- **Key exchange:** ECDHE with X25519 only — `supported_groups` and `key_share` carry the single group 0x001D (`src/tls_handshake.s:204`); in-tree `src/crypto/{x25519,fe25519}.s` +- **Certificate signatures:** ECDSA P-256 (`ecdsa_secp256r1_sha256`, 0x0403) from the [c64-nist-curves](https://github.com/JC-000/c64-nist-curves) submodule at `libs/nistcurves`, via the thin dispatcher `src/crypto/ecdsa_verify.s`. P-384 (0x0503) is advertised but stubbed — see Known Issues. +- **Key derivation:** HKDF-SHA256, built from HMAC-SHA256 (`src/hkdf.s`) +- **PRNG:** HMAC-DRBG seeded from SID voice 3 noise + CIA timer entropy (`src/entropy.s`, `src/crypto/hmac_drbg.s`) ### Zero Page Time-Sharing (ip65 backend) @@ -107,40 +121,80 @@ Under the ip65 backend, the crypto modules and ip65 overlap on zero page $02-$1B ``` $02-$03 Shared tmp (save/restore around ip65 calls) -$04-$09 word32 pointers (ChaCha20) +$04-$09 word32 pointers (ChaCha20 / Poly1305) $0A-$12 SHA-256 accumulators $14-$17 mult66 pointers (fe25519) / ChaCha20 vars (time-shared) $18-$1D ChaCha20 + Poly1305 vars -$22-$3C ECDSA bignum / field arithmetic -$FB-$FE General pointers (save/restore around ip65 calls) +$1E-$21 TLS record layer (record pointer, index, direction) +$22-$2B ECDSA bignum pointers (fp_src1..fp_loop) +$2C-$37 fe25519 field arithmetic +$38-$3A x25519 ladder state (shares $39-$3A with fp_mul_i/j) +$3B-$3C ec_scalar_ptr +$FB-$FF General pointers (save/restore around ip65 calls) ``` +The authoritative list is `src/constants.inc`; the table above is a +summary of it. + The ip65 TCP callback (fired during `ip65_process`) copies received data into a ring buffer using only ip65's ZP context. After `ip65_process` returns and crypto ZP is restored, buffered data is processed through TLS. ## Memory Map +The two backends do **not** share a layout. Each is defined by its own +ld65 config, and the tables below were read back from +`build/c64-https.map` after `make clean && make [BACKEND=uci]`. + +Common to both: + ``` -$0000-$00FE Zero page (time-shared, see above) +$0000-$00FF Zero page (time-shared, see above) $0100-$01FF CPU stack -$0200-$033F KERNAL/BASIC work area -$0334-$03FF Scratch / test harness trampoline -$0801-$08FF BASIC stub + boot -$0900-$1FFF TLS 1.3 engine + HTTP client + net wrapper (~6 KB) -$2000-$3FFF ip65 code + BSS (~8 KB) -$4000-$5FFF Crypto: ChaCha20, Poly1305, AEAD (~8 KB) -$6000-$6FFF Crypto: SHA-256, HMAC-SHA256, HKDF (~4 KB) -$7000-$77FF Crypto: ECDSA/ECDH P-256 (~2 KB) -$7800-$7BFF Quarter-square multiply table (1 KB, runtime-generated) -$7C00-$8DFF Code: ECDSA verify, DER decode, TLS cert, ECDH (~4.5 KB) -$8E00-$93FF Optimization tables: REU DMA, sqtab2, mul38 (~1.5 KB, below ROM) -$9400-$BFFF Data buffers: TLS state, crypto state, record buffers (~11 KB) - ($A000-$BFFF under BASIC ROM, banked out at boot) -$C000-$CFFF Free RAM (4 KB, overflow buffers) +$0200-$03FF KERNAL/BASIC work area + test-harness trampoline scratch $DE00-$DE0F RR-Net CS8900a I/O registers (ip65 backend) -$DF1B-$DF1F UCI command/data registers (UCI backend, U64E only) +$DF1B-$DF1F UCI command/data registers (UCI backend, U64E / C64U only) +``` + +ip65 backend — `cfg/c64-https-ip65.cfg`: + ``` +$0801-$1FFF LOADER BASIC stub + boot + HTTP + most of TLS +$2000-$3FFF NET_CODE ip65 blob (6,951 B) + LOADER_OVERFLOW + + CRYPTO_AUX_CODE2 +$4000-$4F8B NET_BSS ip65 blob's BSS +$4F8C-$5FFF CRYPTO_OVERLAY 4,212 B; holds TLS_CODE + CRYPTO_AUX_CODE +$6000-$9FFF CRYPTO_RESIDENT 16 KB code + rodata, incl. the + libs/nistcurves P-256 verify path +$A000-$BFFF CRYPTO_COLD_SHADOW 8 KB BSS under the BASIC ROM shadow + (cert_buf $A000, tls_rec_buf $A600, + tables $BA00, sqtab_lo $BC00) +$C000-$CFFF TCP_BUF tcp_recv_buf, 4 KB ring +``` + +UCI backend — `cfg/c64-https-uci.cfg`: + +``` +$0801-$1FFF LOADER BASIC stub + boot + HTTP + most of TLS +$2000-$3B65 NET_CODE UCI adapter (~2 KB) + LOADER_OVERFLOW + + TLS_CODE + CRYPTO_AUX_CODE(2) +$3B66-$41FF NET_BSS_TAIL LIB_NISTCURVES_P256_BSS spill +$4200-$5FFF CRYPTO_OVERLAY 7.5 KB overlay slot — empty in the + shipped build; claimed by the X25519 + sibling / P-384 / P-256-embed flags +$6000-$9FFF CRYPTO_HOT 16 KB code + rodata + UCI_BSS +$A000-$BFFF CRYPTO_COLD_SHADOW 8 KB BSS (same tenants as ip65) +$C000-$DFFF OVERLAY_FILE_PAD zero-pad in the PRG; at runtime the + 4 KB TCP_BUF ring lives at $C000 +$E000-$FDFF OVERLAY_BLOB_CURVE P-384 curve overlay blob slot (empty + by default — P-384 is not built) +``` + +No segment may cross $A000: boot zeroes $A000-$BFFF as BSS, so anything +executable there would be wiped on first call. -TLS 1.3 records can be up to 16,384 bytes, but we negotiate `max_fragment_length` (RFC 6066) to limit records to 512 or 1024 bytes, fitting within C64 RAM constraints. +TLS 1.3 records can be up to 16,384 bytes. The ClientHello negotiates +`max_fragment_length` (RFC 6066) with value 1 = **512 bytes** +(`src/tls_handshake.s:281`), and `TLS_RECORD_MAX = 512` in +`src/constants.inc` sizes the buffers to match. ## Building @@ -152,22 +206,50 @@ TLS 1.3 records can be up to 16,384 bytes, but we negotiate `max_fragment_length ```bash git clone --recursive https://github.com/JC-000/c64-https.git cd c64-https -make # Build build/c64-https.prg (default BACKEND=ip65) -make BACKEND=uci # Build the Ultimate 64 Elite (UCI) variant -make run # Build and launch in VICE (x64sc) -make clean # Remove build artifacts +make # build/c64-https.prg (default BACKEND=ip65, REU profile) +make BACKEND=uci # the Ultimate 64 / C64 Ultimate (UCI) variant +make USE_NISTCURVES_ONCHIP=1 # the no-REU "onchip" P-256 verify profile +make run # build and launch in VICE (x64sc) +make clean # remove build artifacts +make package # all four backend x profile release artifacts into dist/ ``` +**Run `make clean` whenever you change `BACKEND=` or any flag.** make +tracks source timestamps, not the command line, and `BACKEND=` selects +an include path (`-I src/net/$(BACKEND)`) rather than a `-D` define, so +an object built for the other backend counts as up to date. Both failure +modes are silent and exit 0: a mixed link that is the correct size but +carries the wrong backend's tuning constants, or no relink at all, +leaving the other backend's PRG in `build/`. Neither the exit code nor +the file size distinguishes them — compare the **PRG's** sha256 if a +build matters. (Object hashes cannot serve: ca65 stamps build time into +every `.o`, so no two builds agree; ld65 does not propagate it, which is +what makes the PRG deterministic.) + ### ip65 Build (ip65 backend only) -The Makefile automatically builds ip65 from the submodule into a flat binary blob at $2000, using a custom ld65 linker config (`ip65-build/ip65.cfg`). The blob is then linked into the ca65 build via `.incbin`. The UCI backend does not use this blob. +ip65 is built from the submodule into a flat binary blob at $2000, using +a custom ld65 linker config (`ip65-build/ip65.cfg`), and linked into the +ca65 build via `.incbin`. **A plain `make` does not produce that blob and +cannot** — `.incbin` is invisible to make's dependency graph, so there is +no rule connecting the two. Run `make ip65-libs && make ip65-blob` once +per clone, as in "Before you build or test" above. The build is +deterministic: 6,951 B, sha256 `cf1a5ff7809af4e4655e385b378b936054f41046ff2b7604828af3240c2d90dd`. +`make clean` does not remove it, which is why the step is normally +invisible. The UCI backend does not use the blob at all. ## Project Status -Current status: +Current status (measured 2026-08-14 with cc65 from Homebrew; +`ls -l build/c64-https.prg` and `grep -c '^al ' build/labels.txt` after +`make clean && make [BACKEND=uci]`): + +- ip65 build: 47,105 B PRG, 2292 labels +- uci build: 62,977 B PRG, 2400 labels -- 38 KB binary (ip65 build), 1738 labels -- 38 KB binary (uci build), 1816 labels +Much of both PRGs is deliberate zero fill — the ld65 configs mark the +inter-region gaps `fill = yes` so the load addresses stay right — so +neither figure is a code-size measurement. Progress: @@ -175,7 +257,7 @@ Progress: - [x] ip65 submodule integration — 6.8 KB binary blob at $2000 (TCP/UDP/DNS/DHCP/ARP + RR-Net CS8900a) - [x] Network wrapper with ZP time-sharing — save/restore $02-$1B around ip65 calls - [x] Crypto primitives — ChaCha20, Poly1305, AEAD (from c64-wireguard), SHA-256, HMAC-DRBG (from c64-aes256-ecdsa) -- [x] Optimized X25519/fe25519 — REU DMA multiply tables, mult66 quarter-square, self-mod code, 4x-unrolled cswap (~30% faster, 12,782 jiffies / 3.6 min per keygen) +- [x] Optimized X25519/fe25519 — REU DMA multiply tables, mult66 quarter-square, self-mod code, 4x-unrolled cswap. `tools/bench_x25519.py` measures one basepoint scalar multiply at **12,635 jiffies = 211 s (3.5 min)** of C64 time (NTSC, VIC-II blanked, ~14 s wall clock under VICE warp) - [x] HKDF-SHA256 — Extract, Expand, Expand-Label, Derive-Secret (RFC 5869 + TLS 1.3) - [x] TLS 1.3 record layer — encrypt/decrypt with ChaCha20-Poly1305, nonce construction, sequence numbers - [x] TLS 1.3 handshake — ClientHello builder (x25519 key_share, SNI), ServerHello parser, streaming transcript hash @@ -184,32 +266,44 @@ Progress: - [x] TLS 1.3 key schedule integration testing — all 9 HKDF steps verified against RFC 8448 + Finished MAC - [x] Entropy/DRBG initialization — SID voice 3 noise + CIA timer seeding at boot, DRBG fills for TLS random values - [x] X.509 certificate parsing — DER parser extracts TBS, public key, signature (r,s), curve ID for P-256 and P-384 -- [x] ECDSA signature verification — P-256 and P-384, full verify (s⁻¹, scalar mul, point add, Jacobian→affine) +- [x] ECDSA P-256 signature verification — supplied by the `libs/nistcurves` submodule (`ecdsa_verify_256`), always resident under both backends; `src/crypto/ecdsa_verify.s` is a thin dispatcher that packs the big-endian input struct. P-384 verify is **not** built — see Known Issues. - [x] HTTP/1.1 GET request — build GET, parse response (status + headers + body), plain HTTP end-to-end - [x] **End-to-end HTTPS GET demo (both backends)** — TLS 1.3 handshake + HTTP GET completes against a local Python TLS listener (ECDSA-P256 cert). Returns `http_status=200`, body `"HELLO FROM TLS SERVER"`. - UCI: real Ultimate 64 Elite hardware at both 48 MHz turbo and stock 1 MHz. See `tools/uci/test_https_local.py` (supports `TURBO_MHZ` env var). - - ip65: VICE + RR-Net + bridge rig at stock 1 MHz (no WARP). See `tests/test_phase3_https_1mhz.py`. Wall-clock ~2-3 h end-to-end, dominated by ECDSA-P256 verify. + - ip65: VICE + RR-Net at stock 1 MHz, no warp. The bridge-rig script is `tests/test_phase3_https_1mhz.py`; the hardware-free macOS feth/pcap rig is `tests/test_vice_https_macos.py`, and that is where the wall-clock below was taken. ### Known Issues -- **ECDSA P-256 verify** runs ~85 s/op on the U64E at 48 MHz turbo (UCI backend) and scales to ~60-70 min at stock 1 MHz (both backends). Sufficient for the local listener (which holds the connection open), but exceeds typical 10-30 s real-world server handshake windows. Real-internet ECDSA-P256 targets need a sibling-style optimized P-256 implementation (parallel to the `c64-x25519` work). -- **P-384** ECDSA is currently stubbed (both backends). Cert chains requiring P-384 will not verify until restored. +- **The handshake is slow, and the ECDSA P-256 verify dominates it.** Every figure here is quoted from the measurement record in `CLAUDE.md` and was taken at the **`libs/nistcurves` v0.6.0 pin**; HEAD is v0.9.1 and nobody has re-run the sweep, so treat them as a baseline rather than as HEAD. End-to-end handshake + GET against the local listener, U64E, master 2ceb5b1: **80.8 s** (REU profile, 48 MHz), **45.5 s** (onchip profile, 48 MHz), **1,157.7 s** (REU, stock 1 MHz). On the REU-less stock-C64 path (ip65 + onchip, no REU, honest 1 MHz in VICE) the whole run measured **2,159.7 s = 36.0 min**, of which the verify stretch alone was 1,416.7 s. That is fine for the local listener, which holds the connection open; it exceeds a typical 10-30 s real-world server handshake window. +- **P-384** ECDSA is stubbed at the TLS layer. The dispatcher advertises `ecdsa_secp384r1_sha384` (0x0503) and routes to `src/crypto/ecdsa_verify_384.s`, but no P-384 build target completes. Measured 2026-08-14: `make p384-overlay` from a clean tree stops at `No rule to make target 'build/labels.txt'`, and after a main build has produced that file it stops at `Segment 'LIB_NISTCURVES_SHA384_TABLES' overflows memory area 'OVERLAY_REGION' by 1536 bytes`. Cert chains requiring P-384 will not verify. +- **`USE_X25519_SIBLING=1` does not link on either backend** at the current `libs/x25519` pin. Measured 2026-08-14: `make clean && make USE_X25519_SIBLING=1` and the `BACKEND=uci` equivalent both die with `ld65: Error: Duplicate external identifier: 'reu_mul_tables_init'`. The in-tree X25519 in `src/crypto/{x25519,fe25519}.s` is what every shipped artifact contains. - **Live internet HTTP GET (UCI backend)** has not been re-verified since the FPGA-fence rework; only the local multi-segment listener is exercised regularly. - **VICE 3.9** previously appeared to crash on chained HMAC-SHA256 calls (backend-independent — affects the crypto-only test suites), but this was caused by hardcoded port numbers bypassing the test harness port allocator. With proper `ViceInstanceManager` usage (no hardcoded ports), all N=1..10 chained calls succeed reliably. ## Test Automation -253 tests across 11 suites (+ 1 standalone diagnostic), using the [`c64-test-harness`](../c64-test-harness) package to drive VICE via its binary monitor protocol. VICE runs the **ip65 backend by default** (the UCI backend targets real U64E hardware — see the Ultimate 64 Elite Hardware Tests section below). The parallel runner allocates a fresh VICE instance per suite (with REU support for x25519) to avoid state contamination. All tests log VICE PID and port for multi-agent safety. +**266 assertions across 11 suites**, plus several standalone scripts the +parallel runner does not cover, using the +[`c64-test-harness`](https://github.com/JC-000/c64-test-harness) package +to drive VICE via its binary monitor protocol. VICE runs the **ip65 +backend by default** (the UCI backend targets real U64E hardware — see +the Ultimate 64 Elite Hardware Tests section below). The parallel runner +allocates a fresh VICE instance per suite (with REU, which the sibling +P-256 code requires) to avoid state contamination. All tests log VICE +PID and port for multi-agent safety. + +Suite counts below were taken from a `tools/run_all_tests.py` run on +2026-08-14 (266/266, 2 min 5 s wall clock on an M-series Mac). ```bash -pip install -e ../c64-test-harness +python3 -m pip install -e ../c64-test-harness -# Run all 11 suites in parallel (one VICE instance per suite, ~5 min with ECDSA) +# Run all 11 suites in parallel (one VICE instance per suite, ~2 min) python3 tools/run_all_tests.py -python3 tools/run_all_tests.py --skip-slow # Skip x509/ECDSA (~5s wall time) +python3 tools/run_all_tests.py --skip-slow # Skip the x509/ECDSA suite: 255 assertions, ~45 s python3 tools/run_all_tests.py --workers 6 # Limit concurrent VICE instances -# Individual suites +# Individual suites (the 11 the runner aggregates) python3 tools/test_net.py # 60 tests: ip65 integration, ZP save/restore, ring buffer, TCP recv callback python3 tools/test_sha256.py # 7 tests: NIST vectors, boundary cases, random inputs python3 tools/test_crypto.py # 22 tests: ChaCha20/Poly1305/AEAD RFC 7539 vectors + random @@ -220,14 +314,19 @@ python3 tools/test_tls_handshake.py # 21 tests: transcript hash, ClientHello, Se python3 tools/test_keyschedule_steps.py # 9 tests: key schedule step-by-step (RFC 8448 vectors) python3 tools/test_entropy.py # 7 tests: SID/CIA hardware init, DRBG seeding, output quality python3 tools/test_http.py # 27 tests: HTTP/1.1 GET builder, response parser, status codes -python3 tools/test_x25519.py # 71 tests: fe25519 field ops, x25519_clamp, scalarmult (--slow for RFC 7748 vectors) -python3 tools/test_chained_hmac.py # 10 tests: chained HMAC-SHA256 stability (N=1..10, standalone) +python3 tools/test_x25519.py # 73 tests: fe25519 field ops, x25519_clamp, scalarmult + RFC 7748 vectors + +# Standalone scripts, not aggregated by run_all_tests.py +python3 tools/test_chained_hmac.py # 10 cases: chained HMAC-SHA256 stability (N=1..10) +python3 tools/test_finished_verify.py # 18 cases: the server-Finished REJECTION path, driven over DMA +python3 tools/test_ecdsa_kat_oracle.py # 6 vectors: ECDSA P-256 KAT, 3 valid + 3 negative CAVP +python3 tools/test_package_verify.py # 31 cases: pure-logic tests for the release gate (no VICE, no build) # Benchmark -python3 tools/bench_x25519.py # X25519 key generation (~3.6 min C64 time, ~8s warp) +python3 tools/bench_x25519.py # X25519 basepoint multiply: 12,635 jiffies / 211 s C64 time, ~14 s wall under warp -# Integration tests (require tap-c64 interface, dnsmasq; see scripts/setup-tap-networking.sh in c64-test-harness) -python3 tools/test_dns.py # 4 tests: DNS resolution via ip65 over TAP (known host, second host, unknown host) +# Integration tests (require the bridge/TAP rig + dnsmasq; see scripts/setup-bridge-tap.sh below) +python3 tools/test_dns.py # 4 tests: DNS resolution via ip65 over TAP (label, known host, second host, unknown host) python3 tools/test_http_integration.py # 5 tests: end-to-end plain HTTP GET over TAP (DNS + TCP + request/response) # End-to-end bridge tests (require br-c64 bridge, RR-Net; see below) @@ -237,7 +336,19 @@ sudo PYTHONPATH=tools python3 tests/test_phase2_http.py # Plain HTTP GET over ### End-to-End Bridge Tests (ip65 backend) -Full end-to-end tests that drive the real c64-https binary in VICE over a Linux bridge with RR-Net ethernet (the same pattern used by [`c64-test-harness` bridge networking](../c64-test-harness/docs/bridge_networking.md)). These exercise the **ip65/RR-Net path only**. All three phases pass: DHCP (phase1), plain HTTP (phase2), and HTTPS (phase3 via `tests/test_phase3_https_1mhz.py`). VICE runs at **normal speed** (warp breaks RR-Net DHCP), so these tests need generous timeouts (~90-120s per phase; the phase3 HTTPS run is ~2-3 h at 1 MHz). +Full end-to-end tests that drive the real c64-https binary in VICE over a Linux bridge with RR-Net ethernet (the same pattern used by [`c64-test-harness` bridge networking](https://github.com/JC-000/c64-test-harness/blob/master/docs/bridge_networking.md)). These exercise the **ip65/RR-Net path only**: DHCP (phase1), plain HTTP (phase2), and HTTPS (phase3 via `tests/test_phase3_https_1mhz.py`). VICE runs at **normal speed** (warp breaks RR-Net DHCP), so these tests need generous timeouts (~90-120s per phase). + +The HTTPS phase is long. The nearest measured figure is from the +hardware-free macOS rig rather than this Linux bridge: +`tests/test_vice_https_macos.py`, ip65 + onchip profile with no REU, +honest 1 MHz, **2,159.7 s = 36.0 min** from `G` to `CONNECTION CLOSED`. +Budget accordingly; do not assume the bridge rig matches it exactly. + +On macOS the equivalent rig uses a feth pair plus pcap instead of a +Linux bridge — `sudo bash tools/rig-up-macos.sh`, and a VICE built with +the pcap driver's `geteuid()==0` gate patched out, because stock macOS +VICE binaries reject unprivileged `-ethernetiodriver pcap`. See the +"VICE ip65 rig" section of `CLAUDE.md`. **Setup:** @@ -259,6 +370,7 @@ The setup script creates `br-c64` with `tap-c64-0`/`tap-c64-1`, assigns `10.0.65 | `vice_on_bridge.py` | `launch_vice_on_bridge()` → `ViceHandle`, `shutdown_vice()` | | `c64_menu.py` | `press_key()`, `wait_for_screen_text()`, `get_screen_text()` | | `http_listener.py` | `start_http_listener()` → `HttpListenerHandle`, `stop_http_listener()` | +| `https_listener.py` | `start_https_listener()` → `HttpsListenerHandle`, `stop_https_listener()` | ### Ultimate 64 Elite Hardware Tests (UCI backend) @@ -279,25 +391,43 @@ python3 tools/uci/phase2_check.py # DHCP + local IP readback python3 tools/uci/phase3_tcp_echo.py # TCP connect/send/recv python3 tools/uci/test_http_local.py # HTTP GET against local listener python3 tools/uci/test_https_local.py # HTTPS GET (TLS 1.3 + ECDSA-P256) +python3 tools/uci/test_https_bad_finished.py # client must ABORT on a forged server Finished ``` +`test_https_bad_finished.py` is the negative path: it talks to +`tools/https_e2e/evil_listener.py`, a hand-rolled TLS 1.3 server that +flips one bit of the server Finished `verify_data` before encryption +(corrupting the ciphertext instead would be caught by Poly1305 and never +reach the Finished comparison). Run `FINISHED_MODE=good` first as the +control. `tools/test_finished_verify.py` is the VICE-only equivalent. + `test_https_local.py` is the end-to-end HTTPS demo (UCI backend only): it boots the U64E at 48 MHz turbo, connects to a local Python TLS listener using the test cert under `tools/https_e2e/certs/`, and confirms a full TLS 1.3 handshake + HTTP GET. That cert is gitignored throwaway material — the directory is empty in a fresh clone and the pair is generated on first use, with no dependency beyond the standard library (`python3 tools/https_e2e/ensure_certs.py` mints it by hand). With `DEBUG_CAPTURE=1`, each run writes a timestamped artifact directory under `$UCI_DEBUG_DIR` (default `/tmp/uci_https_debug/`) with raw 6510 bus trace, TLS state snapshot, and listener result. Environment variables honored by `test_https_local.py`: - `U64_HOST` (default `192.168.1.81`) — U64E address -- `TURBO_MHZ` (default `48`) — C64 CPU speed. `TURBO_MHZ=1` runs the test at stock 1 MHz with every wall-clock budget auto-scaled by 48x (~2-3 h total, validated end-to-end on real U64E hardware). +- `TURBO_MHZ` (default `48`) — C64 CPU speed. `TURBO_MHZ=1` runs the test at stock 1 MHz with every wall-clock budget auto-scaled, and is validated end-to-end on real U64E hardware; the handshake + GET itself measured 1,157.7 s (~19 min) there, not the full budget. - `HTTPS_PORT` (default `443`, falls back to `4433` if the bind fails) -- `SENTINEL_POLL_TIMEOUT`, `ACCEPT_TIMEOUT` — per-test overrides in seconds; default to `600 * (48 / TURBO_MHZ)`. +- `SENTINEL_POLL_TIMEOUT`, `ACCEPT_TIMEOUT` — per-test overrides in seconds; default to `600 * max(1, 48 / TURBO_MHZ)`. +- `EXTERNAL_LISTENER=1` (plus `EXTERNAL_HOST`, `EXTERNAL_PORT`, default `4433`) — skip the inline listener and point the C64 at an out-of-band server, e.g. the `c64-https-listener.py` from a release. - `DEBUG_CAPTURE` (default `1`) — set to `0` to disable the bounded 6510 bus stream. - `KEEP_DEBUG_ON_PASS` (default `0`) — set to `1` to preserve artifacts on PASS runs. - `UCI_DEBUG_DIR` (default `/tmp/uci_https_debug`) — base directory for run artifacts. ## Related Projects -- [c64-aes256-ecdsa](../c64-aes256-ecdsa) — AES-256, SHA-256, ECDSA P-256, HMAC-DRBG -- [c64-wireguard](../c64-wireguard) — ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD -- [c64-test-harness](../c64-test-harness) — VICE test automation framework +Vendored as submodules and linked into the PRG: + +- [c64-nist-curves](https://github.com/JC-000/c64-nist-curves) — `libs/nistcurves`, the ECDSA P-256 verify used for CertificateVerify +- [c64-x25519](https://github.com/JC-000/c64-x25519) — `libs/x25519`, an alternative X25519 behind `USE_X25519_SIBLING=1` (not currently linkable — see Known Issues) +- [ip65](https://github.com/cc65/ip65) — the TCP/IP stack behind the ip65 backend + +Not vendored — origin of code that now lives in-tree, or tooling: + +- [c64-wireguard](https://github.com/JC-000/c64-wireguard) — ChaCha20, Poly1305, ChaCha20-Poly1305 AEAD +- [c64-aes256-ecdsa](https://github.com/JC-000/c64-aes256-ecdsa) — AES-256, SHA-256, ECDSA P-256, HMAC-DRBG +- [c64-test-harness](https://github.com/JC-000/c64-test-harness) — VICE test automation framework +- [c64-lib-contract](https://github.com/JC-000/c64-lib-contract) — the segment-naming / manifest contract the two crypto submodules follow ## License