Skip to content

Commit

Permalink
Update policy's PCR7 value in hostapp-update hook
Browse files Browse the repository at this point in the history
When performing a hostapp-update, we may touch file and efivars that are
measured into PCR7. Re-generate the predicted value and reseal the LUKS
passphrase using this new digest.

Change-type: patch
Signed-off-by: Joseph Kogut <joseph@balena.io>
  • Loading branch information
jakogut committed Mar 20, 2024
1 parent 3e0911a commit f05deea
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 1 deletion.
2 changes: 1 addition & 1 deletion meta-balena-common/recipes-core/images/balena-image.bb
Expand Up @@ -39,7 +39,7 @@ IMAGE_INSTALL = " \

# add packages for LUKS operations if necessary
IMAGE_INSTALL:append = "${@oe.utils.conditional('SIGN_API','','',' cryptsetup lvm2-udevrules',d)}"
IMAGE_INSTALL:append = "${@bb.utils.contains('MACHINE_FEATURES', 'tpm', ' tpm2-tools libtss2-tcti-device', '',d)}"
IMAGE_INSTALL:append = "${@bb.utils.contains('MACHINE_FEATURES', 'tpm', ' tpm2-tools libtss2-tcti-device os-helpers-tpm2', '',d)}"

generate_rootfs_fingerprints () {
# Generate fingerprints file for root filesystem
Expand Down
Expand Up @@ -9,6 +9,8 @@ set -o errexit

# shellcheck source=/dev/null
. /usr/libexec/os-helpers-logging
# shellcheck disable=SC1091
[ -f /usr/libexec/os-helpers-tpm2 ] && . /usr/libexec/os-helpers-tpm2

DURING_UPDATE="${DURING_UPDATE:-0}"
UMOUNT_EFIVARS=0
Expand Down Expand Up @@ -40,6 +42,155 @@ umountEfiVars() {
}

updateKeys() {
PCRS="0,2,3,7"
PCR_VAL_BIN_CURRENT="$(mktemp -t)"
PCR_VAL_BIN_UPDATED="$(mktemp -t)"
PCR_VAL_BIN_EFIBIN="$(mktemp -t)"
GRUB_BIN="$(find /mnt/sysroot/inactive -name bootx64.efi.secureboot -print -quit)"
KERNEL_BIN="$(find /mnt/sysroot/inactive -name bzImage -print -quit)"
if [ -z "${GRUB_BIN}" ] || [ -z "${KERNEL_BIN}" ]; then
fail "Unable to add kernel and bootloader hashes to PCR7 digest"
fi

EFI_BINARIES="${GRUB_BIN} ${KERNEL_BIN}"

for pcr in $(echo ${PCRS} | sed 's/,/ /g'); do
case $pcr in
7)
# the signatures from the updated database are appended to the
# existing variable, removing duplicates
SIGNATURE_LENGTH=76 # sizeof(EFI_SIGNATURE_LIST) + SHA256_DIGEST_SIZE + EFI_GUID_SIZE
db_override="$( \
{ dd if=/sys/firmware/efi/efivars/"db-${EFI_IMAGE_SECURITY_DATABASE_GUID}" \
status=none \
bs=1 \
skip=4 | xxd -p -c ${SIGNATURE_LENGTH} ; \
dd if=/resin-boot/balena-keys/db.esl \
status=none | xxd -p -c ${SIGNATURE_LENGTH} ; \
} | awk '!seen[$0]++' )"
current_digest="$(tpm2_pcrread --quiet "sha256:$pcr" -o /proc/self/fd/1 | _hexencode)"
#shellcheck disable=SC2154
updated_digest="$(compute_pcr7 "${secureboot_override}" \
"${pk_override}" \
"${kek_override}" \
"${db_override}" \
"${dbx_override}")"

cp "${PCR_VAL_BIN_CURRENT}" "${PCR_VAL_BIN_UPDATED}"
printf "%s" "$updated_digest" | _hexdecode \
| dd of="${PCR_VAL_BIN_UPDATED}" \
status=none \
bs=1 \
seek="$(du -b "${PCR_VAL_BIN_UPDATED}" | cut -f1)"

# This OS release may not have the bootloader version required
# to read the TPM event log, which means we can't assess if the
# firmware measures EFI binary signatures into PCR7.
#
# Create a combined policy that authenticates with PCR7 values
# calculated with and without the EFI binary hashes.
cp "${PCR_VAL_BIN_CURRENT}" "${PCR_VAL_BIN_EFIBIN}"
for bin in ${EFI_BINARIES}; do
extend="$(tcgtool -s "$bin" \
| tcgtool -e "db-${EFI_IMAGE_SECURITY_DATABASE_GUID}" \
| _sha256 )"
updated_digest=$(printf '%s%s' "$updated_digest" "$extend" | _hexdecode | _sha256)
done

printf "%s" "$updated_digest" | _hexdecode \
| dd of="${PCR_VAL_BIN_EFIBIN}" \
status=none \
bs=1 \
seek="$(du -b "${PCR_VAL_BIN_EFIBIN}" | cut -f1)"
digest="$current_digest"
;;
*)
digest="$(tpm2_pcrread --quiet "sha256:$pcr" -o /proc/self/fd/1 | _hexencode)"
;;
esac

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

