#!/usr/bin/env bash

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

# This script may not work with softhsm2 2.0.0 but with >= 2.2.0

if [ -z "$(type -P p11tool)" ]; then
	echo "Need p11tool from gnutls"
	exit 77
fi

if [ -z "$(type -P softhsm2-util)" ]; then
	echo "Need softhsm2-util from softhsm2 package"
	exit 77
fi

NAME=swtpm-test
PIN=${PIN:-1234}
SO_PIN=${SO_PIN:-1234}
SOFTHSM_SETUP_CONFIGDIR=${SOFTHSM_SETUP_CONFIGDIR:-~/.config/softhsm2}
export SOFTHSM2_CONF=${SOFTHSM_SETUP_CONFIGDIR}/softhsm2.conf

UNAME_S="$(uname -s)"

case "${UNAME_S}" in
Darwin)
	if ! msg=$(sudo -v -n); then
		echo "Need password-less sudo rights on OS X to change /etc/gnutls/pkcs11.conf"
		exit 1
	fi
	;;
esac

teardown_softhsm() {
	local configdir=${SOFTHSM_SETUP_CONFIGDIR}
	local configfile=${SOFTHSM2_CONF}
	local bakconfigfile=${configfile}.bak
	local tokendir=${configdir}/tokens

	softhsm2-util --token "${NAME}" --delete-token &>/dev/null

	case "${UNAME_S}" in
	Darwin*)
		if [ -f /etc/gnutls/pkcs11.conf.bak ]; then
			sudo rm -f /etc/gnutls/pkcs11.conf
			sudo mv /etc/gnutls/pkcs11.conf.bak \
			   /etc/gnutls/pkcs11.conf &>/dev/null
		fi
		;;
	esac

	if [ -f "$bakconfigfile" ]; then
		mv "$bakconfigfile" "$configfile"
	else
		rm -f "$configfile"
	fi
	if [ -d "$tokendir" ]; then
		rm -rf "${tokendir}"
	fi
	return 0
}

setup_softhsm() {
	local msg tokenuri keyuri
	local configdir=${SOFTHSM_SETUP_CONFIGDIR}
	local configfile=${SOFTHSM2_CONF}
	local bakconfigfile=${configfile}.bak
	local tokendir=${configdir}/tokens
	local rc

	case "${UNAME_S}" in
	Darwin*)
		if [ -f /etc/gnutls/pkcs11.conf.bak ]; then
			echo "/etc/gnutls/pkcs11.conf.bak already exists; need to 'teardown' first"
			return 1
		fi
		sudo mv /etc/gnutls/pkcs11.conf \
			/etc/gnutls/pkcs11.conf.bak &>/dev/null
		if [ "$(id -u)" -eq 0 ]; then
			SONAME="$(sudo -u nobody brew ls --verbose softhsm | \
				  grep -E "\.so$")"
		else
			SONAME="$(brew ls --verbose softhsm | \
				  grep -E "\.so$")"
		fi
		sudo mkdir -p /etc/gnutls &>/dev/null
		sudo bash -c "echo 'load=${SONAME}' > /etc/gnutls/pkcs11.conf"
		;;
	esac

	if ! [ -d "$configdir" ]; then
		mkdir -p "$configdir"
	fi
	mkdir -p "${tokendir}"

	if [ -f "$configfile" ]; then
		mv "$configfile" "$bakconfigfile"
	fi

	if ! [ -f "$configfile" ]; then
		cat <<_EOF_ > "$configfile"
