#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# check-ssl.sh - diagnose TLS interception / "self signed certificate in chain"
#
# Run this WHILE CONNECTED TO THE PROBLEM WLAN.
#
#   bash check-ssl.sh
#
# It writes everything to  ./ssl-report-<timestamp>/  :
#   report.txt        full human-readable report
#   captured-cas/     every CA certificate the network actually presented
#   captured-bundle.pem  all of them concatenated (this is what we install later)
#
# No sudo, no changes to the system. Read-only diagnosis + cert capture.
# ---------------------------------------------------------------------------
set -uo pipefail

HOSTS=(
  models.opencode.ai
  api.opencode.ai
  opencode.ai
  models.dev
  api.anthropic.com
  registry.npmjs.org
  github.com
  raw.githubusercontent.com
)

# MCP server URLs are discovered from the opencode config at runtime, so no
# private hostname has to be hard-coded in this script.
OC_CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
MCP_URLS=()
while IFS= read -r u; do
  [ -n "$u" ] && MCP_URLS+=("$u")
done < <(
  cat "$OC_CONF_DIR"/opencode.json "$OC_CONF_DIR"/opencode.jsonc \
      "$OC_CONF_DIR"/config.json ./opencode.json ./.mcp.json 2>/dev/null \
  | grep -oE '"url"[[:space:]]*:[[:space:]]*"https?://[^"]+"' \
  | sed 's/.*"\(https\?:\/\/[^"]*\)"/\1/' | sort -u
)

# probe the MCP hosts alongside the fixed list
for u in "${MCP_URLS[@]}"; do
  mh="$(echo "$u" | sed -E 's#^https?://##; s#[:/].*##')"
  case " ${HOSTS[*]} " in *" $mh "*) ;; *) HOSTS+=("$mh") ;; esac
done

TS="$(date +%Y%m%d-%H%M%S)"
OUT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ssl-report-$TS"
CAS="$OUT/captured-cas"
REPORT="$OUT/report.txt"
BUNDLE="$OUT/captured-bundle.pem"
mkdir -p "$CAS"
: > "$REPORT"
: > "$BUNDLE"

# log to both console and report
log() { printf '%s\n' "$*" | tee -a "$REPORT"; }
hdr() { log ""; log "=============================================================="; log "$*"; log "=============================================================="; }

hdr "Environment"
log "date          : $(date -Is)"
log "host          : $(hostname)"
log "distro        : $( . /etc/os-release 2>/dev/null && echo "${PRETTY_NAME:-unknown}" )"
log "openssl       : $(openssl version 2>&1)"
log "curl          : $(curl --version 2>&1 | head -1)"
log "node          : $(command -v node >/dev/null && node --version || echo 'not installed')"
log "opencode      : $(command -v opencode >/dev/null && opencode --version 2>&1 | head -1 || echo 'not installed')"
log ""
log "-- WLAN / routing --"
log "default route : $(ip route 2>/dev/null | awk '/^default/{print; exit}')"
log "DNS servers   : $(grep -s '^nameserver' /etc/resolv.conf | awk '{print $2}' | tr '\n' ' ')"
log ""
log "-- TLS-relevant env vars --"
for v in SSL_CERT_FILE SSL_CERT_DIR REQUESTS_CA_BUNDLE CURL_CA_BUNDLE NODE_EXTRA_CA_CERTS AWS_CA_BUNDLE \
         HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy \
         NODE_TLS_REJECT_UNAUTHORIZED; do
  log "  $(printf '%-28s' "$v") = ${!v:-<unset>}"
done
log ""
log "-- System trust store --"
SYS_BUNDLE=/etc/ssl/certs/ca-certificates.crt
if [ -f "$SYS_BUNDLE" ]; then
  log "  $SYS_BUNDLE : $(grep -c 'BEGIN CERTIFICATE' "$SYS_BUNDLE") certificates"
else
  log "  $SYS_BUNDLE : MISSING"
