#!/usr/bin/env bash
#set -x

# For the license, see the LICENSE file in the root directory.

# shellcheck disable=SC1091

if [ "$(id -u)" -ne 0 ]; then
	echo "Need to be root to run this test."
	exit 77
fi

# tpmtool is not packaged everywhere ...
if [ -z "$(type -P tpmtool)" ]; then
	echo "Could not find tpmtool in PATH"
	exit 77
fi

ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:=$(dirname "$0")}
SRCDIR=${abs_top_srcdir:-$(dirname "$0")/..}

PATH=$ROOT/src/swtpm:$PATH

source "${TESTDIR}/test_config"
source "${TESTDIR}/common"

if [ -n "${SWTPM_TEST_UNINSTALLED}" ]; then
	SWTPM_CREATE_TPMCA=${SRCDIR}/samples/swtpm-create-tpmca
else
	SWTPM_CREATE_TPMCA=/usr/share/swtpm/swtpm-create-tpmca
fi
if [ ! -x "${SWTPM_CREATE_TPMCA}" ]; then
	echo "Could not find swtpm-create-tpmca"
	exit 77
fi

SWTPM_INTERFACE=socket+socket
SWTPM_SERVER_NAME=localhost
SWTPM_SERVER_PORT=65434
SWTPM_CTRL_PORT=65435

TCSD_LISTEN_PORT=65436

SRK_PASSWORD=srk
OWNER_PASSWORD=owner

workdir="$(mktemp -d)" || exit 1

TCSD_CONF=${workdir}/tcsd.conf
TCSD_SYSTEM_PS_FILE=${workdir}/system_ps_file
TCSD_PIDFILE=${workdir}/tcsd.pid
SWTPM_LOCALCA_DIR="${workdir}/my localca"
SWTPM_LOCALCA_CONF="${workdir}/my localca/swtpm-localca.conf"

# Captured TCSD file when using a SRK_PASSWORD=srk
TCSD_FILE="AQEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAABLwEAAAAAAwAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAQEAAAARAAAAAAEAAAABAAMAAQAAAAwAAAgAAAAAAgAAAAAAAAAA
AAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

function cleanup()
{
	if [ -n "${TCSD_PID}" ]; then
		kill_quiet -15 "${TCSD_PID}"
	fi
	if [ -n "${SWTPM_PID}" ]; then
		kill_quiet -9 "${SWTPM_PID}"
	fi
	if [ -n "${BASH_PID}" ]; then
		kill_quiet -9 "${BASH_PID}"
	fi
	rm -rf "${workdir}"
}

trap "cleanup" SIGTERM EXIT
skip_test_no_tpm12 "${SWTPM_EXE}"

PATH=${ROOT}/src/swtpm_bios:${ROOT}/src/swtpm_cert:${PATH}

