#!/usr/bin/env bash
# ==========================================================================
#     _   _ _ ____            ____          _____
#    | | | (_)  _ \ ___ _ __ / ___|___  _ _|_   _| __ __ _  ___ ___ _ __
#    | |_| | | |_) / _ \ '__| |   / _ \| '_ \| || '__/ _` |/ __/ _ \ '__|
#    |  _  | |  __/  __/ |  | |__| (_) | | | | || | | (_| | (_|  __/ |
#    |_| |_|_|_|   \___|_|   \____\___/|_| |_|_||_|  \__,_|\___\___|_|
#
#       ---  High-Performance Connectivity Tracer (HiPerConTracer)  ---
#                 https://www.nntb.no/~dreibh/hipercontracer/
# ==========================================================================
#
# High-Performance Connectivity Tracer (HiPerConTracer)
# Copyright (C) 2015-2025 by Thomas Dreibholz
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact: dreibh@simula.no

set -eu


# ###### Usage ##############################################################
usage () {
   echo >&2 "Usage: $0 [-C|--crl crl] [-h|--help] ca_certificate certificate [...]"
   exit 1
}


# ###### Main program #######################################################

# ====== Handle arguments ===================================================
DIRNAME=$(dirname "$0")
GETOPT="$(PATH=/usr/local/bin:${PATH} which getopt)"
options="$(${GETOPT} -o c:h --long crl:,help -a -- "$@")"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
   usage
fi

CRL=""
eval set -- "${options}"
while [ $# -gt 0 ] ; do
   case "$1" in
      -C | --crl)
         CRL="$2"
         shift 2
         ;;
      -h | --help)
         usage
         # shift
         ;;
      --)
         shift
         break
         ;;
  esac
