#!/bin/bash
# TPM Wrapper - to unify tpm and tpm2 subcommands

. /etc/functions

SECRET_DIR="/tmp/secret"
PRIMARY_HANDLE="0x81000000"
ENC_SESSION_FILE="$SECRET_DIR/enc.ctx"
DEC_SESSION_FILE="$SECRET_DIR/dec.ctx"
PRIMARY_HANDLE_FILE="$SECRET_DIR/primary.handle"

# PCR size in bytes.  Set when we determine what TPM version is in use.
# TPM1 PCRs are always 20 bytes.  TPM2 is allowed to provide multiple PCR banks
# with different algorithms - we always use SHA-256, so they are 32 bytes.
PCR_SIZE=

# Export CONFIG_TPM2_CAPTURE_PCAP=y from your board config to capture tpm2 pcaps to
# /tmp/tpm0.pcap; Wireshark can inspect these.  (This must be enabled at build
# time so the pcap TCTI driver is included.)
if [ -n "$CONFIG_TPM2_CAPTURE_PCAP" ]; then
	export TPM2TOOLS_TCTI="pcap:device:/dev/tpmrm0"
	export TCTI_PCAP_FILE="/tmp/tpm0.pcap"
fi

set -e -o pipefail
if [ -r "/tmp/config" ]; then
	. /tmp/config
else
	. /etc/config
fi

TRACE_FUNC

# Busybox xxd lacks -r, and we get hex dumps from TPM1 commands.  This converts
# a hex dump to binary data using sed and printf
hex2bin() {
	sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf
}

# Render a password as 'hex:<hexdump>' for use with tpm2-tools.  Passwords
# should always be passed this way to avoid ambiguity.  (Passing with no prefix
# would choke if the password happened to start with 'file:' or 'hex:'.  Passing
# as a file still chokes if the password begins with 'hex:', oddly tpm2-tools
# accepts 'hex:' in the file content.)
tpm2_password_hex() {
	echo "hex:$(echo -n "$1" | xxd -p | tr -d ' \n')"
}

# usage: tpmr pcrread [-a] <index> <file>
# Reads PCR binary data and writes to file.
# -a: Append to file.  Default is to overwrite.
tpm2_pcrread() {
	TRACE_FUNC
	if [ "$1" = "-a" ]; then
		APPEND=y
		shift
	fi

	index="$1"
	file="$2"

	if [ -z "$APPEND" ]; then
		# Don't append - truncate file now so real command always
		# appends
		true >"$file"
	fi

	DO_WITH_DEBUG tpm2 pcrread -Q -o >(cat >>"$file") "sha256:$index"
}
tpm1_pcrread() {
	TRACE_FUNC
	if [ "$1" = "-a" ]; then
		APPEND=y
		shift
	fi

	index="$1"
	file="$2"

	if [ -z "$APPEND" ]; then
		# Don't append - truncate file now so real command always
		# appends
		true >"$file"
	fi

	DO_WITH_DEBUG tpm pcrread -ix "$index" | hex2bin >>"$file"
}

# is_hash - Check if a value is a valid hash of a given type
# usage: is_hash <alg> <value>
is_hash() {
	# Must only contain 0-9a-fA-F
	if [ "$(echo -n "$2" | tr -d '0-9a-fA-F' | wc -c)" -ne 0 ]; then return 1; fi
	# SHA-1 hashes are 40 chars
	if [ "$1" = "sha1" ] && [ "${#2}" -eq 40 ]; then return 0; fi
	# SHA-256 hashes are 64 chars
	if [ "$1" = "sha256" ] && [ "${#2}" -eq 64 ]; then return 0; fi
	return 1
}

