#!/bin/bash
#
# Written by Stephan Mueller <smueller@chronox.de>
#
# Checker script to validate the X.509 CSR along with private keys
# can be used to create certificates that can be validated with OpenSSL and vice
# versa.
#
# To utilize this script, perform the following steps:
#
# 1. compile leancrypto with X.509 generator enabled
# 2. update variable LC_X509_GENERATOR below to point to the lc_x509_generator
#    tool
# 3. Execute this script
#
# Expected result: no failures should be shown
#

LC_X509_GENERATOR="lc_x509_generator"
LC_PKCS7_GENERATOR="lc_pkcs7_generator"
OPENSSL="/usr/bin/openssl"

TESTTYPE=$1


if [ -n "$2" ]
then
	LC_X509_GENERATOR=$2
fi

if [ -n "$3" ]
then
	LC_PKCS7_GENERATOR=$3
fi

if [ -n "$4" ]
then
	OPENSSL=$4
fi

if [ ! -x "$LC_X509_GENERATOR" ]
then
	exit 77
fi

if [ ! -x "$LC_PKCS7_GENERATOR" ]
then
	exit 77
fi

if [ ! -x "$OPENSSL" ]
then
	exit 77
fi

# We need OpenSSL version 3.5.2 as a minimum
opensslver=$($OPENSSL --version | cut -f 2 -d" ")
if [ -z "$opensslver" ]
then
	exit 77
fi
openssl_ver_may=$(echo $opensslver | cut -f1 -d ".")
openssl_ver_min=$(echo $opensslver | cut -f2 -d ".")
openssl_ver_patch=$(echo $opensslver | cut -f3 -d ".")
if [ "$openssl_ver_may" -lt "3" ]
then
	exit 77
fi

if [ "$openssl_ver_may" -eq "3" -a "$openssl_ver_min" -lt "5" ]
then
	exit 77
fi

if [ "$openssl_ver_may" -eq "3" -a "$openssl_ver_min" -eq "5"  -a "$openssl_ver_patch" -lt "2" ]
then
	exit 77
fi

TMPDIR="./tmp.$$.$RANDOM"

global_failure_count=0

trap "rm -rf $TMPDIR" 0 1 2 3 15
mkdir $TMPDIR

pk_file="${TMPDIR}/siggen_tester_csr.der"
sk_file="${TMPDIR}/siggen_tester_privkey.der"

signer_pk_file="${TMPDIR}/siggen_tester_signer_cert.der"
signer_sk_file="${TMPDIR}/siggen_tester_signer_privkey.der"

color()
{
	bg=0
	echo -ne "\033[0m"
	while [[ $# -gt 0 ]]; do
		code=0
		case $1 in
			black) code=30 ;;
			red) code=31 ;;
			green) code=32 ;;
			yellow) code=33 ;;
			blue) code=34 ;;
			magenta) code=35 ;;
			cyan) code=36 ;;
			white) code=37 ;;
			background|bg) bg=10 ;;
			foreground|fg) bg=0 ;;
			reset|off|default) code=0 ;;
			bold|bright) code=1 ;;
		esac
		[[ $code == 0 ]] || echo -ne "\033[$(printf "%02d" $((code+bg)))m"
		shift
	done
}

echo_success()
{
	echo $(color "green")[SUCCESS]$(color off) "$@"
}

echo_fail()
{
	echo $(color "red")[FAILURE]$(color off) "$@"
	global_failure_count=$(($global_failure_count+1))
}

echo_info()
{
	echo $(color "magenta")[INFO]$(color off) "$@"
}

report_result() {
	echo "=== Final Result ==="
	if [ $global_failure_count -eq 0 ]
	then
		echo_success "No failures"
		exit 0
	else
		echo_fail "Total number of failures: $global_failure_count"
		exit 1
	fi
}

