Files
OpenFUT/scripts/roster-cert-verify.py
T
funman300 fc55de19fa test(fifa17-tls): reproducible isolated confirmation of the roster-cert fix
Runs the REAL roster_server.py under `sudo unshare -n` (so port 8081 is free and
production is never touched) and validates its certificate BY THE DIALED IP, proving the
before/after the SAN fix (fbc0da2) targets:

  * OLD cert (DNS-only, production's current shape) -> a by-IP-verifying client is
    rejected with "IP address mismatch, certificate is not valid for '127.0.0.1'" — the
    certificate_unknown class the FIFA client hit.
  * NEW cert (fixed generator, DNS + IP SANs) -> verifies through the actual roster
    server and returns the roster XML (200, application/xml).

roster-cert-verify.py is the client probe (trusts the served self-signed cert as CA,
checks it against the dialed IP, then GETs /fifa17/fut/rosterupdate.xml).
roster-cert-iso-test.sh drives the real server with each cert and asserts new=pass,
old=fail. Two harness bugs were found and fixed while writing it (a shared /tmp log the
production run owns, and a subshell pid that left the first server alive so the "old"
probe hit a stale server presenting the new cert — the tell was "self-signed" instead
of "IP mismatch"), so the final before/after is clean.

Complements the in-process check: this exercises the production server code path, not a
hand-rolled server. Live production confirmation still needs the container rebuilt with
OPENFUT_ADVERTISE set (operator-gated).
2026-08-18 16:37:19 +00:00

64 lines
2.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Connect to the roster server and VERIFY its cert by the dialed IP, then GET the roster.
Mirrors the client's failing path: dial the roster by IP over TLS and validate the
presented certificate against that IP. Exit 0 only if the cert verifies AND the roster
XML comes back 200; exit 1 on cert-verify failure (the certificate_unknown class).
argv: <cafile> <dial_ip> <port>
"""
import socket
import ssl
import sys
cafile, dial_ip, port = sys.argv[1], sys.argv[2], int(sys.argv[3])
ctx = ssl.create_default_context(cafile=cafile) # trust the server's self-signed cert as CA
ctx.check_hostname = True
try:
ctx.minimum_version = ssl.TLSVersion.TLSv1
except Exception:
pass
try:
ctx.set_ciphers("ALL:@SECLEVEL=0")
except Exception:
pass
try:
raw = socket.create_connection((dial_ip, port), timeout=8)
except Exception as e:
print(f" CONNECT-FAIL {type(e).__name__}: {e}")
sys.exit(2)
try:
# server_hostname is the IP the roster is dialed by; ssl matches it against the
# cert's iPAddress SANs — exactly the check the DNS-only cert failed.
s = ctx.wrap_socket(raw, server_hostname=dial_ip)
except ssl.SSLCertVerificationError as e:
print(f" VERIFY-FAIL {e.verify_message or e}")
sys.exit(1)
except Exception as e:
print(f" TLS-FAIL {type(e).__name__}: {e}")
sys.exit(1)
try:
ver, cipher = s.version(), s.cipher()[0]
req = (f"GET /fifa17/fut/rosterupdate.xml HTTP/1.1\r\nHost: {dial_ip}:{port}\r\n"
"Connection: close\r\n\r\n")
s.sendall(req.encode())
buf = b""
while True:
chunk = s.recv(4096)
if not chunk:
break
buf += chunk
s.close()
status = buf.split(b"\r\n", 1)[0].decode(errors="replace")
body = buf.split(b"\r\n\r\n", 1)[1] if b"\r\n\r\n" in buf else b""
print(f" VERIFIED {ver} {cipher}")
print(f" {status} body={len(body)}B head={body[:60]!r}")
sys.exit(0 if status.endswith("200 OK") and body else 3)
except Exception as e:
print(f" GET-FAIL {type(e).__name__}: {e}")
sys.exit(3)