SESSION_CTX=$(mktemp -t)
EFI_MOUNT_DIR="/mnt/efi"
POLICY_PATH="$(mktemp -d policies.XXXXX)"
PASSPHRASE_FILE="$(mktemp -t)"
RESULT_DIR="$(mktemp -d)"
CURRENT_POLICY_PATH="$(find /mnt/efi -name "policies.*")"
for UNLOCK_PCRS in 0,2,3,7 0,1,2,3; do
[ -f "${SESSION_CTX}" ] && tpm2_flushcontext "${SESSION_CTX}" || true
tpm2_startauthsession --policy-session -S "${SESSION_CTX}"
tpm2_policypcr -S "${SESSION_CTX}" -l "sha256:${UNLOCK_PCRS}"
POLICIES="$(find "${CURRENT_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')"
fi

if hw_decrypt_passphrase "$EFI_MOUNT_DIR" "session:${SESSION_CTX}" "$PASSPHRASE_FILE"; then
UNLOCK_PCRS_SUCCESS="${UNLOCK_PCRS}"
break
fi
done

POLICY_UPDATED="${POLICY_PATH}/policy.updated"
POLICY_EFIBIN="${POLICY_PATH}/policy.efibin"
POLICY_COMBINED="$(mktemp -t)"
if [ "${UNLOCK_PCRS_SUCCESS}" = "0,2,3,7" ]; then
tpm2_createpolicy --policy-pcr \
-l "sha256:${PCRS}" \
-f "${PCR_VAL_BIN_UPDATED}" \
-L "${POLICY_UPDATED}"
tpm2_createpolicy --policy-pcr \
-l "sha256:${PCRS}" \
-f "${PCR_VAL_BIN_EFIBIN}" \
-L "${POLICY_EFIBIN}"
tpm2_startauthsession -S "${SESSION_CTX}"

if firmware_measures_efibins; then
info "Using PCR7 digest with EFI binary measurements"
POLICY="${POLICY_EFIBIN}"
else
if [ $? = 1 ]; then
info "Using PCR7 digest without EFI binary measurements"
POLICY="${POLICY_UPDATED}"
# exit code 2 means we don't have access to the TPM event log,
# and can't definitively tell whether or not EFI binaries are
# measured into PCR7, so unlock with both digests
elif [ $? = 2 ]; then
info "Creating combined policy"
tpm2_policyor -S "${SESSION_CTX}" \
-L "${POLICY_COMBINED}" \
"sha256:$(find "${POLICY_PATH}" -type f | sort | xargs | sed 's/ /,/g')"
POLICY="${POLICY_COMBINED}"
cp -rf "${POLICY_PATH}" "${EFI_MOUNT_DIR}"
rm -rf "${CURRENT_POLICY_PATH}"
fi
fi

tpm2_flushcontext "${SESSION_CTX}"
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"
mv "${RESULT_DIR}/passphrase.enc" "${EFI_MOUNT_DIR}/balena-luks.enc"
elif [ "${UNLOCK_PCRS_SUCCESS}" = "0,1,2,3" ]; then
warn "Unlocked passphrase without PCR7, delaying policy update until rollback-health success"
# update the second stage bootloader kernel binary, as the hash of the
# original may not be present in the signature database
cp "${KERNEL_BIN}"{,.sig} "${EFI_MOUNT_DIR}/"
cp "${GRUB_BIN}" "${EFI_MOUNT_DIR}/EFI/BOOT/bootx64.efi"
else
fail "Failed to update policy sealing LUKS passphrase"
fi

sync

# This only updates db at this moment
# PK and KEK need to be implemented
DB_SYSFS_FILE="db-d719b2cb-3d3a-4596-a3bc-dad00e67656f"
Expand Down

0 comments on commit f05deea

Please sign in to comment.