diff --git a/Makefile b/Makefile index 6c8bf9626..bb2f09084 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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. # diff --git a/initrd/.ash_history b/initrd/.ash_history index 12d10333b..42f495001 100644 --- a/initrd/.ash_history +++ b/initrd/.ash_history @@ -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 diff --git a/initrd/bin/tpmr b/initrd/bin/tpmr index e7e61ceba..dda72e17e 100755 --- a/initrd/bin/tpmr +++ b/initrd/bin/tpmr @@ -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" } @@ -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 @@ -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 + fi + if [ "$2" -ge 8 ]; then + echo >&2 "Illegal PCR number ($2)" + return + 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 + 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 +# 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 @@ -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" @@ -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") @@ -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 @@ -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 "$@" @@ -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 "$@" ;; diff --git a/targets/qemu.mk b/targets/qemu.mk index 930afa7b3..59286a3e1 100644 --- a/targets/qemu.mk +++ b/targets/qemu.mk @@ -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 \