#!/usr/bin/env bash

# Define colors uses for status output
RED=$(tput setaf 1 2> /dev/null)
GREEN=$(tput setaf 2 2> /dev/null)
RESET=$(tput sgr0 2> /dev/null)

CABUNDLE_MAX_ISSUERS=32

# Branding
BRANDING_FILE=/usr/share/foreman-installer/katello-certs-check-branding
if [[ -f "${BRANDING_FILE}" ]]; then
  source "${BRANDING_FILE}"
fi
PROJECT=${PROJECT:-Katello}
SERVER_SCENARIO=${SERVER_SCENARIO:-katello}
INSTALLER=${INSTALLER:-foreman-installer}
CERTS_TOOL=${CERTS_TOOL:-foreman-proxy-certs-generate}
SERVER_TARGET=${SERVER_TARGET:-foreman}
PROXY_TARGET=${PROXY_TARGET:-foreman-proxy}
PROXY_VAR=${PROXY_VAR:-FOREMAN_PROXY}

function usage () {
  cat <<HELP >&2
Verifies, that custom SSL certificate files are usable
as part of the ${PROJECT} installation. When passing filenames use absolute paths.

usage: $0 -t [${SERVER_TARGET}|${PROXY_TARGET}] -c CERT_FILE -k KEY_FILE -b CA_BUNDLE_FILE
HELP
}

while getopts "t:c:k:b:" opt; do
    case $opt in
        t)
            TARGET=$(echo $OPTARG|tr '[:upper:]' '[:lower:]')
            ;;
        c)
            CERT_FILE="$(readlink -f $OPTARG)"
            ;;
        k)
            KEY_FILE="$(readlink -f $OPTARG)"
            ;;
        b)
            CA_BUNDLE_FILE="$(readlink -f $OPTARG)"
            ;;
        h)
            usage
            exit 0
            ;;
        ?)
            usage
            exit 1
            ;;
    esac
done

EXIT_CODE=0

if [ -z "$CERT_FILE" -o -z "$KEY_FILE" -o -z "$CA_BUNDLE_FILE" ]; then
    echo 'One of the required parameters is missing.' >&2
    usage
    exit 1
fi

function error () {
    echo -e "\n${RED}[FAIL]${RESET}\n"
    CURRENT_EXIT_CODE=$1
    EXIT_CODE=$((EXIT_CODE|CURRENT_EXIT_CODE))
    echo -e $2 >&2
}

function success () {
    echo -e "\n${GREEN}[OK]${RESET}\n"
}

function check-files-exist() {
    if [[ ! -f "$CA_BUNDLE_FILE" ]] || [[ ! -f "$CERT_FILE" ]] || [[ ! -f "$KEY_FILE" ]] ; then
        echo "One of '${CERT_FILE}', '${KEY_FILE}' or '${CA_BUNDLE_FILE}' not found" >&2
        exit 1
    fi
}

function check-server-cert-encoding () {
    printf 'Checking server certificate encoding: '
    openssl x509 -inform pem -in $CERT_FILE -noout -text &> /dev/null
    if [[ $? == "0" ]]; then
        success
    else
        openssl x509 -inform der -in $CERT_FILE -noout -text &> /dev/null
        if [[ $? == "0" ]]; then
            error 8 "The server certificate is in DER encoding, which is incompatible.\n\n"
            printf "Run the following command to convert the certificate to PEM encoding,\n"
            printf "then test it again.\n"
            printf "# openssl x509 -in %s -outform pem -out %s.pem\n\n" $(basename $CERT_FILE) $(basename $CERT_FILE)
            printf "When you run $(basename $0) again, use file\n"
            printf "%s.pem for the server certificate.\n\n" $(basename $CERT_FILE)
        else
            error 9 "The encoding of the server certificate is unknown."
        fi
    fi
}

function check-expiration () {
    CERT_EXP=$(openssl x509 -noout -enddate -in $CERT_FILE | sed -e 's/notAfter=//' | awk '{$NF="";}1')
    CA_EXP=$(openssl x509 -noout -enddate -in $CA_BUNDLE_FILE | sed -e 's/notAfter=//' | awk '{$NF="";}1')
    DATE_TODAY=$(date -u +%Y%m%d%H%M%S)
    CERT_DATE=$(date -d"${CERT_EXP}" +%Y%m%d%H%M%S)
    CA_DATE=$(date -d"${CA_EXP}" +%Y%m%d%H%M%S)
    printf "Checking expiration of certificate: "
    if [[ $DATE_TODAY -gt $CERT_DATE ]]; then
        error 6 "The certificate \"$CERT_FILE\" has already expired on: $CERT_EXP"
    else
        success
    fi
    printf "Checking expiration of CA bundle: "
    if [[ $DATE_TODAY -gt $CA_DATE ]]; then
        error 7 "The CA bundle \"$CA_BUNDLE_FILE\" has already expired on: $CA_EXP"
    else
        success
    fi
}

