#!/bin/bash -e

DOMAIN_NAME='mycompany.local'

DEFAULT_NODES=( 'conjur-master-1'
                'conjur-master-2'
                'conjur-master-3'
                'follower-1'
                'follower-2' )

CERT_PASSWORD='secret'

IMAGE_TAG='conjurinc-certgen'

REQUIRED_DIRS=( 'certs'
                'crl'
                'csr'
                'newcerts'
                'private' )

RSA_KEY_LENGTH=4096

BASE_PATH='certificates'
BASE_ROOT_CERT_PATH="${BASE_PATH}/root"
BASE_NODE_CERT_PATH="${BASE_PATH}/nodes"

CURRENT_DIR="$(pwd)"

if [[ "$#" -lt 1 ]]; then
  echo "Usage: $0 <number_of_intermediate_CAs> [node_name...]"
  exit 1
fi

# Grab the cert depth level from CLI args
INTERMEDIATE_CERT_DEPTH="${1}"
shift
echo "Trying to generate certs with ${INTERMEDIATE_CERT_DEPTH} intermediate CAs"

# Grab the node names from CLI args if they were specified
leaf_nodes="${DEFAULT_NODES[@]}"
if [[ "$#" -gt 0 ]]; then
  leaf_nodes="$@"
fi
echo "Leaf nodes: $leaf_nodes"

first_leaf_node="${leaf_nodes[0]}"
if [[ "${first_leaf_node}" != "${first_leaf_node%%.*}" ]]; then
  DOMAIN_NAME="${first_leaf_node#*.}"
  echo "Domain name detected: ${DOMAIN_NAME}"

  new_leaf_nodes=""
  for leaf_node in "${leaf_nodes}"; do
    new_leaf_nodes+=" ${leaf_nodes%%.*}"
  done
  leaf_nodes="${new_leaf_nodes}"
  echo "Leaf nodes: $leaf_nodes"
fi

echo "**************************************************"
echo "Building our SSL docker image..."
docker build -t "${IMAGE_TAG}" .

alt_names=""
for node_name in ${leaf_nodes}; do
  alt_names+="DNS:${node_name}.${DOMAIN_NAME},"
done

# Trim last character (comma)
alt_names="${alt_names%?}"

san=""
signer_name=""

function ssl_proxy {
  local work_dir="$1"
  shift

  local cmd="$@"

  if [ "${DEBUG}" == "true" ]; then
    set -x
  fi

  docker run -it \
             --rm \
             -v "${CURRENT_DIR}:/src" \
             -v "${CURRENT_DIR}/configs:/configs" \
             -w "/src/${work_dir}" \
             -e "SAN=${san}" \
             -e "SIGNER_NAME=${signer_name}" \
             "${IMAGE_TAG}" bash -c "
    $cmd
  "

  if [ "${DEBUG}" == "true" ]; then
    set +x
  fi

}

signer_name="root"
if [ ! -f "${BASE_ROOT_CERT_PATH}/certs/root.cert.pem" ]; then
  echo "**************************************************"
  echo "CA cert not found. Regenerating CA..."

  echo "Performing some housekeeping for CA..."
  for ca_dir in ${REQUIRED_DIRS[@]}; do
    ca_dir_path="${BASE_ROOT_CERT_PATH}/${ca_dir}"
    mkdir -p "${ca_dir_path}"
  done

  chmod 700 "${BASE_ROOT_CERT_PATH}/private"
  touch "${BASE_ROOT_CERT_PATH}/index.txt"
  echo 1000 > "${BASE_ROOT_CERT_PATH}/serial"

  echo "Generating root CA RSA key..."
  ssl_proxy "${BASE_ROOT_CERT_PATH}" \
    openssl genrsa -aes256 \
                   -passout "pass:$CERT_PASSWORD" \
                   -out "private/root.key.pem" \
                   "${RSA_KEY_LENGTH}"
  ssl_proxy "${BASE_ROOT_CERT_PATH}" chmod 400 "private/root.key.pem"

  echo "Generating root CA certificate from the RSA key..."
  ssl_proxy "${BASE_ROOT_CERT_PATH}" \
    openssl req -config "/configs/openssl.cnf" \
      -passout "pass:$CERT_PASSWORD" \
      -passin "pass:$CERT_PASSWORD" \
      -subj "\"/C=US/ST=Massachusetts/O=CyberArk/OU=Conjur/CN=Root CA\"" \
      -key "private/root.key.pem" \
      -new -x509 -days 7300 -sha256 -extensions v3_ca \
      -out "certs/root.cert.pem"

  ssl_proxy "${BASE_ROOT_CERT_PATH}" chmod 444 "certs/root.cert.pem"

  if [ "${DEBUG}" == "true" ]; then
    ssl_proxy "${BASE_ROOT_CERT_PATH}" openssl x509 -noout -text -in "certs/root.cert.pem"
  fi