check_one() {
	local inputfile=$1
	local inform=$2

	if [ ! -f "$inputfile" ]
	then
		echo_fail "Cannot find $inputfile"
		exit 1
	fi

	echo "=== Checking file $inputfile with Leancrypto ==="
	$LC_X509_GENERATOR --print-x509 $inputfile
	if [ $? -ne 0 ]
	then
		echo_fail "Parsing of file $inputfile was unsuccessful"
	else
		echo_success "Parsing of file $inputfile was successful"
	fi

	# TODO: it is totally bizare why sometimes OpenSSL says it cannot
	# find a certificate
#	echo "=== Checking file $inputfile with OpenSSL ==="
#	$OPENSSL x509 -in $inputfile -inform $inform -text -noout
#	if [ $? -ne 0 ]
#	then
#		echo_fail "Parsing of file $inputfile was unsuccessful"
#	else
#		echo_success "Parsing of file $inputfile was successful"
#	fi

	if [ "x$inform" = "xPEM" ]
	then
		# Check DOS-Variant as well
		echo "=== Checking DOS-variant of file $inputfile with Leancrypto ==="
		sed -i 's/$/\r/' $inputfile
		$LC_X509_GENERATOR --print-x509 $inputfile
		if [ $? -ne 0 ]
		then
			echo_fail "Parsing of file $inputfile was unsuccessful"
		else
			echo_success "Parsing of file $inputfile was successful"
		fi
	fi
}

lc_generate_signer_cert_pkcs8() {
	local keytype=$1
	local pem_output=$2

	rm -f ${signer_sk_file} ${signer_pk_file}

	echo_info "Leancrypto: Generate X.509 certificate and associated PKCS#8 private key"

	$LC_X509_GENERATOR \
	 $pem_output \
	 --keyusage digitalSignature \
	 --keyusage keyEncipherment \
	 --keyusage keyCertSign \
	 --keyusage critical \
	 --ca \
	 --valid-days 365 \
	 --subject-cn 'leancrypto test CA' \
	 --create-keypair-pkcs8 $keytype \
	 --sk-file $signer_sk_file \
	 --outfile $signer_pk_file

	if [ $? -ne 0 ]
	then
		echo_fail "Failed leancrypto-internal certificate / PKCS#8 key generation"
	else
		echo_success "Successful leancrypto-internal certificate / PKCS#8 key generation"
	fi
}

lc_generate_csr_pkcs8() {
	local keytype=$1
	local pem_output=$2

	rm -f ${pk_file} ${sk_file}

	echo_info "Leancrypto: Generate X.509 CSR and associated PKCS#8 private key"

	$LC_X509_GENERATOR \
	 $pem_output \
	 --csr \
	 --subject-cn 'leancrypto test CSR' \
	 --create-keypair-pkcs8 $keytype \
	 --sk-file $sk_file \
	 --outfile $pk_file

	if [ $? -ne 0 ]
	then
		echo_fail "Failed leancrypto-internal CSR / PKCS#8 key generation"
	else
		echo_success "Successful leancrypto-internal CSR / PKCS#8 key generation"
	fi
}

lc_generate_csr_pkcs8_seed() {
	local keytype=$1
	local pem_output=$2

	rm -f ${pk_file} ${sk_file}

	echo_info "Leancrypto: Generate X.509 CSR and associated PKCS#8 private key"

	$LC_X509_GENERATOR \
	 $pem_output \
	 --csr \
	 --subject-cn 'leancrypto test CSR' \
	 --create-keypair-pkcs8-seed $keytype \
	 --sk-file $sk_file \
	 --outfile $pk_file

	if [ $? -ne 0 ]
	then
		echo_fail "Failed leancrypto-internal CSR / PKCS#8 key generation"
	else
		echo_success "Successful leancrypto-internal CSR / PKCS#8 key generation"
	fi

	local sk_size=""

	if [ "$(uname -s)" = "Darwin" ]
	then
		sk_size=$(stat -f "%z" $sk_file)
	else
		sk_size=$(stat --printf="%s" $sk_file)
	fi

	# Use 170 to cover the largest seed keys of SLH-DSA
	if [ $sk_size -lt 170 ]
	then
		echo_success "PKCS#8 with seed key generated"
	else
		echo_fail "PKCS#8 does not seem to be a seed key"
	fi
}

lc_sign_csr() {
	local inform=$1
	local pem_output=$2

	rm -f ${pk_file}.cert.der

	echo_info "Leancrypto: Create X.509 certificate using the X.509 CSR and an X.509 signer and associated PKCS#8 private key"

	$LC_X509_GENERATOR \
	 $pem_output \
	 --x509-csr ${pk_file} \
	 -o ${pk_file}.cert.der \
	 --keyusage digitalSignature \
	 --keyusage keyEncipherment \
	 --keyusage keyCertSign \
	 --keyusage critical \
	 --valid-days 365 \
	 --x509-signer  ${signer_pk_file} \
	 --signer-sk-file ${signer_sk_file} \
	 --print

	if [ $? -ne 0 ]
	then
		echo_fail "Failed leancrypto signature generation"
	else
		echo_success "Successful leancrypto signature generation"
	fi

	check_one ${pk_file}.cert.der $inform
}

