writeonce/scripts/tls-server-accept.sh
shoney.arickathil dd426d0581 test(tls): rv2 8 phase E — both AEADs cross-checked against openssl over the wire
- tls-server-accept.sh: each probe pins openssl s_client's -ciphersuites — ec/ChaCha20-Poly1305, rsa/AES-128-GCM, plus openssl's default list whose first suite (AES-256-GCM) the server must skip — 5/0
- tls-accept.sh: the Python/OpenSSL stub prints the negotiated suite; the happy-path ok line carries it — 5/0 (ChaCha under the peer's server-preference default)
- rv2 8 story: E landed (real-protocol interop replaces the infeasible `openssl enc` AEAD check); D (encrypted-cookie wrapper) re-homed to porch as the consumer's phase after porch 2 — fork auto-approved, review_pending; status: done
- porch 2: the encrypted-cookie out-of-scope bullet now points at the landed primitives and names the wrapper as its follow-on
- board row rv2 8: in-progress -> done

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ac3bf74da4f45f624d7d3b440c8bcf17f4aec3a9)
2026-09-15 01:16:13 +02:00

101 lines
5 KiB
Bash
Executable file

#!/usr/bin/env bash
# scripts/tls-server-accept.sh — runtime-v2 9 phase G's gate: inbound TLS 1.3.
# The runtime suite proves the server FSM offline (loopback against the client
# driver); this proves interop with a real client — `openssl s_client`
# validating our hand-rolled server handshake and exchanging application data,
# for both an ECDSA-P256 and an RSA server certificate. Each probe pins the
# client's cipher suite, so both AEADs run against the reference peer over the
# wire (rv2 8 phase E's cross-check), plus one probe with openssl's default
# list, whose first entry we do not implement. Log: /tmp/tls-server.log.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WOC="$ROOT/compiler/_build/default/bin/woc"
WOVM="$ROOT/runtime/wovm"
APP="$ROOT/docs/examples/tls-server"
PORT="${TLS_SERVER_PORT:-18553}"
LOG=/tmp/tls-server.log
pass=0; fail=0
ok() { echo "ok $1"; pass=$((pass + 1)); }
bad() { echo "FAIL $1"; fail=$((fail + 1)); }
WORK="$(mktemp -d)"
SRV_PID=""
cleanup() { [[ -n "$SRV_PID" ]] && kill -KILL "$SRV_PID" 2>/dev/null; rm -rf "$WORK"; }
trap cleanup EXIT
mkdir -p "$WORK/data"
[[ -x "$WOVM" ]] || { echo "tls-server: wovm not built — run: make -C runtime wovm" >&2; exit 1; }
[[ -x "$WOC" ]] || { echo "tls-server: woc not built — run: just woc-build" >&2; exit 1; }
command -v openssl >/dev/null || { echo "tls-server: needs openssl" >&2; exit 1; }
python3 -c 'import cryptography' 2>/dev/null || { echo "tls-server: needs python3 cryptography" >&2; exit 1; }
# ---- 1. a test CA + an EC leaf and an RSA leaf (SAN localhost) ------------
cat > "$WORK/mk.py" <<'PY'
import datetime, sys
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization as ser
from cryptography.hazmat.primitives.asymmetric import ec, rsa
d = sys.argv[1]
NB = datetime.datetime(2020,1,1); NA = datetime.datetime(2035,1,1)
def nm(cn): return x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)])
cak = ec.generate_private_key(ec.SECP256R1())
ca = (x509.CertificateBuilder().subject_name(nm("wo-test-ca")).issuer_name(nm("wo-test-ca"))
.public_key(cak.public_key()).serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA)
.add_extension(x509.BasicConstraints(ca=True, path_length=None), True).sign(cak, hashes.SHA256()))
open(d+"/ca.pem","wb").write(ca.public_bytes(ser.Encoding.PEM))
def leaf(key, tag):
c = (x509.CertificateBuilder().subject_name(nm("localhost")).issuer_name(ca.subject)
.public_key(key.public_key()).serial_number(x509.random_serial_number())
.not_valid_before(NB).not_valid_after(NA)
.add_extension(x509.SubjectAlternativeName([x509.DNSName("localhost")]), False)
.add_extension(x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), False)
.sign(cak, hashes.SHA256()))
open(d+"/"+tag+".pem","wb").write(c.public_bytes(ser.Encoding.PEM))
open(d+"/"+tag+".key","wb").write(key.private_bytes(ser.Encoding.PEM,
ser.PrivateFormat.PKCS8, ser.NoEncryption()))
leaf(ec.generate_private_key(ec.SECP256R1()), "ec")
leaf(rsa.generate_private_key(public_exponent=65537, key_size=2048), "rsa")
PY
python3 "$WORK/mk.py" "$WORK" || { bad "cert generation"; echo "tls-server: $fail failures"; exit 1; }
ok "test CA + EC leaf + RSA leaf (SAN localhost, EKU serverAuth) generated"
# ---- 2. build the server -------------------------------------------------
{ echo; echo "== tls-server-accept $(date -Is) port $PORT =="; } >>"$LOG"
if "$WOC" --emit "$APP" -o "$WORK/srv.wob" 2>"$WORK/cerr"; then
ok "build: tls-server compiles"
else
bad "build: $(head -3 "$WORK/cerr")"; echo "tls-server: $fail failures"; exit 1
fi
# ---- 3. openssl s_client interop, per key type ---------------------------
probe() { # $1 = tag (ec|rsa) ; $2 = port (distinct per probe — avoids a bind race) ; $3 = openssl -ciphersuites list
local p="$2"
kill -KILL "$SRV_PID" 2>/dev/null
WO_DATA="$WORK/data" "$WOVM" "$WORK/srv.wob" "$p" "$WORK/$1.pem" "$WORK/$1.key" >>"$LOG" 2>&1 &
SRV_PID=$!; disown "$SRV_PID" 2>/dev/null || true
for _ in $(seq 1 100); do
if ( exec 3<>"/dev/tcp/127.0.0.1/$p" ) 2>/dev/null; then break; fi
sleep 0.05
done
local out
out="$({ printf 'GET / HTTP/1.0\r\n\r\n'; sleep 1; } | \
timeout 12 openssl s_client -connect "127.0.0.1:$p" -CAfile "$WORK/ca.pem" \
-servername localhost -tls1_3 -ciphersuites "$3" -verify_return_error -quiet 2>/dev/null)"
if [[ "$out" == *"hello-wo-tls"* ]]; then
ok "$1 [$3]: openssl s_client validated the cert + got the reply"
else
bad "$1 [$3]: no reply (out: ${out:0:80})"
fi
}
probe ec "$PORT" TLS_CHACHA20_POLY1305_SHA256
probe rsa "$((PORT + 1))" TLS_AES_128_GCM_SHA256
# openssl's default order: the server must skip the AES-256 suite it does not
# implement and pick from the rest
probe ec "$((PORT + 2))" TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
echo "tls-server: $((pass + fail)) checks, $fail failures"
[[ $fail -eq 0 ]]