#!/bin/bash
# Unseal a disk key from TPM and add to a new initramfs
set -e -o pipefail
. /etc/functions

TRACE "Under /bin/kexec-insert-key"

TMP_KEY_DEVICES="/tmp/kexec/kexec_key_devices.txt"
TMP_KEY_LVM="/tmp/kexec/kexec_key_lvm.txt"

INITRD="$1"

if [ -z "$INITRD" ]; then
	die "Usage: $0 /boot/initramfs... "
fi

if [ ! -r "$TMP_KEY_DEVICES" ]; then
	die "No devices defined for disk encryption"
fi

if [ -r "$TMP_KEY_LVM" ]; then
	# Activate the LVM volume group
	VOLUME_GROUP=$(cat $TMP_KEY_LVM)
	if [ -z "$TMP_KEY_LVM" ]; then
		die "No LVM volume group defined for activation"
	fi
	lvm vgchange -a y $VOLUME_GROUP ||
		die "$VOLUME_GROUP: unable to activate volume group"
fi

# Measure the LUKS headers before we unseal the disk key
cat "$TMP_KEY_DEVICES" | cut -d\  -f1 | xargs /bin/qubes-measure-luks ||
	die "LUKS measure failed"

# Unpack the initrd and fixup the crypttab
# this is a hack to split it into two parts since
# we know that the first 0x3400 bytes are the microcode
INITRD_DIR=/tmp/secret/initrd
SECRET_CPIO=/tmp/secret/initrd.cpio
bootdir=$(dirname "$INITRD")
mkdir -p "$INITRD_DIR/etc"

# Attempt to unseal the disk key from the TPM
# should we give this some number of tries?
unseal_failed="n"
if ! kexec-unseal-key "$INITRD_DIR/secret.key"; then
	unseal_failed="y"
	echo "!!! Failed to unseal the TPM LUKS disk key"
fi

# Override PCR 4 so that user can't read the key
DEBUG "Extending TPM PCR 4 to prevent further secret unsealing"
tpmr extend -ix 4 -ic generic ||
	die 'Unable to scramble PCR'

# Check to continue
if [ "$unseal_failed" = "y" ]; then
	confirm_boot="n"
	read \
		-n 1 \
		-p "Do you wish to boot and use the disk recovery key? [Y/n] " \
		confirm_boot

	if [ "$confirm_boot" != 'y' \
		-a "$confirm_boot" != 'Y' \
		-a -n "$confirm_boot" ] \
		; then
		die "!!! Aborting boot due to failure to unseal TPM disk key"
	fi
fi

echo
echo '+++ Building initrd'
# pad the initramfs (dracut doesn't pad the last gz blob)
# without this the kernel init/initramfs.c fails to read
# the subsequent uncompressed/compressed cpio
dd if="$INITRD" of="$SECRET_CPIO" bs=512 conv=sync ||
	die "Failed to copy initrd to /tmp"

if [ "$unseal_failed" = "n" ]; then
	# kexec-save-default might have created crypttab overrides to be injected in initramfs through additional cpio
	if [ -r "$bootdir/kexec_initrd_crypttab_overrides.txt" ]; then
		echo "+++ $bootdir/kexec_initrd_crypttab_overrides.txt found..."
		echo "+++ Preparing initramfs crypttab overrides as defined under $bootdir/kexec_initrd_crypttab_overrides.txt to be injected through cpio at next kexec call..."
		# kexec-save-default has found crypttab files under initrd and saved them
		cat "$bootdir/kexec_initrd_crypttab_overrides.txt" | while read line; do
			crypttab_file=$(echo "$line" | awk -F ':' {'print $1'})
			crypttab_entry=$(echo "$line" | awk -F ':' {'print $NF'})
			# Replace each initrd crypttab file with modified entry containing /secret.key path
			mkdir -p "$INITRD_DIR/$(dirname $crypttab_file)"
			echo "$crypttab_entry" | tee -a "$INITRD_DIR/$crypttab_file" >/dev/null
			echo "+++ initramfs's $crypttab_file will be overriden with: $crypttab_entry"
		done
	else
		# No crypttab files were found under selected default boot option's initrd file
		#  Meanwhile, force crypttab to be created from scratch on both possible locations: /etc/crypttab and /cryptroot/crypttab
		crypttab_files="etc/crypttab cryptroot/crypttab"
		for crypttab_file in $crypttab_files; do
			mkdir -p "$INITRD_DIR/$(dirname $crypttab_file)"
			# overwrite crypttab to mirror behavior of seal-key
			echo "+++ The following $crypttab_file overrides will be passed through concatenated secret/initrd.cpio at kexec call:"
			for uuid in $(cat "$TMP_KEY_DEVICES" | cut -d\  -f2); do
				# NOTE: discard operation (TRIM) is activated by default if no crypptab found in initrd
				echo "luks-$uuid UUID=$uuid /secret.key luks,discard" | tee -a "$INITRD_DIR/$crypttab_file"
			done
		done
	fi
	(
		cd "$INITRD_DIR"
		find . -type f | cpio -H newc -o
	) >>"$SECRET_CPIO"
fi