ossl_generate_csr_pkcs8() {
	local keytype=$1
	local inform=$2

	rm -f ${pk_file} ${sk_file}

	echo_info "OpenSSL Generate X.509 CSR and associated PKCS#8 private key"

	$OPENSSL genpkey -algorithm $keytype -out $sk_file -outform $inform
	$OPENSSL pkey -in $sk_file -inform $inform -pubout -out $pk_file.raw.pem
	$OPENSSL req \
	 -new \
	 -key $sk_file \
	 -out $pk_file \
	 -outform $inform \
	 -subj "/CN=OpenSSL test CSR"

	if [ $? -ne 0 ]
	then
		echo_fail "Failed OpenSSL CSR / PKCS#8 key generation"
	else
		echo_success "Successful OpenSSL CSR / PKCS#8 key generation"
	fi
}

ossl_sign_csr() {
	local inform=$1

	rm -f ${pk_file}.cert.der

	echo_info "OpenSSL: Create X.509 certificate using the X.509 CSR and an X.509 signer and associated PKCS#8 private key"

	$OPENSSL x509 \
	 -req \
	 -in ${pk_file} \
	 -out ${pk_file}.cert.der \
	 -outform $inform \
	 -CA ${signer_pk_file} \
	 -CAkey ${signer_sk_file} \
	 -CAcreateserial

	if [ $? -ne 0 ]
	then
		echo_fail "Failed leancrypto signature generation"
	else
		echo_success "Successful leancrypto signature generation"
	fi

	check_one ${pk_file}.cert.der $inform
}

################################################################################
# TEST 1
#
# Leancrypto generation of CSR and use it for certificate generation
#
lc_keygen_lc_op() {
	lc_generate_signer_cert_pkcs8 $1
	lc_generate_csr_pkcs8 $1

	lc_sign_csr "DER"
	ossl_sign_csr "PEM"
}

################################################################################
# TEST 2
#
# OpenSSL generation of CSR and use it for certificate generation
#
ossl_keygen_lc_op() {
	lc_generate_signer_cert_pkcs8 $1
	ossl_generate_csr_pkcs8 $2 "DER"

	lc_sign_csr "DER"
}

################################################################################
# TEST 3
#
# Leancrypto seed key generation of CSR and use it for certificate generation
#
# NOTE: SLH-DSA out of the box generates keys where the PK and SK are seeds.
# Thus, the key type generated by ossl_generate_csr_pkcs8 and
# lc_generate_cert_pkcs8_seed is identical. Yet, we test SLH-DSA here for
# completeness reasons to ensure that the different API calls work with
# SLH-DSA as well.
#
# NOTE OpenSSL does not support reading PKCS#8 blobs with seed keys, thus
# only perform sigver with OpenSSL
#
lc_keygen_seed_lc_op() {
	lc_generate_signer_cert_pkcs8 $1
	lc_generate_csr_pkcs8_seed $1

	lc_sign_csr "DER"
	ossl_sign_csr "DER"
}

################################################################################
# TEST 4
#
# Leancrypto generation of CSR in PEM format and use it for certificate
# generation
#
lc_keygen_lc_op_pem() {
	lc_generate_signer_cert_pkcs8 $1 "--pem-output"
	lc_generate_csr_pkcs8 $1 "--pem-output"

	lc_sign_csr "PEM" "--pem-output"

	# Do not execute OpenSSL tester as we may have composite algos
	#ossl_sign_csr "PEM"
}

################################################################################
# TEST 5
#
# OpenSSL generation of CSR in PEM format and use it for certificate generation
#
ossl_keygen_lc_op_pem() {
	lc_generate_signer_cert_pkcs8 $1 "--pem-output"
	ossl_generate_csr_pkcs8 $2 "PEM"

	lc_sign_csr "PEM" "--pem-output"
}