# extend_pcr_state - extend a PCR state value with more hashes or raw data (which is hashed)
# usage:
# extend_pcr_state <alg> <initial_state> <files/hashes...>
# alg - either 'sha1' or 'sha256' to specify algorithm
# initial_state - a hash value setting the initial state
# files/hashes... - any number of files or hashes, state is extended once for each item
extend_pcr_state() {
	local alg="$1"
	local state="$2"
	local next extend
	shift 2

	while [ "$#" -gt 0 ]; do
		next="$1"
		shift
		if is_hash "$alg" "$next"; then
			extend="$next"
		else
			extend="$("${alg}sum" <"$next" | cut -d' ' -f1)"
		fi
		state="$(echo "$state$extend" | hex2bin | "${alg}sum" | cut -d' ' -f1)"
	done
	echo "$state"
}

# There are 3 (and a half) possible formats of event log, each of them requires
# different arguments for grep. Those formats are shown below as heredocs to
# keep all the data, including whitespaces:
# 1) TPM2 log, which can hold multiple hash algorithms at once:
: <<'EOF'
TPM2 log:
	Specification: 2.00
	Platform class: PC Client
TPM2 log entry 1:
	PCR: 2
	Event type: Action
	Digests:
		 SHA256: de73053377e1ae5ba5d2b637a4f5bfaeb410137722f11ef135e7a1be524e3092
		 SHA1: 27c4f1fa214480c8626397a15981ef3a9323717f
	Event data: FMAP: FMAP
EOF
# 2) TPM1.2 log (aka TCPA), digest is always SHA1:
: <<'EOF'
TCPA log:
	Specification: 1.21
	Platform class: PC Client
TCPA log entry 1:
	PCR: 2
	Event type: Action
	Digest: 27c4f1fa214480c8626397a15981ef3a9323717f
	Event data: FMAP: FMAP
EOF
# 3) coreboot-specific format:
#   3.5) older versions printed 'coreboot TCPA log', even though it isn't TCPA
: <<'EOF'
coreboot TPM log:

 PCR-2 27c4f1fa214480c8626397a15981ef3a9323717f SHA1 [FMAP: FMAP]
EOF

# awk script to handle all of the above. Note this gets squashed to one line so
# semicolons are required.
AWK_PROG='
BEGIN {
	getline;
	hash_regex="([a-fA-F0-9]{40,})";
	if ($0 == "TPM2 log:") {
		RS="\n[^[:space:]]";
		pcr="PCR: " pcr;
		alg=toupper(alg) ": " hash_regex;
	} else if ($0 == "TCPA log:") {
		RS="\n[^[:space:]]";
		pcr="PCR: " pcr;
		alg="Digest: " hash_regex;
	} else if ($0 ~ /^coreboot (TCPA|TPM) log:$/) {
		pcr="PCR-" pcr;
		alg=hash_regex " " toupper(alg) " ";
	} else {
		print "Unknown TPM event log format:", $0 > "/dev/stderr";
		exit -1;
	}
}
$0 ~ pcr {
	match($0, alg);
	print gensub(alg, "\\1", "g", substr($0, RSTART, RLENGTH));
}
'