# run the test with the given owner and SRK passwords
# @param1: owner password; empty means to use well known password
# @param2: SRK password; empty means to use well known password
# @param3: vTPM is a TPM 2.0
function run_test() {
	local owner_password="$1"
	local srk_password="$2"
	local vtpm_is_tpm2="$3"

	local params certinfo regex regexs fil i skip

	rm -rf "${workdir:?}"/*

	cat <<_EOF_ > "${workdir}/swtpm_setup.conf"
create_certs_tool=${SWTPM_LOCALCA}
create_certs_tool_config=${workdir}/swtpm-localca.conf
create_certs_tool_options=/dev/null
_EOF_

	params=""
	if [ -n "${owner_password}" ]; then
		params="${params} --ownerpass ${owner_password}"
	else
		params="${params} --owner-well-known"
	fi
	params="${params} --srkpass ${srk_password}"

	# First setup the TPM and take ownership of it and set SRK password
	if ! $SWTPM_SETUP \
		--runas root \
		--tpm-state "${workdir}" \
		--logfile "${workdir}/logfile" \
		--config "${workdir}/swtpm_setup.conf" \
		--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
		--take-ownership \
		${params:+${params}} >/dev/null; then
		echo "Error: Could not run $SWTPM_SETUP."
		echo "Setup Logfile:"
		cat "${workdir}/logfile"
		exit 1
	fi

	echo "Successfully took ownership of TPM and set owner and SRK passwords."

	run_swtpm "${SWTPM_INTERFACE}" \
		--flags not-need-init \
		--tpmstate "dir=${workdir}"

	# Startup the TPM
	res="$(swtpm_cmd_tx "${SWTPM_INTERFACE}" '\x00\xC1\x00\x00\x00\x0C\x00\x00\x00\x99\x00\x01')"
	exp=' 00 c4 00 00 00 0a 00 00 00 00'
	if [ "$res" != "$exp" ]; then
		echo "Error: Did not get expected result from TPM_Startup(ST_Clear)"
		echo "expected: $exp"
		echo "received: $res"
		exit 1
	fi

	echo "$TCSD_FILE" | base64 -d > "${TCSD_SYSTEM_PS_FILE}"

	# Setup the TCSD config file and start TCSD with it
	cat <<_EOF_ > "${TCSD_CONF}"
port = ${TCSD_LISTEN_PORT}
system_ps_file = ${TCSD_SYSTEM_PS_FILE}
_EOF_

	# Due to recent changes in tcsd we have to try with TSS_USER=tss and TSS_USER=root
	# Before the following worked:
	#   - tss:tss  0600   for  TSS_USER=tss and TSS_GROUP=tss
	#   - root:tss 0640   for  TSS_USER=root and TSS_GROUP=tss
	# After the changes:
	#   - root:tss 0640   for  TSS_USER=tss and TSS_GROUP=tss
	while :; do
		chown "${TSS_USER}:${TSS_GROUP}" "${TCSD_CONF}"
		if [ "${TSS_USER}" == "${TSS_GROUP}" ]; then
			chmod 0600 "${TCSD_CONF}"
		else
			chmod 0640 "${TCSD_CONF}"
		fi

		bash -c "TCSD_USE_TCP_DEVICE=1 TCSD_TCP_DEVICE_PORT=${SWTPM_SERVER_PORT} tcsd -c \"${TCSD_CONF}\" -e -f &>/dev/null & echo \$! > \"${TCSD_PIDFILE}\"; wait" &
		BASH_PID=$!

		if wait_for_file "${TCSD_PIDFILE}" 3; then
			echo "Error: Could not get TCSD's PID file"
			exit 1
		fi

		# wait for PID to be written
		sleep 0.5
		TCSD_PID=$(cat "${TCSD_PIDFILE}")
		if ! kill_quiet -0 "${TCSD_PID}"; then
			# Try again with root unless we already tried
			if [ "$TSS_USER" != "root" ]; then
				TSS_USER="root"
				continue
			fi
			echo "Error: TCSD with pid ${TCSD_PID} must have terminated"
			exit 1
		fi
		break
	done

	if ! ${SWTPM_CREATE_TPMCA} \
		--dir "${SWTPM_LOCALCA_DIR}" \
		--srk-password "${srk_password}" \
		--register \
		--group "${TSS_GROUP}" \
		--tss-tcsd-port "${TCSD_LISTEN_PORT}" \
		--outfile "${SWTPM_LOCALCA_CONF}" &>/dev/null; then
		echo "Error: Could not create TPM CA"
		exit 1
	fi

	for fil in \
		swtpm-localca-rootca-cert.pem \
		swtpm-localca-rootca-privkey.pem \
		swtpm-localca-tpmca-cert.pem \
		swtpm-localca-tpmca-pubkey.pem; do
		if [ ! -r "${SWTPM_LOCALCA_DIR}/${fil}" ]; then
			echo "Error: TPM CA tool did not create file ${fil}."
			exit 1
		fi
	done

	params=""
	if [ -n "${srk_password}" ]; then
		params="^parentkey_password ="
	fi

	for regex in \
		"^statedir = " \
		"^signingkey = " \
		"^issuercert = " \
		"^certserial = " \
		"^TSS_TCSD_HOSTNAME = " \
		"^TSS_TCSD_PORT = " \
		${params}; do
		if [ -n "${regex}" ] && \
		   ! grep -q -E "${regex}" "${SWTPM_LOCALCA_CONF}"; then
			echo "Error: Could not find regex '${regex}' in CA config file."
			cat "${SWTPM_LOCALCA_CONF}"
			exit 1
		fi
	done

	params=""
	if [ "${vtpm_is_tpm2}" -ne 0 ]; then
		params="--tpm2"
		skip=0
	else
		skip=7 # header in cert
	fi

	# make sure we can actually sign with this new certificate
	if ! ${SWTPM_LOCALCA} \
		--type ek \
		--ek x=739192d8f1004283957a7b1568d610b41c637ccc114aadcac4908c20456468fa,y=59f63ac06f8011f6fdd1460c6bc8e3e0a2d090d4fc188c7e04870e06795ce8ae \
		--dir "${workdir}" --vmid test \
		${params} \
		--tpm-spec-family 2.0 --tpm-spec-revision 146 --tpm-spec-level 00 \
		--tpm-model swtpm --tpm-version 20170101 --tpm-manufacturer IBM \
		--configfile "${SWTPM_LOCALCA_CONF}" \
		--optsfile /dev/null; then
		echo "Error: The CA could not sign with the new certificate"
		exit 1
	fi
	if [ ! -f "${workdir}/ek.cert" ]; then
		echo "Error: The CA did not produce a certificate"
		exit 1
	fi
	#  cert was for example 541 bytes long
	if [ "$(get_filesize "${workdir}/ek.cert")" -lt 500 ]; then
		echo "Error: The certificate's size is dubious"
		ls -l "${workdir}/ek.cert"
		exit 1
	fi

	# Check the contents of the certificate
	certinfo=$(dd "if=${workdir}/ek.cert" bs=1 "skip=$skip" status=none | \
		   "$CERTTOOL" -i --inder)
	regexs=('^[[:space:]]+2.23.133.8.1$'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.3=.*'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.2=.*'
		'^[[:space:]]+directoryName:.*(,)?2.23.133.2.1=.*'
		'^[[:space:]]+Certificate Authority \(CA\): FALSE$'
		'^[[:space:]]+Unknown extension 2.5.29.9 \(not critical\):$'
		'^[[:space:]]+Hexdump: 3019301706056781050210310e300c0c03322e3002010002020092$')
	if [ "${vtpm_is_tpm2}" -ne 0 ]; then
		# TPM 2.0; due to ecc: Key agreement
		regexs+=('^[[:space:]]+Key agreement\.$'
			 '^[[:space:]]+Signature Algorithm: RSA-SHA256$')
	else
		regexs+=('^[[:space:]]+Key encipherment\.$'
			 '^[[:space:]]+Signature Algorithm: RSA-SHA1$')
	fi

	for ((i=0; i < ${#regexs}; i++)); do \
		if [ -n "${regexs[$i]}" ] && \
		   ! echo "${certinfo}" | grep -q -E "${regexs[$i]}"; then
			echo "Error: Could not match regex '${regexs[$i]}' with certificate info:"
			echo "${certinfo}"
			exit 1
		fi
	done

	# Send SIGTERM to TCSD
	kill_quiet -15 "${TCSD_PID}"

	# Shut down TPM
	if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then
		echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
		exit 1
	fi

	if wait_process_gone "${SWTPM_PID}" 4; then
		echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
		exit 1
	fi

	if wait_process_gone "${SWTPM_PID}" 4; then
		echo "Error: tcsd should not be running anymore."
		exit 1
	fi
} # run_test

run_test "${OWNER_PASSWORD}" "${SRK_PASSWORD}" 1
echo "Test 1: OK"

run_test "${OWNER_PASSWORD}" "${SRK_PASSWORD}" 0
echo "Test 2: OK"

run_test "" "${SRK_PASSWORD}" 1
echo "Test 3: OK"

run_test "" "${SRK_PASSWORD}" 0
echo "Test 4: OK"

exit 0