fi
log "  locally added CAs (/usr/local/share/ca-certificates):"
find /usr/local/share/ca-certificates -type f 2>/dev/null | while read -r f; do
  log "    $f"
  log "        subject: $(openssl x509 -in "$f" -noout -subject 2>/dev/null | sed 's/^subject= *//')"
done
[ -f /usr/local/share/corp-bundle.pem ] && \
  log "  /usr/local/share/corp-bundle.pem : $(grep -c 'BEGIN CERTIFICATE' /usr/local/share/corp-bundle.pem) certificates"

# ---------------------------------------------------------------------------
# Per-host TLS probe
# ---------------------------------------------------------------------------
INTERCEPTED=0
TRUSTED_INTERCEPT=0
FAILED=0

for h in "${HOSTS[@]}"; do
  hdr "HOST: $h"

  raw="$OUT/.raw-$h.txt"
  if ! echo | timeout 20 openssl s_client -connect "$h:443" -servername "$h" -showcerts >"$raw" 2>&1; then
    :  # s_client returns non-zero on verify failure too; keep going
  fi

  if ! grep -q 'BEGIN CERTIFICATE' "$raw"; then
    log "  !! COULD NOT COMPLETE TLS HANDSHAKE"
    log "$(sed 's/^/     /' "$raw" | head -15)"
    FAILED=$((FAILED+1))
    continue
  fi

  log "-- certificate chain as presented --"
  log "$(grep -E '^ *[0-9]+ s:|^ *i:' "$raw" | sed 's/^/  /')"

  VERIFY="$(grep -E 'Verify return code' "$raw" | tail -1 | sed 's/^ *//')"
  log ""
  log "  openssl (system store) : ${VERIFY:-unknown}"

  # curl with system store
  CURL_OUT="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "https://$h/" 2>&1)"
  if [[ "$CURL_OUT" =~ ^[0-9]+$ ]]; then
    log "  curl (system store)    : OK (HTTP $CURL_OUT)"
  else
    log "  curl (system store)    : FAIL - $CURL_OUT"
    FAILED=$((FAILED+1))
  fi

  # node with its own bundled roots + NODE_EXTRA_CA_CERTS as currently set
  if command -v node >/dev/null; then
    NODE_OUT="$(timeout 25 node -e "
      require('https').get('https://$h/', r => { console.log('OK ' + r.statusCode); r.destroy(); process.exit(0); })
        .on('error', e => { console.log('FAIL ' + (e.code || e.message)); process.exit(0); });
    " 2>&1 | tail -1)"
    log "  node (current env)     : $NODE_OUT"
  fi

  # node ignoring NODE_EXTRA_CA_CERTS -> shows what a runtime with only
  # built-in Mozilla roots sees. This is the closest proxy for Bun/opencode.
  if command -v node >/dev/null; then
    NODE_BARE="$(env -u NODE_EXTRA_CA_CERTS -u SSL_CERT_FILE timeout 25 node -e "
      require('https').get('https://$h/', r => { console.log('OK ' + r.statusCode); r.destroy(); process.exit(0); })
        .on('error', e => { console.log('FAIL ' + (e.code || e.message)); process.exit(0); });
    " 2>&1 | tail -1)"
    log "  node (builtin roots)   : $NODE_BARE   <- what Bun/opencode roughly sees"
  fi

  # ---- capture every CA in the presented chain (leaf excluded) -------------
  # This is the reliable way to get the corporate MITM CA: take it straight
  # off the wire instead of hunting through the Windows store.
  awk -v dir="$CAS" -v host="$h" '
    /-----BEGIN CERTIFICATE-----/ { n++; f = dir "/" host "-depth" (n-1) ".pem"; capture=1 }
    capture { print > f }
    /-----END CERTIFICATE-----/ { capture=0 }
  ' "$raw"

  # drop depth0 (the leaf - we never want to trust that)
  rm -f "$CAS/$h-depth0.pem"

  ROOT_ISSUER="$(grep -E '^ *i:' "$raw" | tail -1 | sed 's/^ *i: *//')"
  log ""
  log "  top-of-chain issuer    : $ROOT_ISSUER"

  # heuristic: is this a known public CA, or something corporate?
  if echo "$ROOT_ISSUER" | grep -qiE 'digicert|globalsign|let.?s encrypt|isrg|google trust|amazon|sectigo|comodo|baltimore|entrust|godaddy|usertrust|verisign|thawte|geotrust|certum|buypass|identrust'; then
    log "  verdict                : looks like a PUBLIC CA (no interception on this host)"
  elif echo "$VERIFY" | grep -q '0 (ok)'; then
    log "  verdict                : intercepted, but the CA is TRUSTED - working as intended"
    TRUSTED_INTERCEPT=$((TRUSTED_INTERCEPT+1))
  else
    log "  verdict                : *** INTERCEPTED AND NOT TRUSTED - install the CA ***"
    INTERCEPTED=$((INTERCEPTED+1))
  fi

  rm -f "$raw"