# usage: replay_pcr <alg> <pcr_num> [ <input_file>|<input_hash> ... ]
# Replays PCR value from CBMEM event log. Note that this contains only the
# measurements performed by firmware, without those performed by Heads (USB
# modules, LUKS header etc). First argument is PCR number, followed by optional
# hashes and/or files extended to given PCR after firmware. Resulting PCR value
# is returned in binary form.
replay_pcr() {
	TRACE_FUNC
	if [ -z "$2" ]; then
		echo >&2 "No PCR number passed"
		return
	fi
	if [ "$2" -ge 8 ]; then
		echo >&2 "Illegal PCR number ($2)"
		return
	fi
	local log=$(cbmem -L)
	local alg="$1"
	local pcr="$2"
	local alg_digits=0
	# SHA-1 hashes are 40 chars
	if [ "$alg" = "sha1" ]; then alg_digits=40; fi
	# SHA-256 hashes are 64 chars
	if [ "$alg" = "sha256" ]; then alg_digits=64; fi
	shift 2
	replayed_pcr=$(extend_pcr_state $alg $(printf "%.${alg_digits}d" 0) \
		$(echo "$log" | awk -v alg=$alg -v pcr=$pcr -f <(echo $AWK_PROG)) $@)
	echo $replayed_pcr | hex2bin
	DEBUG "Replayed cbmem -L clean boot state of PCR=$pcr ALG=$alg : $replayed_pcr"
	# To manually introspect current PCR values:
	# PCR-2:
	#     tpmr calcfuturepcr 2 | xxd -p
	# PCR-4, in case of recovery shell (bash used for process substitution):
	#     bash -c "tpmr calcfuturepcr 4 <(echo -n recovery)" | xxd -p
	# PCR-4, in case of normal boot passing through kexec-select-boot:
	#     bash -c "tpmr calcfuturepcr 4 <(echo -n generic)" | xxd -p
	# PCR-5, depending on which modules are loaded for given board:
	#     tpmr calcfuturepcr 5 module0.ko module1.ko module2.ko | xxd -p
	# PCR-6 and PCR-7: similar to 5, but with different files passed
	#  (6: LUKS header, 7: user related cbfs files loaded from cbfs-init)
}

tpm2_extend() {
	TRACE_FUNC
	while true; do
		case "$1" in
		-ix)
			index="$2"
			shift 2
			;;
		-ic)
			hash="$(echo -n "$2" | sha256sum | cut -d' ' -f1)"
			shift 2
			;;
		-if)
			hash="$(sha256sum "$2" | cut -d' ' -f1)"
			shift 2
			;;
		*)
			break
			;;
		esac
	done
	tpm2 pcrextend "$index:sha256=$hash"
	DO_WITH_DEBUG tpm2 pcrread "sha256:$index"
}

tpm2_counter_read() {
	TRACE_FUNC
	while true; do
		case "$1" in
		-ix)
			index="$2"
			shift 2
			;;
		*)
			break
			;;
		esac
	done
	echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)"
}

tpm2_counter_inc() {
	TRACE_FUNC
	while true; do
		case "$1" in
		-ix)
			index="$2"
			shift 2
			;;
		-pwdc)
			pwd="$2"
			shift 2
			;;
		*)
			break
			;;
		esac
	done
	tpm2 nvincrement "0x$index" >/dev/console
	echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)"
}

tpm1_counter_create() {
	TRACE_FUNC
	# tpmr handles the TPM owner password (from cache or prompt), but all
	# other parameters for TPM1 are passed directly, and TPM2 mimics the
	# TPM1 interface.
	prompt_tpm_owner_password
	if ! tpm counter_create -pwdo "$(cat "/tmp/secret/tpm_owner_password")" "$@"; then
		DEBUG "Failed to create counter from tpm1_counter_create. Wiping /tmp/secret/tpm_owner_password"
		shred -n 10 -z -u /tmp/secret/tpm_owner_password
		die "Unable to create counter from tpm1_counter_create"
	fi
}

tpm2_counter_create() {
	TRACE_FUNC
	while true; do
		case "$1" in
		-pwdc)
			pwd="$2"
			shift 2
			;;
		-la)
			label="$2"
			shift 2
			;;
		*)
			break
			;;
		esac
	done
	prompt_tpm_owner_password
	rand_index="1$(dd if=/dev/urandom bs=1 count=3 | xxd -pc3)"
	tpm2 nvdefine -C o -s 8 -a "ownerread|authread|authwrite|nt=1" \
		-P "$(tpm2_password_hex "$(cat "/tmp/secret/tpm_owner_password")")" "0x$rand_index" >/dev/console ||
		{
			DEBUG "Failed to create counter from tpm2_counter_create. Wiping /tmp/secret/tpm_owner_password"
			shred -n 10 -z -u /tmp/secret/tpm_owner_password
			die "Unable to create counter from tpm2_counter_create"
		}
	echo "$rand_index: (valid after an increment)"
}

