#!/bin/sh

# ------------------------------------------------------------
# Company:  Software- und Organisations-Service GmbH
# Date:     2022-11-16
# Purpose:  create keystore/truststore for JS7 components
# Platform: AIX, Linux, MacOS: bash, ksh, zsh, dash
# ------------------------------------------------------------
#
# Example:  ./js7_create_certificate_store.sh \
#    --keystore=./certs/https-keystore.p12 \
#    --truststore=./certs/https-truststore.p12 \
#    --key=./certs/apmaccs.key \
#    --cert=./certs/apmaccs.crt \
#    --alias=apmaccs \
#    --password=jobscheduler \
#    --ca-cert=./certs/ca-bundle.crt \
#    --chain
#
# Example:  ./js7_create_certificate_store.sh \
#    --keystore=./certs/https-keystore.p12 \
#    --truststore=./certs/https-truststore.p12 \
#    --key=./certs/apmaccs.key \
#    --cert=./certs/apmaccs.crt \
#    --alias=apmaccs \
#    --password=jobscheduler \
#    --ca-root=./certs/root-ca.crt \
#    --ca-intermediate=./certs/sos.intermediate-ca.crt \
#    --chain

set -e

# ------------------------------
# Initialize variables
# ------------------------------

keystore_file=
truststore_file=
server_alias=
server_cert_file=
server_key_file=
password=
ca_cert_file=
ca_intermediate_files=
ca_root_file=

hostname=$(hostname)
log_dir=
backup_dir=
show_logs=
make_chain=
make_dirs=
start_time=$(date +"%Y-%m-%dT%H-%M-%S")

Usage()
{
    >&2 echo ""
    >&2 echo "Usage: $(basename "$0") [Options] [Switches]"
    >&2 echo ""
    >&2 echo "  Options:"
    >&2 echo "    --keystore=<file>                     | required: path to keystore file in PKCS12 format"
    >&2 echo "    --truststore==<file>                  | optional: path to truststore file in PKCS12 format"
    >&2 echo "    --key=<private-key-file>              | required: path to private key file in .pem format"
    >&2 echo "    --cert=<cert-file>                    | required: path to certificate file in .pem format"
    >&2 echo "    --alias=<alias-name>                  | required: alias name for keystore entry"
    >&2 echo "    --password=<password>                 | required: password for certificate store, optionally for private key"
    >&2 echo "    --ca-cert=<ca-cert-bundle-file>       | optional: path to CA Certificate Bundle file in .pem format"
    >&2 echo "    --ca-root=<ca-root-file>              | optional: path to Root CA Certificate file in .pem format"
    >&2 echo "    --ca-intermediate=<ca-file[,ca-file]> | optional: paths to Intermediate CA Certificate files in .pem format"
    >&2 echo "    --backup-dir=<directory>              | optional: backup directory for existing certificate stores"
    >&2 echo "    --log-dir=<directory>                 | optional: log directory for log output of this script"
    >&2 echo "  Switches:"
    >&2 echo "    -h | --help                           | displays usage"
    >&2 echo "    --chain                               | add certificate chain to keystore"
    >&2 echo "    --show-logs                           | shows log output of the script"
    >&2 echo "    --make-dirs                           | creates the specified directories if they do not exist"
    >&2 echo ""
}

Log()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "$@" >> "${log_file}"
    fi

    if [ -z "${show_logs}" ]
    then
        echo "$@"
    fi
}

LogError()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "[ERROR]" "$@" >> "${log_file}"
    fi
    
    >&2 echo "[ERROR]" "$@"
}

# ------------------------------
# Process command line options
# ------------------------------

