Skip to content

Commit

Permalink
Move policy update to HUP commit hook
Browse files Browse the repository at this point in the history
When migrating the TPM2 policy used to secure the LUKS passphrase to use
different PCRs, we temporarily want to maintain fallback capability in
case the newly installed hostapp doesn't pass healthchecks. This allows
the system to boot back into the original OS and try again.

In order to do so, we leave the passphrase in place with the old PCR
authentication policy. The cryptsetup hook in the initramfs will try
PCRs 0,2,3,7 and if those don't work we fallback to the original PCRs.

Once the new system successfully boots, we'll re-encrypt the passphrase
and use the new PCRs to create a policy to secure the key.

Change-type: patch
Signed-off-by: Joseph Kogut <joseph@balena.io>
  • Loading branch information
jakogut committed Mar 20, 2024
1 parent 3d78d26 commit 80f9bd8
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 22 deletions.
Expand Up @@ -78,28 +78,7 @@ cryptsetup_run() {
if hw_decrypt_passphrase "$EFI_MOUNT_DIR" "session:${SESSION_CTX}" "$PASSPHRASE_FILE"; then
info "Successfully unlocked LUKS passphrase using the TPM"
elif hw_decrypt_passphrase "$EFI_MOUNT_DIR" "pcr:sha256:0,1,2,3" "$PASSPHRASE_FILE"; then
info "Unlocked LUKS passphrase without PCR7, 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
POLICY="$(mktemp)"
tpm2_createpolicy --policy-pcr -l "${PCRS}" -L "${POLICY}"
hw_encrypt_passphrase "$PASSPHRASE_FILE" "$POLICY" "$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 secure boot configuration now"
info "Unlocked LUKS passphrase without PCR7, will re-encrypt after rollback-health"
else
umount "$EFI_MOUNT_DIR"
fail "Failed to unlock LUKS passphrase using the TPM"
Expand Down
@@ -0,0 +1,129 @@
#!/bin/sh

# Copyright 2024 Balena Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Update the TPM2 sealing policy on commit.
#
# This updates the passphrase to use the newly computed value of PCR7 if any
# measured variables have been changed, such as by appending to dbx during HUP.
#
# Additonally, the hook will migrate from systems that were setup to seal the
# LUKS passphrase using PCRs 0,1,2,3 to using 0,2,3,7.
#
# This is done in a commit hook to preserve fallback capability until the new
# OS is healthy.
#

set -o errexit

# shellcheck disable=SC1091
. /usr/libexec/os-helpers-sb

EFI_MOUNT_DIR="/mnt/efi"
PASSPHRASE_FILE="$(mktemp -t)"
SESSION_CTX="$(mktemp -t)"
POLICY_PATH="$(find "${EFI_MOUNT_DIR}" -type d -name "policies.*")"
tpm2_startauthsession --policy-session -S "${SESSION_CTX}"
tpm2_policypcr -S "${SESSION_CTX}" -l "sha256:0,2,3,7"

update_reason=""
POLICIES="$(find "${POLICY_PATH}" -type f | sort | xargs)"
if [ "$(echo "${POLICIES}" | wc -w)" -gt 1 ]; then
tpm2_policyor -S "${SESSION_CTX}" "sha256:$(echo "${POLICIES}" | sed 's/ /,/g')"
update_reason="Combined policy in use"
fi

if hw_decrypt_passphrase "${EFI_MOUNT_DIR}" "session:${SESSION_CTX}" "${PASSPHRASE_FILE}"; then
echo "Unlocked passphrase using pcr:sha256:0,2,3,7"
elif hw_decrypt_passphrase "${EFI_MOUNT_DIR}" "pcr:sha256:0,1,2,3" "${PASSPHRASE_FILE}"; then
echo "Unlocked passphrase using pcr:sha256:0,1,2,3, migrating to 0,2,3,7"
update_reason="Legacy PCRs in use"
else
echo "Failed to unlock passphrase, abort"
exit 1
fi

POLICY="$(mktemp -t)"
PCRS="0,2,3,7"
PCR_VAL_BIN="$(mktemp -t)"
RESULT_DIR="$(mktemp -d)"
EFI_BINARIES=" \
$(find "${EFI_MOUNT_DIR}" -name bootx64.efi -print -quit) \
$(find /boot -name bzImage -print -quit)
"

for pcr in $(echo ${PCRS} | sed 's/,/ /g'); do
case $pcr in
7)
digest="$(compute_pcr7)"

