#!/bin/bash

# OCSP server configuration
# Defines where the OCSP responder will be accessible in the Docker network
OCSP_URL="http://ocsp.localhost.example:8888"

# Base directory for all certificate-related files
# All paths in this script will be relative to this directory
CERT_DIR="./_data/certs"
XMPP_BASE_DIR="./_data/xmpp"

# Check if OpenSSL is installed
if ! command -v openssl &> /dev/null; then
    echo "Error: OpenSSL is not installed"
    exit 1
fi

# Function to check which server directories exist
get_server_instances() {
    local instances=()
    # Look for numbered directories in the xmpp base directory
    for dir in "$XMPP_BASE_DIR"/*/; do
        if [[ -d "$dir" ]]; then
            # Extract the number from the directory name
            local num=$(basename "$dir")
            if [[ "$num" =~ ^[0-9]+$ ]]; then
                instances+=("$num")
            fi
        fi
    done
    echo "${instances[@]}"
}

# Create the PKI directory structure:
# We only need:
# - root-ca: Root Certificate Authority files
# - intermediate-ca: Intermediate Certificate Authority files
# - ocsp-responder: OCSP responder certificates
mkdir -p "${CERT_DIR}/ca/root-ca/private" \
         "${CERT_DIR}/ca/intermediate-ca/private" \
         "${CERT_DIR}/ca/ocsp-responder"
chmod 700 "${CERT_DIR}/ca/root-ca/private" "${CERT_DIR}/ca/intermediate-ca/private"

# Initialize the certificate databases and serial number counters
# index.txt acts as a database of all certificates
# serial defines the next certificate serial number
echo -n > "${CERT_DIR}/ca/intermediate-ca/index.txt"
echo 1000 > "${CERT_DIR}/ca/intermediate-ca/serial"

# Generate the Root CA private key and certificate
# This is the top-level certificate authority that signs the intermediate CA
openssl genrsa -out "${CERT_DIR}/ca/root-ca/private/ca.key" 4096

openssl req -new -x509 -key "${CERT_DIR}/ca/root-ca/private/ca.key" \
    -out "${CERT_DIR}/ca/root-ca/ca.crt" -days 3650 \
    -subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=Test Openfire Root CA"

# Generate the Intermediate CA private key and certificate signing request (CSR)
# The intermediate CA will be used to sign the server and OCSP responder certificates
openssl genrsa -out "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" 4096

openssl req -new -key "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" \
    -out "${CERT_DIR}/ca/intermediate-ca/intermediate.csr" \
    -subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=Test Openfire Intermediate CA"

# Sign the Intermediate CA certificate with the Root CA
# The certificate includes:
# - CA capabilities (basicConstraints)
# - Permission to sign certificates (keyUsage)
# - Location of the OCSP responder (authorityInfoAccess)
openssl x509 -req -in "${CERT_DIR}/ca/intermediate-ca/intermediate.csr" \
    -CA "${CERT_DIR}/ca/root-ca/ca.crt" \
    -CAkey "${CERT_DIR}/ca/root-ca/private/ca.key" -CAcreateserial \
    -out "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" -days 1825 \
    -extfile <(printf 'basicConstraints=critical,CA:true,pathlen:0\nkeyUsage=critical,digitalSignature,keyCertSign,cRLSign\nauthorityInfoAccess=OCSP;URI:%s' "$OCSP_URL")

# Function to generate a server certificate and add it to the certificate database
# Parameters:
#   $1: instance number (1, 2, or 3 for xmpp1/xmpp2/xmpp3)
generate_server_cert() {
    local instance=$1
    local server_name="xmpp${instance}"

    # Generate server private key and CSR
    openssl genrsa -out "${CERT_DIR}/${server_name}.key" 2048

    # Create OpenSSL config file for SAN support
    cat > "${CERT_DIR}/${server_name}.cnf" << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
C = GB
ST = London
L = London
O = Test Openfire
CN = ${server_name}.localhost.example

[v3_req]
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${server_name}.localhost.example
DNS.2 = *.${server_name}.localhost.example
EOF

    # Generate CSR with the config file
    openssl req -new -key "${CERT_DIR}/${server_name}.key" \
        -out "${CERT_DIR}/${server_name}.csr" \
        -config "${CERT_DIR}/${server_name}.cnf"

    # Create OpenSSL config for certificate signing
    cat > "${CERT_DIR}/${server_name}_sign.cnf" << EOF
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
authorityInfoAccess = OCSP;URI:${OCSP_URL}

[alt_names]
DNS.1 = ${server_name}.localhost.example
DNS.2 = *.${server_name}.localhost.example
EOF

    # Sign the server certificate with the Intermediate CA
    openssl x509 -req -in "${CERT_DIR}/${server_name}.csr" \
        -CA "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" \
        -CAkey "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" -CAcreateserial \
        -out "${CERT_DIR}/${server_name}.crt" -days 365 \
        -extfile "${CERT_DIR}/${server_name}_sign.cnf"

    # Create the full certificate chain
    cat "${CERT_DIR}/${server_name}.crt" \
        "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" \
        "${CERT_DIR}/ca/root-ca/ca.crt" > "${CERT_DIR}/${server_name}_chain.pem"

    # Add to certificate database
    SERIAL=$(openssl x509 -in "${CERT_DIR}/${server_name}.crt" -noout -serial | cut -d'=' -f2)
    SUBJECT=$(openssl x509 -in "${CERT_DIR}/${server_name}.crt" -noout -subject | cut -d'=' -f2-)
    printf 'V\t%s\t\t%s\tunknown\t%s\n' "$(date -u +%y%m%d%H%M%SZ)" "$SERIAL" "$SUBJECT" >> "${CERT_DIR}/ca/intermediate-ca/index.txt"

    # Clean up temporary config files
    rm -f "${CERT_DIR}/${server_name}.cnf" "${CERT_DIR}/${server_name}_sign.cnf"

    # Display the certificate's subject and SANs for verification
    echo "Certificate generated for ${server_name}:"
    openssl x509 -in "${CERT_DIR}/${server_name}.crt" -noout -subject -ext subjectAltName
}

# Generate the OCSP responder certificate
# This certificate will be used to sign OCSP responses
openssl genrsa -out "${CERT_DIR}/ca/ocsp-responder/ocsp.key" 2048

openssl req -new -key "${CERT_DIR}/ca/ocsp-responder/ocsp.key" \
    -out "${CERT_DIR}/ca/ocsp-responder/ocsp.csr" \
    -subj "/C=GB/ST=London/L=London/O=Test Openfire/CN=ocsp.example.com"

# Sign the OCSP responder certificate
# The certificate includes:
# - Non-CA status (basicConstraints)
# - OCSP signing capability (extendedKeyUsage)
openssl x509 -req -in "${CERT_DIR}/ca/ocsp-responder/ocsp.csr" \
    -CA "${CERT_DIR}/ca/intermediate-ca/intermediate.crt" \
    -CAkey "${CERT_DIR}/ca/intermediate-ca/private/intermediate.key" -CAcreateserial \
    -out "${CERT_DIR}/ca/ocsp-responder/ocsp.crt" -days 365 \
    -extfile <(printf 'basicConstraints=critical,CA:false\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=OCSPSigning')

# Get the list of server instances that exist
SERVER_INSTANCES=($(get_server_instances))

if [ ${#SERVER_INSTANCES[@]} -eq 0 ]; then
    echo "Error: No server directories found in ${XMPP_BASE_DIR}"
    exit 1
fi

echo "Found server directories for instances: ${SERVER_INSTANCES[*]}"

# Generate certificates for each existing server instance
for instance in "${SERVER_INSTANCES[@]}"; do
    echo "Generating certificates for xmpp${instance}"
    generate_server_cert "$instance"
done

# Verify the certificate chain for all generated certificates
# This ensures that all certificates are properly signed and trusted
echo "Verifying certificates..."
openssl verify -CAfile "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt"

# Verify each server certificate
for instance in "${SERVER_INSTANCES[@]}"; do
    openssl verify -CAfile <(cat "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt") \
        "${CERT_DIR}/xmpp${instance}.crt"
done

openssl verify -CAfile <(cat "${CERT_DIR}/ca/root-ca/ca.crt" "${CERT_DIR}/ca/intermediate-ca/intermediate.crt") \
    "${CERT_DIR}/ca/ocsp-responder/ocsp.crt"

printf '\nCertificate generation complete.'
printf '\nOCSP URL configured as: %s\n' "$OCSP_URL"
printf 'Generated certificates for servers: %s\n\n' "${SERVER_INSTANCES[*]}"
