#!/bin/bash # TPM Wrapper - to unify tpm and tpm2 subcommands . /etc/functions SECRET_DIR="/tmp/secret" PRIMARY_HANDLE="0x81000000" ENC_SESSION_FILE="$SECRET_DIR/enc.ctx" DEC_SESSION_FILE="$SECRET_DIR/dec.ctx" PRIMARY_HANDLE_FILE="$SECRET_DIR/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= # Export CONFIG_TPM2_CAPTURE_PCAP=y from your board config to capture tpm2 pcaps to # /tmp/tpm0.pcap; Wireshark can inspect these. (This must be enabled at build # time so the pcap TCTI driver is included.) if [ -n "$CONFIG_TPM2_CAPTURE_PCAP" ]; then export TPM2TOOLS_TCTI="pcap:device:/dev/tpmrm0" export TCTI_PCAP_FILE="/tmp/tpm0.pcap" fi set -e -o pipefail if [ -r "/tmp/config" ]; then . /tmp/config else . /etc/config fi TRACE_FUNC # 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 } # Render a password as 'hex:' for use with tpm2-tools. Passwords # should always be passed this way to avoid ambiguity. (Passing with no prefix # would choke if the password happened to start with 'file:' or 'hex:'. Passing # as a file still chokes if the password begins with 'hex:', oddly tpm2-tools # accepts 'hex:' in the file content.) tpm2_password_hex() { echo "hex:$(echo -n "$1" | xxd -p | tr -d ' \n')" } # usage: tpmr pcrread [-a] # Reads PCR binary data and writes to file. # -a: Append to file. Default is to overwrite. tpm2_pcrread() { TRACE_FUNC 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 DO_WITH_DEBUG tpm2 pcrread -Q -o >(cat >>"$file") "sha256:$index" } tpm1_pcrread() { TRACE_FUNC 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 DO_WITH_DEBUG tpm pcrread -ix "$index" | hex2bin >>"$file" } # is_hash - Check if a value is a valid hash of a given type # usage: is_hash is_hash() { # Must only contain 0-9a-fA-F if [ "$(echo -n "$2" | tr -d '0-9a-fA-F' | wc -c)" -ne 0 ]; then return 1; fi # SHA-1 hashes are 40 chars if [ "$1" = "sha1" ] && [ "${#2}" -eq 40 ]; then return 0; fi # SHA-256 hashes are 64 chars if [ "$1" = "sha256" ] && [ "${#2}" -eq 64 ]; then return 0; fi return 1 } # extend_pcr_state - extend a PCR state value with more hashes or raw data (which is hashed) # usage: # extend_pcr_state # alg - either 'sha1' or 'sha256' to specify algorithm # 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() { local alg="$1" local state="$2" local next extend shift 2 while [ "$#" -gt 0 ]; do next="$1" shift if is_hash "$alg" "$next"; then extend="$next" else extend="$("${alg}sum" <"$next" | cut -d' ' -f1)" fi state="$(echo "$state$extend" | hex2bin | "${alg}sum" | cut -d' ' -f1)" done echo "$state" } # There are 3 (and a half) possible formats of event log, each of them requires # different arguments for grep. Those formats are shown below as heredocs to # keep all the data, including whitespaces: # 1) TPM2 log, which can hold multiple hash algorithms at once: : <<'EOF' TPM2 log: Specification: 2.00 Platform class: PC Client TPM2 log entry 1: PCR: 2 Event type: Action Digests: SHA256: de73053377e1ae5ba5d2b637a4f5bfaeb410137722f11ef135e7a1be524e3092 SHA1: 27c4f1fa214480c8626397a15981ef3a9323717f Event data: FMAP: FMAP EOF # 2) TPM1.2 log (aka TCPA), digest is always SHA1: : <<'EOF' TCPA log: Specification: 1.21 Platform class: PC Client TCPA log entry 1: PCR: 2 Event type: Action Digest: 27c4f1fa214480c8626397a15981ef3a9323717f Event data: FMAP: FMAP EOF # 3) coreboot-specific format: # 3.5) older versions printed 'coreboot TCPA log', even though it isn't TCPA : <<'EOF' coreboot TPM log: PCR-2 27c4f1fa214480c8626397a15981ef3a9323717f SHA1 [FMAP: FMAP] EOF # awk script to handle all of the above. Note this gets squashed to one line so # semicolons are required. AWK_PROG=' BEGIN { getline; hash_regex="([a-fA-F0-9]{40,})"; if ($0 == "TPM2 log:") { RS="\n[^[:space:]]"; pcr="PCR: " pcr; alg=toupper(alg) ": " hash_regex; } else if ($0 == "TCPA log:") { RS="\n[^[:space:]]"; pcr="PCR: " pcr; alg="Digest: " hash_regex; } else if ($0 ~ /^coreboot (TCPA|TPM) log:$/) { pcr="PCR-" pcr; alg=hash_regex " " toupper(alg) " "; } else { print "Unknown TPM event log format:", $0 > "/dev/stderr"; exit -1; } } $0 ~ pcr { match($0, alg); print gensub(alg, "\\1", "g", substr($0, RSTART, RLENGTH)); } ' # usage: replay_pcr [ | ... ] # Replays PCR value from CBMEM event log. Note that this contains only the # measurements performed by firmware, without those performed by Heads (USB # modules, LUKS header etc). First argument is PCR number, followed by optional # hashes and/or files extended to given PCR after firmware. Resulting PCR value # is returned in binary form. replay_pcr() { TRACE_FUNC 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 log=$(cbmem -L) 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) \ $(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" # To manually introspect current PCR values: # PCR-2: # tpmr calcfuturepcr 2 | xxd -p # PCR-4, in case of recovery shell (bash used for process substitution): # bash -c "tpmr calcfuturepcr 4 <(echo -n recovery)" | xxd -p # PCR-4, in case of normal boot passing through kexec-select-boot: # bash -c "tpmr calcfuturepcr 4 <(echo -n generic)" | xxd -p # PCR-5, depending on which modules are loaded for given board: # tpmr calcfuturepcr 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) } tpm2_extend() { TRACE_FUNC while true; do case "$1" in -ix) index="$2" shift 2 ;; -ic) hash="$(echo -n "$2" | sha256sum | cut -d' ' -f1)" shift 2 ;; -if) hash="$(sha256sum "$2" | cut -d' ' -f1)" shift 2 ;; *) break ;; esac done tpm2 pcrextend "$index:sha256=$hash" DO_WITH_DEBUG tpm2 pcrread "sha256:$index" } tpm2_counter_read() { TRACE_FUNC while true; do case "$1" in -ix) index="$2" shift 2 ;; *) break ;; esac done echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)" } tpm2_counter_inc() { TRACE_FUNC while true; do case "$1" in -ix) index="$2" shift 2 ;; -pwdc) pwd="$2" shift 2 ;; *) break ;; esac done tpm2 nvincrement "0x$index" >/dev/console echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)" } tpm1_counter_create() { TRACE_FUNC # tpmr handles the TPM owner password (from cache or prompt), but all # other parameters for TPM1 are passed directly, and TPM2 mimics the # TPM1 interface. prompt_tpm_owner_password if ! tpm counter_create -pwdo "$(cat "/tmp/secret/tpm_owner_password")" "$@"; then DEBUG "Failed to create counter from tpm1_counter_create. Wiping /tmp/secret/tpm_owner_password" shred -n 10 -z -u /tmp/secret/tpm_owner_password die "Unable to create counter from tpm1_counter_create" fi } tpm2_counter_create() { TRACE_FUNC while true; do case "$1" in -pwdc) pwd="$2" shift 2 ;; -la) label="$2" shift 2 ;; *) break ;; esac done prompt_tpm_owner_password rand_index="1$(dd if=/dev/urandom bs=1 count=3 | xxd -pc3)" tpm2 nvdefine -C o -s 8 -a "ownerread|authread|authwrite|nt=1" \ -P "$(tpm2_password_hex "$(cat "/tmp/secret/tpm_owner_password")")" "0x$rand_index" >/dev/console || { DEBUG "Failed to create counter from tpm2_counter_create. Wiping /tmp/secret/tpm_owner_password" shred -n 10 -z -u /tmp/secret/tpm_owner_password die "Unable to create counter from tpm2_counter_create" } echo "$rand_index: (valid after an increment)" } tpm2_startsession() { TRACE_FUNC mkdir -p "$SECRET_DIR" tpm2 flushcontext -Q \ --transient-object || die "tpm2_flushcontext: unable to flush transient handles" tpm2 flushcontext -Q \ --loaded-session || die "tpm2_flushcontext: unable to flush sessions" tpm2 flushcontext -Q \ --saved-session || die "tpm2_flushcontext: unable to flush saved session" tpm2 readpublic -Q -c "$PRIMARY_HANDLE" -t "$PRIMARY_HANDLE_FILE" #TODO: do the right thing to not have to suppress "WARN: check public portion the tpmkey manually" see https://github.com/linuxboot/heads/pull/1630#issuecomment-2075120429 tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$ENC_SESSION_FILE" 2>&1 > /dev/null #TODO: do the right thing to not have to suppress "WARN: check public portion the tpmkey manually" see https://github.com/linuxboot/heads/pull/1630#issuecomment-2075120429 tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$DEC_SESSION_FILE" 2>&1 > /dev/null tpm2 sessionconfig -Q --disable-encrypt "$DEC_SESSION_FILE" } # Use cleanup_session() with at_exit to release a TPM2 session and delete the # session file. E.g.: # at_exit cleanup_session "$SESSION_FILE" cleanup_session() { TRACE_FUNC session_file="$1" if [ -f "$session_file" ]; then DEBUG "Clean up session: $session_file" # Nothing else we can do if this fails, still remove the file tpm2 flushcontext -Q "$session_file" || DEBUG "Flush failed for session $session_file" rm -f "$session_file" else DEBUG "No need to clean up session: $session_file" fi } # Clean up a file by shredding it. No-op if the file wasn't created. Use with # at_exit, e.g.: # at_exit cleanup_shred "$FILE" cleanup_shred() { TRACE_FUNC shred -n 10 -z -u "$1" 2>/dev/null || true } # tpm2_destroy: Destroy a sealed file in the TPM. The mechanism differs by # TPM version - TPM2 evicts the file object, so it no longer exists. tpm2_destroy() { index="$1" # Index of the sealed file size="$2" # Size of zeroes to overwrite for TPM1 (unused in TPM2) # Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc. handle="$(printf "0x81%6s" "$index" | tr ' ' 0)" # remove possible data occupying this handle tpm2 evictcontrol -Q -C p -c "$handle" 2>/dev/null || die "Unable to evict secret from TPM NVRAM" } # tpm1_destroy: Destroy a sealed file in the TPM. The mechanism differs by # TPM version - TPM1 overwrites the file with zeroes, since this can be done # without authorization. (Deletion requires authorization.) tpm1_destroy() { index="$1" # Index of the sealed file size="$2" # Size of zeroes to overwrite for TPM1 dd if=/dev/zero bs="$size" count=1 of=/tmp/wipe-totp-zero tpm nv_writevalue -in "$index" -if /tmp/wipe-totp-zero || die "Unable to wipe sealed secret from TPM NVRAM" } # tpm2_seal: Seal a file against PCR values and, optionally, a password. # If a password is given, both the PCRs and password are required to unseal the # file. PCRs are provided as a PCR list and data file. PCR data must be # provided - TPM2 allows the TPM to fall back to current PCR values, but it is # not required to support this. tpm2_seal() { TRACE_FUNC file="$1" #$KEY_FILE index="$2" pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix) pcrf="$4" sealed_size="$5" # Not used for TPM2 pass="$6" # May be empty to seal with no password tpm_password="$7" # Owner password - will prompt if needed and not empty # TPM Owner Password is always needed for TPM2. 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 # value using a trial session. TRIAL_SESSION="$SECRET_DIR/sealfile_trial.session" AUTH_POLICY="$SECRET_DIR/sealfile_auth.policy" rm -f "$TRIAL_SESSION" "$AUTH_POLICY" tpm2 startauthsession -g sha256 -S "$TRIAL_SESSION" # We have to clean up the session at_exit cleanup_session "$TRIAL_SESSION" # 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 "sha256:$pcrl" -f "$pcrf" -S "$TRIAL_SESSION" -L "$AUTH_POLICY" CREATE_PASS_ARGS=() if [ "$pass" ]; then # Add an object authorization policy (the object authorization # will be the password). Save the digest, this is the resulting # policy. tpm2 policypassword -Q -S "$TRIAL_SESSION" -L "$AUTH_POLICY" # Pass the password to create later. Pass the sha256sum of the # password to the TPM so the password is not limited to 32 chars # in length. CREATE_PASS_ARGS=(-p "$(tpm2_password_hex "$pass")") fi # Create the object with this policy and the auth value. # NOTE: We disable USERWITHAUTH and enable ADMINWITHPOLICY so the # password cannot be used on its own, the PCRs are also required. # (The default is to allow either policy auth _or_ password auth. In # this case the policy includes the password, and we don't want to allow # the password on its own.) tpm2 create -Q -C "$PRIMARY_HANDLE_FILE" \ -i "$file" \ -u "$SECRET_DIR/$bname.priv" \ -r "$SECRET_DIR/$bname.pub" \ -L "$AUTH_POLICY" \ -S "$DEC_SESSION_FILE" \ -a "fixedtpm|fixedparent|adminwithpolicy" \ "${CREATE_PASS_ARGS[@]}" tpm2 load -Q -C "$PRIMARY_HANDLE_FILE" \ -u "$SECRET_DIR/$bname.priv" -r "$SECRET_DIR/$bname.pub" \ -c "$SECRET_DIR/$bname.seal.ctx" prompt_tpm_owner_password # remove possible data occupying this handle tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \ -c "$handle" 2>/dev/null || true DO_WITH_DEBUG --mask-position 6 \ tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \ -c "$SECRET_DIR/$bname.seal.ctx" "$handle" || { DEBUG "Failed to write sealed secret to NVRAM from tpm2_seal. Wiping /tmp/secret/tpm_owner_password" shred -n 10 -z -u /tmp/secret/tpm_owner_password die "Unable to write sealed secret to TPM NVRAM" } } tpm1_seal() { TRACE_FUNC 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" # 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" at_exit cleanup_shred "$sealed_file" POLICY_ARGS=() 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") 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 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 # 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 "Unable to assert physical presence" prompt_tpm_owner_password tpm nv_definespace -in "$index" -sz "$sealed_size" \ -pwdo "$tpm_owner_password" -per 0 || warn "Unable to define TPM NVRAM space; trying anyway" tpm nv_writevalue -in "$index" -if "$sealed_file" || { DEBUG "Failed to write sealed secret to NVRAM from tpm1_seal. Wiping /tmp/secret/tpm_owner_password" shred -n 10 -z -u /tmp/secret/tpm_owner_password die "Unable to write sealed secret to TPM 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 # no password was used to seal). tpm2_unseal() { TRACE_FUNC index="$1" pcrl="$2" #0,1,2,3,4,5,6,7 (does not include algorithm prefix) sealed_size="$3" file="$4" pass="$5" # TPM2 doesn't care about sealed_size, only TPM1 needs that. We don't # have to separately read the sealed file on TPM2. # Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc. handle="$(printf "0x81%6s" "$index" | tr ' ' 0)" DEBUG "tpm2_unseal: handle=$handle pcrl=$pcrl file=$file pass=$(mask_param "$pass")" # If we don't have the primary handle (TPM hasn't been reset), tpm2 will # print nonsense error messages about an unexpected handle value. We # can't do anything without a primary handle. if [ ! -f "$PRIMARY_HANDLE_FILE" ]; then DEBUG "tpm2_unseal: No primary handle, cannot attempt to unseal" warn "No TPM primary handle. You must reset TPM to seal secret to TPM NVRAM" exit 1 fi POLICY_SESSION="$SECRET_DIR/unsealfile_policy.session" rm -f "$POLICY_SESSION" tpm2 startauthsession -Q -g sha256 -S "$POLICY_SESSION" --policy-session at_exit cleanup_session "$POLICY_SESSION" # Check the PCR policy tpm2 policypcr -Q -l "sha256:$pcrl" -S "$POLICY_SESSION" UNSEAL_PASS_SUFFIX="" if [ "$pass" ]; then # Add the object authorization policy (the actual password is # provided later, but we must include this so the policy we # attempt to use is correct). tpm2 policypassword -Q -S "$POLICY_SESSION" # When unsealing, include the password with the auth session UNSEAL_PASS_SUFFIX="+$(tpm2_password_hex "$pass")" fi tpm2 unseal -Q -c "$handle" -p "session:$POLICY_SESSION$UNSEAL_PASS_SUFFIX" \ -S "$ENC_SESSION_FILE" >"$file" } tpm1_unseal() { TRACE_FUNC index="$1" pcrl="$2" sealed_size="$3" file="$4" pass="$5" # pcrl (the PCR list) is unused in TPM1. The TPM itself knows which # PCRs were used to seal and checks them. We can't verify that it's # correct either, so just ignore it in TPM1. sealed_file="$SECRET_DIR/tpm1_unseal_sealed.bin" at_exit cleanup_shred "$sealed_file" rm -f "$sealed_file" tpm nv_readvalue \ -in "$index" \ -sz "$sealed_size" \ -of "$sealed_file" || die "Unable to read sealed file from TPM NVRAM" PASS_ARGS=() if [ "$pass" ]; then PASS_ARGS=(-pwdd "$pass") fi tpm unsealfile \ -if "$sealed_file" \ -of "$file" \ "${PASS_ARGS[@]}" \ -hk 40000000 } tpm2_reset() { TRACE_FUNC tpm_owner_password="$1" mkdir -p "$SECRET_DIR" # output TPM Owner Password to a file to be reused in this boot session until recovery shell/reboot DEBUG "Caching TPM Owner Password to $SECRET_DIR/tpm_owner_password" echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password" tpm2 clear -c platform || warn "Unable to clear TPM on platform hierarchy" tpm2 changeauth -c owner "$(tpm2_password_hex "$tpm_owner_password")" tpm2 changeauth -c endorsement "$(tpm2_password_hex "$tpm_owner_password")" tpm2 createprimary -C owner -g sha256 -G "${CONFIG_PRIMARY_KEY_TYPE:-rsa}" \ -c "$SECRET_DIR/primary.ctx" -P "$(tpm2_password_hex "$tpm_owner_password")" tpm2 evictcontrol -C owner -c "$SECRET_DIR/primary.ctx" "$PRIMARY_HANDLE" \ -P "$(tpm2_password_hex "$tpm_owner_password")" shred -u "$SECRET_DIR/primary.ctx" tpm2_startsession # Set the dictionary attack parameters. TPM2 defaults vary widely, we # want consistent behavior on any TPM. # * --max-tries=10: Allow 10 failures before lockout. This allows the # user to quickly "burst" 10 failures without significantly impacting # the rate allowed for a dictionary attacker. # Most TPM2 flows ask for the TPM Owner Password 2-4 times, so this allows # a handful of mistypes and some headroom for an expected unseal # failure if firmware is updated. # Remember that an auth failure is also counted any time an unclean # shutdown occurs (see TPM2 spec part 1, section 19.8.6, "Non-orderly # Shutdown"). # * --recovery-time=3600: Forget an auth failure every 1 hour. # * --lockout-recovery-time: After a failed lockout recovery auth, the # TPM must be reset to try again. # # Heads does not offer a way to reset dictionary attack lockout, instead # the TPM can be reset and new secrets sealed. tpm2 dictionarylockout -Q --setup-parameters \ --max-tries=10 \ --recovery-time=3600 \ --lockout-recovery-time=0 \ --auth="session:$ENC_SESSION_FILE" # Set a random DA lockout password, so the DA lockout can't be cleared # with a password. Heads doesn't offer dictionary attach reset, instead # the TPM can be reset and new secrets sealed. # # The default lockout password is empty, so we must set this, and we # don't need to provide any auth (use the default empty password). tpm2 changeauth -Q -c lockout \ "hex:$(dd if=/dev/urandom bs=32 count=1 status=none | xxd -p | tr -d ' \n')" } tpm1_reset() { TRACE_FUNC tpm_owner_password="$1" mkdir -p "$SECRET_DIR" # output tpm_owner_password to a file to be reused in this boot session until recovery shell/reboot DEBUG "Caching TPM Owner Password to $SECRET_DIR/tpm_owner_password" echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password" # Make sure the TPM is ready to be reset tpm physicalpresence -s tpm physicalenable tpm physicalsetdeactivated -c tpm forceclear tpm physicalenable tpm takeown -pwdo "$tpm_owner_password" # And now turn it all back on tpm physicalpresence -s tpm physicalenable tpm physicalsetdeactivated -c } # Perform final cleanup before boot and lock the platform heirarchy. tpm2_kexec_finalize() { TRACE_FUNC # Flush sessions and transient objects tpm2 flushcontext -Q --transient-object || warn "tpm2_flushcontext: unable to flush transient handles" tpm2 flushcontext -Q --loaded-session || warn "tpm2_flushcontext: unable to flush sessions" tpm2 flushcontext -Q --saved-session || warn "tpm2_flushcontext: unable to flush saved session" # Add a random passphrase to platform hierarchy to prevent TPM2 from # 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 status=none | xxd -p) tpm2 changeauth -c platform "$randpass" || warn "Failed to lock platform hierarchy of TPM2" } tpm2_shutdown() { TRACE_FUNC # Prepare for shutdown. # This is a "clear" shutdown (do not preserve runtime state) since we # are not going to resume later, we are powering off (or rebooting). tpm2 shutdown -Q --clear } if [ "$CONFIG_TPM" != "y" ]; then echo >&2 "No TPM!" exit 1 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 replay_pcr "sha1" "$@" ;; counter_create) shift tpm1_counter_create "$@" ;; destroy) shift tpm1_destroy "$@" ;; seal) shift tpm1_seal "$@" ;; startsession) ;; # Nothing on TPM1. unseal) shift tpm1_unseal "$@" ;; reset) shift tpm1_reset "$@" ;; kexec_finalize) ;; # Nothing on TPM1. shutdown) ;; # Nothing on TPM1. *) DEBUG "Direct translation from tpmr to tpm1 call" DO_WITH_DEBUG exec tpm "$@" ;; esac exit 0 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) replay_pcr "sha256" "$@" ;; extend) tpm2_extend "$@" ;; counter_read) tpm2_counter_read "$@" ;; counter_increment) tpm2_counter_inc "$@" ;; counter_create) tpm2_counter_create "$@" ;; destroy) tpm2_destroy "$@" ;; seal) tpm2_seal "$@" ;; startsession) tpm2_startsession "$@" ;; unseal) tpm2_unseal "$@" ;; reset) tpm2_reset "$@" ;; kexec_finalize) tpm2_kexec_finalize "$@" ;; shutdown) tpm2_shutdown "$@" ;; *) echo "Command $subcmd not wrapped!" exit 1 ;; esac