Skip to content
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