done
if [ $# -lt 1 ] ; then
   usage
fi
CA="$1"
shift

# ====== Check availability of tools ========================================
OPENSSL="$(which openssl || true)"
CERTTOOL="$(which certtool || true)"
CERTUTIL="$(which certutil || true)"
CRLUTIL="$(which crlutil || true)"

# ====== Look for CA certificate ============================================
if [ ! -e "${CA}" ] ; then
   echo >&2 "ERROR: Unable to find CA certificate file ${CA}!"
   exit 1
fi

# ====== Look for CRL =======================================================
if [ "${CRL}" != "" ] && [ ! -e "${CRL}" ] ; then
   echo >&2 "ERROR: Unable to find CRL file ${CRL}!"
   exit 1
fi


# ====== Check each certificate =============================================
errors=0
while [ $# -gt 0 ] ; do

   # ====== Look for certificate ============================================
   CERT="$1"
   shift
   if [ ! -e "${CERT}" ] ; then
      echo >&2 "ERROR: Unable to find certificate file ${CERT}!"
      exit 1
   fi

   # ====== Show hierarchy ==================================================
   echo -e "\e[34mHierarchy in ${CERT}:\e[0m"
   "${OPENSSL}" crl2pkcs7 -nocrl -certfile "${CERT}" | \
      "${OPENSSL}" pkcs7 -print_certs -noout | awk '
BEGIN {
   S=0
}
/^$/       { next }
/^issuer/  { next }
/^subject/ { S++; N=sprintf("%d. ", S); print("\x1b[33m", N $0, "\x1b[0m"); next }
# /^issuer/  { print "\x1b[37m   " $0 "\x1b[0m"; next }
{ print }
'

   # ====== Verify certificate with CA certificate ==========================
   echo -e "\e[34mVerifying ${CERT} with CA ${CA}:\e[0m"
   if [ "${CA}" == "${CERT}" ] ; then
      echo "WARNING: Only checking self-signature!"
   fi


   # ====== OpenSSL =========================================================
   echo -en "\e[33mOpenSSL:\e[0m "
   if [ "${OPENSSL}" != "" ] ; then
      crlOption=""
      if [ "${CRL}" != "" ] ; then
         crlOption="-CRLfile \"${CRL}\" -crl_check"
      fi
      command="\"${OPENSSL}\" verify -CAfile \"${CA}\" ${crlOption} --untrusted \"${CERT}\" \"${CERT}\""
      if sh -c "${command}" >/dev/null 2>&1 ; then
         echo -e "\e[32;1mOKAY\e[0m"
      else
         errors=$((errors + 1))
         echo -e "\e[31;1mFAILED!\e[0m"
         sh -c "${command}" || true
      fi
   else
      echo "(GnuTLS CertTool is not installed)"
   fi


   # ====== GNU TLS =========================================================
   echo -en "\e[33mGNU TLS:\e[0m "
   if [ "${CERTTOOL}" != "" ] ; then
      crlOption=""
      if [ "${CRL}" != "" ] ; then
         crlOption="--load-crl=\"${CRL}\""
      fi
      command="\"${CERTTOOL}\" --verify --verify-profile=high --load-ca-certificate=\"${CA}\" ${crlOption} --infile=\"${CERT}\""
      if sh -c "${command}" >/dev/null 2>&1 ; then
         echo -e "\e[32;1mOKAY\e[0m"
      else
         errors=$((errors + 1))
         echo -e "\e[31;1mFAILED!\e[0m"
         sh -c "${command}" || true
      fi
   else
      echo "(GnuTLS CertTool is not installed)"
   fi


   # ====== NSS =============================================================
   echo -en "\e[33mNSS:    \e[0m "
   if [ "${CERTUTIL}" != "" ] && [ "${CRLUTIL}" != "" ] ; then

      # NOTE:
      # NSS does not support importing a certificate with CAs bundled! It is
      # necessary to extract each CA, and then process them individually!

      # ------ Create temporary directory for database ----------------------
      trap 'rm -rf "${db}"' EXIT
      db=$(mktemp --tmpdir -d "check-certificate-nss.XXXXXXXXXX")
      mkdir -p "${db}/tmp"

      "${CERTUTIL}" --empty-password -N -d "${db}"
      if [ "${CA}" != "${CERT}" ] ; then
         "${CERTUTIL}" -A -n "My Root CA" -t "CT,C,C" -e -d "${db}" -a -i "${CA}" -u L
      fi
      "${CERTUTIL}" -A -n "My Certificate" -t ",," -e -d "${db}" -a -i "${CERT}"

      # ------ Extract certificate and process each sub-CA individually -----
      "${DIRNAME}/extract-pem" "${CERT}" --skip-first-entry --skip-last-entry \
         --quiet --output-prefix "${db}/tmp/certificate-"
      subCA=0
      find "${db}/tmp" -name "certificate-[0-9]*.crt" | sort -r | \
         while read -r name ; do
            subCA=$((subCA + 1))
            "${CERTUTIL}" -A -n "My Sub CA ${subCA}" -t ",," -e -d "${db}" -a -i "${name}" -u L
         done
      find "${db}/tmp" -name "certificate-[0-9]*.crl"

      # ------ Extract CRL file and process each CRL individually -----------
      if [ "${CRL}" != "" ] ; then
         "${DIRNAME}/extract-pem" "${CRL}" --quiet --output-prefix "${db}/tmp/crl-"

         # NSS wants the CRL in DER instead of PEM format, i.e. it needs
         # to be converted first. Finally, it can be imported by NSS.
         find "${db}/tmp" -name "crl-*.crl" | \
            while read -r crl ; do
               "${OPENSSL}" crl -outform der -in "${crl}" -out "${crl}.der"
               "${CRLUTIL}" -I -d "${db}" -a -i "${crl}.der"
            done
      fi

      # "${CERTUTIL}" -L -d "${db}"
      # "${CRLUTIL}" -L -d "${db}"

      # ------ Finally, it is possible to verify the validity chain ---------
      declare -A types=(
         ["server"]="V"
         ["client"]="C"
         ["CA"]="L"
         ["email_signer"]="S"
         ["email_recipient"]="R"
      )
      good=0
      for type in "${!types[@]}" ; do
         setting="${types[$type]}"
         "${CERTUTIL}" -V -e -u "${setting}" -n "My Certificate" -d "${db}" >/dev/null 2>&1 && \
            good=$((good + 1)) && \
            if [ ${good} -eq 1 ] ; then echo -en "\e[32;1mOKAY\e[0m" ; fi && \
            echo -en "   \e[36m${type}\e[0m"
      done
      echo ""
      if [ ${good} -eq 0 ] ; then
         errors=$((errors + 1))
         echo -e "\e[31;1mFAILED!\e[0m"
         "${CERTUTIL}" -V -e -u "${setting}" -n "My Certificate" -d "${db}" || true
         # "${CERTUTIL}" -L -d "${db}"
         # "${CRLUTIL}" -L -d "${db}"
      fi
   else
      echo "(NSS CertUtil is not installed)"
   fi

done

# ====== Check for errors ===================================================
if [ ${errors} -gt 0 ] ; then
   echo -e "\e[37;41;1m***** CERTIFICATE CHECKS FAILED! *****\e[0m"
   exit 1
fi
