Skip to content

Commit

Permalink
initrdscripts: allow for cryptsetup to support different secure boot
Browse files Browse the repository at this point in the history
implementations

Change-type: patch
Signed-off-by: Alex Gonzalez <alexg@balena.io>
  • Loading branch information
alexgg committed Feb 22, 2024
1 parent 10b435b commit 3d932c8
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 131 deletions.
132 changes: 3 additions & 129 deletions meta-balena-common/recipes-core/initrdscripts/files/cryptsetup
Expand Up @@ -3,144 +3,18 @@
# 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
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
# 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
}
115 changes: 115 additions & 0 deletions 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
}
Expand Up @@ -11,6 +11,7 @@ SRC_URI:append = " \
file://rootfs \
file://finish \
file://cryptsetup \
file://cryptsetup-efi-tpm \
file://kexec \
file://udevcleanup \
file://recovery \
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 3d932c8

Please sign in to comment.