tpm2_startsession() {
	TRACE_FUNC
	mkdir -p "$SECRET_DIR"
	tpm2 flushcontext -Q \
		--transient-object ||
		die "tpm2_flushcontext: unable to flush transient handles"

	tpm2 flushcontext -Q \
		--loaded-session ||
		die "tpm2_flushcontext: unable to flush sessions"

	tpm2 flushcontext -Q \
		--saved-session ||
		die "tpm2_flushcontext: unable to flush saved session"
	tpm2 readpublic -Q -c "$PRIMARY_HANDLE" -t "$PRIMARY_HANDLE_FILE"
	tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$ENC_SESSION_FILE"
	tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$DEC_SESSION_FILE"
	tpm2 sessionconfig -Q --disable-encrypt "$DEC_SESSION_FILE"
}

# Use cleanup_session() with at_exit to release a TPM2 session and delete the
# session file.  E.g.:
#   at_exit cleanup_session "$SESSION_FILE"
cleanup_session() {
	TRACE_FUNC
	session_file="$1"
	if [ -f "$session_file" ]; then
		DEBUG "Clean up session: $session_file"
		# Nothing else we can do if this fails, still remove the file
		tpm2 flushcontext -Q "$session_file" || DEBUG "Flush failed for session $session_file"
		rm -f "$session_file"
	else
		DEBUG "No need to clean up session: $session_file"
	fi
}

# Clean up a file by shredding it.  No-op if the file wasn't created.  Use with
# at_exit, e.g.:
#   at_exit cleanup_shred "$FILE"
cleanup_shred() {
	TRACE_FUNC
	shred -n 10 -z -u "$1" 2>/dev/null || true
}

# tpm2_destroy: Destroy a sealed file in the TPM.  The mechanism differs by
# TPM version - TPM2 evicts the file object, so it no longer exists.
tpm2_destroy() {
	index="$1" # Index of the sealed file
	size="$2"  # Size of zeroes to overwrite for TPM1 (unused in TPM2)

	# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
	handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"

	# remove possible data occupying this handle
	tpm2 evictcontrol -Q -C p -c "$handle" 2>/dev/null ||
		die "Unable to evict secret from TPM NVRAM"
}

# tpm1_destroy: Destroy a sealed file in the TPM.  The mechanism differs by
# TPM version - TPM1 overwrites the file with zeroes, since this can be done
# without authorization.  (Deletion requires authorization.)
tpm1_destroy() {
	index="$1" # Index of the sealed file
	size="$2"  # Size of zeroes to overwrite for TPM1

	dd if=/dev/zero bs="$size" count=1 of=/tmp/wipe-totp-zero
	tpm nv_writevalue -in "$index" -if /tmp/wipe-totp-zero ||
		die "Unable to wipe sealed secret from TPM NVRAM"
}

