#!/usr/bin/env bash
# ca-rotate-intermediate.sh — Rotate the broad-announce intermediate CA
# against the existing root. Run annually (~30 days before the
# intermediate's not_after). The root key never moves.
#
# Usage:
#   scripts/cert-manager/ca-rotate-intermediate.sh <env> <root-dir> [<output-dir>]
#
# Examples:
#   scripts/cert-manager/ca-rotate-intermediate.sh dev /secure/pki/dev
#   scripts/cert-manager/ca-rotate-intermediate.sh prod /secure/pki/prod /tmp/pki-new
#
# Args:
#   env          dev, staging, prod (matches the dir name from ca-init.sh)
#   root-dir     Directory containing root-ca.key + root-ca.crt (offline)
#   output-dir   Where to write the new intermediate. Defaults to root-dir
#                (overwrites in place). If you want a side-by-side, pass
#                a different dir and swap manually.
#
# After this script:
#   - You have a new intermediate-ca.{key,crt,chain.pem} in output-dir.
#   - You need to:
#     1. Update the K8s Secret holding the intermediate (sealed-secrets /
#        external-secrets rotate).
#     2. Trigger cert-manager to re-issue all serving certs that
#        reference this CA (kubectl annotate certificate -n broad-announce
#        --all cert-manager.io/issue-temporary-certificate=true, or
#        delete+recreate the Certificate CRs).
#     3. Verify the rotation with scripts/cert-manager/ca-verify.sh.
#
# Safety: this script does NOT touch the root key. If the root itself
# needs rotation (every 10 years, or compromise), use ca-rotate-root.sh
# (separate runbook — see docs/runbooks/mtls-incident.md).
#
# Requires: openssl 3.x, jq, the existing root-ca.key + root-ca.crt.

set -euo pipefail

ENV_NAME="${1:-}"
ROOT_DIR="${2:-}"
OUTPUT_DIR="${3:-$ROOT_DIR}"

if [[ -z "$ENV_NAME" || -z "$ROOT_DIR" ]]; then
  echo "usage: $0 <env> <root-dir> [<output-dir>]" >&2
  exit 1
fi

for bin in openssl jq; do
  if ! command -v "$bin" >/dev/null 2>&1; then
    echo "FATAL: $bin not found in PATH" >&2
    exit 1
  fi
done

ROOT_KEY="$ROOT_DIR/root-ca.key"
ROOT_CRT="$ROOT_DIR/root-ca.crt"

if [[ ! -f "$ROOT_KEY" || ! -f "$ROOT_CRT" ]]; then
  echo "FATAL: root-ca.key or root-ca.crt missing in $ROOT_DIR" >&2
  exit 1
fi

mkdir -p "$OUTPUT_DIR"
chmod 700 "$OUTPUT_DIR"

# Passphrase handling — same as ca-init.sh
if [[ -n "${BA_CA_PASSPHRASE:-}" ]]; then
  ROOT_PASS_ARGS=(-passin "env:BA_CA_PASSPHRASE")
else
  ROOT_PASS_ARGS=()
fi

# Check current intermediate expiry — refuse to rotate if not within 30d of expiry
CURRENT_INT_CRT="$ROOT_DIR/intermediate-ca.crt"
if [[ -f "$CURRENT_INT_CRT" ]]; then
  NOT_AFTER_EPOCH=$(openssl x509 -in "$CURRENT_INT_CRT" -noout -enddate | cut -d'=' -f2 | xargs -I{} date -d "{}" +%s 2>/dev/null || openssl x509 -in "$CURRENT_INT_CRT" -noout -enddate | cut -d'=' -f2 | xargs -I{} date -j -f "%b %d %H:%M:%S %Y %Z" "{}" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (NOT_AFTER_EPOCH - NOW_EPOCH) / 86400 ))
  echo "current intermediate expires in $DAYS_LEFT days"
  if [[ $DAYS_LEFT -gt 60 && "${ROTATE_FORCE:-}" != "1" ]]; then
    echo "FATAL: refusing to rotate more than 60 days before expiry" >&2
    echo "  current: $DAYS_LEFT days left" >&2
    echo "  re-run closer to expiry, or set ROTATE_FORCE=1 (DANGEROUS)" >&2
    exit 1
  fi
fi

