#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS" "$REPRO_DIR"
cd "$ROOT"

# Keep the complete session diagnostic visible, but do not bind this actively-written
# file into runtime_manifest.json.
exec > >(tee "$LOGS/reproduction_steps.log") 2>&1

CACHE_CONTEXT="$ROOT/project_cache_context.json"
REPO_FALLBACK="$ROOT/artifacts/curl"
CACHE_ROOT=""
if [ -r "$CACHE_CONTEXT" ] && command -v jq >/dev/null 2>&1 && \
   [ "$(jq -r '.prepared // false' "$CACHE_CONTEXT" 2>/dev/null)" = true ]; then
  candidate="$(jq -r '.project_cache_dir // empty' "$CACHE_CONTEXT")"
  if [ -n "$candidate" ] && mkdir -p "$candidate" 2>/dev/null && [ -w "$candidate" ]; then
    CACHE_ROOT="$candidate"
  fi
fi
if [ -n "$CACHE_ROOT" ]; then
  REPO="$CACHE_ROOT/repo"
  DEPS="$CACHE_ROOT/deps-cve-2026-82208"
  BUILD_ROOT="$CACHE_ROOT/build-cve-2026-82208"
else
  REPO="$REPO_FALLBACK"
  DEPS="$ROOT/artifacts/deps-cve-2026-82208"
  BUILD_ROOT="$ROOT/artifacts/build-cve-2026-82208"
fi
mkdir -p "$(dirname "$REPO")" "$DEPS" "$BUILD_ROOT"

FIXED_COMMIT="ed0338befd1d865a8ea1fbaa90013a096dedd07a"
CURL_URL="https://github.com/curl/curl.git"
WOLFSSL_URL="https://github.com/wolfSSL/wolfssl.git"
WOLFSSL_REF="v5.8.4-stable"
WOLFSSL_EXPECTED_COMMIT="59f4fa568615396fbf381b073b220d1e8d61e4c2"

write_failure_manifest() {
  python3 - "$REPRO_DIR/runtime_manifest.json" <<'PY'
import json, sys
with open(sys.argv[1], "w", encoding="utf-8") as f:
    json.dump({
      "entrypoint_kind": "function_call",
      "entrypoint_detail": "TLS transfers through libcurl wolfSSL backend with CA cache and CURLOPT_SSL_CTX_FUNCTION",
      "service_started": False,
      "healthcheck_passed": False,
      "target_path_reached": False,
      "runtime_stack": ["libcurl", "wolfSSL", "local Python HTTPS peer"],
      "proof_artifacts": [],
      "artifact_sha256": {},
      "notes": "Runtime attempt did not reach the confirmation gate; inspect logs/reproduction_steps.log"
    }, f, indent=2)
    f.write("\n")
PY
}
write_failure_manifest

need_tools=""
for tool in autoconf automake libtoolize pkg-config make gcc openssl; do
  command -v "$tool" >/dev/null 2>&1 || need_tools="$need_tools $tool"
done
if [ -n "$need_tools" ]; then
  echo "Installing required build tools:$need_tools"
  sudo apt-get update
  sudo apt-get install -y autoconf automake libtool pkg-config make gcc g++ openssl ca-certificates
fi

if [ ! -d "$REPO/.git" ]; then
  git clone --filter=blob:none "$CURL_URL" "$REPO"
fi
git -C "$REPO" remote set-url origin "$CURL_URL"
git -C "$REPO" fetch --force origin "$FIXED_COMMIT"
FIXED_RESOLVED="$(git -C "$REPO" rev-parse FETCH_HEAD^{commit})"
VULN_COMMIT="$(git -C "$REPO" rev-parse "$FIXED_RESOLVED^")"
if [ "${FIXED_RESOLVED#${FIXED_COMMIT}}" = "$FIXED_RESOLVED" ]; then
  echo "Resolved fixed commit $FIXED_RESOLVED does not match ticket prefix $FIXED_COMMIT" >&2
  exit 1
fi

echo "Vulnerable commit: $VULN_COMMIT"
echo "Fixed commit:      $FIXED_RESOLVED"
git -C "$REPO" diff "$VULN_COMMIT" "$FIXED_RESOLVED" -- lib/vtls/wolfssl.c > "$LOGS/fix.patch"
if ! grep -q 'x509_store_setup = TRUE' "$LOGS/fix.patch"; then
  echo "Expected wolfSSL X.509 setup flag patch not found" >&2
  exit 1