done

# ---------------------------------------------------------------------------
# MCP endpoints
# ---------------------------------------------------------------------------
hdr "MCP endpoint check"

if [ "${#MCP_URLS[@]}" -eq 0 ]; then
  log "  no MCP servers found in $OC_CONF_DIR - skipped"
else
  for u in "${MCP_URLS[@]}"; do
    mh="$(echo "$u" | sed -E 's#^https?://##; s#[:/].*##')"
    log ""
    log "-- $u --"

    log "  DNS A    : $(getent ahostsv4 "$mh" 2>/dev/null | awk '{print $1}' | sort -u | tr '\n' ' ')"
    log "  DNS AAAA : $(getent ahostsv6 "$mh" 2>/dev/null | awk '{print $1}' | sort -u | tr '\n' ' ')"

    # TCP reachability per address family
    if timeout 10 bash -c "</dev/tcp/$(getent ahostsv4 "$mh" 2>/dev/null | awk 'NR==1{print $1}')/443" 2>/dev/null; then
      log "  TCP/443 v4 : open"
    else
      log "  TCP/443 v4 : BLOCKED / unreachable"
    fi

    # No credentials are sent. An HTTP 401 proves TLS + HTTP round-trip works
    # end to end, which is exactly what we need to know.
    BODY="$OUT/.mcpbody"
    CODE="$(curl -sS -o "$BODY" -w '%{http_code}' --max-time 25 "$u" 2>"$OUT/.mcperr")"
    if [ -n "$CODE" ] && [ "$CODE" != "000" ]; then
      log "  GET  status: $CODE"
      log "  server hdr : $(curl -sSI --max-time 20 "$u" 2>/dev/null | grep -i '^server:' | tr -d '\r' | head -1)"
      log "  body (first 200 chars):"
      log "    $(head -c 200 "$BODY" 2>/dev/null | tr '\n' ' ' | tr -d '\r')"
      case "$CODE" in
        401|403)
          if grep -qiE 'block|denied|policy|filter|proxy|forbidden by' "$BODY" 2>/dev/null; then
            log "  !! body looks like a PROXY BLOCK PAGE, not the MCP server"
          else
            log "  -> looks like a genuine auth challenge from the MCP server (good)"
          fi ;;
        2*|405|406) log "  -> endpoint reachable" ;;
        *) log "  -> unexpected status; compare against the hotspot baseline" ;;
      esac
    else
      log "  GET  status: FAILED - $(tr -d '\n' < "$OUT/.mcperr" | head -c 300)"
    fi

    # JSON-RPC initialize, unauthenticated - shows whether the proxy mangles POST/streaming
    RPC="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 25 \
            -X POST -H 'Content-Type: application/json' \
            -H 'Accept: application/json, text/event-stream' \
            -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
            "$u" 2>&1 | tail -1)"
    log "  POST rpc   : $RPC"

    rm -f "$BODY" "$OUT/.mcperr"
  done
fi

