From fc411bb6f166208bcb264328535d70957df715cc Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 11 Aug 2026 04:38:23 +0000 Subject: [PATCH] scripts: require one certificate across the whole FIFA-facing TLS stack Two live gates were lost to a second variable I had been asked to eliminate. The Rust redirector was pointed at the repo's fifa17-recon/tools/redir_cert.pem (fingerprint F9:16:1A...), while the running container serves a different cert baked into its image (E7:F9:46...) which the Python redirector, roster and the rest of the stack all share. So the A/B compared TLS implementation AND certificate identity at once. FIFA 17's ProtoSSL caches the server certificate for a backend. The redirector is the first TLS connection of a session, so its cert becomes the one the client expects; the next service presenting a different cert fails its handshake. That is why the redirect itself always succeeded and the failure surfaced later, on the roster fetch -- "An error occurred downloading the FUT Squad Update". It stayed invisible because Python's socketserver swallows it: a handshake failure at accept() raises ssl.SSLError, which subclasses OSError and is discarded by _handle_request_noblock. No request log, no stderr. Every server looked healthy while the client could not talk to any of them. Confirmed on the wire: tls-observe in front of the roster server captured four ClientHellos from the client, correct SNI and the same 8 static-RSA suites it offers the redirector, none of which produced a request. The check is mutation-tested against the real bug: with a redirector started on the stale repo cert it exits 1 and names the mismatch. --- scripts/check-tls-parity.sh | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100755 scripts/check-tls-parity.sh diff --git a/scripts/check-tls-parity.sh b/scripts/check-tls-parity.sh new file mode 100755 index 0000000..4e94060 --- /dev/null +++ b/scripts/check-tls-parity.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Every FIFA-facing TLS service must present the SAME certificate. +# +# check-tls-parity.sh [ ...] +# +# WHY +# +# FIFA 17's ProtoSSL caches the server certificate it saw for a backend. The +# redirector is the first TLS connection of a session, so whatever cert it +# presents becomes the one the client expects from that backend. If a later +# service — roster, UTAS — presents a different cert, that handshake fails. +# +# Python's socketserver makes this invisible: a handshake failure at accept() +# raises ssl.SSLError, which subclasses OSError and is silently swallowed by +# _handle_request_noblock. No request log, no stderr, nothing. The observable +# symptom is a client that logs in fine and then cannot download the FUT Squad +# Update, with every server looking healthy. +# +# This cost two live gates. The Rust redirector was pointed at the repo's +# fifa17-recon/tools/redir_cert.pem while the running container serves a +# different cert baked into its image — so the A/B had two variables, TLS +# implementation AND certificate identity, when it was meant to have one. +# +# Run this before arming any gate that puts a new TLS implementation in front +# of the client. +set -uo pipefail + +[[ $# -ge 2 ]] || { echo "usage: check-tls-parity.sh [...]" >&2; exit 2; } + +fingerprint() { + timeout 8 openssl s_client -connect "$1" /dev/null \ + | openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2 +} + +ref=""; ref_target=""; rc=0 +for target in "$@"; do + fp="$(fingerprint "$target")" + if [[ -z "$fp" ]]; then + printf " %-24s %s\n" "$target" "NO TLS / unreachable" + rc=1 + continue + fi + printf " %-24s %s\n" "$target" "$fp" + if [[ -z "$ref" ]]; then + ref="$fp"; ref_target="$target" + elif [[ "$fp" != "$ref" ]]; then + rc=1 + fi +done + +if [[ $rc -eq 0 ]]; then + echo "OK: all $# services present the same certificate" +else + echo "REFUSING: certificate mismatch across the FIFA-facing stack (reference: $ref_target)" >&2 + echo " A client that caches the first cert it sees will fail on the others." >&2 +fi +exit $rc