Arguments()
{
    for option in "$@"
    do
        case "${option}" in
            -h|--help)              Usage
                                    exit
                                    ;;
            --keystore=*)           keystore_file=$(echo "${option}" | sed 's/--keystore=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --truststore=*)         truststore_file=$(echo "${option}" | sed 's/--truststore=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --key=*)                server_key_file=$(echo "${option}" | sed 's/--key=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --cert=*)               server_cert_file=$(echo "${option}" | sed 's/--cert=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --alias=*)              server_alias=$(echo "${option}" | sed 's/--alias=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --password=*)           password=$(echo "${option}" | sed 's/--password=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --ca-cert=*)            ca_cert_file=$(echo "${option}" | sed 's/--ca-cert=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --ca-root=*)            ca_root_file=$(echo "${option}" | sed 's/--ca-root=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --ca-intermediate=*)    ca_intermediate_files=$(echo "${option}" | sed 's/--ca-intermediate=//' | sed 's/^"//' | sed 's/"$//' | sed -r 's/[,]+/ /g')
                                    ;;
            --log-dir=*)            log_dir=$(echo "${option}" | sed 's/--log-dir=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --backup-dir=*)         backup_dir=$(echo "${option}" | sed 's/--backup-dir=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --chain)                make_chain=1
                                    ;;
            --make-dirs)            make_dirs=1
                                    ;;
            --show-logs)            show_logs=1
                                    ;;
            *)                      >&2 echo "unknown option: ${option}"
                                    Usage
                                    exit 1
                                    ;;
        esac
    done

    if [ -z "${server_key_file}" ] && [ -n "${keystore_file}" ]
    then
        LogError "To create a keystore (--keystore) the private key file must be specified: --key="
        Usage
        exit 1
    fi

    if [ -n "${server_key_file}" ] && [ ! -f "${server_key_file}" ]
    then
        LogError "Key file not found: --key=${server_key_file}"
        Usage
        exit 1
    fi

    if [ -n "${server_key_file}" ] && [ -n "$(< "${server_key_file}" grep -E 'ENCRYPTED')" ] && [ -z "${password}" ]
    then
        LogError "Key requires a passphrase: --password="
        Usage
        exit 1
    fi

    if [ -z "${server_cert_file}" ] && [ -n "${keystore_file}" ]
    then
        LogError "To create a keystore (--keystore) the certificate file must be specified: --cert="
        Usage
        exit 1
    fi

    if [ -n "${server_cert_file}" ] && [ ! -f "${server_cert_file}" ]
    then
        LogError "Certificate file not found: --cert=${server_cert_file}"
        Usage
        exit 1
    fi

    if [ -z "${server_alias}" ] && [ -n "${keystore_file}" ]
    then
        LogError "To create a keystore (--keystore) the alias name must be specified: --alias="
        Usage
        exit 1
    fi

    if [ -z "${password}" ]
    then
        LogError "For keystore and truststore the password must be specified: --password="
        Usage
        exit 1
    fi

    if [ -z "${ca_cert_file}" ] && [ -z "${ca_root_file}" ]
    then
        if [ -n "${truststore_file}" ] || ( [ -n "${keystore_file}" ] && [ -n "${make_chain}" ] )
        then
            LogError "To create a truststore (--truststore) or a keystore (--keystore) with a certificate chain (--chain) a CA Certificate Bundle file or Root CA Certificate file must be specified: --ca-cert= or --ca-root="
            Usage
            exit 1
        fi
    fi

    if [ -n "${ca_cert_file}" ] && [ ! -f "${ca_cert_file}" ]
    then
        LogError "CA Certificate Bundle file not found: --ca-cert=${ca_cert_file}"
        Usage
        exit 1
    fi

    if [ -n "${ca_root_file}" ] && [ ! -f "${ca_root_file}" ]
    then
        LogError "Root CA Certificate file not found: --ca-root=${ca_root_file}"
        Usage
        exit 1
    fi

    if [ -n "${ca_intermediate_files}" ]
    then
        for i in ${ca_intermediate_files[@]}; do
            if [ ! -f "$i" ] 
            then
                LogError "Intermediate CA Certificate file not found: --ca-intermediate=$i"
            fi
        done
    fi

    if [ -z "${make_dirs}" ] && [ -n "${keystore_file}" ] && [ ! -d "$(dirname "${keystore_file}")" ]
    then
        LogError "Keystore directory not found and -make-dirs switch not present: --keystore=$keystore_file}"
        Usage
        exit 1
    fi

    if [ -z "${make_dirs}" ] && [ -n "${truststore_file}" ] && [ ! -d "$(dirname "${truststore_file}")" ]
    then
        LogError "Truststore directory not found and -make-dirs switch not present: --truststore=$truststore_file}"
        Usage
        exit 1
    fi

    if [ -z "${make_dirs}" ] && [ -n "${backup_dir}" ] && [ ! -d "${backup_dir}" ]
    then
        LogError "Backup directory not found and -make-dirs switch not present: --backup-dir=${backup_dir}"
        Usage
        exit 1
    fi

    if [ -z "${make_dirs}" ] && [ -n "${log_dir}" ] && [ ! -d "${log_dir}" ]
    then
        LogError "Log directory not found and -make-dirs switch not present: --log-dir=${log_dir}"
        Usage
        exit 1
    fi

    # initialize logging
    if [ -n "${log_dir}" ]
    then
        # create log directory if required
        if [ ! -d "${log_dir}" ] && [ -n "${make_dirs}" ]
        then
            mkdir -p "${log_dir}"
        fi
    
        log_file="${log_dir}/certificate_store_js7.${hostname}.${start_time}.log"
        while [ -f "${log_file}" ]
        do
            sleep 1
            start_time=$(date +"%Y-%m-%dT%H-%M-%S")
            log_file="${log_dir}/certificate_store_js7.${hostname}.${start_time}.log"
        done
        
        touch "${log_file}"
    fi

    Log "-- begin of log --------------"
    Log "$0" "$@"
    Log "-- begin of output -----------"
}

