Skip to content

Commit

Permalink
Pre-calculate and sign PCR 11 values on build (#69)
Browse files Browse the repository at this point in the history
* feat: pre-calculate PCR11 values during build

* feat: support signing of pre-calculated PCR values

* fix: replace tabs with spaces

* fix: update indentation consistently

* chore: code cleanup + add sources

* fix: consistent capitalization when using hex values

* chore: remove useless tabs

* chore: add further comments/sources

* chore: remove newline

* feat: support PCR signing with AWS KMS keys (WIP)

* feat: update certificate path to use specific tpm signing keys + verify digest signature on build

* fix: correctly check if keys for signing tpm/secureboot are available

* chore: standardize use of the term `tpm2` instead of `tpm`

* fix: fixes shellcheck related stuff

* fix: fixes more shellcheck related stuff
  • Loading branch information
brdanin authored Mar 11, 2024
1 parent f83c131 commit f1864b0
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 15 deletions.
9 changes: 8 additions & 1 deletion builder/image.d/make_repart_disk
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Before=initrd-root-device.target systemd-fsck-root.service
[Service]
ExecStart=
ExecStart=/usr/bin/systemd-repart --root=/ --factory-reset=yes --dry-run=no --tpm2-device=auto --tpm2-pcrs=7+11 $dev_path
ExecStart=/usr/bin/systemd-repart --root=/ --factory-reset=yes --dry-run=no --tpm2-device=auto $dev_path
ExecStartPost=/usr/bin/udevadm settle
EOF

Expand All @@ -34,3 +34,10 @@ cat > "$target/etc/systemd/system/[email protected]/override.conf"
[Unit]
Before=systemd-repart.service
EOF

mkdir -p "$target/etc/systemd/system/systemd-pcrphase-initrd.service.d"
cat > "$target/etc/systemd/system/systemd-pcrphase-initrd.service.d/override.conf" << EOF
[Unit]
After=sys-devices-platform-MSFT0101:00-tpm-tpm0.device
Requires=sys-devices-platform-MSFT0101:00-tpm-tpm0.device
EOF
10 changes: 5 additions & 5 deletions builder/image.d/makepart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ partitions="$(mktemp)"
final_partition="$(mktemp)"
secureboot_partitions="$(mktemp)"
efi_partition="$(mktemp)"
tpm_used="$(mktemp)"
tpm2_used="$(mktemp)"

part_num=0

Expand Down Expand Up @@ -103,7 +103,7 @@ sed 's/#.*//;/^[[:space:]]*$/d' \
fi

if [[ "$tpm2" = 1 ]]; then
echo 1 > "$tpm_used"
echo 1 > "$tpm2_used"
fi

if [[ "$depth" = 0 ]]; then
Expand Down Expand Up @@ -265,9 +265,9 @@ if [[ -z "$(cat "$root_hash")" ]]; then
fi

secureboot_flags=()
if [ -n "$(cat "$tpm_used")" ]; then
if [ -n "$(cat "$tpm2_used")" ]; then
mkdir -p "$dracut_include/usr/lib/sysusers.d"
echo "u tss - \"TPM software stack\" /var/lib/tpm /bin/false" > "$dracut_include/usr/lib/sysusers.d/tpm2-tss.conf"
echo "u tss - \"TPM2 software stack\" /var/lib/tpm /bin/false" > "$dracut_include/usr/lib/sysusers.d/tpm2-tss.conf"

secureboot_flags+=("--tpm2")
fi
Expand All @@ -291,5 +291,5 @@ fi
cat "$partitions" "$final_partition" >&3

# cleanup
rm "$fstab" "$veritytab" "$root_hash" "$root_repart" "$partitions" "$final_partition" "$secureboot_partitions" "$tpm_used"
rm "$fstab" "$veritytab" "$root_hash" "$root_repart" "$partitions" "$final_partition" "$secureboot_partitions" "$tpm2_used"
rm -rf "$dracut_include"
140 changes: 131 additions & 9 deletions builder/image.d/makesecureboot
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -Eeufo pipefail

veritytab=
cert_base="/builder/cert/secureboot.db"
tpm2_sign_base="/builder/cert/tpm-sign"
timestamp=0
secureboot="true"
dracut_include=
Expand Down Expand Up @@ -43,6 +44,14 @@ while [ $# -gt 0 ]; do
tpm2="tpm2-tss"
shift
;;
--tpm2-sign)
tpm2_sign_base="${2%.crt}"
if [ -n "$tpm2_sign_base" ] && [ "$tpm2_sign_base.crt" != "$2" ]; then
echo "cert file must end in .crt" >&2
exit 1
fi
shift 2
;;
-i|--include)
dracut_include="$2"
shift 2
Expand Down Expand Up @@ -79,7 +88,7 @@ chroot "$rootfs" env dracut \
--no-hostonly \
--force \
--kver "$kernel_version" \
--modules "bash dash systemd systemd-initrd systemd-veritysetup systemd-repart kernel-modules kernel-modules-extra terminfo udev-rules dracut-systemd base fs-lib shutdown crypt $tpm2" \
--modules "bash dash systemd systemd-initrd systemd-veritysetup systemd-repart kernel-modules kernel-modules-extra terminfo udev-rules dracut-systemd base fs-lib shutdown crypt systemd-pcrphase $tpm2" \
--install "/etc/veritytab cryptsetup head mkfs.ext4 systemd-escape lsblk" \
--include "$dracut_include" "/" \
--reproducible \
Expand All @@ -105,31 +114,144 @@ case "$BUILDER_ARCH" in
;;
esac

