#!/bin/bash
# Generate every soft (file-based) client certificate permutation via the
# signing API, ready for ./test-soft-certs.sh
#
# Produces 3 leaf key types x 3 signing CAs = 9 key/cert pairs named
#   <name>-<leafkeytype>-ca-<catype>.key / .csr / .crt
# plus the three root CA certificates the test script needs.
#
# Usage: ./make-soft-certs.sh <name> [--force]
#   ./make-soft-certs.sh ep2            # skip pairs that already exist
#   ./make-soft-certs.sh ep2 --force    # regenerate everything
#
# Needs OpenSSL 3.5+ (for ML-DSA) and either jq or python3 to read the API
# response.

set -u

NAME="${1:-}"
FORCE="${2:-}"

if [ -z "$NAME" ]; then
    echo "Usage: $0 <name> [--force]"
    exit 1
fi

HOST=sctest.yksi.me
OPENSSL="${OPENSSL_BIN:-openssl}"

# Leaf key algorithms, keyed by the label used in the filename.
declare -A KEYGEN=(
    [rsa]="-algorithm RSA -pkeyopt rsa_keygen_bits:2048"
    [ecdsa]="-algorithm EC -pkeyopt ec_paramgen_curve:P-256"
    [mldsa]="-algorithm ML-DSA-44"
    [mldsa65]="-algorithm ML-DSA-65"
    [mldsa87]="-algorithm ML-DSA-87"
)

# Signing CAs: filename label -> API caType -> local root cert filename.
declare -A CA_API=( [ca-rsa]=rsa [ca-ecdsa]=ecdsa [ca-mldsa]=mldsa [ca-mldsa65]=mldsa65 [ca-mldsa87]=mldsa87 )
declare -A CA_FILE=(
    [ca-rsa]=YK-RSA-Root-CA.crt
    [ca-ecdsa]=YK-ECDSA-Root-CA.crt
    [ca-mldsa]=YK-ML-DSA-44-Root-CA.crt
    [ca-mldsa65]=YK-ML-DSA-65-Root-CA.crt
    [ca-mldsa87]=YK-ML-DSA-87-Root-CA.crt
)

# ---------------------------------------------------------------------------

if ! "$OPENSSL" list -signature-algorithms 2>/dev/null | grep -qi 'ML-DSA'; then
    echo "ERROR: $OPENSSL has no ML-DSA support (needs OpenSSL 3.5+)."
    echo "       Current: $("$OPENSSL" version)"
    echo "       Set OPENSSL_BIN to a 3.5+ binary if you have one elsewhere."
    exit 1
fi

# Pull the signed certificate out of the JSON response.
if command -v jq >/dev/null 2>&1; then
    extract_cert() { jq -r '.certificate // empty'; }
    extract_err()  { jq -r '.error // empty'; }
elif command -v python3 >/dev/null 2>&1; then
    extract_cert() { python3 -c 'import sys,json;print(json.load(sys.stdin).get("certificate",""),end="")'; }
    extract_err()  { python3 -c 'import sys,json;print(json.load(sys.stdin).get("error",""),end="")'; }
else
    echo "ERROR: need jq or python3 to parse the API response."
    exit 1
fi

made=0; skipped=0; failed=0

echo "Downloading root CA certificates..."
for ca in ca-rsa ca-ecdsa ca-mldsa ca-mldsa65 ca-mldsa87; do
    out="${CA_FILE[$ca]}"
    if curl -sfk "https://$HOST/api/cert/ca/${CA_API[$ca]}" -o "$out"; then
        echo "  $out"
    else
        echo "  FAILED to download $out"
        failed=$((failed+1))
    fi
done

for kt in rsa ecdsa mldsa mldsa65 mldsa87; do
    for ca in ca-rsa ca-ecdsa ca-mldsa ca-mldsa65 ca-mldsa87; do
        slug="$NAME-$kt-$ca"

        if [ -f "$slug.key" ] && [ -f "$slug.crt" ] && [ "$FORCE" != "--force" ]; then
            echo "SKIP  $slug (already exists; --force to regenerate)"
            skipped=$((skipped+1))
            continue
        fi

        echo ""
        echo "=== $slug  ($kt leaf, signed by ${ca#ca-} CA) ==="

        # 1. private key
        if ! "$OPENSSL" genpkey ${KEYGEN[$kt]} -out "$slug.key" 2>/dev/null; then
            echo "  FAILED: could not generate $kt key"
            failed=$((failed+1))
            continue
        fi

        # 2. CSR. CN carries the full slug so the nine certs stay tellable
        #    apart in a browser or Windows certificate picker.
        if ! "$OPENSSL" req -new -key "$slug.key" -out "$slug.csr" \
                -subj "/CN=$slug" 2>/dev/null; then
            echo "  FAILED: could not build CSR"
            failed=$((failed+1))
            continue
        fi

        # 3. hand the CSR to the signing API
        resp=$(curl -sk -F "testerName=$NAME" -F "caType=${CA_API[$ca]}" \
                    -F "csr=@$slug.csr" "https://$HOST/api/cert/request")

        cert=$(printf '%s' "$resp" | extract_cert 2>/dev/null)

        if [ -z "$cert" ]; then
            echo "  FAILED: API returned no certificate"
            err=$(printf '%s' "$resp" | extract_err 2>/dev/null)
            echo "  ${err:-$resp}" | head -2 | sed 's/^/  /'
            rm -f "$slug.crt"
            failed=$((failed+1))
            continue
        fi

        printf '%s' "$cert" > "$slug.crt"

        # 4. verify the pair actually matches before declaring success --
        #    this is the exact failure that cost us a debugging round.
        certpub=$("$OPENSSL" x509 -in "$slug.crt" -noout -pubkey 2>/dev/null | "$OPENSSL" sha256)
        keypub=$("$OPENSSL" pkey -in "$slug.key" -pubout 2>/dev/null | "$OPENSSL" sha256)

        if [ -z "$certpub" ] || [ "$certpub" != "$keypub" ]; then
            echo "  FAILED: signed cert does not match the generated key"
            failed=$((failed+1))
            continue
        fi

        issuer=$("$OPENSSL" x509 -in "$slug.crt" -noout -issuer 2>/dev/null)
        echo "  OK  key+cert match, $issuer"
        made=$((made+1))
    done
done

echo ""
echo "generated=$made skipped=$skipped failed=$failed"
echo ""
if [ $failed -eq 0 ]; then
    echo "Now run:  ./test-soft-certs.sh $NAME"
fi
[ $failed -eq 0 ]