fi

echo "**************************************************"

for intermediate_cert_index in $(seq 1 ${INTERMEDIATE_CERT_DEPTH}); do
  intermediate_cert_name="intermediate_${intermediate_cert_index}"
  intermediate_cert_cn="Intermediate CA ${intermediate_cert_index}"
  intermediate_cert_file="${intermediate_cert_name}.cert.pem"
  intermediate_key_file="${intermediate_cert_name}.key.pem"
  intermediate_csr_file="${intermediate_cert_name}.csr.pem"

  intermediate_ca_path="${BASE_PATH}/${intermediate_cert_name}"

  if [[ ! -f "${CURRENT_DIR}/${intermediate_ca_path}" ]]; then
    echo "Performing some housekeeping for intermediate CA..."
    for ca_dir in ${REQUIRED_DIRS[@]}; do
      ca_dir_path="${intermediate_ca_path}/${ca_dir}"
      mkdir -p "${ca_dir_path}"
    done

    chmod 700 "${intermediate_ca_path}/private"
    touch "${intermediate_ca_path}/index.txt"
    echo 1000 > "${intermediate_ca_path}/crlnumber"
    echo 1000 > "${intermediate_ca_path}/serial"
  fi

  echo "Checking intermediate cert number ${intermediate_cert_index}..."
  if [ ! -f "${intermediate_ca_path}/certs/${intermediate_cert_file}" ]; then
    echo "Intermediate cert '${intermediate_cert_name}' not found. Regenerating..."

    echo "Generating intermediate RSA key (${intermediate_cert_name})..."
    ssl_proxy "${intermediate_ca_path}" \
      openssl genrsa -aes256 \
        -passout "pass:${CERT_PASSWORD}" \
        -out "private/${intermediate_key_file}" \
        "${RSA_KEY_LENGTH}"
    ssl_proxy "${intermediate_ca_path}" chmod 400 "private/${intermediate_key_file}"

    echo "Generating intermediate CSR (${intermediate_cert_name}) from the RSA key..."
    signer_name="${intermediate_cert_name}"
    ssl_proxy "${intermediate_ca_path}" \
      openssl req -config "/configs/openssl.cnf" \
        -new -sha256 \
        -passout "pass:${CERT_PASSWORD}" \
        -passin "pass:${CERT_PASSWORD}" \
        -subj "\"/C=US/ST=Massachusetts/L=Newton/O=CyberArk/OU=Conjur/CN=${intermediate_cert_cn}\"" \
        -key "private/${intermediate_key_file}" \
        -out "csr/${intermediate_csr_file}"

    skip_cert_chain="false"
    signer_name="intermediate_$(( intermediate_cert_index - 1 ))"
    if [[ "${intermediate_cert_index}" == "1" ]]; then
      echo "First intermediate cert detected. Signing with root key."
      skip_cert_chain="true"
      signer_name="root"
    fi

    echo "Using signer cert '${signer_name}'"

    echo "Creating a parent CA-signed intermediate cert (${intermediate_cert_name}) from the CSR..."
    ssl_proxy "${intermediate_ca_path}" \
      openssl ca -config "/configs/openssl.cnf" \
        -extensions v3_intermediate_ca \
        -days 3650 -notext -md sha256 -batch \
        -passin "pass:${CERT_PASSWORD}" \
        -in "csr/${intermediate_csr_file}" \
        -out "certs/${intermediate_cert_file}"

    ssl_proxy "${intermediate_ca_path}" chmod 444 "certs/${intermediate_cert_file}"

    if [ "${DEBUG}" == "true" ]; then
      ssl_proxy "${intermediate_ca_path}" openssl x509 -noout -text \
        -in "certs/${intermediate_cert_file}"
    fi

    echo "Building cert chain list for verification..."
    untrusted_certs_params=""
    if [ "${skip_cert_chain}" != "true" ]; then
      untrusted_certs_file="${BASE_PATH}/intermediate_${intermediate_cert_index}/rootless-ca-chain.cert.pem"
      for cert_chain_index in $( seq 1 $((${intermediate_cert_index} - 1)) ); do
        cert_path="${CURRENT_DIR}/${BASE_PATH}/intermediate_${cert_chain_index}/certs/intermediate_${cert_chain_index}.cert.pem"
        cat "${cert_path}" >> "${CURRENT_DIR}/${untrusted_certs_file}"
      done
      untrusted_certs_params="-untrusted /src/${untrusted_certs_file}"
    fi

    echo "Validating that the generated cert (${intermediate_cert_name}) is signed with the right CA key..."
    ssl_proxy "${intermediate_ca_path}" \
      openssl verify -CAfile "/src/certificates/root/certs/root.cert.pem" \
        ${untrusted_certs_params} \
        "/src/${intermediate_ca_path}/certs/${intermediate_cert_file}"
  fi