fi
# Verify the vulnerable function preamble lacks the moved assignment while the
# fixed function preamble contains it, rather than trusting a release label.
python3 - "$REPO" "$VULN_COMMIT" "$FIXED_RESOLVED" <<'PY_PATCH'
import subprocess, sys
repo, vuln, fixed = sys.argv[1:]
def preamble(commit):
    src = subprocess.check_output(["git", "-C", repo, "show", commit + ":lib/vtls/wolfssl.c"], text=True)
    start = src.index("CURLcode Curl_wssl_setup_x509_store")
    end = src.index("/* Consider the X509 store cacheable", start)
    return src[start:end]
assert "x509_store_setup = TRUE" not in preamble(vuln), "vulnerable checkout already contains patch"
assert "x509_store_setup = TRUE" in preamble(fixed), "fixed checkout lacks patch"
PY_PATCH

WOLF_SRC="$DEPS/wolfssl-src"
WOLF_PREFIX="$DEPS/wolfssl-prefix"
if [ ! -d "$WOLF_SRC/.git" ]; then
  git clone --depth 1 --branch "$WOLFSSL_REF" "$WOLFSSL_URL" "$WOLF_SRC"
fi
git -C "$WOLF_SRC" fetch --depth 1 origin "refs/tags/$WOLFSSL_REF:refs/tags/$WOLFSSL_REF" || true
git -C "$WOLF_SRC" checkout --detach "$WOLFSSL_REF"
WOLF_COMMIT="$(git -C "$WOLF_SRC" rev-parse HEAD)"
if [ "$WOLF_COMMIT" != "$WOLFSSL_EXPECTED_COMMIT" ]; then
  echo "wolfSSL tag identity mismatch: expected $WOLFSSL_EXPECTED_COMMIT, got $WOLF_COMMIT" >&2
  exit 1
fi

if [ ! -f "$WOLF_PREFIX/lib/pkgconfig/wolfssl.pc" ]; then
  rm -rf "$WOLF_PREFIX"
  mkdir -p "$WOLF_PREFIX"
  (cd "$WOLF_SRC" && ./autogen.sh)
  (cd "$WOLF_SRC" && ./configure \
    --prefix="$WOLF_PREFIX" \
    --enable-opensslextra \
    --enable-curl \
    --disable-shared \
    --enable-static)
  make -C "$WOLF_SRC" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)"
  make -C "$WOLF_SRC" install
fi

build_curl() {
  role="$1"
  commit="$2"
  src="$BUILD_ROOT/src-$role"
  out="$BUILD_ROOT/out-$role"
  prefix="$BUILD_ROOT/prefix-$role"
  if ! git -C "$src" rev-parse --git-dir >/dev/null 2>&1; then
    rm -rf "$src"
    git -C "$REPO" worktree add --detach "$src" "$commit"
  fi
  git -C "$src" checkout --detach "$commit"
  actual="$(git -C "$src" rev-parse HEAD)"
  [ "$actual" = "$commit" ] || { echo "$role worktree identity mismatch" >&2; exit 1; }
  if [ ! -f "$prefix/.cve-2026-82208-build-v2" ]; then
    rm -rf "$out" "$prefix"
    mkdir -p "$out" "$prefix"
    (cd "$src" && autoreconf -fi)
    (cd "$out" && PKG_CONFIG_PATH="$WOLF_PREFIX/lib/pkgconfig" "$src/configure" \
      --prefix="$prefix" \
      --with-wolfssl="$WOLF_PREFIX" \
      --disable-shared --enable-static \
      --without-libpsl --without-libidn2 --without-librtmp \
      --without-libssh2 --without-zstd --without-brotli \
      --without-ca-bundle --without-ca-path --without-nghttp2 \
      --disable-docs --disable-ldap --disable-ldaps --disable-rtsp --disable-dict \
      --disable-file --disable-ftp --disable-gopher --disable-imap \
      --disable-mqtt --disable-pop3 --disable-smb --disable-smtp \
      --disable-telnet --disable-tftp --disable-manual)
    make -C "$out" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)"
    make -C "$out" install
    touch "$prefix/.cve-2026-82208-build-v2"
  fi
}