# ---------------------------------------------------------------------------
# opencode itself
# ---------------------------------------------------------------------------
hdr "opencode runtime check"
OC_LOG="$HOME/.local/share/opencode/log/opencode.log"
if command -v opencode >/dev/null; then
  # remember where the log ends now, so we only report NEW lines and don't
  # get confused by errors from previous sessions on other networks
  BEFORE=0
  [ -f "$OC_LOG" ] && BEFORE=$(wc -l < "$OC_LOG")
  log "triggering opencode (models fetch) ..."
  timeout 45 opencode models >/dev/null 2>&1

  log ""
  log "-- opencode mcp list --"
  log "$(timeout 90 opencode mcp list 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | sed 's/^/  /')"
  log ""

  if [ -f "$OC_LOG" ]; then
    NEW="$(tail -n "+$((BEFORE+1))" "$OC_LOG" \
           | grep -ia 'self signed\|self-signed\|UNABLE_TO_VERIFY\|CERT_\|certificate\|mcp\|ECONNREFUSED\|ETIMEDOUT\|ENOTFOUND\|EAI_AGAIN\|fetch failed\|level=ERROR' \
           | tail -25 | sed 's/^/  /')"
    if [ -n "$NEW" ]; then
      log "errors/MCP lines produced by THIS run:"
      log "$NEW"
    else
      log "  no new errors from this run - opencode looks healthy."
    fi
  else
    log "  no opencode log at $OC_LOG"
  fi
else
  log "  opencode not on PATH - skipped"
fi

# ---------------------------------------------------------------------------
# Build the captured bundle
# ---------------------------------------------------------------------------
hdr "Captured CA certificates"
COUNT=0
# de-duplicate by fingerprint
declare -A SEEN
for f in "$CAS"/*.pem; do
  [ -e "$f" ] || continue
  FP="$(openssl x509 -in "$f" -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2)"
  SUBJ="$(openssl x509 -in "$f" -noout -subject 2>/dev/null | sed 's/^subject= *//')"
  ISS="$(openssl x509 -in "$f" -noout -issuer 2>/dev/null | sed 's/^issuer= *//')"
  EXP="$(openssl x509 -in "$f" -noout -enddate 2>/dev/null | cut -d= -f2)"
  if [ -z "$FP" ] || [ -n "${SEEN[$FP]:-}" ]; then rm -f "$f"; continue; fi
  SEEN[$FP]=1
  COUNT=$((COUNT+1))
  log ""
  log "  [$COUNT] $(basename "$f")"
  log "       subject : $SUBJ"
  log "       issuer  : $ISS"
  log "       expires : $EXP"
  {
    echo "# Subject: $SUBJ"
    echo "# Issuer : $ISS"
    echo "# Expires: $EXP"
    openssl x509 -in "$f" 2>/dev/null
    echo
  } >> "$BUNDLE"
done
log ""
log "  $COUNT unique CA certificate(s) captured -> $BUNDLE"

# ---------------------------------------------------------------------------
hdr "SUMMARY"
log "  hosts probed                    : ${#HOSTS[@]}"
log "  hosts that failed               : $FAILED"
log "  intercepted, CA trusted (fine)  : $TRUSTED_INTERCEPT"
log "  intercepted, NOT trusted (bad)  : $INTERCEPTED"
log ""
if [ "$INTERCEPTED" -eq 0 ] && [ "$TRUSTED_INTERCEPT" -gt 0 ] && [ "$FAILED" -eq 0 ]; then
  log "  => This network intercepts TLS, but the corporate CA is installed and"
  log "     every probed host verifies. Nothing to do."
elif [ "$INTERCEPTED" -gt 0 ]; then
  log "  => TLS interception detected. The CA(s) doing it are in:"
  log "       $BUNDLE"
  log "     That file is actually what needs to be trusted (system store AND"
  log "     NODE_EXTRA_CA_CERTS, because opencode is a Bun binary that ignores"
  log "     SSL_CERT_FILE and the system store)."
else
  log "  => No interception detected on this network for these hosts."
  log "     If opencode still failed above, the cause is the runtime's trust"
  log "     store, not the network."
fi
log ""
log "  Full report : $REPORT"
log "  Captured CAs: $CAS"
log ""
log "  Bring this whole folder back:  $OUT"