# tpm2_seal: Seal a file against PCR values and, optionally, a password.
# If a password is given, both the PCRs and password are required to unseal the
# file.  PCRs are provided as a PCR list and data file.  PCR data must be
# provided - TPM2 allows the TPM to fall back to current PCR values, but it is
# not required to support this.
tpm2_seal() {
	TRACE_FUNC
	file="$1" #$KEY_FILE
	index="$2"
	pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
	pcrf="$4"
	sealed_size="$5"  # Not used for TPM2
	pass="$6"         # May be empty to seal with no password
	tpm_password="$7" # Owner password - will prompt if needed and not empty
	# TPM Owner Password is always needed for TPM2.

	mkdir -p "$SECRET_DIR"
	bname="$(basename $file)"

	# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
	handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"

	DEBUG "tpm2_seal: file=$file handle=$handle pcrl=$pcrl pcrf=$pcrf pass=$(mask_param "$pass")"

	# Create a policy requiring both PCRs and the object's authentication
	# value using a trial session.
	TRIAL_SESSION="$SECRET_DIR/sealfile_trial.session"
	AUTH_POLICY="$SECRET_DIR/sealfile_auth.policy"
	rm -f "$TRIAL_SESSION" "$AUTH_POLICY"
	tpm2 startauthsession -g sha256 -S "$TRIAL_SESSION"
	# We have to clean up the session
	at_exit cleanup_session "$TRIAL_SESSION"
	# Save the policy hash in case the password policy is not used (we have
	# to get this from the last step, whichever it is).
	tpm2 policypcr -Q -l "sha256:$pcrl" -f "$pcrf" -S "$TRIAL_SESSION" -L "$AUTH_POLICY"
	CREATE_PASS_ARGS=()
	if [ "$pass" ]; then
		# Add an object authorization policy (the object authorization
		# will be the password).  Save the digest, this is the resulting
		# policy.
		tpm2 policypassword -Q -S "$TRIAL_SESSION" -L "$AUTH_POLICY"
		# Pass the password to create later.  Pass the sha256sum of the
		# password to the TPM so the password is not limited to 32 chars
		# in length.
		CREATE_PASS_ARGS=(-p "$(tpm2_password_hex "$pass")")
	fi

	# Create the object with this policy and the auth value.
	# NOTE: We disable USERWITHAUTH and enable ADMINWITHPOLICY so the
	# password cannot be used on its own, the PCRs are also required.
	# (The default is to allow either policy auth _or_ password auth.  In
	# this case the policy includes the password, and we don't want to allow
	# the password on its own.)
	tpm2 create -Q -C "$PRIMARY_HANDLE_FILE" \
		-i "$file" \
		-u "$SECRET_DIR/$bname.priv" \
		-r "$SECRET_DIR/$bname.pub" \
		-L "$AUTH_POLICY" \
		-S "$DEC_SESSION_FILE" \
		-a "fixedtpm|fixedparent|adminwithpolicy" \
		"${CREATE_PASS_ARGS[@]}"

	tpm2 load -Q -C "$PRIMARY_HANDLE_FILE" \
		-u "$SECRET_DIR/$bname.priv" -r "$SECRET_DIR/$bname.pub" \
		-c "$SECRET_DIR/$bname.seal.ctx"
	prompt_tpm_owner_password
	# remove possible data occupying this handle
	tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \
		-c "$handle" 2>/dev/null || true
	DO_WITH_DEBUG --mask-position 6 \
		tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \
		-c "$SECRET_DIR/$bname.seal.ctx" "$handle" ||
		{
			DEBUG "Failed to write sealed secret to NVRAM from tpm2_seal. Wiping /tmp/secret/tpm_owner_password"
			shred -n 10 -z -u /tmp/secret/tpm_owner_password
			die "Unable to write sealed secret to TPM NVRAM"
		}
}
tpm1_seal() {
	TRACE_FUNC
	file="$1"
	index="$2"
	pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
	pcrf="$4"
	sealed_size="$5"
	pass="$6"         # May be empty to seal with no password
	tpm_owner_password="$7" # Owner password - will prompt if needed and not empty

	sealed_file="$SECRET_DIR/tpm1_seal_sealed.bin"
	at_exit cleanup_shred "$sealed_file"

	POLICY_ARGS=()

	DEBUG "tpm1_seal arguments: file=$file index=$index pcrl=$pcrl pcrf=$pcrf sealed_size=$sealed_size pass=$(mask_param "$pass") tpm_password=$(mask_param "$tpm_password")"


	# If a password was given, add it to the policy arguments
	if [ "$pass" ]; then
		POLICY_ARGS+=(-pwdd "$pass")
	fi

	# Transform the PCR list and PCR file to discrete arguments
	IFS=',' read -r -a PCR_LIST <<<"$pcrl"
	pcr_file_index=0
	for pcr in "${PCR_LIST[@]}"; do
		# Read each PCR_SIZE block from the file and pass as hex
		POLICY_ARGS+=(-ix "$pcr"
			"$(dd if="$pcrf" skip="$pcr_file_index" bs="$PCR_SIZE" count=1 status=none | xxd -p | tr -d ' ')"
		)
		pcr_file_index=$((pcr_file_index + 1))
	done

	tpm sealfile2 \
		-if "$file" \
		-of "$sealed_file" \
		-hk 40000000 \
		"${POLICY_ARGS[@]}"
	
	# try it without the TPM Owner Password first
	if ! tpm nv_writevalue -in "$index" -if "$sealed_file"; then
		# to create an nvram space we need the TPM Owner Password
		# and the TPM physical presence must be asserted.
		#
		# The permissions are 0 since there is nothing special
		# about the sealed file
		tpm physicalpresence -s ||
			warn "Unable to assert physical presence"

		prompt_tpm_owner_password

		tpm nv_definespace -in "$index" -sz "$sealed_size" \
			-pwdo "$tpm_owner_password" -per 0 ||
			warn "Unable to define TPM NVRAM space; trying anyway"

		tpm nv_writevalue -in "$index" -if "$sealed_file" ||
			{
				DEBUG "Failed to write sealed secret to NVRAM from tpm1_seal. Wiping /tmp/secret/tpm_owner_password"
				shred -n 10 -z -u /tmp/secret/tpm_owner_password
				die "Unable to write sealed secret to TPM NVRAM"
			}
	fi
}