INT_KEY="$OUTPUT_DIR/intermediate-ca.key"
INT_CRT="$OUTPUT_DIR/intermediate-ca.crt"
INT_CSR="$OUTPUT_DIR/intermediate-ca.csr"
INT_CHAIN="$OUTPUT_DIR/intermediate-ca-chain.pem"
INT_BACKUP="$OUTPUT_DIR/intermediate-ca.previous.$(date -u +%Y%m%d).pem"

# Back up the current intermediate (if any) before overwriting
if [[ -f "$INT_CRT" && -f "$INT_KEY" && "$OUTPUT_DIR" == "$ROOT_DIR" ]]; then
  echo "backing up current intermediate to $INT_BACKUP"
  cp "$INT_CRT" "$INT_BACKUP"
  chmod 644 "$INT_BACKUP"
fi

echo "=== broad-announce intermediate CA rotation ==="
echo "env:         $ENV_NAME"
echo "root dir:    $ROOT_DIR"
echo "output dir:  $OUTPUT_DIR"
echo

echo "[1/4] Generating new intermediate CA key (ECDSA P-256)..."
openssl ecparam -name prime256v1 -genkey -noout -out "$INT_KEY"
chmod 600 "$INT_KEY"

echo "[2/4] Creating CSR..."
openssl req -new -sha256 -key "$INT_KEY" \
  -subj "/CN=broad-announce Intermediate CA ($ENV_NAME)/O=broad-announce/OU=PKI" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign,digitalSignature" \
  -addext "extendedKeyUsage=serverAuth,clientAuth" \
  -addext "subjectKeyIdentifier=hash" \
  -out "$INT_CSR"

echo "[3/4] Signing new intermediate with root (1y)..."
openssl x509 -req -in "$INT_CSR" -CA "$ROOT_CRT" -CAkey "$ROOT_KEY" "${ROOT_PASS_ARGS[@]}" \
  -CAcreateserial -sha384 -days 365 \
  -extfile <(cat <<'EOF'
basicConstraints=critical,CA:TRUE,pathlen:0
keyUsage=critical,keyCertSign,cRLSign,digitalSignature
extendedKeyUsage=serverAuth,clientAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
EOF
) \
  -out "$INT_CRT"
chmod 644 "$INT_CRT"

cat "$INT_CRT" "$ROOT_CRT" > "$INT_CHAIN"
chmod 644 "$INT_CHAIN"

echo "[4/4] Verifying new chain..."
openssl verify -CAfile "$ROOT_CRT" "$INT_CRT"

NEW_FP=$(openssl x509 -in "$INT_CRT" -noout -fingerprint -sha256 | cut -d'=' -f2)
NEW_NOT_AFTER=$(openssl x509 -in "$INT_CRT" -noout -enddate | cut -d'=' -f2)

cat > "$OUTPUT_DIR/ca-bundle.json" <<EOF
{
  "env": "$ENV_NAME",
  "rotated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "root": {
    "cert_pem": "$ROOT_CRT",
    "fingerprint_sha256": "$(openssl x509 -in "$ROOT_CRT" -noout -fingerprint -sha256 | cut -d'=' -f2)",
    "path": "$(realpath "$ROOT_CRT")"
  },
  "intermediate": {
    "key_pem": "$INT_KEY",
    "cert_pem": "$INT_CRT",
    "chain_pem": "$INT_CHAIN",
    "fingerprint_sha256": "$NEW_FP",
    "not_after": "$NEW_NOT_AFTER",
    "path": "$(realpath "$INT_CRT")"
  },
  "next_steps": [
    "1. Re-import the intermediate into K8s (sealed-secrets / external-secrets rotate).",
    "2. Restart cert-manager controller: kubectl rollout restart deploy/cert-manager -n cert-manager",
    "3. Re-issue serving certs: kubectl delete certificates --all -n broad-announce && kubectl apply -f deploy/cert-manager/serving-certs.yaml",
    "4. Run scripts/cert-manager/ca-verify.sh to confirm all certs valid."
  ]
}
EOF
chmod 644 "$OUTPUT_DIR/ca-bundle.json"

echo
echo "=== done ==="
echo "NEW INTERMEDIATE FINGERPRINT: $NEW_FP"
echo "expires: $NEW_NOT_AFTER"
echo
echo "next: re-import to K8s and re-issue serving certs (see ca-bundle.json)"