# ------------------------------
# Create certificate store
# ------------------------------

Create_keystore_without_certificate_chain()
{
    temp_output_file="/tmp/js7_output_openssl_$$.tmp"
    echo "" > "${temp_output_file}"
    rc=0

    if [ -n "${make_dirs}" ] && [ ! -d "$(dirname ${keystore_file})" ]
    then
        mkdir -p "$(dirname ${keystore_file})"
    fi

    if [ -n "$(< "${server_key_file}" grep -E 'ENCRYPTED')" ]
    then
        Log ".. creating keystore: openssl pkcs12 -export -in ${server_cert_file} -inkey ${server_key_file} -name ${server_alias} -passin pass:********  -passout pass:********  -out ${keystore_file}"
        ( openssl pkcs12 -export -in "${server_cert_file}" -inkey "${server_key_file}" -name "${server_alias}" -passin pass:"${password}" -passout pass:"${password}" -out "${keystore_file}" >  "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"
    else
        Log ".. creating keystore: openssl pkcs12 -export -in ${server_cert_file} -inkey ${server_key_file} -name ${server_alias} -passout pass:******** -out ${keystore_file}"
        ( openssl pkcs12 -export -in "${server_cert_file}" -inkey "${server_key_file}" -name "${server_alias}" -passout pass:"${password}" -out "${keystore_file}" >  "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"
    fi

    if [ "${rc}" -ne 0 ]
    then
        LogError "failed to create keystore, exit code: ${rc}"
        exit "${rc}"
    fi

    if [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi
}

Create_keystore_with_certificate_chain()
{
    temp_output_file="/tmp/js7_output_openssl_$$.tmp"
    temp_cert_file="/tmp/js7_certificate_chain_$$.tmp"
    echo "" > "${temp_output_file}"
    echo "" > "${temp_cert_file}"

    Log ".. creating certificate chain: ${temp_cert_file}"
    Log ".... adding server certificate to chain: ${server_cert_file}"
    cat "${server_cert_file}" > "${temp_cert_file}"    
    
    if [ -n "${ca_cert_file}" ]
    then
        Log ".... adding CA Certificate Bundle to chain: ${ca_cert_file}"
        cat "${ca_cert_file}" >> "${temp_cert_file}"
    else
        if [ -n "${ca_root_file}" ]
        then
            if [ -n "${ca_intermediate_files}" ]
            then
                for i in ${ca_intermediate_files[@]}; do
                    Log ".... adding Intermediate CA Certificate to chain: ${i}"
                    cat "${i}" >> "${temp_cert_file}"
                done
            fi
    
            Log ".... adding Root CA Certificate to chain: ${ca_root_file}"
            cat "${ca_root_file}" >> "${temp_cert_file}"
        else
            LogError "none of CA Certificate Bundle file or Root CA Certificate file is present: cannot create certificate chain"
            exit 2
        fi
    fi

    # verify certificate chain
    if [ -n "${ca_cert_file}" ]
    then
        Log ".. verfying temporary certificate chain: openssl verify -CAfile ${ca_cert_file} ${temp_cert_file}"
        rc=0
        ( openssl verify -CAfile "${ca_cert_file}" "${temp_cert_file}" > "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

        if [ "${rc}" -ne 0 ]
        then
            LogError "failed to verify temporary certificate chain, exit code: ${rc}"
            exit "${rc}"
        else
            Log ".. verification of temporary certificate chain ok"
        fi
    fi

    rc=0
    
    if [ -n "${make_dirs}" ] && [ ! -d "$(dirname ${keystore_file})" ]
    then
        mkdir -p "$(dirname ${keystore_file})"
    fi

    if [ -n "$(< "${server_key_file}" grep -E 'ENCRYPTED')" ]
    then
        Log ".. creating keystore with certificate chain: openssl pkcs12 -export -in ${temp_cert_file} -inkey ${server_key_file} -name ${server_alias} -passin pass:******** -passout pass:******** -out ${keystore_file}"
        ( openssl pkcs12 -export -in "${temp_cert_file}" -inkey "${server_key_file}" -name "${server_alias}" -passin pass:"${password}" -passout pass:"${password}" -out "${keystore_file}" > "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"
    else
        Log ".. creating keystore with certificate chain: openssl pkcs12 -export -in ${temp_cert_file} -inkey ${server_key_file} -name ${server_alias} -passout pass:******** -out ${keystore_file}"
        ( openssl pkcs12 -export -in "${temp_cert_file}" -inkey "${server_key_file}" -name "${server_alias}" -passout pass:"${password}" -out "${keystore_file}" > "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"
    fi

    if [ "${rc}" -ne 0 ]
    then
        LogError "failed to create keystore with certificate chain, exit code: ${rc}"
        exit "${rc}"
    fi
  
    if [ -f "${temp_cert_file}" ]
    then
        rm -f "${temp_cert_file}"
    fi

    if [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi
}

Create_truststore_from_certificate_chain()
{
    temp_output_file="/tmp/js7_output_openssl_$$.tmp"
    echo "" > "${temp_output_file}"

    file_basename=$(basename "${ca_cert_file}")
    file_alias=${file_basename%%.*}

    Log ".. importing CA Certificate Bundle to truststore: keytool -importcert -alias ${file_alias} -file ${ca_cert_file} -noprompt -trustcacerts -storetype PKCS12 -keystore ${truststore_file}"
    
    if [ -n "${make_dirs}" ] && [ -d "$(dirname ${truststore_file})" ]
    then
        mkdir -p "$(dirname ${truststore_file})"
    fi
    
    rc=0
    ( keytool -importcert -alias "${file_alias}" -file "${ca_cert_file}" -noprompt -trustcacerts -storetype PKCS12 -keystore "${truststore_file}" -storepass "${password}" >  "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

    if [ "${rc}" -ne 0 ]
    then
        LogError "failed to import CA Certificate Bundle to truststore, exit code: ${rc}"
        exit "${rc}"
    fi
  
    if [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi
}

Create_truststore_from_certificates()
{
    temp_output_file="/tmp/js7_output_openssl_$$.tmp"
    echo "" > "${temp_output_file}"

    file_basename=$(basename "${ca_root_file}")
    file_alias=${file_basename%%.*}

    Log ".. importing Root CA Certificate to truststore: keytool -importcert -alias ${file_alias} -file ${ca_root_file} -noprompt -trustcacerts -storetype PKCS12 -keystore ${truststore_file}"

    if [ -n "${make_dirs}" ] && [ -d "$(dirname ${truststore_file})" ]
    then
        mkdir -p "$(dirname ${truststore_file})"
    fi
    
    rc=0
    ( keytool -importcert -alias "${file_alias}" -file "${ca_root_file}" -noprompt -trustcacerts -storetype PKCS12 -keystore "${truststore_file}" -storepass "${password}" >  "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

    if [ "${rc}" -ne 0 ]
    then
        LogError "failed to import Root CA Certificate to truststore, exit code: ${rc}"
        exit "${rc}"
    fi

    if [ -n "${ca_intermediate_files}" ]
    then
        rc=0
        for i in ${ca_intermediate_files[@]}; do
            file_basename=$(basename "${i}")
            file_alias=${file_basename%%.*}

            Log ".. importing Intermediate CA Certificate to truststore: keytool -importcert -alias ${file_alias} -file ${i} -noprompt -trustcacerts -storetype PKCS12 -keystore ${truststore_file}"
            ( keytool -importcert -alias "${file_alias}" -file "${i}" -noprompt -trustcacerts -storetype PKCS12 -keystore "${truststore_file}" -storepass "${password}" >  "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

            if [ "${rc}" -ne 0 ]
            then
                LogError "failed importing Intermediate CA Certificate ${i} to truststore, exit code: ${rc}"
                exit "${rc}"
            fi
        done
    fi

    if [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi
}

Process()
{
    # take backup of existing keystore/truststore
    if [ -n "${backup_dir}" ]
    then

        if [ -n "${make_dirs}" ] && [ -n "${backup_dir}" ] && [ ! -d "${backup_dir}" ]
        then
            Log "Creating backup directory: ${backup_dir}"
            mkdir -p "${backup_dir}"
        fi

        if [ -n "${keystore_file}" ] && [ -f "${keystore_file}" ]
        then
            backup_file="${backup_dir}/backup_js7_keystore.${hostname}.${start_time}.tar"
            Log ".. creating keystore backup with file: ${backup_file}.gz"

            file_parent_dir=$(dirname "${keystore_file}")
            file_basename=$(basename "${keystore_file}")
            Log ".... using backup command: tar -cf ${backup_file} -C ${file_parent_dir} ${file_basename}"
            tar -cf "${backup_file}" -C "${file_parent_dir}" "${file_basename}"
            gzip "${backup_file}"
        fi

        if [ -n "${truststore_file}" ] && [ -f "${truststore_file}" ]
        then
            backup_file="${backup_dir}/backup_js7_truststore.${hostname}.${start_time}.tar"
            Log ".. creating truststore backup with file: ${backup_file}.gz"

            file_parent_dir=$(dirname "${truststore_file}")
            file_basename=$(basename "${truststore_file}")
            Log ".... using backup command: tar -cf ${backup_file} -C ${file_parent_dir} ${file_basename}"
            tar -cf "${backup_file}" -C "${file_parent_dir}" "${file_basename}"
            gzip "${backup_file}"
        fi
    fi

    # remove keystore file
    if [ -n "${keystore_file}" ] && [ -f "${keystore_file}" ]
    then
        rm -f "${keystore_file}"
    fi

    # remove truststore file
    if [ -n "${truststore_file}" ] && [ -f "${truststore_file}" ]
    then
        rm -f "${truststore_file}"
    fi

    # create keystore
    if [ -n "${keystore_file}" ]
    then
        if [ -n "${make_chain}" ]
        then
            Create_keystore_with_certificate_chain
        else
            Create_keystore_without_certificate_chain
        fi

        # verify keystore
        temp_output_file="/tmp/js7_output_openssl_$$.tmp"
        echo "" > "${temp_output_file}"

        Log ".. verifying keystore: openssl pkcs12 -info -in ${keystore_file}"
        rc=0
        ( openssl pkcs12 -info -passin pass:"${password}" -noout -in "${keystore_file}" > "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

        if [ "${rc}" -ne 0 ]
        then
            LogError "failed to verify keystore, exit code: ${rc}"
            exit "${rc}"
        fi
    fi

    # create truststore
    if [ -n "${truststore_file}" ]
    then
        if [ -n "${ca_cert_file}" ]
        then
            Create_truststore_from_certificate_chain
        else
            Create_truststore_from_certificates
        fi

        # verify truststore
        temp_output_file="/tmp/js7_output_openssl_$$.tmp"
        echo "" > "${temp_output_file}"

        Log ".. verifying truststore: openssl pkcs12 -info -in ${truststore_file}"
        rc=0
        ( openssl pkcs12 -info -passin pass:"${password}" -noout -in "${truststore_file}" > "${temp_output_file}" 2>&1 ) || rc=$?; Log "$(cat ${temp_output_file})"

        if [ "${rc}" -ne 0 ]
        then
            LogError "failed to verify truststore, exit code: ${rc}"
            exit "${rc}"
        fi
    fi

    if [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi
}

# ------------------------------
# Cleanup temporary resources
# ------------------------------

End()
{
    if [ -n "${temp_cert_file}" ] && [ -f "${temp_cert_file}" ]
    then
        rm -f "${temp_cert_file}"
    fi

    if [ -n "${temp_output_file}" ]  && [ -f "${temp_output_file}" ]
    then
        rm -f "${temp_output_file}"
    fi

    unset keystore_file
    unset server_alias
    unset server_cert_file
    unset server_key_file
    unset password
    unset ca_cert_file
    unset ca_intermediate_files
    unset ca_root_file

    unset hostname
    unset log_dir
    unset backup_dir
    unset show_logs
    unset make_chain
    
    unset file_parent_dir
    unset file_basename
    unset file_alias
    
    set +e    
}

# ------------------------------
# Enable trap and start
# ------------------------------

trap 'End EXIT' EXIT
trap 'End SIGTERM' TERM
trap 'End SIGINT' INT

Arguments "$@"
Process
