#!/bin/bash
set -eux  # Exit on error, print commands

# The root CA for running the tests with --protocol=ssl
COMMON_CA=common/ca/ca.cnf

# The root CAs used in IceSSL/configuration tests
CONFIGURATION_CAS=$(find configuration -name 'ca*.cnf')

# The intermediate CAs used in IceSSL/configuration tests
INTERMEDIATE_CAS=$(find configuration -name 'i*.cnf')

# The client and server certificates generated by the script.
CLIENT_CERTS=$(find . -name 'client*.cnf')
SERVER_CERTS=$(find . -name 'server*.cnf')

# The root CAs generated by the script.
ROOT_CAS="${COMMON_CA} ${CONFIGURATION_CAS}"

# Default days for certificates, we use 398 for compatibility with macOs requirements.
DEFAULT_DAYS=398

# Default password for PKCS12 files
DEFAULT_PASSWORD="password"

# -legacy is needed for compatibility macOS Keychain, it doesn't support OpenSSL 3.0 defaults
COMMON_PKCS12_ARGS=(-legacy -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1)

# Initialize the CA directory structure.
init_ca_dir(){
    ca_dir=$1
    # Remove existing files
    rm -rf "${ca_dir}"/newcerts "${ca_dir}"/crl "${ca_dir}"/index.txt* "${ca_dir}"/serial* "${ca_dir}"/crlnumber

    # Create the required directories
    mkdir -p "${ca_dir}/newcerts" "${ca_dir}/crl"
    touch "${ca_dir}/index.txt"
    echo "1000" > "${ca_dir}/serial"
    echo "1000" > "${ca_dir}/crlnumber"
}

# If the filename contains "expired" or "notyet", run the command with date time adjusted by faketime, otherwise run
# the command normally.
run_with_faketime_if_needed() {
    local filename="$1"
    shift
    local cmd=("$@")

    local offset=""
    if [[ "$filename" == *expired* ]]; then
        offset="-$((DEFAULT_DAYS + 1))d"
    elif [[ "$filename" == *notyet* ]]; then
        offset="+$((DEFAULT_DAYS + 1))d"
    fi

    if [[ -n "$offset" ]]; then
        faketime -f "$offset" "${cmd[@]}"
    else
        "${cmd[@]}"
    fi
}

# Add the certificate chain up to the ROOT CA without including it.
add_cert_chain() {
    local cert_file="$1"
    local ca_cert="$2"

    ca_dir=$(dirname "${ca_cert}")
    parent_ca_dir=$(dirname "${ca_dir}")
    parent_ca_cert="${parent_ca_dir}"/$(basename "${parent_ca_dir}")_cert.pem

    # If the ca_cert is not the root CA, add it to the chain and do a recursive call to add the parent CA certificate
    # to the chain.
    if [ -f "${parent_ca_cert}" ]; then
        cat "${ca_cert}" >> "${cert_file}"
        add_cert_chain "${cert_file}" "${parent_ca_cert}"
    fi
}

# Create self-signed root CA certificates
for ca in ${ROOT_CAS}; do
    outputdir=$(dirname "${ca}")
    alias=$(basename "${outputdir}")
    cert_file="${outputdir}"/$(basename "${ca}" .cnf)_cert.pem
    cert_der_file="${outputdir}"/$(basename "${ca}" .cnf)_cert.der
    key_file="${outputdir}"/$(basename "${ca}" .cnf)_key.pem
    pkcs12_file="${outputdir}"/$(basename "${ca}" .cnf).p12
    jks_file="${outputdir}"/$(basename "${ca}" .cnf).jks

    init_ca_dir "${outputdir}"

    # Create the self-signed root CA certificate and key
    openssl req -x509 -noenc \
        -days "${DEFAULT_DAYS}" \
        -keyout "${key_file}" \
        -out "${cert_file}" \
        -extensions v3_extensions \
        -config "${ca}"

    # Export the certificate as DER
    openssl x509 -in "${cert_file}" -out "${cert_der_file}" -outform DER

    # Create a PKCS12 file for the root CA
    openssl pkcs12 -export -out "${pkcs12_file}" -inkey "${key_file}" -in "${cert_file}" \
        -name "${alias}" -passout pass:"${DEFAULT_PASSWORD}" "${COMMON_PKCS12_ARGS[@]}"

    # Export PKCS12 as JKS
    rm -f "${jks_file}"
    keytool -importkeystore \
        -srckeystore "${pkcs12_file}" -srcstoretype PKCS12 -srcstorepass "${DEFAULT_PASSWORD}" \
        -destkeystore "${jks_file}" -deststoretype JKS -deststorepass "${DEFAULT_PASSWORD}"
done

# Create Intermediate CA certificates signed by its parent CA
for i in ${INTERMEDIATE_CAS}; do
    outputdir=$(dirname "${i}")
    alias=$(basename "${i}" .cnf)
    ca_dir=$(dirname "${outputdir}")
    ca_config="${ca_dir}"/$(basename "${ca_dir}").cnf
    ca_cert="${ca_dir}"/$(basename "${ca_dir}")_cert.pem
    csr_file="${outputdir}"/$(basename "${i}" .cnf).csr
    cert_file="${outputdir}"/$(basename "${i}" .cnf)_cert.pem
    key_file="${outputdir}"/$(basename "${i}" .cnf)_key.pem
    pkcs12_file="${outputdir}"/$(basename "${i}" .cnf).p12

    init_ca_dir "${outputdir}"

    # Create the intermediate CA certificate signing request and key
    rm -f "${cert_file}" "${csr_file}"
    openssl req -new -noenc -out "${csr_file}" -keyout "${key_file}" -config "${i}"

    # Create the intermediate CA certificate signed by its parent CA
    cmd=(openssl ca -notext -config "${ca_config}" -in "${csr_file}" -out "${cert_file}" \
        -extfile "${i}" -extensions v3_extensions -batch)
    run_with_faketime_if_needed "${cert_file}" "${cmd[@]}"