# Unseal a file sealed by tpm2_seal.  The PCR list must be provided, the
# password must be provided if one was used to seal (and cannot be provided if
# no password was used to seal).
tpm2_unseal() {
	TRACE_FUNC
	index="$1"
	pcrl="$2" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
	sealed_size="$3"
	file="$4"
	pass="$5"

	# TPM2 doesn't care about sealed_size, only TPM1 needs that.  We don't
	# have to separately read the sealed file on TPM2.

	# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
	handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"

	DEBUG "tpm2_unseal: handle=$handle pcrl=$pcrl file=$file pass=$(mask_param "$pass")"

	# If we don't have the primary handle (TPM hasn't been reset), tpm2 will
	# print nonsense error messages about an unexpected handle value.  We
	# can't do anything without a primary handle.
	if [ ! -f "$PRIMARY_HANDLE_FILE" ]; then
		DEBUG "tpm2_unseal: No primary handle, cannot attempt to unseal"
		warn "No TPM primary handle. You must reset TPM to seal secret to TPM NVRAM"
		exit 1
	fi

	POLICY_SESSION="$SECRET_DIR/unsealfile_policy.session"
	rm -f "$POLICY_SESSION"
	tpm2 startauthsession -Q -g sha256 -S "$POLICY_SESSION" --policy-session
	at_exit cleanup_session "$POLICY_SESSION"
	# Check the PCR policy
	tpm2 policypcr -Q -l "sha256:$pcrl" -S "$POLICY_SESSION"
	UNSEAL_PASS_SUFFIX=""

	if [ "$pass" ]; then
		# Add the object authorization policy (the actual password is
		# provided later, but we must include this so the policy we
		# attempt to use is correct).
		tpm2 policypassword -Q -S "$POLICY_SESSION"
		# When unsealing, include the password with the auth session
		UNSEAL_PASS_SUFFIX="+$(tpm2_password_hex "$pass")"
	fi

	tpm2 unseal -Q -c "$handle" -p "session:$POLICY_SESSION$UNSEAL_PASS_SUFFIX" \
		-S "$ENC_SESSION_FILE" >"$file"
}
tpm1_unseal() {
	TRACE_FUNC
	index="$1"
	pcrl="$2"
	sealed_size="$3"
	file="$4"
	pass="$5"

	# pcrl (the PCR list) is unused in TPM1.  The TPM itself knows which
	# PCRs were used to seal and checks them.  We can't verify that it's
	# correct either, so just ignore it in TPM1.

	sealed_file="$SECRET_DIR/tpm1_unseal_sealed.bin"
	at_exit cleanup_shred "$sealed_file"

	rm -f "$sealed_file"

	tpm nv_readvalue \
		-in "$index" \
		-sz "$sealed_size" \
		-of "$sealed_file" ||
		die "Unable to read sealed file from TPM NVRAM"

	PASS_ARGS=()
	if [ "$pass" ]; then
		PASS_ARGS=(-pwdd "$pass")
	fi

	tpm unsealfile \
		-if "$sealed_file" \
		-of "$file" \
		"${PASS_ARGS[@]}" \
		-hk 40000000
}