build_curl vulnerable "$VULN_COMMIT"
build_curl fixed "$FIXED_RESOLVED"

CERT_DIR="$BUILD_ROOT/certs"
mkdir -p "$CERT_DIR"
if [ ! -f "$CERT_DIR/ca.pem" ] || [ ! -f "$CERT_DIR/server.pem" ]; then
  rm -f "$CERT_DIR"/*
  openssl req -x509 -newkey rsa:2048 -nodes -days 2 \
    -subj '/CN=CVE-2026-82208 Test CA' \
    -keyout "$CERT_DIR/ca.key" -out "$CERT_DIR/ca.pem" >/dev/null 2>&1
  openssl req -newkey rsa:2048 -nodes -subj '/CN=localhost' \
    -keyout "$CERT_DIR/server.key" -out "$CERT_DIR/server.csr" >/dev/null 2>&1
  cat > "$CERT_DIR/ext.cnf" <<'EOF_CERT'
subjectAltName=DNS:localhost,IP:127.0.0.1
extendedKeyUsage=serverAuth
EOF_CERT
  openssl x509 -req -days 2 -in "$CERT_DIR/server.csr" \
    -CA "$CERT_DIR/ca.pem" -CAkey "$CERT_DIR/ca.key" -CAcreateserial \
    -extfile "$CERT_DIR/ext.cnf" -out "$CERT_DIR/server.pem" >/dev/null 2>&1
fi

SERVER_PY="$BUILD_ROOT/https_peer.py"
cat > "$SERVER_PY" <<'PY'
import http.server, ssl, sys
class Handler(http.server.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def do_GET(self):
        body = b"CVE-2026-82208 peer response\n"
        self.send_response(200)
        self.send_header("Content-Length", str(len(body)))
        self.send_header("Connection", "close")
        self.end_headers()
        self.wfile.write(body)
        self.close_connection = True
        print("SERVER_REQUEST path=%s" % self.path, flush=True)
    def log_message(self, fmt, *args):
        print("SERVER_LOG " + (fmt % args), flush=True)
class Server(http.server.ThreadingHTTPServer):
    allow_reuse_address = True
port, cert, key = int(sys.argv[1]), sys.argv[2], sys.argv[3]
s = Server(("127.0.0.1", port), Handler)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(cert, key)
s.socket = ctx.wrap_socket(s.socket, server_side=True)
print("SERVER_LISTEN port=%d" % port, flush=True)
s.serve_forever()
PY

HARNESS_C="$BUILD_ROOT/repro.c"
cat > "$HARNESS_C" <<'C'
#include <curl/curl.h>
#define WOLFSSL_USE_OPTIONS_H
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static size_t sink(char *p, size_t s, size_t n, void *u) {
  (void)p; (void)u; return s * n;
}

static CURLcode replace_with_empty_store(CURL *curl, void *sslctx, void *parm) {
  WOLFSSL_CTX *ctx = (WOLFSSL_CTX *)sslctx;
  WOLFSSL_X509_STORE *store;
  (void)curl; (void)parm;
  store = wolfSSL_X509_STORE_new();
  if(!store) {
    fprintf(stderr, "CALLBACK_STORE_ALLOC_FAILED\n");
    return CURLE_OUT_OF_MEMORY;
  }
  wolfSSL_CTX_set_cert_store(ctx, store);
  fprintf(stderr, "CALLBACK_EMPTY_STORE_INSTALLED\n");
  return CURLE_OK;
}

static CURLcode run_one(CURLM *multi, const char *url, const char *ca,
                        int strict_callback, long *http_code) {
  CURL *easy = curl_easy_init();
  CURLMcode mc;
  CURLcode result = CURLE_FAILED_INIT;
  int running = 0;
  int msgs = 0;
  CURLMsg *msg;
  if(!easy) return CURLE_OUT_OF_MEMORY;
  curl_easy_setopt(easy, CURLOPT_URL, url);
  curl_easy_setopt(easy, CURLOPT_CAINFO, ca);
  curl_easy_setopt(easy, CURLOPT_CA_CACHE_TIMEOUT, 600L);
  curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
  curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
  curl_easy_setopt(easy, CURLOPT_FRESH_CONNECT, 1L);
  curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1L);
  curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, sink);
  curl_easy_setopt(easy, CURLOPT_TIMEOUT, 10L);
  curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
  if(strict_callback)
    curl_easy_setopt(easy, CURLOPT_SSL_CTX_FUNCTION, replace_with_empty_store);
  mc = curl_multi_add_handle(multi, easy);
  if(mc != CURLM_OK) goto out;
  do {
    mc = curl_multi_perform(multi, &running);
    if(mc != CURLM_OK) break;
    if(running) {
      int nfds = 0;
      mc = curl_multi_poll(multi, NULL, 0, 1000, &nfds);
      if(mc != CURLM_OK) break;
    }
  } while(running);
  while((msg = curl_multi_info_read(multi, &msgs))) {
    if(msg->msg == CURLMSG_DONE && msg->easy_handle == easy)
      result = msg->data.result;
  }
  curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, http_code);
  curl_multi_remove_handle(multi, easy);
out:
  curl_easy_cleanup(easy);
  return result;
}

int main(int argc, char **argv) {
  CURLM *multi;
  CURLcode first, second;
  long first_http = 0, second_http = 0;
  if(argc != 4) {
    fprintf(stderr, "usage: %s vulnerable|fixed URL CA\n", argv[0]);
    return 2;
  }
  if(curl_global_init(CURL_GLOBAL_DEFAULT)) return 2;
  multi = curl_multi_init();
  if(!multi) return 2;
  first = run_one(multi, argv[2], argv[3], 0, &first_http);
  fprintf(stderr, "FIRST_RESULT code=%d name=%s http=%ld\n",
          (int)first, curl_easy_strerror(first), first_http);
  second = run_one(multi, argv[2], argv[3], 1, &second_http);
  fprintf(stderr, "SECOND_RESULT code=%d name=%s http=%ld\n",
          (int)second, curl_easy_strerror(second), second_http);
  curl_multi_cleanup(multi);
  curl_global_cleanup();
  if(first != CURLE_OK) return 3;
  if(!strcmp(argv[1], "vulnerable")) {
    if(second == CURLE_OK && second_http == 200) {
      fprintf(stderr, "VULNERABILITY_CONFIRMED cached_CA_overrode_callback_policy\n");
      return 0;
    }
    fprintf(stderr, "VULNERABILITY_NOT_OBSERVED\n");
    return 4;
  }
  if(second == CURLE_PEER_FAILED_VERIFICATION || second == CURLE_SSL_CACERT ||
     second == CURLE_SSL_CACERT_BADFILE) {
    fprintf(stderr, "FIXED_NEGATIVE_CONTROL callback_policy_enforced\n");
    return 0;
  }
  fprintf(stderr, "FIXED_CONTROL_UNEXPECTED_RESULT\n");
  return 5;
}
C

compile_harness() {
  role="$1"
  prefix="$BUILD_ROOT/prefix-$role"
  pc="$prefix/lib/pkgconfig"
  cc="${CC:-cc}"
  "$cc" -O2 -Wall -Wextra -o "$BUILD_ROOT/repro-$role" "$HARNESS_C" \
    -I"$prefix/include" -I"$WOLF_PREFIX/include" \
    "$prefix/lib/libcurl.a" "$WOLF_PREFIX/lib/libwolfssl.a" \
    -lz -lpthread -lm -ldl
  "$BUILD_ROOT/repro-$role" --help >/dev/null 2>&1 || [ "$?" -eq 2 ]
}
compile_harness vulnerable
compile_harness fixed

# Select an unused localhost port.
PORT="$(python3 - <<'PY'
import socket
s=socket.socket(); s.bind(('127.0.0.1', 0)); print(s.getsockname()[1]); s.close()
PY
)"
SERVER_DIAG="$LOGS/https-peer.log"
python3 "$SERVER_PY" "$PORT" "$CERT_DIR/server.pem" "$CERT_DIR/server.key" >"$SERVER_DIAG" 2>&1 &
SERVER_PID=$!
cleanup() {
  kill "$SERVER_PID" 2>/dev/null || true
  wait "$SERVER_PID" 2>/dev/null || true
}
trap cleanup EXIT
for _ in $(seq 1 100); do
  grep -q 'SERVER_LISTEN' "$SERVER_DIAG" 2>/dev/null && break
  kill -0 "$SERVER_PID" 2>/dev/null || { cat "$SERVER_DIAG"; exit 1; }
  sleep 0.05
done
grep -q 'SERVER_LISTEN' "$SERVER_DIAG" || { echo "HTTPS peer did not become ready"; exit 1; }
URL="https://localhost:$PORT/ca-cache-test"

for role in vulnerable fixed; do
  for attempt in 1 2; do
    log="$REPRO_DIR/${role}-attempt-${attempt}.log"
    echo "Running $role attempt $attempt"
    timeout 30 "$BUILD_ROOT/repro-$role" "$role" "$URL" "$CERT_DIR/ca.pem" >"$log" 2>&1
    cat "$log"
  done
done

# Freeze the peer transcript after all requests before hashing it.
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
trap - EXIT
cp "$SERVER_DIAG" "$REPRO_DIR/https-peer.log"

grep -q 'VULNERABILITY_CONFIRMED' "$REPRO_DIR/vulnerable-attempt-1.log"
grep -q 'VULNERABILITY_CONFIRMED' "$REPRO_DIR/vulnerable-attempt-2.log"
grep -q 'FIXED_NEGATIVE_CONTROL' "$REPRO_DIR/fixed-attempt-1.log"
grep -q 'FIXED_NEGATIVE_CONTROL' "$REPRO_DIR/fixed-attempt-2.log"
[ "$(grep -c 'SERVER_REQUEST' "$REPRO_DIR/https-peer.log")" -eq 6 ]

cat > "$REPRO_DIR/target-identity.log" <<EOF_IDENTITY
repository=$CURL_URL
vulnerable_commit=$VULN_COMMIT
fixed_commit=$FIXED_RESOLVED
wolfssl_repository=$WOLFSSL_URL
wolfssl_commit=$WOLF_COMMIT
backend=wolfSSL
ca_cache_timeout=600
callback_policy=replace trust store with empty store
EOF_IDENTITY

IDENTITY="git:${CURL_URL}@${VULN_COMMIT}"
TARGET_DIGEST="$(printf '%s' "$IDENTITY" | sha256sum | awk '{print $1}')"
PROOFS=(
  "repro/vulnerable-attempt-1.log"
  "repro/vulnerable-attempt-2.log"
  "repro/fixed-attempt-1.log"
  "repro/fixed-attempt-2.log"
  "repro/https-peer.log"
  "repro/target-identity.log"
)
python3 - "$REPRO_DIR/runtime_manifest.json" "$CURL_URL" "$VULN_COMMIT" "$TARGET_DIGEST" "$WOLF_COMMIT" "${PROOFS[@]}" <<'PY'
import hashlib, json, os, platform, sys
out, repo, commit, digest, wolf_commit, *paths = sys.argv[1:]
root = os.environ["PRUVA_ROOT"]
checksums = {}
for rel in paths:
    with open(os.path.join(root, rel), "rb") as f:
        checksums[rel] = hashlib.sha256(f.read()).hexdigest()
data = {
  "entrypoint_kind": "function_call",
  "entrypoint_detail": "Two sequential TLS transfers through libcurl's wolfSSL backend using CA caching; the second installs an empty trust store via CURLOPT_SSL_CTX_FUNCTION",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": ["libcurl multi API", "wolfSSL", "local Python HTTPS peer"],
  "target_identity": {
    "repository_url": repo,
    "commit_sha": commit,
    "target_digest": digest,
    "platform": "linux",
    "architecture": platform.machine()
  },
  "proof_artifacts": paths,
  "artifact_sha256": checksums,
  "notes": "Vulnerable and fixed commits tested twice; wolfSSL dependency commit " + wolf_commit
}
with open(out, "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2)
    f.write("\n")
PY
python3 -m json.tool "$REPRO_DIR/runtime_manifest.json" >/dev/null

echo "CVE-2026-82208 CONFIRMED: vulnerable CA-cache hit overrides callback-selected trust store; fixed commit fails closed."