directories.tokendir = ${tokendir}
objectstore.backend = file
log.level = DEBUG
slots.removable = false
_EOF_
	fi

	if ! msg=$(p11tool --list-tokens 2>&1 | grep "token=${NAME}" | tail -n1); then
		echo "Could not list existing tokens"
		echo "$msg"
	fi
	tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')

	if [ -z "$tokenuri" ]; then
		if ! msg=$(softhsm2-util \
			   --init-token --pin "${PIN}" --so-pin "${SO_PIN}" \
			   --free --label "${NAME}" 2>&1); then
			echo "Could not initialize token"
			echo "$msg"
			return 2
		fi

		slot=$(echo "$msg" | \
		       sed -n 's/.* reassigned to slot \([0-9]*\)$/\1/p')
		if [ -z "$slot" ]; then
			slot=$(softhsm2-util --show-slots | \
			       grep -E "^Slot " | head -n1 |
			       sed -n 's/Slot \([0-9]*\)/\1/p')
			if [ -z "$slot" ]; then
				echo "Could not parse slot number from output."
				echo "$msg"
				return 3
			fi
		fi

		if ! msg=$(p11tool --list-tokens 2>&1 | \
			   grep "token=${NAME}" | tail -n1); then
			echo "Could not list existing tokens"
			echo "$msg"
		fi
		tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
		if [ -z "${tokenuri}" ]; then
			echo "Could not get tokenuri!"
			return 4
		fi

		# more recent versions of p11tool have --generate-privkey ...
		if ! msg=$(GNUTLS_PIN=$PIN p11tool \
			   --generate-privkey=rsa --bits 2048 --label mykey --login \
			"${tokenuri}" 2>&1); then
			# ... older versions have --generate-rsa
			if ! msg=$(GNUTLS_PIN=$PIN p11tool \
				   --generate-rsa --bits 2048 --label mykey --login \
				   "${tokenuri}" 2>&1); then
				echo "Could not create RSA key!"
				echo "$msg"
				return 5
			fi
		fi
	fi

	getkeyuri_softhsm "$slot"
	rc=$?
	if [ $rc -ne 0 ]; then
		teardown_softhsm
	fi

	return $rc
}

_getkeyuri_softhsm() {
	local msg tokenuri keyuri

	if ! msg=$(p11tool --list-tokens 2>&1 | grep "token=${NAME}"); then
		echo "Could not list existing tokens"
		echo "$msg"
		return 5
	fi
	tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
	if [ -z "$tokenuri" ]; then
		echo "Could not get token URL"
		echo "$msg"
		return 6
	fi
	if ! msg=$(p11tool --list-all "${tokenuri}" 2>&1); then
		echo "Could not list object under token $tokenuri"
		echo "$msg"
		softhsm2-util --show-slots
		return 7
	fi

	keyuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
	if [ -z "$keyuri" ]; then
		echo "Could not get key URL"
		echo "$msg"
		return 8
	fi
	echo "$keyuri"
	return 0
}

getkeyuri_softhsm() {
	local keyuri rc

	keyuri=$(_getkeyuri_softhsm)
	rc=$?
	if [ $rc -ne 0 ]; then
		return $rc
	fi
	echo "keyuri: $keyuri?pin-value=${PIN}&module-name=softhsm2"
	return 0
}

getpubkey_softhsm() {
	local keyuri rc

	keyuri=$(_getkeyuri_softhsm)
	rc=$?
	if [ $rc -ne 0 ]; then
		return $rc
	fi
	GNUTLS_PIN=${PIN} p11tool --export-pubkey "${keyuri}" --login 2>/dev/null
	return $?
}

usage() {
	cat <<_EOF_
Usage: $0 [command]

Supported commands are:

setup      : Setup the user's account for softhsm and create a
             token and key with a test configuration

getkeyuri  : Get the key's URI; may only be called after setup

getpubkey  : Get the public key in PEM format; may only be called after setup

teardown   : Remove the temporary softhsm test configuration

_EOF_
}

main() {
	local ret

	if [ $# -lt 1 ]; then
		usage "$0"
		echo -e "Missing command.\n\n"
		return 1
	fi
	case "$1" in
	setup)
		setup_softhsm
		ret=$?
		;;
	getkeyuri)
		getkeyuri_softhsm
		ret=$?
		;;
	getpubkey)
		getpubkey_softhsm
		ret=$?
		;;
	teardown)
		teardown_softhsm
		ret=$?
		;;
	*)
		echo -e "Unsupported command: $1\n\n"
		usage "$0"
		ret=1
	esac
	return $ret
}

main "$@"
exit $?