function check-cert-ca-flag () {
    printf "Checking if server certificate has CA:TRUE flag "
    openssl x509 -in $CERT_FILE -noout -text | grep -q CA:TRUE
    if [[ $? -ne 0 ]]; then
        success
    else
        error 7 "The server certificate is marked as a CA and can not be used."
    fi
}

function check-passphrase () {
    printf "Checking for private key passphrase: "
    CHECK=$(cat $KEY_FILE | grep ENCRYPTED)
    if [[ $? == "0" ]]; then
        error 2 "The $KEY_FILE contains a passphrase, remove the key's passphrase by doing:
        \nmv $KEY_FILE $KEY_FILE.old
        \nopenssl pkey -in $KEY_FILE.old -out $KEY_FILE"
        exit 1
    else
        success
    fi
}

function check-priv-key () {
    printf "Checking to see if the private key matches the certificate: "
    CERT_PUBKEY_SUM=$(openssl x509 -noout -pubkey -in $CERT_FILE | openssl sha512)
    KEY_PUBKEY_SUM=$(openssl pkey -pubout -in $KEY_FILE | openssl sha512)
    if [[ "$CERT_PUBKEY_SUM" != "$KEY_PUBKEY_SUM" ]]; then
        error 2 "The $KEY_FILE does not match the $CERT_FILE"
    else
        success
    fi
}

function check-ca-bundle () {
    printf "Checking CA bundle against the certificate file: "
    ERROR_PATTERN="error [0-9]+ at"
    CHECK=$(openssl verify -no-CApath -no-CAstore -CAfile $CA_BUNDLE_FILE -purpose sslserver -verbose $CERT_FILE 2>&1)
    CHECK_STATUS=$?

    if [[ $CHECK_STATUS != "0" || $CHECK =~ $ERROR_PATTERN ]]; then
        error 4 "The $CA_BUNDLE_FILE does not verify the $CERT_FILE"
        echo -e "${CHECK/OK/}\n"
    else
        success
    fi
}

function check-ca-bundle-size () {
    printf "Checking CA bundle size: "
    CHECK=$(grep -c "^--*BEGIN" $CA_BUNDLE_FILE)
    printf $CHECK
    if [[ $CHECK -lt $CABUNDLE_MAX_ISSUERS ]]; then
        success
    else
        CERRTISSUER=$(openssl x509 -noout -in $CERT_FILE -issuer 2>&1)
        error 10 "The CA bundle counts $CHECK issuers. Please trim your CA bundle and include only the certs relevant to your cert file"
        echo $CERTISSUER
        echo
    fi
}

function check-ca-bundle-trust-rules () {
    printf "Checking if CA bundle has trust rules: "
    CHECK=$(grep 'BEGIN TRUSTED CERTIFICATE'  $CA_BUNDLE_FILE| wc -l)
    printf $CHECK
    if [[ $CHECK -lt 1 ]]; then
        success
    else
        error 10 "The CA bundle contains $CHECK certificate(s) with trust rules. This may create problems for older systems to trust the bundle. Please, recreate the bundle using certificates without trust rules"
        echo
    fi
}

function check-cert-san () {
    printf "Checking Subject Alt Name on certificate "
    DNS_LIST=$(openssl x509 -noout -text -in $CERT_FILE | grep DNS:)
    if [[ $? == "0" ]]; then
        success
        printf "Checking if any Subject Alt Name on certificate matches the Subject CN"
        SUBJECT_CN=$(openssl x509 -in $CERT_FILE -noout -subject -nameopt multiline|grep commonName|cut -d '=' -f 2|tr -d ' ')
        for DNS in ${DNS_LIST}
          do
          DNS_VALUE="$( echo ${DNS//DNS:/} | tr -d ',')"
          if [ $SUBJECT_CN == $DNS_VALUE ]
          then
            success
            return
          fi
        done
        error 11 "The $CERT_FILE does not have a Subject Alt Name matching the Subject CN"
    else
        error 11 "The $CERT_FILE does not contain a Subject Alt Name. Common Name is deprecated, use Subject Alt Name instead. See: https://tools.ietf.org/html/rfc2818#section-3.1"
    fi
}

