From 3d932c8a8034fa0bafa6651f3b381823a3e738ff Mon Sep 17 00:00:00 2001 From: Alex Gonzalez Date: Fri, 17 Nov 2023 15:23:01 +0100 Subject: [PATCH] initrdscripts: allow for cryptsetup to support different secure boot implementations Change-type: patch Signed-off-by: Alex Gonzalez --- .../initrdscripts/files/cryptsetup | 132 +----------------- .../initrdscripts/files/cryptsetup-efi-tpm | 115 +++++++++++++++ .../initramfs-framework_%.bbappend | 10 +- 3 files changed, 126 insertions(+), 131 deletions(-) create mode 100644 meta-balena-common/recipes-core/initrdscripts/files/cryptsetup-efi-tpm diff --git a/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup b/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup index adeda4f205..ce43539f5b 100644 --- a/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup +++ b/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup @@ -3,60 +3,7 @@ # shellcheck disable=SC1091 . /usr/libexec/os-helpers-logging . /usr/libexec/os-helpers-fs -. /usr/libexec/os-helpers-tpm2 -. /usr/libexec/os-helpers-efi -# Give a chance to the by-state directory to appear -# We do not expect any particular device or partition to come up -# but if balenaOS is correctly configured on the device the by-state -# directory will eventually be created by the custom udev rule. -# This is useful if the rootfs is on a device that takes a while -# to initialize such as a USB disk. -wait4file "/dev/disk/by-state" "50" -EFI_DEV=$(get_state_path_from_label "balena-efi") - -wait4udev() { - # Wait for udev processing of a DM device to finish - # - # After cryptsetup luksOpen returns to shell, udev events initializing - # the DM device are still being generated in the background. - # 95-dm-notify.rules will notify dmsetup when udev processing is finished - # but we found no way to hook to this from shell. - # - # This function waits for a particular udev env variable to be defined - # on the specified device. This is not the cleanest solution but it works - # for this use-case - it indicates the last necessary udev event is being - # processed and, if necessary from a shell, a subsequent settle will block - # until the processing finishes. - # - # The function will fail if the variable does not appear in a reasonable amount of time. - - DM_DEVICE="$1" - - I=0 - # DM_UDEV_RULES_VSN is the last variable that 10-dm.rules defines for an active device - while ! udevadm info "${DM_DEVICE}" | grep -q "DM_UDEV_RULES_VSN=2"; do - if [ "${I}" -gt 4 ]; then - return 1 - fi - - sleep 1 - I=$["${I}" + 1] - done - - return 0 -} - -ensure_luks2() { - LUKS_DEVICE="$1" - - LUKS_VERSION=$(cryptsetup luksDump "${LUKS_DEVICE}" | grep "^Version:" | cut -f 2) - - if [ "${LUKS_VERSION}" = 1 ]; then - info "Converting ${LUKS_DEVICE} to LUKS2" - cryptsetup convert -q --type luks2 "${LUKS_DEVICE}" - fi -} cryptsetup_enabled() { # Flasher should not try to unlock the partitions @@ -64,83 +11,10 @@ cryptsetup_enabled() { return 1 fi - # Ensure that secure boot is enabled and in user mode before unlocking - if ! user_mode_enabled; then - return 1 - fi - - # Only run if the EFI partition is split - if [ ! -e "$EFI_DEV" ]; then - return 1 - fi - - # Check whether there are any LUKS partitions - if ! lsblk -nlo fstype | grep -q crypto_LUKS; then - return 1 - fi - - return 0 + # To be implemented + return 1 } cryptsetup_run() { - # Die if anything fails here - set -e - - EFI_MOUNT_DIR="/efi" - mkdir "$EFI_MOUNT_DIR" - mount "$EFI_DEV" "$EFI_MOUNT_DIR" - - PASSPHRASE_FILE=/balena-luks.pwd - if hw_decrypt_passphrase "$EFI_MOUNT_DIR" "0,1,2,3" "$PASSPHRASE_FILE"; then - info "Successfully unlocked LUKS passphrase using the TPM" - elif hw_decrypt_passphrase "$EFI_MOUNT_DIR" "0,2,3" "$PASSPHRASE_FILE"; then - info "Unlocked LUKS passphrase without UEFI config protection, re-locking" - - RESULT_DIR="/balena-luks" - mkdir -p "$RESULT_DIR" - - # Create a PCR protection policy - # We are using PCRs 0, 1, 2 and 3 - # This ensures secure boot is enabled and no UEFI configuration has been tampered with - hw_encrypt_passphrase "$PASSPHRASE_FILE" "0,1,2,3" "$RESULT_DIR" - - # Ditch the old key pair from the TPM and replace existing files with new ones - tpm2_evictcontrol -c "$EFI_MOUNT_DIR/balena-luks.ctx" - mv "$RESULT_DIR/persistent.ctx" "$EFI_MOUNT_DIR/balena-luks.ctx" && sync - mv "$RESULT_DIR/passphrase.enc" "$EFI_MOUNT_DIR/balena-luks.enc" && sync - - rm -rf "$RESULT_DIR" - - sync - - info "Passphrase lock includes UEFI configuration now" - else - umount "$EFI_MOUNT_DIR" - fail "Failed to unlock LUKS passphrase using the TPM" - fi - - BOOT_DEVICE=$(lsblk -nlo pkname "${EFI_DEV}") - - # Unlock all the partitions - cryptsetup luksOpen does not wait for udev processing - # of the DM device to complete, it just kicks off the process and returns. - # Since this is async, we can perform all the luksOpens here, note the device names - # and wait for them in a separate loop later - LUKS_UNLOCKED="" - for LUKS_UUID in $(lsblk -nlo uuid,fstype "/dev/${BOOT_DEVICE}" | grep crypto_LUKS | cut -d " " -f 1); do - ensure_luks2 "/dev/disk/by-uuid/${LUKS_UUID}" - cryptsetup luksOpen --key-file $PASSPHRASE_FILE UUID="${LUKS_UUID}" luks-"${LUKS_UUID}" - LUKS_UNLOCKED="${LUKS_UNLOCKED} luks-${LUKS_UUID}" - done - - # Wait for udev processing of each unlocked device - for DM_NAME in ${LUKS_UNLOCKED}; do - wait4udev "/dev/mapper/${DM_NAME}" - done - - rm -f "$PASSPHRASE_FILE" - umount "$EFI_MOUNT_DIR" - rmdir "$EFI_MOUNT_DIR" - - # Revert dying on error - set +e + return 1 } diff --git a/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup-efi-tpm b/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup-efi-tpm new file mode 100644 index 0000000000..6b80953e16 --- /dev/null +++ b/meta-balena-common/recipes-core/initrdscripts/files/cryptsetup-efi-tpm @@ -0,0 +1,115 @@ +#!/bin/sh + +# shellcheck disable=SC1091 +. /usr/libexec/os-helpers-logging +. /usr/libexec/os-helpers-fs +. /usr/libexec/os-helpers-tpm2 +. /usr/libexec/os-helpers-efi +. /usr/sbin/balena-config-defaults + +# Give a chance to the by-state directory to appear +# We do not expect any particular device or partition to come up +# but if balenaOS is correctly configured on the device the by-state +# directory will eventually be created by the custom udev rule. +# This is useful if the rootfs is on a device that takes a while +# to initialize such as a USB disk. +wait4file "/dev/disk/by-state" "50" +EFI_DEV=$(get_state_path_from_label "${BALENA_NONENC_BOOT_LABEL}") + +ensure_luks2() { + LUKS_DEVICE="$1" + + LUKS_VERSION=$(cryptsetup luksDump "${LUKS_DEVICE}" | grep "^Version:" | cut -f 2) + + if [ "${LUKS_VERSION}" = 1 ]; then + info "Converting ${LUKS_DEVICE} to LUKS2" + cryptsetup convert -q --type luks2 "${LUKS_DEVICE}" + fi +} + +cryptsetup_enabled() { + # Flasher should not try to unlock the partitions + if [ "$bootparam_flasher" = "true" ]; then + return 1 + fi + + # Ensure that secure boot is enabled and in user mode before unlocking + if ! user_mode_enabled; then + return 1 + fi + + # Only run if the EFI partition is split + if [ ! -e "$EFI_DEV" ]; then + return 1 + fi + + # Check whether there are any LUKS partitions + if ! lsblk -nlo fstype | grep -q crypto_LUKS; then + return 1 + fi + + return 0 +} + +cryptsetup_run() { + # Die if anything fails here + set -e + + EFI_MOUNT_DIR="/efi" + mkdir "$EFI_MOUNT_DIR" + mount "$EFI_DEV" "$EFI_MOUNT_DIR" + + PASSPHRASE_FILE=/balena-luks.pwd + if hw_decrypt_passphrase "$EFI_MOUNT_DIR" "0,1,2,3" "$PASSPHRASE_FILE"; then + info "Successfully unlocked LUKS passphrase using the TPM" + elif hw_decrypt_passphrase "$EFI_MOUNT_DIR" "0,2,3" "$PASSPHRASE_FILE"; then + info "Unlocked LUKS passphrase without UEFI config protection, re-locking" + + RESULT_DIR="/balena-luks" + mkdir -p "$RESULT_DIR" + + # Create a PCR protection policy + # We are using PCRs 0, 1, 2 and 3 + # This ensures secure boot is enabled and no UEFI configuration has been tampered with + hw_encrypt_passphrase "$PASSPHRASE_FILE" "0,1,2,3" "$RESULT_DIR" + + # Ditch the old key pair from the TPM and replace existing files with new ones + tpm2_evictcontrol -c "$EFI_MOUNT_DIR/balena-luks.ctx" + mv "$RESULT_DIR/persistent.ctx" "$EFI_MOUNT_DIR/balena-luks.ctx" && sync + mv "$RESULT_DIR/passphrase.enc" "$EFI_MOUNT_DIR/balena-luks.enc" && sync + + rm -rf "$RESULT_DIR" + + sync + + info "Passphrase lock includes UEFI configuration now" + else + umount "$EFI_MOUNT_DIR" + fail "Failed to unlock LUKS passphrase using the TPM" + fi + + BOOT_DEVICE=$(lsblk -nlo pkname "${EFI_DEV}") + + # Unlock all the partitions - cryptsetup luksOpen does not wait for udev processing + # of the DM device to complete, it just kicks off the process and returns. + # Since this is async, we can perform all the luksOpens here, note the device names + # and wait for them in a separate loop later + LUKS_UNLOCKED="" + for LUKS_UUID in $(lsblk -nlo uuid,fstype "/dev/${BOOT_DEVICE}" | grep crypto_LUKS | cut -d " " -f 1); do + ensure_luks2 "/dev/disk/by-uuid/${LUKS_UUID}" + cryptsetup luksOpen --key-file $PASSPHRASE_FILE UUID="${LUKS_UUID}" luks-"${LUKS_UUID}" + LUKS_UNLOCKED="${LUKS_UNLOCKED} luks-${LUKS_UUID}" + done + + # Wait for udev processing of each unlocked device + for DM_NAME in ${LUKS_UNLOCKED}; do + wait4udev "/dev/mapper/${DM_NAME}" + done + + rm -f "$PASSPHRASE_FILE" + umount "$EFI_MOUNT_DIR" + rmdir "$EFI_MOUNT_DIR" + + # Revert dying on error + set +e +} diff --git a/meta-balena-common/recipes-core/initrdscripts/initramfs-framework_%.bbappend b/meta-balena-common/recipes-core/initrdscripts/initramfs-framework_%.bbappend index d14646d593..bdb8c0ecce 100644 --- a/meta-balena-common/recipes-core/initrdscripts/initramfs-framework_%.bbappend +++ b/meta-balena-common/recipes-core/initrdscripts/initramfs-framework_%.bbappend @@ -11,6 +11,7 @@ SRC_URI:append = " \ file://rootfs \ file://finish \ file://cryptsetup \ + file://cryptsetup-efi-tpm \ file://kexec \ file://udevcleanup \ file://recovery \ @@ -30,7 +31,12 @@ do_install:append() { install -m 0755 ${WORKDIR}/resindataexpander ${D}/init.d/88-resindataexpander install -m 0755 ${WORKDIR}/rorootfs ${D}/init.d/89-rorootfs install -m 0755 ${WORKDIR}/udevcleanup ${D}/init.d/98-udevcleanup - install -m 0755 ${WORKDIR}/cryptsetup ${D}/init.d/72-cryptsetup + if [ ${@bb.utils.contains('MACHINE_FEATURES', 'efi', 'true', 'false',d)} = 'true' ] && + [ ${@bb.utils.contains('MACHINE_FEATURES', 'tpm', 'true', 'false',d)} = 'true' ]; then + install -m 0755 ${WORKDIR}/cryptsetup-efi-tpm ${D}/init.d/72-cryptsetup + else + install -m 0755 ${WORKDIR}/cryptsetup ${D}/init.d/72-cryptsetup + fi install -m 0755 ${WORKDIR}/recovery ${D}/init.d/00-recovery install -m 0755 ${WORKDIR}/kexec ${D}/init.d/92-kexec @@ -87,7 +93,7 @@ RDEPENDS:initramfs-module-fsuuidsinit = "${PN}-base" FILES:initramfs-module-fsuuidsinit = "/init.d/75-fsuuidsinit" SUMMARY:initramfs-module-cryptsetup = "Unlock encrypted partitions" -RDEPENDS:initramfs-module-cryptsetup = "${PN}-base cryptsetup libgcc lvm2-udevrules os-helpers-logging os-helpers-fs" +RDEPENDS:initramfs-module-cryptsetup = "${PN}-base cryptsetup libgcc lvm2-udevrules os-helpers-logging os-helpers-fs balena-config-vars-config" RDEPENDS:initramfs-module-cryptsetup:append = "${@bb.utils.contains('MACHINE_FEATURES', 'tpm', ' os-helpers-tpm2', '',d)}" RDEPENDS:initramfs-module-cryptsetup:append = "${@bb.utils.contains('MACHINE_FEATURES', 'efi', ' os-helpers-efi', '',d)}" FILES:initramfs-module-cryptsetup = "/init.d/72-cryptsetup"