# create unified image
/usr/lib/systemd/ukify build \
PKCS11_MODULE_PATH="/usr/lib/$(uname -m)-linux-gnu/pkcs11/aws_kms_pkcs11.so"
export PKCS11_MODULE_PATH

if [[ -n "$tpm2" ]]; then
if [ -f "$tpm2_sign_base.key" ]; then
pcr_key_params=(-sign "$tpm2_sign_base.key")
elif [ -f "$tpm2_sign_base.arn" ]; then
pcr_key_params=(-keyform engine -engine pkcs11 -sign "pkcs11:token=$(basename "$(cat "$tpm2_sign_base.arn")" | cut -c -32)")
else
echo "neither $tpm2_sign_base.key nor $tpm2_sign_base.arn exists, but at least one is required" >&2
exit 1
fi

unified_image_tmp="$(mktemp)"
pcr_tmp="$(mktemp)"

pcr_pub_key="$(mktemp)"
openssl x509 -pubkey -noout -in "$tpm2_sign_base.crt" > "$pcr_pub_key"

# pre-calculate PCR11 values
/usr/lib/systemd/ukify build \
--stub "$rootfs/usr/lib/systemd/boot/efi/linux$(tr '[:upper:]' '[:lower:]' <<< "$uefi_arch").efi.stub" \
--linux "$kernel_file" \
--initrd "$initrd" \
--cmdline "$cmdline" \
--output "$unified_image_tmp" \
--os-release "@$rootfs/etc/os-release" \
--pcrpkey "$pcr_pub_key" \
--pcr-banks "sha256" \
--measure > "$pcr_tmp"
# TODO: replace ukify with systemd-measure

# generate .pcrsig section
# [1]: https://uapi-group.org/specifications/specs/unified_kernel_image/
# [2]: https://www.freedesktop.org/software/systemd/man/latest/systemd-measure.html#sign
pcr_sig="$(mktemp)"