function check-cert-usage-key-encipherment () {
    printf "Checking Key Usage extension on certificate for Key Encipherment "
    CHECK=$(openssl x509 -noout -text -in $CERT_FILE | grep -A1 'X509v3 Key Usage:' | grep 'Key Encipherment')
    if [[ $? == "0" ]]; then
        success
    else
        error 4 "The $CERT_FILE does not allow for the 'Key Encipherment' key usage."
    fi
}

function check-shortname () {
    printf "Checking for use of shortname as CN"

    SUBJECT_CN=$(openssl x509 -in $CERT_FILE -noout -subject -nameopt multiline|grep commonName|cut -d '=' -f 2|tr -d ' ')
    if [[ $SUBJECT_CN != *"."* ]]; then
        error 1 "The $(basename $CERT_FILE) is using a shortname for Common Name (CN) and cannot be used with $PROJECT.\n"
    fi

    DNS_LIST=$(openssl x509 -noout -text -in $CERT_FILE | grep DNS:)
    if [[ $? == "0" ]]; then
        for DNS in ${DNS_LIST}
          do
          DNS_VALUE="$( echo ${DNS//DNS:/} | tr -d ',')"

          if [[ $DNS_VALUE == *"."* ]]; then
            success
            return
          fi
        done
        error 1 "The $(basename $CERT_FILE) is using only shortnames for Subject Alt Name and cannot be used with $PROJECT.\n"
    fi
}

check-files-exist
check-server-cert-encoding
check-expiration
check-cert-ca-flag
check-passphrase
check-priv-key
check-ca-bundle
check-ca-bundle-size
check-ca-bundle-trust-rules
check-cert-san
check-cert-usage-key-encipherment
check-shortname

if [[ $EXIT_CODE == "0" ]] && ([[ $TARGET == ${SERVER_TARGET} ]] || [[ -z "$TARGET" ]]) ; then
    echo -e "${GREEN}Validation succeeded${RESET}\n"
    cat <<EOF

To install the ${PROJECT} server with the custom certificates, run:

    ${INSTALLER} --scenario ${SERVER_SCENARIO} \\
                      --certs-server-cert "$(readlink -f $CERT_FILE)" \\
                      --certs-server-key "$(readlink -f $KEY_FILE)" \\
                      --certs-server-ca-cert "$(readlink -f $CA_BUNDLE_FILE)"

To update the certificates on a currently running ${PROJECT} installation, run:

    ${INSTALLER} --scenario ${SERVER_SCENARIO} \\
                      --certs-server-cert "$(readlink -f $CERT_FILE)" \\
                      --certs-server-key "$(readlink -f $KEY_FILE)" \\
                      --certs-server-ca-cert "$(readlink -f $CA_BUNDLE_FILE)" \\
                      --certs-update-server --certs-update-server-ca

To use them inside a NEW \$${PROXY_VAR}, rerun this command with -t ${PROXY_TARGET}
EOF
elif [[ $EXIT_CODE == "0" ]]; then
  echo -e "${GREEN}Validation succeeded${RESET}\n"
  cat <<EOF

  To use them inside a NEW \$${PROXY_VAR}, run this command:

      ${CERTS_TOOL} --foreman-proxy-fqdn "\$${PROXY_VAR}" \\
                                   --certs-tar  "~/\$${PROXY_VAR}-certs.tar" \\
                                   --server-cert "$(readlink -f $CERT_FILE)" \\
                                   --server-key "$(readlink -f $KEY_FILE)" \\
                                   --server-ca-cert "$(readlink -f $CA_BUNDLE_FILE)"

  To use them inside an EXISTING \$${PROXY_VAR}, run this command INSTEAD:

      ${CERTS_TOOL} --foreman-proxy-fqdn "\$${PROXY_VAR}" \\
                                   --certs-tar  "~/\$${PROXY_VAR}-certs.tar" \\
                                   --server-cert "$(readlink -f $CERT_FILE)" \\
                                   --server-key "$(readlink -f $KEY_FILE)" \\
                                   --server-ca-cert "$(readlink -f $CA_BUNDLE_FILE)" \\
                                   --certs-update-server
EOF
else
  exit $EXIT_CODE
fi