tpm2_reset() {
	TRACE_FUNC
	tpm_owner_password="$1"
	mkdir -p "$SECRET_DIR"
	# output TPM Owner Password to a file to be reused in this boot session until recovery shell/reboot
	DEBUG "Caching TPM Owner Password to $SECRET_DIR/tpm_owner_password"
	echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password"
	tpm2 clear -c platform || warn "Unable to clear TPM on platform hierarchy"
	tpm2 changeauth -c owner "$(tpm2_password_hex "$tpm_owner_password")"
	tpm2 changeauth -c endorsement "$(tpm2_password_hex "$tpm_owner_password")"
	tpm2 createprimary -C owner -g sha256 -G "${CONFIG_PRIMARY_KEY_TYPE:-rsa}" \
		-c "$SECRET_DIR/primary.ctx" -P "$(tpm2_password_hex "$tpm_owner_password")"
	tpm2 evictcontrol -C owner -c "$SECRET_DIR/primary.ctx" "$PRIMARY_HANDLE" \
		-P "$(tpm2_password_hex "$tpm_owner_password")"
	shred -u "$SECRET_DIR/primary.ctx"
	tpm2_startsession

	# Set the dictionary attack parameters.  TPM2 defaults vary widely, we
	# want consistent behavior on any TPM.
	# * --max-tries=10: Allow 10 failures before lockout.  This allows the
	#   user to quickly "burst" 10 failures without significantly impacting
	#   the rate allowed for a dictionary attacker.
	#   Most TPM2 flows ask for the TPM Owner Password 2-4 times, so this allows
	#   a handful of mistypes and some headroom for an expected unseal
	#   failure if firmware is updated.
	#   Remember that an auth failure is also counted any time an unclean
	#   shutdown occurs (see TPM2 spec part 1, section 19.8.6, "Non-orderly
	#   Shutdown").
	# * --recovery-time=3600: Forget an auth failure every 1 hour.
	# * --lockout-recovery-time: After a failed lockout recovery auth, the
	#   TPM must be reset to try again.
	#
	# Heads does not offer a way to reset dictionary attack lockout, instead
	# the TPM can be reset and new secrets sealed.
	tpm2 dictionarylockout -Q --setup-parameters \
		--max-tries=10 \
		--recovery-time=3600 \
		--lockout-recovery-time=0 \
		--auth="session:$ENC_SESSION_FILE"

	# Set a random DA lockout password, so the DA lockout can't be cleared
	# with a password.  Heads doesn't offer dictionary attach reset, instead
	# the TPM can be reset and new secrets sealed.
	#
	# The default lockout password is empty, so we must set this, and we
	# don't need to provide any auth (use the default empty password).
	tpm2 changeauth -Q -c lockout \
		"hex:$(dd if=/dev/urandom bs=32 count=1 status=none | xxd -p | tr -d ' \n')"
}
tpm1_reset() {
	TRACE_FUNC
	tpm_owner_password="$1"
	mkdir -p "$SECRET_DIR"
	# output tpm_owner_password to a file to be reused in this boot session until recovery shell/reboot
	DEBUG "Caching TPM Owner Password to $SECRET_DIR/tpm_owner_password"
	echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password"
	# Make sure the TPM is ready to be reset
	tpm physicalpresence -s
	tpm physicalenable
	tpm physicalsetdeactivated -c
	tpm forceclear
	tpm physicalenable
	tpm takeown -pwdo "$tpm_owner_password"

	# And now turn it all back on
	tpm physicalpresence -s
	tpm physicalenable
	tpm physicalsetdeactivated -c
}