case $TESTTYPE
in
	"ML-DSA87" | "ML-DSA-87")
		lc_keygen_lc_op "ML-DSA87" "ML-DSA-87"
		ossl_keygen_lc_op "ML-DSA87" "ML-DSA-87"
		lc_keygen_seed_lc_op "ML-DSA87" "ML-DSA-87"
		lc_keygen_lc_op_pem "ML-DSA87" "ML-DSA-87"
		ossl_keygen_lc_op_pem "ML-DSA87" "ML-DSA-87"
	;;
	"ML-DSA65" | "ML-DSA-65")
		lc_keygen_lc_op "ML-DSA65" "ML-DSA-65"
		ossl_keygen_lc_op "ML-DSA65" "ML-DSA-65"
		lc_keygen_seed_lc_op "ML-DSA65" "ML-DSA-65"
		lc_keygen_lc_op_pem "ML-DSA65" "ML-DSA-65"
		ossl_keygen_lc_op "ML-DSA65" "ML-DSA-65"
	;;
	"ML-DSA44" | "ML-DSA-44")
		lc_keygen_lc_op "ML-DSA44" "ML-DSA-44"
		ossl_keygen_lc_op "ML-DSA44" "ML-DSA-44"
		lc_keygen_seed_lc_op "ML-DSA44" "ML-DSA-44"
		lc_keygen_lc_op_pem "ML-DSA44" "ML-DSA-44"
		ossl_keygen_lc_op_pem "ML-DSA44" "ML-DSA-44"
	;;
	"ML-DSA87-ED448")
		lc_keygen_lc_op_pem "ML-DSA87-ED448"
	;;
	"ML-DSA65-ED25519")
		lc_keygen_lc_op_pem "ML-DSA65-ED25519"
	;;
	"ML-DSA44-ED25519")
		lc_keygen_lc_op_pem "ML-DSA44-ED25519"
	;;
	"SLH-DSA-SHAKE-128F" | "SLH-DSA-SHAKE-128f")
		lc_keygen_lc_op "SLH-DSA-SHAKE-128F" "SLH-DSA-SHAKE-128f"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-128F" "SLH-DSA-SHAKE-128f"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-128F" "SLH-DSA-SHAKE-128f"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-128F" "SLH-DSA-SHAKE-128f"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-128F" "SLH-DSA-SHAKE-128f"
	;;
	"SLH-DSA-SHAKE-128S" | "SLH-DSA-SHAKE-128s")
		lc_keygen_lc_op "SLH-DSA-SHAKE-128S" "SLH-DSA-SHAKE-128s"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-128S" "SLH-DSA-SHAKE-128s"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-128S" "SLH-DSA-SHAKE-128s"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-128S" "SLH-DSA-SHAKE-128s"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-128S" "SLH-DSA-SHAKE-128s"
	;;
	"SLH-DSA-SHAKE-192F" | "SLH-DSA-SHAKE-192f")
		lc_keygen_lc_op "SLH-DSA-SHAKE-192F" "SLH-DSA-SHAKE-192f"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-192F" "SLH-DSA-SHAKE-192f"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-192F" "SLH-DSA-SHAKE-192f"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-192F" "SLH-DSA-SHAKE-192f"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-192F" "SLH-DSA-SHAKE-192f"
	;;
	"SLH-DSA-SHAKE-192S" | "SLH-DSA-SHAKE-192s")
		lc_keygen_lc_op "SLH-DSA-SHAKE-192S" "SLH-DSA-SHAKE-192s"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-192S" "SLH-DSA-SHAKE-192s"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-192S" "SLH-DSA-SHAKE-192s"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-192S" "SLH-DSA-SHAKE-192s"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-192S" "SLH-DSA-SHAKE-192s"
	;;
	"SLH-DSA-SHAKE-256F" | "SLH-DSA-SHAKE-256f")
		lc_keygen_lc_op "SLH-DSA-SHAKE-256F" "SLH-DSA-SHAKE-256f"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-256F" "SLH-DSA-SHAKE-256f"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-256F" "SLH-DSA-SHAKE-256f"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-256F" "SLH-DSA-SHAKE-256f"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-256F" "SLH-DSA-SHAKE-256f"
	;;
	"SLH-DSA-SHAKE-256S" | "SLH-DSA-SHAKE-256s")
		lc_keygen_lc_op "SLH-DSA-SHAKE-256S" "SLH-DSA-SHAKE-256s"
		ossl_keygen_lc_op "SLH-DSA-SHAKE-256S" "SLH-DSA-SHAKE-256s"
		lc_keygen_seed_lc_op "SLH-DSA-SHAKE-256S" "SLH-DSA-SHAKE-256s"
		lc_keygen_lc_op_pem "SLH-DSA-SHAKE-256S" "SLH-DSA-SHAKE-256s"
		ossl_keygen_lc_op_pem "SLH-DSA-SHAKE-256S" "SLH-DSA-SHAKE-256s"
	;;
	*)
		echo_fail "Unknown test type $TESTTYPE"
	;;
esac

################################################################################
report_result
