Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP: introspection - replicate TPM PCRs measurements directly from measured content (TCPA/TPM Event log) #1568

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
22 changes: 11 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -358,19 +358,19 @@ define define_module =
echo -n '$($1_repo)|$($1_commit_hash)' > "$$@"; \
fi
if [ ! -e "$(build)/$($1_base_dir)/.patched" ]; then \
if [ -r patches/$($1_patch_name).patch ]; then \
if [ -r patches/$($1_patch_name).patch ]; then \
( git apply --verbose --reject --binary --directory build/$(CONFIG_TARGET_ARCH)/$($1_base_dir) ) \
< patches/$($1_patch_name).patch \
|| exit 1 ; \
< patches/$($1_patch_name).patch \
|| exit 1 ; \
fi && \
if [ -d patches/$($1_patch_name) ] && \
[ -r patches/$($1_patch_name) ] ; then \
for patch in patches/$($1_patch_name)/*.patch ; do \
echo "Applying patch file : $$$$patch " ; \
if [ -d patches/$($1_patch_name) ] && \
[ -r patches/$($1_patch_name) ] ; then \
for patch in patches/$($1_patch_name)/*.patch ; do \
echo "Applying patch file : $$$$patch " ; \
( git apply --verbose --reject --binary --directory build/$(CONFIG_TARGET_ARCH)/$($1_base_dir) ) \
< $$$$patch \
|| exit 1 ; \
done ; \
< $$$$patch \
|| exit 1 ; \
done ; \
fi && \
touch "$(build)/$($1_base_dir)/.patched"; \
fi
Expand Down Expand Up @@ -584,7 +584,7 @@ $(foreach m, $(modules-y), \
)

#
# hack to build cbmem from coreboot
# hack to build cbmem, cbfstool and ifdtool from coreboot
# this must be built *AFTER* musl, but since coreboot depends on other things
# that depend on musl it should be ok.
#
Expand Down
32 changes: 11 additions & 21 deletions initrd/.ash_history
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
#mount /boot in read-only by default
mount /boot
#verify detached signature of /boot content
find /boot/kexec*.txt | gpg --verify /boot/kexec.sig -
#remove invalid kexec_* signed files
mount /dev/sda1 /boot && mount -o remount,rw /boot && rm /boot/kexec* && mount -o remount,ro /boot
#Generate keys from GPG smartcard:
mount-usb && gpg --home=/.gnupg/ --card-edit
#Copy generated public key, private_subkey, trustdb and artifacts to external media for backup:
mount -o remount,rw /media && mkdir -p /media/gpg_keys; gpg --export-secret-keys --armor email@address.com > /media/gpg_keys/private.key && gpg --export --armor email@address.com > /media/gpg_keys/public.key && gpg --export-ownertrust > /media/gpg_keys/otrust.txt && cp -r ./.gnupg/* /media/gpg_keys/ 2> /dev/null
#Insert public key and trustdb export into reproducible rom:
cbfs -o /media/coreboot.rom -a "heads/initrd/.gnupg/keys/public.key" -f /media/gpg_keys/public.key && cbfs -o /media/coreboot.rom -a "heads/initrd/.gnupg/keys/otrust.txt" -f /media/gpg_keys/otrust.txt
#Flush changes to external media:
mount -o,remount ro /media
#Flash modified reproducible rom with inserted public key and trustdb export from precedent step. Flushes actual rom's keys (-c: clean):
flash.sh -c /media/coreboot.rom
#Attest integrity of firmware as it is
seal-totp
#Verify Intel ME state:
cbmem --console | grep '^ME'
cbmem --console | less
mount /boot #mount /boot in read-only by default
find /boot/kexec*.txt | gpg --verify /boot/kexec.sig - #verify detached signature of /boot content
media-scan /dev/sdXZ #scan Y partition of X device for detached signed ISOs to boot from
mount-usb --mode rw #mount usb in read-write mode
mount-usb --mode ro #mount usb in read-only mode
flash.sh -c /media/coreboot.rom #flash coreboot.rom WITHOUT preserving user settings
flash.sh /media/coreboot.rom -p #flash coreboot.rom WITH preserving user settings
cbmem --console | grep '^ME' #view ME console
cbmem --console | less #view coreboot console
tpmr recalculate_firmware_pcr_from_cbfs #Replay coreboot TPM event log from CBFS
tpmr verify_coreboot_measured_boot_tpm_event_log_vs_content_measured #Validate coreboot TPM event log against cbmem FMAP+cbfs content
# Reboot/power off (important for devices with no keyboard to escape recovery shell)
reboot # Press Enter with this command to reboot
poweroff # Press Enter with this command to power off
185 changes: 181 additions & 4 deletions initrd/bin/tpmr
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,28 @@ is_hash() {
# initial_state - a hash value setting the initial state
# files/hashes... - any number of files or hashes, state is extended once for each item
extend_pcr_state() {
TRACE "Under /bin/tpmr:extend_pcr_state"
local alg="$1"
local state="$2"
DEBUG "Initial PCR state: $state"
local next extend
shift 2
local argument=1

while [ "$#" -gt 0 ]; do
next="$1"
shift
if is_hash "$alg" "$next"; then
extend="$next"
DEBUG "Extending PCR state with passed argument #$argument hash: $extend"
else
extend="$("${alg}sum" <"$next" | cut -d' ' -f1)"
DEBUG "Extending PCR state with argument #$argument file: $extend"
fi
state="$(echo "$state$extend" | hex2bin | "${alg}sum" | cut -d' ' -f1)"
argument=$((argument + 1))
done
DEBUG "Extended final PCR state: $state"
echo "$state"
}

Expand Down Expand Up @@ -213,8 +220,10 @@ replay_pcr() {
shift 2
replayed_pcr=$(extend_pcr_state $alg $(printf "%.${alg_digits}d" 0) \
$(echo "$log" | awk -v alg=$alg -v pcr=$pcr -f <(echo $AWK_PROG)) $@)
echo $replayed_pcr | hex2bin
DEBUG "Replayed cbmem -L clean boot state of PCR=$pcr ALG=$alg : $replayed_pcr"

# Output in binary form
echo $replayed_pcr | hex2bin
# To manually introspect current PCR values:
# PCR-2:
# tpmr calcfuturepcr 2 | xxd -p
Expand All @@ -228,6 +237,154 @@ replay_pcr() {
# (6: LUKS header, 7: user related cbfs files loaded from cbfs-init)
}


# Function: read_and_pad_FMAP_from_cbmem
# Description: This function reads the FMAP (Firmware Map) from the cbmem (coreboot memory) and pads it to the next multiple of 512 bytes.
# It then calculates the checksum of the padded FMAP using the specified checksum algorithm (sha1 or sha256) and returns the checksum value.
# Parameters:
# - $1: The checksum algorithm to use (sha1 or sha256)
# Returns:
# - The checksum value of the padded FMAP
# - Returns 1 if an unknown checksum algorithm is provided
read_and_pad_FMAP_from_cbmem() {
TRACE "Under /bin/tpmr:read_and_pad_FMAP_from_cbmem"
# Check if the checksum algorithm is supported and set the appropriate program
if [ "$1" == "sha1" ]; then
checksum_prog="sha1sum"
elif [ "$1" == "sha256" ]; then
checksum_prog="sha256sum"
else
echo >&2 "Unknown checksum algorithm: $1"
return 1
fi

# Create the directory for temporary files
mkdir -p /tmp/secret/
# Fetch the address of the FMAP in memory and write the raw FMAP data to a file
cbmem --rawdump "$(cbmem -l | grep FMAP | awk -F " " '{print $3}')" >/tmp/secret/fmap.raw
# Fetch the size of the FMAP from the raw data (4 bytes at offset 8) and store it as a hexadecimal string
fmap_size_hex=$(hexdump -v -e '/1 "%02x"' -s 8 -n 4 /tmp/secret/fmap.raw)
# Rearrange the bytes in the size to little-endian format
fmap_size_le="${fmap_size_hex:6:2}${fmap_size_hex:4:2}${fmap_size_hex:2:2}${fmap_size_hex:0:2}"
# Convert the size from hexadecimal to decimal
fmap_size=$((16#"$fmap_size_le"))
# Calculate the next multiple of 512 that is greater than or equal to the size of the FMAP
next_multiple=$(( (fmap_size + 511) / 512 * 512 ))
# Calculate the number of bytes needed to fill the fmap.raw file to the next multiple of 512
fill_size=$(( next_multiple - $(stat -c%s /tmp/secret/fmap.raw) ))
# Create a file named fill.ff filled with 'ff' of the required size
dd if=/dev/zero bs=1 count="$fill_size" 2>/dev/null | tr '\0' '\377' >/tmp/secret/fill.ff
# Append the fill.ff file to the fmap.raw file, resulting in a file named fmap_filled.raw
cat /tmp/secret/fmap.raw /tmp/secret/fill.ff >/tmp/secret/fmap_filled.raw
# Caller is expected to use hash format that matches the algorithm used for the PCR
"$checksum_prog" /tmp/secret/fmap_filled.raw | awk -F " " '{print $1}'
# Removal of the tempory files in tmpfs is left to when going to recovery shell or rebooting
}

calc_pcr() {
TRACE "Under /bin/tpmr:calc_pcr"
if [ -z "$2" ]; then
echo >&2 "No PCR number passed"
return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will return a successful status to the caller. Should this be return 1 or even exit 1?

fi
if [ "$2" -ge 8 ]; then
echo >&2 "Illegal PCR number ($2)"
return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

fi
local alg="$1"
local pcr="$2"
local alg_digits=0
# SHA-1 hashes are 40 chars
if [ "$alg" = "sha1" ]; then alg_digits=40; fi
# SHA-256 hashes are 64 chars
if [ "$alg" = "sha256" ]; then alg_digits=64; fi
Comment on lines +297 to +300

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# SHA-1 hashes are 40 chars
if [ "$alg" = "sha1" ]; then alg_digits=40; fi
# SHA-256 hashes are 64 chars
if [ "$alg" = "sha256" ]; then alg_digits=64; fi
case $alg in
(sha1)
# SHA-1 hashes are 40 chars
alg_digits=40;;
(sha256)
# SHA-256 hashes are 64 chars
alg_digits=64;;
(*)
# Invalid
printf 'Invalid hash algorithm %s\n' "$alg" >&2
exit 1;;
esac

shift 2
replayed_pcr=$(extend_pcr_state $alg $(printf "%.${alg_digits}d" 0) $@)
DEBUG "Replayed cbmem -L clean boot state of PCR=$pcr ALG=$alg : $replayed_pcr"
echo $replayed_pcr

# To manually introspect calculated to PCR values:
# TODO: fix the following examples with WORKING examples
# PCR-2:
# bash tpmr calc_pcr 2 <(cbmem -r 464d4150) <(cbfs --read bootblock) \
# <(cbfs --read fallback/romstage) <(cbfs --read fallback/postcar) \
# <(cbfs --read fallback/ramstage) <(cbfs --read bootsplash.jpg) \
# <(cbfs --read fallback/payload) | xxd -p
# PCR-4, in case of recovery shell (bash used for process substitution):
# bash -c "tpmr calc_pcr 4 <(echo -n recovery)" | xxd -p
# PCR-4, in case of normal boot passing through kexec-select-boot:
# bash -c "tpmr calc_pcr 4 <(echo -n generic)" | xxd -p
# PCR-5, depending on which modules are loaded for given board:
# tpmr calc_pcr 5 module0.ko module1.ko module2.ko | xxd -p
# PCR-6 and PCR-7: similar to 5, but with different files passed
# (6: LUKS header, 7: user related cbfs files loaded from cbfs-init)
}


# Function: recalculate_firmware_pcr_from_cbfs
# Description: This function recalculates the firmware PCR (Platform Configuration Register) values from the files measured by coreboot.
# It simulates the measurement process by passing the hashes of the files to the `calc_pcr` function.
# The function uses various `cbfs` commands to read the contents of specific files and calculates their SHA1 hashes.
# The calculated hashes are then passed to `calc_pcr` along with other necessary parameters.
# The function also outputs the PCR values for TPM PCR2 and the TPM event log reported by `cbmem -L`.
#
# Parameters:
# - $1: checksum algorithm (sha1 or sha256)
#
# Usage: recalculate_firmware_pcr_from_cbfs <checksum algo> <file_hash/file_name>
# Examples:
# recalculate_firmware_pcr_from_cbfs sha1 "3E0A13C35B0244B012BE5287A3B52352CC576BAE"
# recalculate_firmware_pcr_from_cbfs sha256 "3E0A13C35B0244B012BE5287A3B52352CC576BAE"
#
# TODO: redo alternative function with files instead of hashes
recalculate_firmware_pcr_from_cbfs()
{
TRACE "Under /bin/tpmr:recalculate_firmware_pcr_from_cbfs"
# We pass hashes of the files that are measured by coreboot, simulating the measurement process
# As of now, Heads uses coreboot custom TPM Event log format, which measures everything in PCR-2

if [ "$1" == "sha1" ]; then
checksum_prog="sha1sum"
PCR_STRING="PCR-2"
elif [ "$1" == "sha256" ]; then
checksum_prog="sha256sum"
PCR_STRING="2 :"
else
echo >&2 "Unknown checksum algorithm: $1"
return 1
fi

calculated_pcr=$(calc_pcr "$1" 2 \
"$(read_and_pad_FMAP_from_cbmem "$1")" \
"$(cbfs --read bootblock | $checksum_prog | awk -F ' ' '{print $1}')" \
"$(cbfs --read fallback/romstage | $checksum_prog | awk -F ' ' '{print $1}')" \
"$(cbfs --read fallback/postcar | $checksum_prog | awk -F ' ' '{print $1}')" \
"$(cbfs --read fallback/ramstage | $checksum_prog | awk -F ' ' '{print $1}')" \
"$(cbfs --read bootsplash.jpg | $checksum_prog | awk -F ' ' '{print $1}')" \
"$(cbfs --read fallback/payload | $checksum_prog | awk -F ' ' '{print $1}')")

DEBUG "Original TPM PCR2 value: $(pcrs | grep "$PCR_STRING")"
DEBUG "TPM event log reported by cbmem -L: $(cbmem -L)"
DEBUG "Calculated TPM PCR2 value from files: $calculated_pcr"
echo "$calculated_pcr"
}

verify_coreboot_measured_boot_tpm_event_log_vs_content_measured()
{
measured_boot=$(tpmr calcfuturepcr 2 | xxd -p)
content_measured=$(tpmr recalculate_firmware_pcr_from_cbfs)

DEBUG "Measured boot from TPM event log: $measured_boot"
DEBUG "Measured boot from content measured by coreboot: $content_measured"

if [ "$measured_boot" == "$content_measured" ]; then
echo "Verified: TPM event log matches content measured by coreboot"
else
echo "Failed: TPM event log does not match content measured by coreboot"
die "TPM event log does not match content measured by coreboot"
fi
}

tpm2_extend() {
TRACE "Under /bin/tpmr:tpm2_extend"
while true; do
Expand Down Expand Up @@ -487,7 +644,7 @@ tpm1_seal() {
pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
pcrf="$4"
sealed_size="$5"
pass="$6" # May be empty to seal with no password
pass="$6" # May be empty to seal with no password
tpm_owner_password="$7" # Owner password - will prompt if needed and not empty

sealed_file="$SECRET_DIR/tpm1_seal_sealed.bin"
Expand All @@ -497,7 +654,6 @@ tpm1_seal() {

DEBUG "tpm1_seal arguments: file=$file index=$index pcrl=$pcrl pcrf=$pcrf sealed_size=$sealed_size pass=$(mask_param "$pass") tpm_password=$(mask_param "$tpm_password")"


# If a password was given, add it to the policy arguments
if [ "$pass" ]; then
POLICY_ARGS+=(-pwdd "$pass")
Expand All @@ -519,7 +675,7 @@ tpm1_seal() {
-of "$sealed_file" \
-hk 40000000 \
"${POLICY_ARGS[@]}"

# try it without the TPM Owner Password first
if ! tpm nv_writevalue -in "$index" -if "$sealed_file"; then
# to create an nvram space we need the TPM Owner Password
Expand Down Expand Up @@ -751,6 +907,18 @@ if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then
shift
replay_pcr "sha1" "$@"
;;
calc_pcr)
shift
calc_pcr "sha1" "$@"
;;
recalculate_firmware_pcr_from_cbfs)
shift
recalculate_firmware_pcr_from_cbfs "sha1"
;;
verify_coreboot_measured_boot_tpm_event_log_vs_content_measured)
shift
verify_coreboot_measured_boot_tpm_event_log_vs_content_measured
;;
counter_create)
shift
tpm1_counter_create "$@"
Expand Down Expand Up @@ -796,6 +964,15 @@ pcrsize)
calcfuturepcr)
replay_pcr "sha256" "$@"
;;
calc_pcr)
calc_pcr "sha256" "$@"
;;
recalculate_firmware_pcr_from_cbfs)
recalculate_firmware_pcr_from_cbfs "sha256"
;;
verify_coreboot_measured_boot_tpm_event_log_vs_content_measured)
verify_coreboot_measured_boot_tpm_event_log_vs_content_measured
;;
extend)
tpm2_extend "$@"
;;
Expand Down
2 changes: 1 addition & 1 deletion targets/qemu.mk
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ run: $(TPMDIR)/.manufacture $(ROOT_DISK_IMG) $(MEMORY_SIZE_FILE) $(USB_FD_IMG)
-qemu-system-x86_64 -drive file="$(ROOT_DISK_IMG)",if=virtio \
--machine q35,accel=kvm:tcg \
-rtc base=utc \
-smp "$$(nproc)" \
-smp 1 \
-vga std \
-m "$$(cat "$(MEMORY_SIZE_FILE)")" \
-serial stdio \
Expand Down