seal-totp, kexec-seal-key: Use common logic for TPM1 and TPM2

Provide tpmr commands pcrread, pcrsize, calcfuturepcr, and seal for
both TPM1 and TPM2.

Combine seal logic for TPM1/TPM2 in seal-totp, kexec-seal-key.  This is
essentially the TPM2 logic now that tpmr provides the same wrapped
commands for both TPM1 and TPM2.

Remove algorithm prefix from PCR list in tpmr unseal for consistency
with tpmr seal.

Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
This commit is contained in:
Jonathon Hall 2023-03-08 12:39:06 -05:00
parent e2c2f2d4e0
commit 7b8824adf1
No known key found for this signature in database
GPG Key ID: 1E9C3CA91AE25114
7 changed files with 222 additions and 179 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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