#!/usr/bin/env bash
#
# armature-ca.sh — Certificate provisioning for Armature mTLS.
#
# Wraps openssl to generate a cluster CA, node certificates (ServerAuth +
# ClientAuth), and user certificates (ClientAuth only). All output is PEM.
#
# Usage:
#   armature-ca init
#   armature-ca issue-node --name <node-name> --san <dns1,ip1,...>
#   armature-ca issue-user --cn <username> [--email <email>]
#
# Files are written to the current directory under ca/, nodes/, users/.

set -euo pipefail

CA_DIR="./ca"
NODES_DIR="./nodes"
USERS_DIR="./users"
CA_DAYS=3650       # 10 years for CA
NODE_DAYS=365      # 1 year for nodes
USER_DAYS=90       # 90 days for users

# ── Helpers ──────────────────────────────────────────────────────────

die() { echo "error: $*" >&2; exit 1; }

require_openssl() {
  command -v openssl >/dev/null 2>&1 || die "openssl not found in PATH"
}

require_ca() {
  [ -f "$CA_DIR/cluster-ca.key" ] || die "CA not initialized. Run: $0 init"
  [ -f "$CA_DIR/cluster-ca.crt" ] || die "CA not initialized. Run: $0 init"
}

# ── init ─────────────────────────────────────────────────────────────

cmd_init() {
  require_openssl
  mkdir -p "$CA_DIR"

  if [ -f "$CA_DIR/cluster-ca.key" ]; then
    die "CA already exists at $CA_DIR/cluster-ca.key — remove it first to reinitialize"
  fi

  # Generate ECDSA P-256 CA key
  openssl ecparam -genkey -name prime256v1 -noout -out "$CA_DIR/cluster-ca.key" 2>/dev/null

  # Self-signed CA certificate
  openssl req -new -x509 \
    -key "$CA_DIR/cluster-ca.key" \
    -out "$CA_DIR/cluster-ca.crt" \
    -days "$CA_DAYS" \
    -subj "/CN=Armature Cluster CA" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,keyCertSign,cRLSign" \
    2>/dev/null

  echo "CA initialized:"
  echo "  $CA_DIR/cluster-ca.crt"
  echo "  $CA_DIR/cluster-ca.key"
  echo ""
  echo "Keep cluster-ca.key secure. It never needs to be on a running node."
}

# ── issue-node ───────────────────────────────────────────────────────

cmd_issue_node() {
  require_openssl
  require_ca

  local name=""
  local san=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) name="$2"; shift 2 ;;
      --san)  san="$2";  shift 2 ;;
      *) die "unknown option: $1" ;;
    esac
  done

  [ -n "$name" ] || die "usage: $0 issue-node --name <node-name> --san <dns1,ip1,...>"
  [ -n "$san" ]  || die "usage: $0 issue-node --name <node-name> --san <dns1,ip1,...>"

  mkdir -p "$NODES_DIR"

  # Generate node key
  openssl ecparam -genkey -name prime256v1 -noout -out "$NODES_DIR/$name.key" 2>/dev/null

  # Build SAN entries from comma-separated list
  local san_entries=""
  IFS=',' read -ra ADDRS <<< "$san"
  for addr in "${ADDRS[@]}"; do
    addr=$(echo "$addr" | xargs) # trim whitespace
    if [[ "$addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
      san_entries="${san_entries}IP:${addr},"
    else
      san_entries="${san_entries}DNS:${addr},"
    fi
  done
  san_entries="${san_entries%,}" # remove trailing comma

  # Create CSR
  openssl req -new \
    -key "$NODES_DIR/$name.key" \
    -out "$NODES_DIR/$name.csr" \
    -subj "/CN=$name" \
    2>/dev/null

  # Create extensions config
  local ext_file
  ext_file=$(mktemp)
  cat > "$ext_file" <<EOF
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = ${san_entries}
EOF

  # Sign with CA
  openssl x509 -req \
    -in "$NODES_DIR/$name.csr" \
    -CA "$CA_DIR/cluster-ca.crt" \
    -CAkey "$CA_DIR/cluster-ca.key" \
    -CAcreateserial \
    -out "$NODES_DIR/$name.crt" \
    -days "$NODE_DAYS" \
    -extfile "$ext_file" \
    2>/dev/null

  rm -f "$NODES_DIR/$name.csr" "$ext_file"

  echo "Node cert issued ($NODE_DAYS days):"
  echo "  $NODES_DIR/$name.crt"
  echo "  $NODES_DIR/$name.key"
  echo ""
  echo "Deploy to the node along with $CA_DIR/cluster-ca.crt"
}

# ── issue-user ───────────────────────────────────────────────────────

cmd_issue_user() {
  require_openssl
  require_ca

  local cn=""
  local email=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --cn)    cn="$2";    shift 2 ;;
      --email) email="$2"; shift 2 ;;
      *) die "unknown option: $1" ;;
    esac
  done

  [ -n "$cn" ] || die "usage: $0 issue-user --cn <username> [--email <email>]"

  mkdir -p "$USERS_DIR"

  # Generate user key
  openssl ecparam -genkey -name prime256v1 -noout -out "$USERS_DIR/$cn.key" 2>/dev/null

  # Build subject
  local subj="/CN=$cn"
  local san_line=""
  if [ -n "$email" ]; then
    san_line="email:${email}"
  fi

  # Create CSR
  openssl req -new \
    -key "$USERS_DIR/$cn.key" \
    -out "$USERS_DIR/$cn.csr" \
    -subj "$subj" \
    2>/dev/null

  # Create extensions config
  local ext_file
  ext_file=$(mktemp)
  cat > "$ext_file" <<EOF
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = clientAuth
EOF

  if [ -n "$san_line" ]; then
    echo "subjectAltName = ${san_line}" >> "$ext_file"
  fi

  # Sign with CA
  openssl x509 -req \
    -in "$USERS_DIR/$cn.csr" \
    -CA "$CA_DIR/cluster-ca.crt" \
    -CAkey "$CA_DIR/cluster-ca.key" \
    -CAcreateserial \
    -out "$USERS_DIR/$cn.crt" \
    -days "$USER_DAYS" \
    -extfile "$ext_file" \
    2>/dev/null

  rm -f "$USERS_DIR/$cn.csr" "$ext_file"

  echo "User cert issued ($USER_DAYS days):"
  echo "  $USERS_DIR/$cn.crt"
  echo "  $USERS_DIR/$cn.key"
  echo ""
  echo "Import both files into the user's browser or CLI tool."
}

# ── Main dispatch ────────────────────────────────────────────────────

case "${1:-}" in
  init)
    shift
    cmd_init "$@"
    ;;
  issue-node)
    shift
    cmd_issue_node "$@"
    ;;
  issue-user)
    shift
    cmd_issue_user "$@"
    ;;
  *)
    echo "Usage: $0 <command>"
    echo ""
    echo "Commands:"
    echo "  init                                    Initialize cluster CA"
    echo "  issue-node --name <n> --san <addrs>     Issue node cert (server+client auth)"
    echo "  issue-user --cn <name> [--email <e>]    Issue user cert (client auth only)"
    exit 1
    ;;
esac