# Perform final cleanup before boot and lock the platform heirarchy.
tpm2_kexec_finalize() {
	TRACE_FUNC

	# Flush sessions and transient objects
	tpm2 flushcontext -Q --transient-object ||
		warn "tpm2_flushcontext: unable to flush transient handles"
	tpm2 flushcontext -Q --loaded-session ||
		warn "tpm2_flushcontext: unable to flush sessions"
	tpm2 flushcontext -Q --saved-session ||
		warn "tpm2_flushcontext: unable to flush saved session"

	# Add a random passphrase to platform hierarchy to prevent TPM2 from
	# being cleared in the OS.
	# This passphrase is only effective before the next boot.
	echo "Locking TPM2 platform hierarchy..."
	randpass=$(dd if=/dev/urandom bs=4 count=1 status=none | xxd -p)
	tpm2 changeauth -c platform "$randpass" ||
		warn "Failed to lock platform hierarchy of TPM2"
}

tpm2_shutdown() {
	TRACE_FUNC

	# Prepare for shutdown.
	# This is a "clear" shutdown (do not preserve runtime state) since we
	# are not going to resume later, we are powering off (or rebooting).
	tpm2 shutdown -Q --clear
}

if [ "$CONFIG_TPM" != "y" ]; then
	echo >&2 "No TPM!"
	exit 1
fi

# TPM1 - most commands forward directly to tpm, but some are still wrapped for
# consistency with tpm2.
if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then
	PCR_SIZE=20 # TPM1 PCRs are always SHA-1
	subcmd="$1"
	# Don't shift yet, for most commands we will just forward to tpm.
	case "$subcmd" in
	pcrread)
		shift
		tpm1_pcrread "$@"
		;;
	pcrsize)
		echo "$PCR_SIZE"
		;;
	calcfuturepcr)
		shift
		replay_pcr "sha1" "$@"
		;;
	counter_create)
		shift
		tpm1_counter_create "$@"
		;;
	destroy)
		shift
		tpm1_destroy "$@"
		;;
	seal)
		shift
		tpm1_seal "$@"
		;;
	startsession) ;; # Nothing on TPM1.
	unseal)
		shift
		tpm1_unseal "$@"
		;;
	reset)
		shift
		tpm1_reset "$@"
		;;
	kexec_finalize) ;; # Nothing on TPM1.
	shutdown) ;;       # Nothing on TPM1.
	*)
		DEBUG "Direct translation from tpmr to tpm1 call"
		DO_WITH_DEBUG exec tpm "$@"
		;;
	esac
	exit 0
fi

# TPM2 - all commands implemented as wrappers around tpm2
PCR_SIZE=32 # We use the SHA-256 PCRs
subcmd="$1"
shift 1
case "$subcmd" in
pcrread)
	tpm2_pcrread "$@"
	;;
pcrsize)
	echo "$PCR_SIZE"
	;;
calcfuturepcr)
	replay_pcr "sha256" "$@"
	;;
extend)
	tpm2_extend "$@"
	;;
counter_read)
	tpm2_counter_read "$@"
	;;
counter_increment)
	tpm2_counter_inc "$@"
	;;
counter_create)
	tpm2_counter_create "$@"
	;;
destroy)
	tpm2_destroy "$@"
	;;
seal)
	tpm2_seal "$@"
	;;
startsession)
	tpm2_startsession "$@"
	;;
unseal)
	tpm2_unseal "$@"
	;;
reset)
	tpm2_reset "$@"
	;;
kexec_finalize)
	tpm2_kexec_finalize "$@"
	;;
shutdown)
	tpm2_shutdown "$@"
	;;
*)
	echo "Command $subcmd not wrapped!"
	exit 1
	;;
esac