if firmware_measures_efibins; then
for bin in ${EFI_BINARIES}; do
extend="$(tcgtool -s "$bin" \
| tcgtool -e "db-${EFI_IMAGE_SECURITY_DATABASE_GUID}" \
| _sha256 )"
digest="$(printf '%s%s' "$digest" "$extend" \
| _hexdecode | _sha256 )"
done
fi

current_digest="$( \
tpm2_pcrread --quiet "sha256:$pcr" -o /proc/self/fd/1 \
| _hexencode)"
if [ "$current_digest" != "$digest" ]; then
update_reason="PCR7 computed value changed"
fi
;;
*)
digest="$(tpm2_pcrread --quiet "sha256:$pcr" -o /proc/self/fd/1 | _hexencode)"
;;
esac

printf "%s" "$digest" | _hexdecode \
| dd of="${PCR_VAL_BIN}" \
status=none \
bs=1 \
seek="$(du -b "${PCR_VAL_BIN}" | cut -f1)"
done

if [ -n "${update_reason}" ]; then
echo "${update_reason}, updating policy"
tpm2_createpolicy --policy-pcr \
-l "sha256:${PCRS}" \
-f "${PCR_VAL_BIN}" \
-L "${POLICY}"
hw_encrypt_passphrase "${PASSPHRASE_FILE}" "${POLICY}" "${RESULT_DIR}"

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

POLICY_PATH="$(find "${EFI_MOUNT_DIR}" -type d -name "policies.*")"

rm -rf "${RESULT_DIR}" \
"${POLICY}" \
"${PCR_VAL_BIN}" \
"${PASSPHRASE_FILE}" \
"${POLICY_PATH}"

sync

# reboot to ensure the passphrase can be unlocked again, otherwise HUP
# won't work until the device is manually rebooted
reboot
else
echo "PCR7 computed value is unchanged"
fi
Expand Up @@ -21,6 +21,7 @@ HOSTAPP_HOOKS = " \
SECUREBOOT_HOOKS = " \
0-signed-update \
95-secureboot/1-fwd_commit_apply-dbx \
95-secureboot/2-fwd_commit_update-policy \
"
SECUREBOOT_HOOK_DIRS = " \
95-secureboot \
Expand Down
Expand Up @@ -19,6 +19,8 @@
[ -f "/usr/libexec/os-helpers-logging" ] && . /usr/libexec/os-helpers-logging
# shellcheck disable=SC1091
[ -f "/usr/libexec/os-helpers-efi" ] && . /usr/libexec/os-helpers-efi
# shellcheck disable=SC1091
[ -f "/usr/libexec/os-helpers-tpm2" ] && . /usr/libexec/os-helpers-tpm2

is_secured() {
if ! command -v user_mode_enabled; then
Expand Down
Expand Up @@ -21,6 +21,8 @@
EFI_IMAGE_SECURITY_DATABASE_GUID="d719b2cb-3d3a-4596-a3bc-dad00e67656f"
EFI_GLOBAL_VARIABLE_GUID="8be4df61-93ca-11d2-aa0d-00e098032b8c"

TPM_EVENTLOG_PATH="/sys/kernel/security/tpm0/binary_bios_measurements"

# busybox's implementation of xxd lacks the -cols option, so remove line breaks
_hexencode() {
xxd -p | tr -d '[:space:]'
Expand Down Expand Up @@ -74,6 +76,23 @@ EOF
printf '%s' "$digest"
}


parse_pcr7_eventtypes() {
awk '
/PCRIndex: 7/ {flag=1; next}
/EventNum:/ {flag=0}
flag && /EventType:/ {gsub("\"", "")print $2}'

This comment has been minimized.

Copy link
@jakogut

jakogut Apr 12, 2024

Author Contributor

This is an awk syntax error

}

firmware_measures_efibins() {
[ -f "${TPM_EVENTLOG_PATH}" ] || exit 2
tpm2_eventlog "${TPM_EVENTLOG_PATH}" \
| parse_pcr7_eventtypes \
| grep -e EV_EFI_VARIABLE_AUTHORITY
}



hw_gen_passphrase() {
tpm2_getrandom 32
}
Expand Down

0 comments on commit 80f9bd8

Please sign in to comment.