Skip to content

Instantly share code, notes, and snippets.

@Maxdamantus
Last active March 10, 2023 05:18
Embed
What would you like to do?
cert.pem
cert.der
key.pem
# Generate key/cert for MOK
openssl req -new -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days $((365*30)) -subj '/CN=custom-sb-cert/'
# DER format can be enrolled in UEFI if not using shim; for convenience, this is copied alongside the signed kernel image when running ./update-image
openssl x509 -outform der -in cert.pem -out cert.der
# If using shim, import of MOK to UEFI (will prompt for one-time password, requires reboot to UEFI to complete enrolment)
mokutil --import cert.der
# target directory within ESP
TPMBOOT_EFI_OUT=/boot/efi/EFI/debian0
# NOTE: panic=0 is important for disabling the root shell in case of failure
TPMBOOT_CMDLINE='root=/dev/mapper/debian--vg-root ro panic=0'
# options to pass to `seal` and `unseal`; should contain a list of PCRs to use
TPMBOOT_TPM2_INITRAMFS_TOOL_OPTS='-p 0,2,7'
#!/bin/sh
PREREQ=""
prereqs() {
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
set -e
copy_exec /usr/lib/x86_64-linux-gnu/libtss2-tcti-device.so.0
copy_exec /usr/bin/tpm2-initramfs-tool
copy_exec /usr/bin/jq
copy_exec "$(dirname "$(realpath "$0")")"/config
copy_exec "$(dirname "$(realpath "$0")")"/tpm2-unseal
# using header checksums to authenticate devices, since otherwise some other device could be used (with a different password) to impersonate a root/resume device such that the tpm key is still unlockable
trusted_devices_luks2="$(
for x in $(cut -f 1 -d ' ' /etc/crypttab); do
# NOTE: only supporting luks2 since the relevant header size needs to be assumed (4096 bytes for luks2)
(cryptsetup status "$x" | grep -q LUKS2) && cryptsetup status "$x"
done | sed 's/^ *device: *//; t; d'
)"
for device in $trusted_devices_luks2; do
# NOTE: authenticating based on the volume key digest, which generally shouldn't change (unless the device is reencrypted)
cryptsetup luksDump --dump-json-metadata "$device" | jq -c '.digests | .[] |= (.keyslots = [])' | sha256sum | cut -f 1 -d ' '
done >"${DESTDIR}"/trusted-devices
# disable autoactivation of LVM volumes, since otherwise an untrusted (even unencrpted) "root" or "resume" volume might be used
echo 'global { event_activation = 0 }' >>"${DESTDIR}"/etc/lvm/lvm.conf
cat >"${DESTDIR}"/scripts/local-block/trusted-devices-check << 'EOF'
#!/bin/sh
PREREQ="cryptroot"
prereqs() {
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
for crypttab in /etc/crypttab /cryptroot/crypttab; do
[ -e "$crypttab" ] || continue
for name in $(cut -f 1 -d ' ' "$crypttab"); do
[ -e /dev/mapper/"$name" ] || continue
device="$(cryptsetup status "$name" | sed 's/^ *device: *//; t; d')"
if cryptsetup luksDump --dump-json-metadata "$device" | jq -c '.digests | .[] |= (.keyslots = [])' | sha256sum | grep -q -f /trusted-devices; then
lvm vgchange -a ay --devices /dev/mapper/"$name"
else
echo "$0: closing unknown crypt device: $name" >&2
cryptsetup close "$name"
fi
done
done
EOF
chmod +x "${DESTDIR}"/scripts/local-block/trusted-devices-check
# NOTE: this is annoyingly getting overridden because the script doesn't exist in one of the expected places ({/etc,/usr/share}/initramfs-tools/scripts)
# NOTE: there's an extra hack in `tpm2-unseal` to add it back in at runtime
(cache_run_scripts "${DESTDIR}"/scripts/local-block)
#!/bin/sh
dir="$(dirname "$(realpath "$0")")"
. "$dir"/config
check_script=/scripts/local-block/trusted-devices-check
order_script="$(dirname "$check_script")"/ORDER
if [ -e "$order_script" ] && ! grep -q "^$check_script" "$order_script"; then
echo "$check_script" '$@' >>"$order_script"
fi
[ "$CRYPTTAB_TRIED" = "" ] && CRYPTTAB_TRIED=0
[ "$CRYPTTAB_TRIED" -gt 0 ] && exec /lib/cryptsetup/askpass Password:
exec tpm2-initramfs-tool unseal $TPMBOOT_TPM2_INITRAMFS_TOOL_OPTS
#!/bin/bash
set -e
dir="$(dirname "$(realpath "$0")")"
. "$dir"/config
# NOTE: debian's version of shimx64 has "grubx64.efi" hardcoded; reusing the name to avoid needing to build/sign a custom version
image_out="$TPMBOOT_EFI_OUT"/grubx64.efi
cert_out="$TPMBOOT_EFI_OUT"/grubx64.der
vmlinuz="$(ls -tr /boot/vmlinuz-* | tail -1)"
# check that selected kernel image is not from the future; reduce the chance of offline attacks against the (presumably unencrypted) /boot partition
find "$vmlinuz" -mtime -0 -exec false {} +
initrd="$(sed 's/\<vmlinuz\>/initrd.img/' <<< "$vmlinuz")"
gen_vmlinuz() {
# replace the UTF-16 "SecureBoot" string with something else in the kernel, so Linux doesn't detect the secureboot state and doesn't enable lockdown mode
# NOTE: this works because the secureboot state is checked at an early stage in kernel startup, so the code is not compressed
sed s/"$(sed 's/./\\x00&/g' <<< SecureBoot)"/"$(sed 's/./\\x00&/g' <<< ZecurePoot)"/ <"$vmlinuz"
}
for x in shimx64.efi fbx64.efi mmx64.efi; do cp /usr/lib/shim/"$x".signed "$TPMBOOT_EFI_OUT"/"$x"; done
tmp="$(mktemp)"
trap "rm -f $tmp" EXIT
objcopy \
--add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000 \
--add-section .cmdline=<(echo "$TPMBOOT_CMDLINE") --change-section-vma .cmdline=0x30000 \
--add-section .splash=/dev/null --change-section-vma .splash=0x40000 \
--add-section .linux=<(gen_vmlinuz) --change-section-vma .linux=0x2000000 \
--add-section .initrd="$initrd" --change-section-vma .initrd=0x3000000 \
/usr/lib/systemd/boot/efi/linuxx64.efi.stub "$tmp"
sbsign --key "$dir"/key.pem --cert "$dir"/cert.pem "$tmp" --output "$image_out"
cp "$dir"/cert.der "$cert_out"
echo Success
#!/bin/bash
set -e
dir="$(dirname "$(realpath "$0")")"
. "$dir"/config
# small sanity check that we're currently booted in the secure system
if ! grep -q 'panic=0' /proc/cmdline; then
echo "Not booted securely. You probably don't want to create a key in this state."
exit 1
fi
# NOTE: this key slot is assumed to be unused for other purposes, since it gets wiped without a prompt
key_slot=23
new_key="$(tpm2-initramfs-tool seal $TPMBOOT_TPM2_INITRAMFS_TOOL_OPTS)"
for x in $(grep tpm2-unseal /etc/crypttab | cut -f2 -d' '); do
# no point in trying to make this atomic, since at this point the old key has already been forgotten by the TPM
cryptsetup luksKillSlot --batch-mode "$x" "$key_slot" || true
cryptsetup luksAddKey --new-key-slot "$key_slot" "$x" <(echo -n "$new_key")
done
echo Success
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment