Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -331,7 +331,11 @@ Scripts under `tools/uci/` require a U64E (default 192.168.1.81,
overridable via the `U64_HOST` environment variable) and use
`DeviceLock` + `enable_uci`/`disable_uci`:

- `boot_check.py` — verify UCI firmware detection and boot banner
- `boot_check.py` — boot the PRG and assert the backend banner
(`BACKEND=uci|ip65`, default uci), the
absence of any `FAILED` line, and that the
menu was reached. `C64_PRG` overrides the
image; `BOOT_TIMEOUT` the menu budget.
- `phase2_check.py` — DHCP acquire + local IP readback
- `phase3_tcp_echo.py` — TCP connect/send/recv against a local echo server
- `test_http_local.py` — HTTP GET against a local test server
Expand DownExpand Up@@ -426,7 +430,9 @@ and writes the 48 B P-384 pubkey into the dedicated
The CertificateVerify signed-content blob is 130 B (RFC 8446 §4.4.3:
64-space pad + 33 B context + 1 B sep + 32 B SHA-256 transcript;
the transcript-hash function stays SHA-256 because c64-https
negotiates only TLS_AES_128_GCM_SHA256 — Phase 5 Fix A). The
offers exactly one cipher suite, TLS_CHACHA20_POLY1305_SHA256
(0x1303, `src/tls_handshake.s:85`, echo-verified at :380), whose
hash is SHA-256 — Phase 5 Fix A). The
end-to-end test is `tools/uci/test_https_local_p384.py` (mirrors
`test_https_local.py` with P-384 cert profile via swapping CERT_PATH
/ KEY_PATH to `tools/https_e2e/certs/server-p384.{pem,key}`); see the
Expand DownExpand Up@@ -493,7 +499,12 @@ Five latent bugs and three new ones were cleared to get here:
- `net_tcp_set_recv_cb` is an RTS stub (no callers in-tree).
- Boot banner line 03 still says "rr-net" under ip65 build even
though Phase 2 made it backend-aware — this is correct/expected
behavior. Under UCI it says "ULTIMATE 64 ELITE (UCI)".
behavior. Under UCI it says "UCI NETWORKING". Those two strings
are the whole of `net_banner_str`
(`src/net/ip65/net_banner.s` / `src/net/uci/net.s`), and
`tools/uci/boot_check.py` asserts against them, so keep the two
in step. (This entry used to claim the UCI line read
"ULTIMATE 64 ELITE (UCI)" — it never did.)
- The delay-loop fence adds ~2.5 ms overhead per UCI register access
at 1 MHz (negligible for networking, but visible in tight loops).
- `http_resp_buf` is rendered through `ascii_chrout` (a small
Expand DownExpand Up@@ -576,6 +587,12 @@ Five latent bugs and three new ones were cleared to get here:
spelling out `ViceConfig(extra_args=["-reu", "-reusize", "512"])`
by hand. The UCI path is unaffected because the U64E hardware has
REU enabled by default; the symptom was VICE-only.
The single deliberate exception is `C64_VICE_NO_REU=1`, which makes
`default_vice_config()` drop the REU flags (and say so on stderr).
It exists so the shipped onchip PRG's "no REU required" claim has a
runnable test — see the packaging validation record for the exact
invocation. Never set it for a REU-profile build: that is precisely
the silent-garbage case above.

### ECDSA P-256 verify wall-clock

Expand DownExpand Up@@ -1055,6 +1072,19 @@ Validation record (2026-07-27, HEAD cb6eab4):
(and with, as control) — the no-REU claim is verified, and
boot.s's unconditional reu_mul_init is harmless with no REU
attached. Both D64 files boot to banner in VICE.
Reproduce it with the `C64_VICE_NO_REU` opt-out (no patching, and
`-reu` stays the default everywhere else):

make clean && make BACKEND=uci USE_NISTCURVES_ONCHIP=1
C64_SKIP_BUILD=1 C64_VICE_NO_REU=1 \
python3 tools/test_ecdsa_kat_oracle.py # 3/3, exit 0
C64_SKIP_BUILD=1 python3 tools/test_ecdsa_kat_oracle.py
# control, 3/3

The flag is only meaningful on an onchip image. Run it against a
REU-profile build and all three valid vectors verify as C=1 with
no error message — that silent-wrong-answer failure mode is why
`-reu` is the default (see "VICE harness gotcha").
- Full shipped chain (zip listener + freshly generated certs +
sha-verified dist PRGs, `EXTERNAL_LISTENER=1`), all HTTP 200 +
canonical body over TLS_CHACHA20_POLY1305_SHA256:
Expand Down
12 changes: 7 additions & 5 deletions src/crypto/ecdsa_verify_384.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,9 +33,9 @@
;
; Phase 5 Fix A: blob length is 130 bytes, not 146. RFC 8446
; §4.4.1 specifies the transcript-hash uses the negotiated cipher
; suite's hash function — c64-https only negotiates
; TLS_AES_128_GCM_SHA256, so the transcript is always 32 B SHA-256
; regardless of the signature scheme. The 46+33+1+32 = 130 layout
; suite's hash function — c64-https offers exactly one suite,
; TLS_CHACHA20_POLY1305_SHA256 (0x1303), so the transcript is
; always 32 B SHA-256 regardless of the signature scheme. The 46+33+1+32 = 130 layout
; is what the server signed; padding to 48 B for SHA-384's digest
; width would feed the verifier a different message than the one
; the server hashed. SHA-384(blob) still produces a 48 B digest
Expand All@@ -49,7 +49,9 @@
; 6. crypto_swap_to_p384_curve -> ecdsa_verify_384.
; C=0 valid / C=1 invalid -- propagated to caller.
;
; Phase 5 note: c64-https only negotiates TLS_AES_128_GCM_SHA256, so
; Phase 5 note: c64-https offers exactly one cipher suite,
; TLS_CHACHA20_POLY1305_SHA256 (0x1303) — see src/tls_handshake.s:85
; and the ServerHello echo check at :380 — and its hash is SHA-256, so
; the TLS 1.3 transcript-hash function is always SHA-256 (RFC 8446
; §4.4.1 ties transcript-hash to the cipher suite's hash, not to the
; signature_algorithm). The signed-content blob therefore embeds a
Expand DownExpand Up@@ -249,7 +251,7 @@ ecdsa_verify_384_tls:
; [98..129] transcript hash (32 B SHA-256). Phase 5 Fix A:
; copy the 32 B SHA-256 tls_transcript verbatim — no padding.
; The TLS 1.3 transcript-hash is bound to the cipher suite
; (SHA-256 via TLS_AES_128_GCM_SHA256), independent from the
; (SHA-256 via TLS_CHACHA20_POLY1305_SHA256), independent of the
; signature_algorithm's hash (SHA-384 here). Padding to 48 B
; would feed the verifier a different message than the server
; signed.
Expand Down
51 changes: 50 additions & 1 deletion tools/_vice_helpers.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,12 +8,37 @@

See user memory ``vice_reu_required_for_p256`` and the project's
"VICE harness gotcha" note in ``CLAUDE.md`` for the canonical motivation.

Opt-in no-REU mode
------------------
Setting ``C64_VICE_NO_REU=1`` in the environment drops the REU flags, so
the packaging claim "the onchip PRG passes the ECDSA KAT without an REU"
has a runnable test instead of requiring a monkeypatched copy of the
script. It is deliberately opt-in and noisy: a no-REU run of a
*REU-profile* build does not error, it silently computes wrong answers
(a valid signature verifies as C=1). Only use it on
``USE_NISTCURVES_ONCHIP=1`` images.
"""

from __future__ import annotations

import os
import sys

from c64_test_harness import ViceConfig

#: Environment variable that opts a run out of the mandatory REU flags.
NO_REU_ENV = "C64_VICE_NO_REU"


def no_reu_requested(env: dict | None = None) -> bool:
"""Return True when the environment opts out of the REU flags.

:param env: mapping to inspect (defaults to ``os.environ``).
"""
src = os.environ if env is None else env
return str(src.get(NO_REU_ENV, "")).strip().lower() in ("1", "true", "yes", "on")


def default_vice_config(
*,
Expand All@@ -35,6 +60,20 @@ def default_vice_config(
options (e.g. ``-warp``, custom monitor flags) without losing the REU
enablement.

Setting ``C64_VICE_NO_REU=1`` omits the REU flags (and announces it on
stderr). That mode exists to test the REU-less onchip profile — the
shipped ``c64-https-uci-onchip.prg`` claims "no REU required", and this
is how that claim is reproduced:

.. code-block:: sh

make clean && make BACKEND=uci USE_NISTCURVES_ONCHIP=1
C64_SKIP_BUILD=1 C64_VICE_NO_REU=1 \\
python3 tools/test_ecdsa_kat_oracle.py

On any other build the same invocation returns wrong answers without
complaining, which is exactly why REU stays the default.

Remaining keyword arguments are forwarded verbatim to ``ViceConfig``;
typical callers pass ``prg_path``, ``warp``, ``ntsc``, ``sound`` etc.

Expand All@@ -44,7 +83,17 @@ def default_vice_config(
:param kwargs: forwarded to :class:`c64_test_harness.ViceConfig`.
:returns: a configured ``ViceConfig`` instance.
"""
base_args = ["-reu", "-reusize", "512"]
if no_reu_requested():
print(
f"[{NO_REU_ENV}] VICE launching WITHOUT -reu — valid only for "
"USE_NISTCURVES_ONCHIP builds; any REU-profile image will "
"silently compute wrong results.",
file=sys.stderr,
flush=True,
)
base_args: list[str] = []
else:
base_args = ["-reu", "-reusize", "512"]
if extra_args:
base_args = base_args + list(extra_args)
return ViceConfig(extra_args=base_args, **kwargs)
4 changes: 2 additions & 2 deletions tools/package/listener/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,8 +12,8 @@ verbatim so the Commodore 64 client sees exactly what it expects.
## What it does

- Serves **TLS 1.3 only** (min = max pinned to TLS 1.3). The C64 advertises
a single cipher suite, `TLS_AES_128_GCM_SHA256`, which the stdlib server
offers among its TLS 1.3 defaults and selects.
a single cipher suite, `TLS_CHACHA20_POLY1305_SHA256` (0x1303), which the
stdlib server offers among its TLS 1.3 defaults and selects.
- Presents a self-signed **ECDSA P-256** (`secp256r1`, `ecdsa-with-SHA256`)
leaf certificate. The C64 verifies the CertificateVerify signature against
this leaf key, so a freshly generated self-signed cert is sufficient —
Expand Down
5 changes: 3 additions & 2 deletions tools/package/listener/listener.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,8 +10,9 @@
protocol; the C64 client parses a fixed shape):

* TLS 1.3 ONLY (``ssl.PROTOCOL_TLS_SERVER`` pinned min = max = TLSv1_3).
The C64 advertises a single cipher suite, TLS_AES_128_GCM_SHA256; the
stdlib server offers it among its TLS 1.3 defaults and picks it.
The C64 advertises a single cipher suite,
TLS_CHACHA20_POLY1305_SHA256 (0x1303); the stdlib server offers it
among its TLS 1.3 defaults and picks it.
* ECDSA P-256 leaf cert (auto-generated by gen_certs.py if ./certs is
missing). The C64 verifies the CertificateVerify signature against the
leaf key, so a fresh self-signed P-256 cert works.
Expand Down
Loading