#!/bin/bash

## Copyright (c) Microsoft. All rights reserved.
## Licensed under the MIT license. See LICENSE file in the project root for full license information.

###############################################################################
# This script demonstrates creating X.509 certificates for an Azure IoT Hub
# CA Cert deployment.
#
# These certs MUST NOT be used in production.  It is expected that production
# certificates will be created using a company's proper secure signing process.
# These certs are intended only to help demonstrate and prototype CA certs.
###############################################################################

set -x # Set trace on
set -o errexit # Exit if command failed
set -o pipefail # Exit if pipe failed

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
ACTION_COMMAND=""
ACTION_PARAM=""
ACTION_PARAM2=""
DEVICE_NAME="iot-device"
OUTPUT_DIR="$PWD/build/cert"
INPUT_DIR=""

while (( "$#" )); do
    case "$1" in
    -h|--help)
        echo -e "${YELLOW}${HELP_MESSAGE}${NC}" >&1
        exit 0
        ;;

    --output)
        OUTPUT_DIR=$2
        echo -e "${GREEN}Output dir: ${OUTPUT_DIR} ${NC}" >&1
        shift 2
        ;;

    -n|--name)
        DEVICE_NAME=$2
        echo -e "${GREEN}Device name: ${DEVICE_NAME} ${NC}" >&1
        shift 2
        ;;

    -p|--password)
        CUSTOM_PASSWD=$2
        echo -e "${GREEN}Using password: ${CUSTOM_PASSWD} ${NC}" >&1
        shift 2
        ;;

    --create_device_certificate)
        ACTION_COMMAND=create_device_certificate
        ACTION_PARAM=$2
        ACTION_PARAM2=$3
        echo -e "${GREEN}Action command:${ACTION_COMMAND}: ${ACTION_PARAM} ${ACTION_PARAM2} ${NC}" >&1
        shift 3
        ;;

    --create_device_certificate_from_intermediate)
        ACTION_COMMAND=create_device_certificate_from_intermediate
        ACTION_PARAM=$2
        ACTION_PARAM2=$3
        echo -e "${GREEN}Action command:${ACTION_COMMAND}: ${ACTION_PARAM} ${ACTION_PARAM2} ${NC}" >&1
        shift 3
        ;;

    --create_root_and_intermediate)
        ACTION_COMMAND=create_root_and_intermediate
        echo -e "${GREEN}Action command: ${ACTION_COMMAND} ${NC}" >&1
        shift 1
        ;;

    -*) # unsupported flags
        echo -e "${RED}Error: Unsupported flag $1" >&2
        echo -e "Use --help for more information${NC}" >&2
        exit 1
        ;;

    *) # wrong input
        echo -e "${RED}Error: Wrong input $1" >&2
        echo -e "Use --help for more information${NC}" >&2
        exit 1
        ;;
    esac
done

root_ca_dir=${OUTPUT_DIR}
home_dir=${root_ca_dir}
algorithm="genrsa"
COUNTRY="US"
STATE="WA"
LOCALITY="Redmond"
ORGANIZATION_NAME="My Organization"
key_bits_length="4096"
root_ca_password=${CUSTOM_PASSWD:="1234"}
intermediate_ca_password=${CUSTOM_PASSWD:="1234"}
days_till_expire=365
ca_chain_prefix="azure-iot-test-only.chain.ca"
intermediate_ca_dir="."
openssl_root_config_file="${SCRIPT_DIR}/openssl_root_ca.cnf"
openssl_intermediate_config_file="${SCRIPT_DIR}/openssl_device_intermediate_ca.cnf"
root_ca_prefix="azure-iot-test-only.root.ca"
intermediate_ca_prefix="azure-iot-test-only.intermediate"

function make_CN_subject()
{
    local result="/CN=${1}"
    case $OSTYPE in win32) result="/${result}"
    esac
    echo "$result"
}

function warn_certs_not_for_production()
{
    echo "Certs generated by this script are not for production (e.g. they have hard-coded passwords of '1234'."
    echo "This script is only to help you understand Azure IoT Hub CA Certificates."
    echo "Use your official, secure mechanisms for this cert generation."
    echo "Also note that these certs will expire in ${days_till_expire} days."
}