done

echo "Building the complete cert chain..."
cert_chain_files=""
for intermediate_cert_index in $(seq ${INTERMEDIATE_CERT_DEPTH} 1); do
  intermediate_cert_name="intermediate_${intermediate_cert_index}"
  intermediate_cert_file="${intermediate_cert_name}.cert.pem"

  cert_chain_files+=" ${CURRENT_DIR}/certificates/${intermediate_cert_name}/certs/${intermediate_cert_file}"
done
cert_chain_files+=" ${CURRENT_DIR}/certificates/root/certs/root.cert.pem"

echo "Creating the complete cert chain file..."
if [ "${DEBUG}" == "true" ]; then
  echo "Cert chain:${cert_chain_files}"
fi
cat ${cert_chain_files} > "${CURRENT_DIR}/certificates/ca-chain.cert.pem"


echo "**************************************************"
echo "Generating node certificates..."

for node_name in ${leaf_nodes}; do
  node_fqdn="${node_name}.${DOMAIN_NAME}"
  node_cert_folder="${BASE_NODE_CERT_PATH}/${node_fqdn}"
  node_cert_file="${node_cert_folder}/${node_fqdn}.cert.pem"
  node_key_file="${node_cert_folder}/${node_fqdn}.key.pem"
  node_csr_file="${node_cert_folder}/${node_fqdn}.csr.pem"

  echo "Checking '${node_fqdn}' cert..."
  if [ ! -f "$node_cert_folder/$node_fqdn.cert.pem" ]; then
    echo "Node '$node_fqdn' cert missing. Regenerating..."
    mkdir -p "${CURRENT_DIR}/$node_cert_folder"

    echo "Generating node RSA keypair..."
    ssl_proxy "." openssl genrsa -out "/src/${node_key_file}" "${RSA_KEY_LENGTH}"

    echo "Creating the leaf CSR from RSA key..."
    san="DNS:${node_name},${alt_names}"
    # CN cannot be >64chars. https://github.com/certbot/certbot/issues/1915#issuecomment-165262834
    node_cn="${node_fqdn%%.*}"
    signer_name="intermediate_${INTERMEDIATE_CERT_DEPTH}"
    ssl_proxy "." openssl req -config "/configs/openssl.cnf" \
        -subj "\"/C=US/ST=Massachusetts/L=Newton/O=CyberArk/OU=Conjur/CN=${node_cn}\"" \
        -extensions san_env \
        -key "/src/${node_key_file}" \
        -new -sha256 -out "/src/${node_csr_file}"

    echo "Signing leaf node with the intermediate cert..."
    ssl_proxy "." openssl ca -config "/configs/openssl.cnf" \
        -extensions server_cert -days 365 -notext -md sha256 -batch \
        -passin "pass:${CERT_PASSWORD}" \
        -extensions san_env \
        -in "/src/${node_csr_file}" \
        -out "/src/${node_cert_file}"
    chmod 444 "${CURRENT_DIR}/${node_cert_file}"

    if [ "${DEBUG}" == "true" ]; then
      ssl_proxy "." \
        openssl x509 -noout -text -in "/src/${node_cert_file}"
    fi

    chmod 400 "${CURRENT_DIR}/${node_key_file}"
  fi
done

# Some of this code is repetitive but it allows for easier visibility
# that all the certs are properly validated at the end of the run
echo "**************************************************"
echo "Validating that the generated node certs are properly signed..."
for node_name in ${leaf_nodes}; do
  node_fqdn="${node_name}.${DOMAIN_NAME}"
  node_cert_folder="${BASE_NODE_CERT_PATH}/${node_fqdn}"
  node_cert_file="${node_cert_folder}/${node_fqdn}.cert.pem"
  ssl_proxy "." \
    openssl verify -CAfile "/src/certificates/ca-chain.cert.pem" \
      "${node_cert_file}"
done