done

# Create client and server certificates signed by its corresponding CA
for i in ${CLIENT_CERTS} ${SERVER_CERTS}; do
    outputdir=$(dirname "${i}")
    alias=$(basename "${i}" .cnf)
    ca_config="${outputdir}"/$(basename "${outputdir}").cnf
    ca_cert="${outputdir}"/$(basename "${outputdir}")_cert.pem
    ca_crl="${outputdir}"/$(basename "${outputdir}").crl.pem
    csr_file="${outputdir}"/$(basename "${i}" .cnf).csr
    cert_file="${outputdir}"/$(basename "${i}" .cnf)_cert.pem
    key_file="${outputdir}"/$(basename "${i}" .cnf)_key.pem
    pkcs12_file="${outputdir}"/$(basename "${i}" .cnf).p12
    jks_file="${outputdir}"/$(basename "${i}" .cnf).jks

    # Create the certificate signing request and key
    rm -f "${cert_file}" "${csr_file}"
    openssl req -new -noenc -out "${csr_file}" -keyout "${key_file}" -config "${i}"

    # Create the certificate signed by its CA
    cmd=(openssl ca -notext -config "${ca_config}" -in "${csr_file}" -out "${cert_file}" \
        -extfile "${i}" -extensions v3_extensions -batch)
    run_with_faketime_if_needed "${cert_file}" "${cmd[@]}"

    # Add the certificate chain app to the ROOT CA without including it.
    add_cert_chain "${cert_file}" "${ca_cert}"

    # Export as PCKS12
    rm -f "${pkcs12_file}"
    openssl pkcs12 -export -out "${pkcs12_file}" -inkey "${key_file}" -in "${cert_file}" -certfile "${ca_cert}" \
        -name "${alias}" -passout pass:"${DEFAULT_PASSWORD}" "${COMMON_PKCS12_ARGS[@]}"

    # Export PKCS12 as JKS
    rm -f "${jks_file}"
    keytool -importkeystore -srckeystore "${pkcs12_file}" -srcstoretype PKCS12 -destkeystore "${jks_file}" \
        -deststoretype JKS -srcstorepass "${DEFAULT_PASSWORD}" -deststorepass "${DEFAULT_PASSWORD}"
done

# Revoke the given cert_file using openssl ca, and update the crl file.
revoke_certificates(){
    ca_config=$1
    cert_files=$2

    outputdir=$(dirname "${ca_config}")
    ca_crl="${outputdir}"/$(basename "${outputdir}").crl.pem

    for i in ${cert_files}; do
        openssl ca -config "${ca_config}" -revoke "${i}" -passin pass:"${DEFAULT_PASSWORD}" -batch
    done
    openssl ca -config "${ca_config}" -gencrl -out "${ca_crl}" -crldays "${DEFAULT_DAYS}" \
        -passin pass:"${DEFAULT_PASSWORD}"
}

# Create a PKCS12 password-less version of the main client/server ca1 certs
openssl pkcs12 -export -out configuration/ca1/server_password_less.p12 \
    -inkey configuration/ca1/server_key.pem \
    -in configuration/ca1/server_cert.pem \
    -name server -passout pass: "${COMMON_PKCS12_ARGS[@]}"

openssl pkcs12 -export -out configuration/ca1/client_password_less.p12 \
    -inkey configuration/ca1/client_key.pem \
    -in configuration/ca1/client_cert.pem \
    -name client -passout pass: "${COMMON_PKCS12_ARGS[@]}"

# Revoke client and server certificates used in certificate revocation tests
revoke_certificates configuration/ca3/ca3.cnf \
    "configuration/ca3/server_revoked_cert.pem configuration/ca3/i1/i1_cert.pem"

revoke_certificates configuration/ca3/i1/i1.cnf configuration/ca3/i1/server_revoked_cert.pem

revoke_certificates configuration/ca4/ca4.cnf \
    "configuration/ca4/server_revoked_cert.pem configuration/ca4/i1/i1_cert.pem"

# Create ca_all_cert.pem, cal_all.p12, and ca_all.jks with all configuration CA certificates
rm -f configuration/ca_all_cert.pem configuration/ca_all.p12 configuration/ca_all.jks
for ca in ${CONFIGURATION_CAS}; do
    ca_dir=$(dirname "${ca}")
    alias=$(basename "${ca_dir}")
    cert_file="${ca_dir}"/"${alias}"_cert.pem
    cat "${cert_file}" >> configuration/ca_all_cert.pem
    keytool -importcert -alias "${alias}" -file "${cert_file}" \
        -keystore configuration/ca_all.p12 -storetype PKCS12 \
        -storepass "${DEFAULT_PASSWORD}" -noprompt

    keytool -importcert -alias "${alias}" -file "${cert_file}" \
        -keystore configuration/ca_all.jks -storetype JKS \
        -storepass "${DEFAULT_PASSWORD}" -noprompt
done

# Cleanup some files we don't need to commit
find . -name '*.old'    -exec rm -fv {} +
find . -name '*.csr'    -exec rm -fv {} +
find . -name '*.attr'    -exec rm -fv {} +
find . -name 'crlnumber' -exec rm -fv {} +
find . -name 'serial'    -exec rm -fv {} +
find . -name 'newcerts'  -type d -exec rm -rfv {} +
find . -name 'crl'  -type d -exec rm -rfv {} +