function generate_root_ca()
{
    local common_name="Azure IoT Hub CA Cert Test Only"
    local password_cmd=" -aes256 -passout pass:${root_ca_password} "

    cd "${home_dir}"
    echo "Creating the Root CA Private Key"

    openssl "${algorithm}" \
            ${password_cmd} \
            -out "${root_ca_dir}/private/${root_ca_prefix}.key.pem" \
            ${key_bits_length}
    [ $? -eq 0 ] || exit $?
    chmod 400 "${root_ca_dir}/private/${root_ca_prefix}.key.pem"
    [ $? -eq 0 ] || exit $?

    echo "Creating the Root CA Certificate"
    password_cmd=" -passin pass:${root_ca_password} "

    openssl req \
            -new \
            -x509 \
            -config "${openssl_root_config_file}" \
            ${password_cmd} \
            -key "${root_ca_dir}/private/${root_ca_prefix}.key.pem" \
            -subj "$(make_CN_subject "${common_name}")" \
            -days ${days_till_expire} \
            -sha256 \
            -extensions v3_ca \
            -out "${root_ca_dir}/certs/${root_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?
    chmod 444 "${root_ca_dir}/certs/${root_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "CA Root Certificate Generated At:"
    echo "---------------------------------"
    echo "    ${root_ca_dir}/certs/${root_ca_prefix}.cert.pem"
    echo ""
    openssl x509 -noout -text \
            -in "${root_ca_dir}/certs/${root_ca_prefix}.cert.pem"

    warn_certs_not_for_production

    [ $? -eq 0 ] || exit $?
}



###############################################################################
# Generate Intermediate CA Cert
###############################################################################
function generate_intermediate_ca()
{
    local common_name="Azure IoT Hub Intermediate Cert Test Only"

    local password_cmd=" -aes256 -passout pass:${intermediate_ca_password} "
    echo "Creating the Intermediate Device CA"
    echo "-----------------------------------"
    cd "${home_dir}"

    openssl "${algorithm}" \
            ${password_cmd} \
            -out "${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem" \
            ${key_bits_length}
    [ $? -eq 0 ] || exit $?
    chmod 400 "${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem"
    [ $? -eq 0 ] || exit $?


    echo "Creating the Intermediate Device CA CSR"
    echo "-----------------------------------"
    password_cmd=" -passin pass:${intermediate_ca_password} "

    openssl req -new -sha256 \
        ${password_cmd} \
        -config "${openssl_intermediate_config_file}" \
        -subj "$(make_CN_subject "${common_name}")" \
        -key "${intermediate_ca_dir}/private/${intermediate_ca_prefix}.key.pem" \
        -out "${intermediate_ca_dir}/csr/${intermediate_ca_prefix}.csr.pem"
    [ $? -eq 0 ] || exit $?

    echo "Signing the Intermediate Certificate with Root CA Cert"
    echo "-----------------------------------"
    password_cmd=" -passin pass:${root_ca_password} "

    openssl ca -batch \
        -config "${openssl_root_config_file}" \
        ${password_cmd} \
        -extensions v3_intermediate_ca \
        -days ${days_till_expire} -notext -md sha256 \
        -in "${intermediate_ca_dir}/csr/${intermediate_ca_prefix}.csr.pem" \
        -out "${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?
    chmod 444 "${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "Verify signature of the Intermediate Device Certificate with Root CA"
    echo "-----------------------------------"
    openssl verify \
            -CAfile "${root_ca_dir}/certs/${root_ca_prefix}.cert.pem" \
            "${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "Intermediate CA Certificate Generated At:"
    echo "-----------------------------------------"
    echo "    ${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
    echo ""
    openssl x509 -noout -text \
            -in "${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "Create Root + Intermediate CA Chain Certificate"
    echo "-----------------------------------"
    cat "${intermediate_ca_dir}/certs/${intermediate_ca_prefix}.cert.pem" \
        "${root_ca_dir}/certs/${root_ca_prefix}.cert.pem" > \
        "${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?
    chmod 444 "${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "Root + Intermediate CA Chain Certificate Generated At:"
    echo "------------------------------------------------------"
    echo "    ${intermediate_ca_dir}/certs/${ca_chain_prefix}.cert.pem"

    warn_certs_not_for_production
}

