diff --git a/initrd/bin/kexec-seal-key b/initrd/bin/kexec-seal-key index 84222765..ed0337a8 100755 --- a/initrd/bin/kexec-seal-key +++ b/initrd/bin/kexec-seal-key @@ -88,109 +88,35 @@ done cat "$KEY_DEVICES" | cut -d\ -f1 | xargs /bin/qubes-measure-luks \ || die "Unable to measure the LUKS headers" -if [ "$CONFIG_TPM" = "y" ] && [ "$CONFIG_TPM2_TOOLS" != "y" ]; then - luks_pcr=`tpm calcfuturepcr -ix 16 -if /tmp/luksDump.txt` - # HOTP USB Secrity Dongle loads USB modules which changes PCR5. - # In the event HOTP USB Security Dongle is enabled, skip verification of PCR5 - # If modules should be loaded during normal boot, skip verification of PCR5 - if [ "$CONFIG_USB_KEYBOARD" = "y" -o -r /lib/modules/libata.ko -o -x /bin/hotp_verification ]; then - pcr_5="X" - else - pcr_5="0000000000000000000000000000000000000000" - fi - - # Note that PCR 4 needs to be set with the "normal-boot" - # path value, which we do not have right now since we are - # in a "init" mode - # used to be -ix 4 f8fa3b6e32e7c6fe04c366e74636e505b28f3b0d \ - # now just all zeros in a normal boot - # PCR 5 must be all zero since no kernel modules should have - # been loaded during a normal boot, but might have been - # loaded in the recovery shell. - # Otherwise use the current values of the PCRs, which will be read - # from the TPM as part of the sealing ("X"). - tpm sealfile2 \ - -if "$KEY_FILE" \ - -of "$TPM_SEALED" \ - -pwdd "$key_password" \ - -hk 40000000 \ - -ix 0 X \ - -ix 1 X \ - -ix 2 X \ - -ix 3 X \ - -ix 4 0000000000000000000000000000000000000000 \ - -ix 5 $pcr_5 \ - -ix 6 $luks_pcr \ - -ix 7 X - - if [ $? -ne 0 ]; then - die "Unable to seal secret" - fi - - shred -n 10 -z -u "$KEY_FILE" 2> /dev/null \ - || die "Failed to delete key file" - - # try it without the owner password first - if ! tpm nv_writevalue \ - -in $TPM_INDEX \ - -if "$TPM_SEALED" \ - ; then - # to create an nvram space we need the TPM owner password - # and the TPM physical presence must be asserted. - # - # The permissions are 0 since there is nothing special - # about the sealed file - tpm physicalpresence -s \ - || warn "Warning: Unable to assert physical presence" - - read -s -p "TPM Owner password: " tpm_password - echo - - tpm nv_definespace \ - -in $TPM_INDEX \ - -sz $TPM_SIZE \ - -pwdo "$tpm_password" \ - -per 0 \ - || warn "Warning: Unable to define NVRAM space; trying anyway" - - - tpm nv_writevalue \ - -in $TPM_INDEX \ - -if "$TPM_SEALED" \ - || die "Unable to write sealed secret to NVRAM" - fi -elif [ "$CONFIG_TPM2_TOOLS" = "y" ]; then - pcrf="/tmp/secret/pcrf.bin" - tpm2 pcrread -o "$pcrf" sha256:0,1,2,3 - # pcr 4 is expected to be zero (init mode) - dd if=/dev/zero bs=32 count=1 >> "$pcrf" - if [ "$CONFIG_USB_KEYBOARD" = "y" -o -r /lib/modules/libata.ko -o -x /bin/hotp_verification ]; then - DEBUG "TPM2, with PCR5 involvement (additional kernel modules are loaded per board config)..." - # Here, we take pcr 5 into consideration if modules are expected to be measured+loaded - tpm2 pcrread -o >(cat >>"$pcrf") sha256:5 - else - DEBUG "TPM2, without PCR5 involvement. Inserting 32 zero bytes under pcrf..." - #no kernel modules are expected to be measured+loaded - dd if=/dev/zero bs=32 count=1 >> "$pcrf" - fi - # Use pcr 23 to precompute the value for pcr 6 - tpm2 pcrreset 23 - tpmr extend -ix 23 -if /tmp/luksDump.txt - tpm2 pcrread -o >(cat >>"$pcrf") sha256:23 - tpm2 pcrreset 23 - # We take into consideration user files in cbfs - tpm2 pcrread -o >(cat >>"$pcrf") sha256:7 - DO_WITH_DEBUG --mask-position 5 \ - tpmr seal "$KEY_FILE" "0x8100000$TPM_INDEX" \ - sha256:0,1,2,3,4,5,6,7 "$pcrf" "$key_password" - if [ $? -eq 0 ]; then - # should be okay if this fails - shred -n 10 -z -u "$pcrf".* 2> /dev/null || true - fi +pcrf="/tmp/secret/pcrf.bin" +tpmr pcrread 0 "$pcrf" +tpmr pcrread -a 1 "$pcrf" +tpmr pcrread -a 2 "$pcrf" +tpmr pcrread -a 3 "$pcrf" +# Note that PCR 4 needs to be set with the "normal-boot" path value, which is 0. +dd if=/dev/zero bs="$(tpmr pcrsize)" count=1 >> "$pcrf" +if [ "$CONFIG_USB_KEYBOARD" = "y" -o -r /lib/modules/libata.ko -o -x /bin/hotp_verification ]; then + DEBUG "Seal with PCR5 involvement (additional kernel modules are loaded per board config)..." + # Here, we take pcr 5 into consideration if modules are expected to be measured+loaded + tpmr pcrread -a 5 "$pcrf" +else + DEBUG "Seal without PCR5 involvement, PCR5=0..." + #no kernel modules are expected to be measured+loaded + dd if=/dev/zero bs="$(tpmr pcrsize)" count=1 >> "$pcrf" fi +# Precompute the value for pcr 6 +tpmr calcfuturepcr -a "/tmp/luksDump.txt" "$pcrf" +# We take into consideration user files in cbfs +tpmr pcrread -a 7 "$pcrf" -shred -n 10 -z -u "$TPM_SEALED" 2> /dev/null \ -|| warn "Failed to delete the sealed secret - continuing" +DO_WITH_DEBUG --mask-position 6 \ + tpmr seal "$KEY_FILE" "$TPM_INDEX" 0,1,2,3,4,5,6,7 "$pcrf" \ + "$TPM_SIZE" "$key_password" + +# should be okay if this fails +shred -n 10 -z -u "$pcrf".* 2> /dev/null || true +shred -n 10 -z -u "$KEY_FILE" 2> /dev/null \ +|| warn "Failed to delete key file - continuing" cp /tmp/luksDump.txt "$paramsdir/kexec_lukshdr_hash.txt" \ || warn "Failed to have hashes of LUKS header - continuing" diff --git a/initrd/bin/kexec-unseal-key b/initrd/bin/kexec-unseal-key index 37c3b80c..a7bf49c4 100755 --- a/initrd/bin/kexec-unseal-key +++ b/initrd/bin/kexec-unseal-key @@ -33,7 +33,7 @@ for tries in 1 2 3; do fi DO_WITH_DEBUG --mask-position 6 \ - tpmr unseal "$TPM_INDEX" "sha256:0,1,2,3,4,5,6,7" "$TPM_SIZE" \ + tpmr unseal "$TPM_INDEX" "0,1,2,3,4,5,6,7" "$TPM_SIZE" \ "$key_file" "$tpm_password" if [ "$?" -eq 0 ]; then diff --git a/initrd/bin/seal-hotpkey b/initrd/bin/seal-hotpkey index 42ff6e58..b4c5f1ad 100755 --- a/initrd/bin/seal-hotpkey +++ b/initrd/bin/seal-hotpkey @@ -27,7 +27,7 @@ else fi if [ "$CONFIG_TPM" = "y" ]; then - tpmr unseal 4d47 sha256:0,1,2,3,4,7 312 "$HOTP_SECRET" \ + tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET" \ || die "Unable to unseal HOTP secret" fi diff --git a/initrd/bin/seal-totp b/initrd/bin/seal-totp index 53bd89b6..096d98eb 100755 --- a/initrd/bin/seal-totp +++ b/initrd/bin/seal-totp @@ -28,73 +28,19 @@ dd \ || die "Unable to generate 20 random bytes" secret="`base32 < $TOTP_SECRET`" -if [ "$CONFIG_TPM" = "y" ] && [ "$CONFIG_TPM2_TOOLS" != "y" ]; then - # Use the current values of the PCRs, which will be read - # from the TPM as part of the sealing ("X"). - # PCR4 == 0 means that we are still in the boot process and - # not a recovery shell. - # should this read the storage root key? - if ! tpm sealfile2 \ - -if "$TOTP_SECRET" \ - -of "$TOTP_SEALED" \ - -hk 40000000 \ - -ix 0 X \ - -ix 1 X \ - -ix 2 X \ - -ix 3 X \ - -ix 4 0000000000000000000000000000000000000000 \ - -ix 7 X \ - ; then - shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null - die "Unable to seal secret" - fi - - shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null - - - # to create an nvram space we need the TPM owner password - # and the TPM physical presence must be asserted. - # - # The permissions are 0 since there is nothing special - # about the sealed file - tpm physicalpresence -s \ - || warn "Warning: Unable to assert physical presence" - - # Try to write it without the password first, and then create - # the NVRAM space using the owner password if it fails for some reason. - if ! tpm nv_writevalue \ - -in $TPM_NVRAM_SPACE \ - -if "$TOTP_SEALED" \ - ; then - warn 'NVRAM space does not exist? Owner password is required' - read -s -p "TPM Owner password: " tpm_password - echo - - tpm nv_definespace \ - -in $TPM_NVRAM_SPACE \ - -sz 312 \ - -pwdo "$tpm_password" \ - -per 0 \ - || die "Unable to define NVRAM space" - - tpm nv_writevalue \ - -in $TPM_NVRAM_SPACE \ - -if "$TOTP_SEALED" \ - || die "Unable to write sealed secret to NVRAM" - fi -elif [ "$CONFIG_TPM2_TOOLS" = "y" ]; then - pcrf="/tmp/secret/pcrf.bin" - tpm2 pcrread -o "$pcrf" sha256:0,1,2,3 - # pcr 4 is expected to be zero (boot mode: init) - dd if=/dev/zero bs=32 count=1 >> "$pcrf" - # pcr 5 (kernel modules loaded) is not measured at sealing/unsealing of totp - # pcr 6 (drive luks header) is not measured at sealing/unsealing of totp - # pcr 7 is containing measurements of user injected stuff in cbfs - tpm2 pcrread -o /dev/stderr sha256:7 2>&1 >/dev/console | cat >> "$pcrf" - tpmr seal "$TOTP_SECRET" "0x8100$TPM_NVRAM_SPACE" sha256:0,1,2,3,4,7 "$pcrf" \ - || die "Unable to write sealed secret to NVRAM" -fi - +pcrf="/tmp/secret/pcrf.bin" +tpmr pcrread 0 "$pcrf" +tpmr pcrread -a 1 "$pcrf" +tpmr pcrread -a 2 "$pcrf" +tpmr pcrread -a 3 "$pcrf" +# pcr 4 is expected to be zero (boot mode: init) +dd if=/dev/zero bs="$(tpmr pcrsize)" count=1 >> "$pcrf" +# pcr 5 (kernel modules loaded) is not measured at sealing/unsealing of totp +# pcr 6 (drive luks header) is not measured at sealing/unsealing of totp +# pcr 7 is containing measurements of user injected stuff in cbfs +tpmr pcrread -a 7 "$pcrf" +tpmr seal "$TOTP_SECRET" "$TPM_NVRAM_SPACE" 0,1,2,3,4,7 "$pcrf" 312 \ + || die "Unable to write sealed secret to NVRAM" shred -n 10 -z -u "$TOTP_SEALED" 2> /dev/null url="otpauth://totp/$HOST?secret=$secret" diff --git a/initrd/bin/tpmr b/initrd/bin/tpmr index e6bfdf0c..eba11e7b 100755 --- a/initrd/bin/tpmr +++ b/initrd/bin/tpmr @@ -9,6 +9,11 @@ ENC_SESSION_FILE="enc.ctx" DEC_SESSION_FILE="dec.ctx" PRIMARY_HANDLE_FILE="primary.handle" +# PCR size in bytes. Set when we determine what TPM version is in use. +# TPM1 PCRs are always 20 bytes. TPM2 is allowed to provide multiple PCR banks +# with different algorithms - we always use SHA-256, so they are 32 bytes. +PCR_SIZE= + set -e -o pipefail if [ -r "/tmp/config" ]; then . /tmp/config @@ -18,6 +23,93 @@ fi TRACE "Under /bin/tpmr" +# Busybox xxd lacks -r, and we get hex dumps from TPM1 commands. This converts +# a hex dump to binary data using sed and printf +hex2bin() { + sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf +} + +# usage: tpmr pcrread [-a] <index> <file> +# Reads PCR binary data and writes to file. +# -a: Append to file. Default is to overwrite. +tpm2_pcrread() { + TRACE "Under /bin/tpmr:tpm2_pcrread" + if [ "$1" = "-a" ]; then + APPEND=y + shift + fi + + index="$1" + file="$2" + + if [ -z "$APPEND" ]; then + # Don't append - truncate file now so real command always + # appends + true >"$file" + fi + + tpm2 pcrread -Q -o >(cat >>"$file") "sha256:$index" +} +tpm1_pcrread() { + TRACE "Under /bin/tpmr:tpm2_pcrread" + if [ "$1" = "-a" ]; then + APPEND=y + shift + fi + + index="$1" + file="$2" + + if [ -z "$APPEND" ]; then + # Don't append - truncate file now so real command always + # appends + true >"$file" + fi + + tpm pcrread -ix "$index" | hex2bin >>"$file" +} + +# usage: tpmr calcfuturepcr [-a] <input_file> <output_file> +# Uses the scratch PCR to calculate a future PCR value (TPM2 23, TPM1 16). The +# data in input file are hashed into a PCR, and the PCR value is placed in +# output_file. +# -a: Append to output_file. Default is to overwrite +tpm2_calcfuturepcr() { + TRACE "Under /bin/tpmr:tpm2_calcfuturepcr" + if [ "$1" = "-a" ]; then + APPEND=y + shift + fi + + input_file="$1" + output_file="$2" + + if [ -z "$APPEND" ]; then + true >"$output_file" + fi + + tpm2 pcrreset -Q 23 + tpmr extend -ix 23 -if "$input_file" + tpm2 pcrread -Q -o >(cat >>"$output_file") sha256:23 + tpm2 pcrreset -Q 23 +} +tpm1_calcfuturepcr() { + TRACE "Under /bin/tpmr:tpm2_calcfuturepcr" + if [ "$1" = "-a" ]; then + APPEND=y + shift + fi + + input_file="$1" + output_file="$2" + + if [ -z "$APPEND" ]; then + true >"$output_file" + fi + + tpm calcfuturepcr -ix 16 -if "$input_file" | hex2bin >>"$output_file" +} + tpm2_extend() { TRACE "Under /bin/tpmr:tpm2_extend" while true; do @@ -146,13 +238,18 @@ cleanup_shred() { tpm2_seal() { TRACE "Under /bin/tpmr:tpm2_seal" file="$1" #$KEY_FILE - handle="$2" # 0x8100000$TPM_INDEX - pcrl="$3" #sha256:0,1,2,3,4,5,6,7 + index="$2" + pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix) pcrf="$4" - pass="$5" + sealed_size="$5" # Not used for TPM2 + pass="$6" + mkdir -p "$SECRET_DIR" bname="`basename $file`" + # Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc. + handle="$(printf "0x81%6s" "$index" | tr ' ' 0)" + DEBUG "tpm2_seal: file=$file handle=$handle pcrl=$pcrl pcrf=$pcrf pass=$(mask_param "$pass")" # Create a policy requiring both PCRs and the object's authentication @@ -165,7 +262,7 @@ tpm2_seal() { trap "cleanup_session '$TRIAL_SESSION'" EXIT # Save the policy hash in case the password policy is not used (we have # to get this from the last step, whichever it is). - tpm2 policypcr -Q -l "$pcrl" -f "$pcrf" -S "$TRIAL_SESSION" -L "$AUTH_POLICY" + tpm2 policypcr -Q -l "sha256:$pcrl" -f "$pcrf" -S "$TRIAL_SESSION" -L "$AUTH_POLICY" CREATE_PASS_ARGS=() if [ "$pass" ]; then # Add an object authorization policy (the object authorization @@ -204,6 +301,64 @@ tpm2_seal() { tpm2 evictcontrol -Q -C o -P "$key_password" \ -c "$SECRET_DIR/$bname.seal.ctx" "$handle" } +tpm1_seal() { + TRACE "Under /bin/tpmr:tpm1_seal" + file="$1" + index="$2" + pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix) + pcrf="$4" + sealed_size="$5" + pass="$6" + + sealed_file="$SECRET_DIR/tpm1_seal_sealed.bin" + trap "cleanup_shred '$sealed_file'" EXIT + + POLICY_ARGS=() + + # If a password was given, add it to the policy arguments + if [ "$pass" ]; then + POLICY_ARGS+=(-pwdd "$pass") + fi + + # Transform the PCR list and PCR file to discrete arguments + IFS=',' read -r -a PCR_LIST <<<"$pcrl" + pcr_file_index=0 + for pcr in "${PCR_LIST[@]}"; do + # Read each PCR_SIZE block from the file and pass as hex + POLICY_ARGS+=(-ix "$pcr" + "$(dd if="$pcrf" skip="$pcr_file_index" bs="$PCR_SIZE" count=1 status=none | xxd -p | tr -d ' ')" + ) + pcr_file_index=$((pcr_file_index+1)) + done + + tpm sealfile2 \ + -if "$file" \ + -of "$sealed_file" \ + -hk 40000000 \ + "${POLICY_ARGS[@]}" + + # try it without the owner password first + if ! tpm nv_writevalue -in "$index" -if "$sealed_file"; then + # to create an nvram space we need the TPM owner password + # and the TPM physical presence must be asserted. + # + # The permissions are 0 since there is nothing special + # about the sealed file + tpm physicalpresence -s \ + || warn "Warning: Unable to assert physical presence" + + read -s -p "TPM Owner password: " tpm_password + echo + + tpm nv_definespace -in "$index" -sz "$sealed_size" \ + -pwdo "$tpm_password" -per 0 \ + || warn "Warning: Unable to define NVRAM space; trying anyway" + + + tpm nv_writevalue -in "$index" -if "$sealed_file" \ + || die "Unable to write sealed secret to NVRAM" + fi +} # Unseal a file sealed by tpm2_seal. The PCR list must be provided, the # password must be provided if one was used to seal (and cannot be provided if @@ -211,7 +366,7 @@ tpm2_seal() { tpm2_unseal() { TRACE "Under /bin/tpmr:tpm2_unseal" index="$1" - pcrl="$2" + pcrl="$2" #0,1,2,3,4,5,6,7 (does not include algorithm prefix) sealed_size="$3" file="$4" pass="$5" @@ -237,7 +392,7 @@ tpm2_unseal() { tpm2 startauthsession -Q -g sha256 -S "$POLICY_SESSION" --policy-session trap "cleanup_session '$POLICY_SESSION'" EXIT # Check the PCR policy - tpm2 policypcr -Q -l "$pcrl" -S "$POLICY_SESSION" + tpm2 policypcr -Q -l "sha256:$pcrl" -S "$POLICY_SESSION" UNSEAL_PASS_SUFFIX="" HMAC_SESSION=/tmp/unsealfile_hmac.session @@ -282,7 +437,7 @@ tpm2_kexec_finalize() { # being cleared in the OS. # This passphrase is only effective before the next boot. echo "Locking TPM2 platform hierarchy..." - randpass=$(dd if=/dev/urandom bs=4 count=1 | xxd -p) + randpass=$(dd if=/dev/urandom bs=4 count=1 status=none | xxd -p) tpm2 changeauth -c platform "$randpass" \ || warn "Failed to lock platform hierarchy of TPM2!" } @@ -330,11 +485,20 @@ fi # TPM1 - most commands forward directly to tpm, but some are still wrapped for # consistency with tpm2. if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then + PCR_SIZE=20 # TPM1 PCRs are always SHA-1 subcmd="$1" # Don't shift yet, for most commands we will just forward to tpm. case "$subcmd" in + pcrread) + shift; tpm1_pcrread "$@";; + pcrsize) + echo "$PCR_SIZE";; + calcfuturepcr) + shift; tpm1_calcfuturepcr "$@";; kexec_finalize) ;; # Nothing on TPM1. + seal) + shift; tpm1_seal "$@";; unseal) shift; tpm1_unseal "$@";; *) @@ -345,9 +509,16 @@ if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then fi # TPM2 - all commands implemented as wrappers around tpm2 +PCR_SIZE=32 # We use the SHA-256 PCRs subcmd="$1" shift 1 case "$subcmd" in + pcrread) + tpm2_pcrread "$@";; + pcrsize) + echo "$PCR_SIZE";; + calcfuturepcr) + tpm2_calcfuturepcr "$@";; extend) tpm2_extend "$@";; counter_read) diff --git a/initrd/bin/unseal-hotp b/initrd/bin/unseal-hotp index 543e41a8..ed347585 100755 --- a/initrd/bin/unseal-hotp +++ b/initrd/bin/unseal-hotp @@ -38,7 +38,7 @@ fi #counter_value=$(printf "%d" 0x${counter_value}) if [ "$CONFIG_TPM" = "y" ]; then - tpmr unseal 4d47 sha256:0,1,2,3,4,7 312 "$HOTP_SECRET" + tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET" fi if ! hotp $counter_value < "$HOTP_SECRET"; then diff --git a/initrd/bin/unseal-totp b/initrd/bin/unseal-totp index f7032deb..32db3192 100755 --- a/initrd/bin/unseal-totp +++ b/initrd/bin/unseal-totp @@ -8,7 +8,7 @@ TOTP_SECRET="/tmp/secret/totp.key" TRACE "Under /bin/unseal-totp" if [ "$CONFIG_TPM" = "y" ]; then - tpmr unseal 4d47 sha256:0,1,2,3,4,7 312 "$TOTP_SECRET" \ + tpmr unseal 4d47 0,1,2,3,4,7 312 "$TOTP_SECRET" \ || die "Unable to unseal totp secret" fi