cat >> "$pcr_sig" << EOF
{
"sha256" : [
EOF

# loop through all calculated PCR values (depending on boot phase)
# and generate valid JSON according to specifications
pcr_counter=0
while read -r pcr_line; do
IFS='=' read -ra PCR <<< "$pcr_line"
echo "${PCR[1]}"

trailing_comma=$( [[ pcr_counter -eq 3 ]] && echo "" || echo "," )

# calculate the PCR policy digest
# [1]: https://github.com/tpm2-software/tpm2-tools/blob/master/man/tpm2_policypcr.1.md
# [2]: https://github.com/fishilico/shared/blob/master/python/crypto/tpm_ea_policy.py#L646
# [3]: https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part3_Commands_pub.pdf#page=244
# [4]: https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part1_Architecture_pub.pdf#page=113
policy_init=$(printf '%0*d' 64 0)

command_code="0000017f"
hash_alg_id="000b"
pcr_index="11"

pcr_value_hash=$(echo -n "${PCR[1]}" | xxd -r -p | openssl dgst -sha256 -binary | xxd -p -c 256)

pcr_select_bit_map=("00" "00" "00")
pcr_select_bit_map[pcr_index / 8]=$(printf "%02x" $((1 << pcr_index % 8)))

pcr_selection=$(printf "00000001%s03%s%s%s" "$hash_alg_id" "${pcr_select_bit_map[0]}" "${pcr_select_bit_map[1]}" "${pcr_select_bit_map[2]}")

policy="${policy_init}${command_code}${pcr_selection}${pcr_value_hash}"
policy_digest=$(echo -n "$policy" | xxd -r -p | openssl dgst -sha256 -binary | xxd -p -c 256)

# calculate and verify the PCR signature
pcr_signature=$(mktemp)
echo -n "$policy_digest" | xxd -r -p | openssl dgst -sha256 "${pcr_key_params[@]}" -out "$pcr_signature"
echo -n "$policy_digest" | xxd -r -p | openssl dgst -sha256 -verify "$pcr_pub_key" -signature "$pcr_signature"

cat >> "$pcr_sig" << EOF
{
"pcrs" : [
11
],
"pkfp" : "$(openssl pkey -pubin -inform PEM -outform DER -in "$pcr_pub_key" | tail -c +25 | openssl dgst -sha256 -hex | awk '{print $2}')",
"pol" : "$policy_digest",
"sig" : "$(openssl base64 -A -in "$pcr_signature")"
}$trailing_comma
EOF

pcr_counter=$((pcr_counter+1))
[[ $pcr_counter -eq 4 ]] && break

done < "$pcr_tmp"

cat >> "$pcr_sig" << EOF
]
}
EOF

# create unified image and append generated .pcrsig section
/usr/lib/systemd/ukify build \
--stub "$rootfs/usr/lib/systemd/boot/efi/linux$(tr '[:upper:]' '[:lower:]' <<< "$uefi_arch").efi.stub" \
--linux "$kernel_file" \
--initrd "$initrd" \
--cmdline "$cmdline" \
--output "$unified_image" \
--os-release "@$rootfs/etc/os-release" \
--pcrpkey "$pcr_pub_key" \
--section ".pcrsig:@$pcr_sig"

rm "$unified_image_tmp"
rm "$pcr_tmp"
rm "$pcr_sig"
rm "$pcr_pub_key"
else
/usr/lib/systemd/ukify build \
--stub "$rootfs/usr/lib/systemd/boot/efi/linux$(tr '[:upper:]' '[:lower:]' <<< "$uefi_arch").efi.stub" \
--linux "$kernel_file" \
--initrd "$initrd" \
--cmdline "$cmdline" \
--output "$unified_image"
--output "$unified_image" \
--os-release "@$rootfs/etc/os-release"
fi

efi_dir="$(mktemp -d)"
mkdir -p "$efi_dir/EFI/BOOT/"

if [[ "$secureboot" = "true" ]]; then
if [ -f "$cert_base.key" ]; then
key_params=(--key "$cert_base.key")
sbs_key_params=(--key "$cert_base.key")
elif [ -f "$cert_base.arn" ]; then
PKCS11_MODULE_PATH="/usr/lib/$(uname -m)-linux-gnu/pkcs11/aws_kms_pkcs11.so"
export PKCS11_MODULE_PATH
key_params=(--engine pkcs11 --key "pkcs11:token=$(basename "$(cat "$cert_base.arn")" | cut -c -32)")
sbs_key_params=(--engine pkcs11 --key "pkcs11:token=$(basename "$(cat "$cert_base.arn")" | cut -c -32)")
else
echo "neither $cert_base.key nor $cert_base.arn exists, but at least one is required" >&2
exit 1
fi

# sign unified image
datefudge -s "@$timestamp" sbsign --cert "$cert_base.crt" "${key_params[@]}" --output "$unified_image_signed" "$unified_image"
datefudge -s "@$timestamp" sbsign --cert "$cert_base.crt" "${sbs_key_params[@]}" --output "$unified_image_signed" "$unified_image"
rm "$unified_image"

ls -lah "$unified_image_signed"
Expand Down
1 change: 1 addition & 0 deletions pkg.list
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ systemd
systemd-ukify
uidmap
xorriso
xxd
xz-utils

0 comments on commit f1864b0

Please sign in to comment.