###############################################################################
# Generate a Certificate for a device using specific openssl extension and
# signed with either the root or intermediate cert.
###############################################################################
function generate_device_certificate_common()
{
    local common_name="${1}"
    local device_prefix="${2}"
    local output_dir="${OUTPUT_DIR}"
    local certificate_dir="${3}"
    local ca_password="${4}"
    local server_pfx_password="1234"
    local password_cmd=" -passin pass:${ca_password} "
    local openssl_config_file="${5}"
    local openssl_config_extension="${6}"
    local cert_type_diagnostic="${7}"

    echo common_name=${common_name}
    echo device_prefix=${device_prefix}
    echo OUTPUT_DIR=${OUTPUT_DIR}
    echo certificate_dir=${certificate_dir}
    echo ca_password=${ca_password}
    echo server_pfx_password=${server_pfx_password}
    echo password_cmd=${password_cmd}
    echo openssl_config_file=${openssl_config_file}
    echo openssl_config_extension=${openssl_config_extension}
    echo cert_type_diagnostic=${cert_type_diagnostic}

    echo "Creating ${cert_type_diagnostic} Certificate"
    echo "----------------------------------------"
    cd "${certificate_dir}"

            #-out "${certificate_dir}/private/${device_prefix}.key.pem" \
    openssl "${algorithm}" \
            -out "./private/${device_prefix}.key.pem" \
            ${key_bits_length}
    [ $? -eq 0 ] || exit $?
    chmod 444 "./private/${device_prefix}.key.pem"
    [ $? -eq 0 ] || exit $?

    echo "Create the ${cert_type_diagnostic} Certificate Request"
    echo "----------------------------------------"
    openssl req -config "${openssl_config_file}" \
        -key "./private/${device_prefix}.key.pem" \
        -subj "$(make_CN_subject "${common_name}")" \
        -new -sha256 -out "./csr/${device_prefix}.csr.pem"
    [ $? -eq 0 ] || exit $?

    openssl ca -batch -config "${openssl_config_file}" \
            ${password_cmd} \
            -extensions "${openssl_config_extension}" \
            -days ${days_till_expire} -notext -md sha256 \
            -in "./csr/${device_prefix}.csr.pem" \
            -out "./certs/${device_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?
    chmod 444 "./certs/${device_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "Verify signature of the ${cert_type_diagnostic}" \
         " certificate with the signer"
    echo "-----------------------------------"
    openssl verify \
            -CAfile "./certs/${ca_chain_prefix}.cert.pem" \
            "./certs/${device_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?

    echo "${cert_type_diagnostic} Certificate Generated At:"
    echo "----------------------------------------"
    echo "    ./certs/${device_prefix}.cert.pem"
    echo ""
    openssl x509 -noout -text \
            -in "./certs/${device_prefix}.cert.pem"
    [ $? -eq 0 ] || exit $?
    echo "Create the ${cert_type_diagnostic} PFX Certificate"
    echo "----------------------------------------"
    openssl pkcs12 -in "./certs/${device_prefix}.cert.pem" \
            -inkey "./private/${device_prefix}.key.pem" \
            -password pass:${server_pfx_password} \
            -export -out "./certs/${device_prefix}.cert.pfx"
    [ $? -eq 0 ] || exit $?
    echo "${cert_type_diagnostic} PFX Certificate Generated At:"
    echo "--------------------------------------------"
    echo "    ./certs/${device_prefix}.cert.pfx"
    [ $? -eq 0 ] || exit $?
}

###############################################################################
# Generate a certificate for a leaf device
# signed with either the root or intermediate cert.
###############################################################################
function generate_leaf_certificate()
{
    local common_name="${1}"
    local device_prefix="${2}"
    local certificate_dir="${3}"
    local ca_password="${4}"
    local openssl_config_file="${5}"

    generate_device_certificate_common "${common_name}" "${device_prefix}" \
                                       "${certificate_dir}" "${ca_password}" \
                                       "${openssl_config_file}" "usr_cert" \
                                       "Leaf Device"
}

###############################################################################
#  Creates required directories and removes left over cert files.
#  Run prior to creating Root CA; after that these files need to persist.
###############################################################################
function prepare_filesystem()
{
    if [ ! -f ${openssl_root_config_file} ]; then
        echo "Missing file ${openssl_root_config_file}"
        exit 1
    fi

    if [ ! -f ${openssl_intermediate_config_file} ]; then
        echo "Missing file ${openssl_intermediate_config_file}"
        exit 1
    fi

    rm -rf ${root_ca_dir}/csr
    rm -rf ${root_ca_dir}/private
    rm -rf ${root_ca_dir}/certs
    rm -rf ${root_ca_dir}/intermediateCerts
    rm -rf ${root_ca_dir}/newcerts

    mkdir -p ${root_ca_dir}/csr
    mkdir -p ${root_ca_dir}/private
    mkdir -p ${root_ca_dir}/certs
    mkdir -p ${root_ca_dir}/intermediateCerts
    mkdir -p ${root_ca_dir}/newcerts

    rm -f ${root_ca_dir}/index.txt
    touch ${root_ca_dir}/index.txt

    rm -f ${root_ca_dir}/serial
    echo 01 > ${root_ca_dir}/serial
}

###############################################################################
# Generates a root and intermediate certificate for CA certs.
###############################################################################
function initial_cert_generation()
{
    prepare_filesystem
    generate_root_ca
    generate_intermediate_ca
}

###############################################################################
# Generates a certificate for verification, chained directly to the root.
###############################################################################
function generate_verification_certificate()
{
    if [ -z $1 ]; then
        echo "Usage: create_verification_certificate <subjectName>"
        exit 1
    fi

    rm -f ${root_ca_dir}/private/verification-code.key.pem
    rm -f ${root_ca_dir}/certs/verification-code.cert.pem
    generate_leaf_certificate "${1}" "verification-code" \
                              "${root_ca_dir}" "${root_ca_password}" \
                              "${openssl_root_config_file}"
}

###############################################################################
# Generates a certificate for a device, chained directly to the root.
###############################################################################
function generate_device_certificate()
{
    device_name_and_id=${1}
    input_dir=${2}

    if [ -z $1 ]; then
        echo "Usage: create_device_certificate <subjectName>"
        exit 1
    fi

    rm -f ${input_dir}/private/${DEVICE_NAME}.key.pem
    rm -f ${input_dir}/certs/${DEVICE_NAME}.key.pem
    rm -f ${input_dir}/certs/${DEVICE_NAME}-full-chain.cert.pem
    generate_leaf_certificate "${device_name_and_id}" "${DEVICE_NAME}" \
                              "${input_dir}" "${root_ca_password}" \
                              "${openssl_root_config_file}"
}


###############################################################################
# Generates a certificate for a device, chained to the intermediate.
###############################################################################
function generate_device_certificate_from_intermediate()
{
    device_name_and_id=${1}
    input_dir=${2}

    if [ -z $1 ]; then
        echo "Usage: create_device_certificate_from_intermediate <subjectName>"
        exit 1
    fi

    FILE="/workspace/build/cert/index.txt"
    SEQUENCE="CN=${device_name_and_id}"

    if grep -q "$SEQUENCE" "$FILE"; then
        echo "Device certificate was already generated for this device name"
    else
        rm -f ${input_dir}/certs/${DEVICE_NAME}.cert.pem
        rm -f ${input_dir}/certs/${DEVICE_NAME}.cert.pfx
        rm -f ${input_dir}/certs/${DEVICE_NAME}-full-chain.cert.pem
        rm -f ${input_dir}/csr/${DEVICE_NAME}.csr.pem
        rm -f ${input_dir}/private/${DEVICE_NAME}.key.pem
        generate_leaf_certificate "${device_name_and_id}" "${DEVICE_NAME}" \
                              "${input_dir}" "${intermediate_ca_password}" \
                              "${openssl_intermediate_config_file}"
    fi

}


###############################################################################
# Generates a certificate for a Edge device, chained to the intermediate.
###############################################################################
function generate_edge_device_certificate()
{
    local device_prefix="new-edge-device"
    if [ -z $1 ]; then
        echo "Usage: create_edge_device_certificate <subjectName>"
        exit 1
    fi
    rm -f ${root_ca_dir}/private/new-edge-device.key.pem
    rm -f ${root_ca_dir}/certs/new-edge-device.cert.pem
    rm -f ${root_ca_dir}/certs/new-edge-device-full-chain.cert.pem

    # Note: Appending a '.ca' to the common name is useful in situations
    # where a user names their hostname as the edge device name.
    # By doing so we avoid TLS validation errors where we have a server or
    # client certificate where the hostname is used as the common name
    # which essentially results in "loop" for validation purposes.
    generate_device_certificate_common "${1}.ca" \
                                       "${device_prefix}" \
                                       "${intermediate_ca_dir}" \
                                       "${intermediate_ca_password}" \
                                       "${openssl_intermediate_config_file}" \
                                       "v3_intermediate_ca" "Edge Device"
}

set +x
if [ "${ACTION_COMMAND}" == "create_root_and_intermediate" ]; then
    initial_cert_generation
elif [ "${ACTION_COMMAND}" == "create_verification_certificate" ]; then
    generate_verification_certificate "${ACTION_PARAM}"
elif [ "${ACTION_COMMAND}" == "create_device_certificate" ]; then
    generate_device_certificate "${ACTION_PARAM}" "${ACTION_PARAM2}"
elif [ "${ACTION_COMMAND}" == "create_device_certificate_from_intermediate" ]; then
    generate_device_certificate_from_intermediate "${ACTION_PARAM}" "${ACTION_PARAM2}"
elif [ "${ACTION_COMMAND}" == "create_edge_device_certificate" ]; then
    generate_edge_device_certificate "${ACTION_PARAM}"
else
    echo "Usage: create_root_and_intermediate                               # Creates a new root and intermediate certificates"
    echo "       create_verification_certificate <subjectName>              # Creates a verification certificate, signed with <subjectName>"
    echo "       create_device_certificate <subjectName>                    # Creates a device certificate signed by root with <subjectName>"
    echo "       create_device_certificate_from_intermediate <subjectName>  # Creates a device certificate signed by intermediate with <subjectName>"
    echo "       create_edge_device_certificate <subjectName>               # Creates an edge device certificate, signed with <subjectName>"
    exit 1
fi




warn_certs_not_for_production
