2023-02-08 21:01:48 +00:00
#!/bin/bash
2022-08-25 18:43:31 +00:00
# TPM Wrapper - to unify tpm and tpm2 subcommands
. /etc/functions
SECRET_DIR="/tmp/secret"
PRIMARY_HANDLE="0x81000000"
2023-11-02 15:30:59 +00:00
ENC_SESSION_FILE="$SECRET_DIR/enc.ctx"
DEC_SESSION_FILE="$SECRET_DIR/dec.ctx"
PRIMARY_HANDLE_FILE="$SECRET_DIR/primary.handle"
2022-08-25 18:43:31 +00:00
2023-03-08 17:39:06 +00:00
# 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=
2023-03-08 21:34:45 +00:00
# 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
2022-08-25 18:43:31 +00:00
set -e -o pipefail
if [ -r "/tmp/config" ]; then
. /tmp/config
else
. /etc/config
fi
2023-02-23 22:05:15 +00:00
2023-03-08 17:39:06 +00:00
# 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() {
2024-08-24 16:49:10 +00:00
TRACE_FUNC
2023-03-08 17:39:06 +00:00
sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf
}
2023-03-10 19:19:43 +00:00
# Render a password as 'hex:<hexdump>' 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() {
2024-08-24 16:49:10 +00:00
TRACE_FUNC
2023-03-10 19:19:43 +00:00
echo "hex:$(echo -n "$1" | xxd -p | tr -d ' \n')"
}
2023-03-08 17:39:06 +00:00
# usage: tpmr pcrread [-a] <index> <file>
# Reads PCR binary data and writes to file.
# -a: Append to file. Default is to overwrite.
tpm2_pcrread() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-03-08 17:39:06 +00:00
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
2024-08-24 16:49:10 +00:00
# overwrites
2023-03-08 17:39:06 +00:00
true >"$file"
fi
2023-03-09 18:28:04 +00:00
DO_WITH_DEBUG tpm2 pcrread -Q -o >(cat >>"$file") "sha256:$index"
2023-03-08 17:39:06 +00:00
}
tpm1_pcrread() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-03-08 17:39:06 +00:00
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
2024-08-24 16:49:10 +00:00
# overwrites
2023-03-08 17:39:06 +00:00
true >"$file"
fi
2023-03-09 18:28:04 +00:00
DO_WITH_DEBUG tpm pcrread -ix "$index" | hex2bin >>"$file"
2023-03-08 17:39:06 +00:00
}
2023-06-30 16:33:09 +00:00
# is_hash - Check if a value is a valid hash of a given type
# usage: is_hash <alg> <value>
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
}
2023-03-08 17:39:06 +00:00
2023-06-30 16:33:09 +00:00
# extend_pcr_state - extend a PCR state value with more hashes or raw data (which is hashed)
# usage:
2024-08-28 18:09:18 +00:00
# extend_pcr_state <alg> <state> <files/hashes...>
2023-06-30 16:33:09 +00:00
# alg - either 'sha1' or 'sha256' to specify algorithm
2024-08-24 16:49:10 +00:00
# state - a hash value setting the initial state
2023-06-30 16:33:09 +00:00
# files/hashes... - any number of files or hashes, state is extended once for each item
extend_pcr_state() {
2024-08-24 16:49:10 +00:00
TRACE_FUNC
2023-06-30 16:33:09 +00:00
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"
2023-11-03 14:15:37 +00:00
else
2023-06-30 16:33:09 +00:00
extend="$("${alg}sum" <"$next" | cut -d' ' -f1)"
fi
state="$(echo "$state$extend" | hex2bin | "${alg}sum" | cut -d' ' -f1)"
done
echo "$state"
}
2023-03-08 17:39:06 +00:00
2023-06-30 16:33:09 +00:00
# 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:
2023-11-03 14:15:37 +00:00
: <<'EOF'
2023-06-30 16:33:09 +00:00
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:
2023-11-03 14:15:37 +00:00
: <<'EOF'
2023-06-30 16:33:09 +00:00
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
2023-11-03 14:15:37 +00:00
: <<'EOF'
2023-06-30 16:33:09 +00:00
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;
}
2023-03-08 17:39:06 +00:00
}
2023-06-30 16:33:09 +00:00
$0 ~ pcr {
match($0, alg);
print gensub(alg, "\\1", "g", substr($0, RSTART, RLENGTH));
}
'
# usage: replay_pcr <alg> <pcr_num> [ <input_file>|<input_hash> ... ]
# 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() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-11-03 14:15:37 +00:00
if [ -z "$2" ]; then
echo >&2 "No PCR number passed"
2023-06-30 16:33:09 +00:00
return
2023-03-08 17:39:06 +00:00
fi
2023-11-03 14:15:37 +00:00
if [ "$2" -ge 8 ]; then
echo >&2 "Illegal PCR number ($2)"
2023-06-30 16:33:09 +00:00
return
2023-03-08 17:39:06 +00:00
fi
2023-11-03 14:15:37 +00:00
local log=$(cbmem -L)
2023-06-30 16:33:09 +00:00
local alg="$1"
local pcr="$2"
local alg_digits=0
# SHA-1 hashes are 40 chars
2023-11-03 14:15:37 +00:00
if [ "$alg" = "sha1" ]; then alg_digits=40; fi
2023-06-30 16:33:09 +00:00
# SHA-256 hashes are 64 chars
2023-11-03 14:15:37 +00:00
if [ "$alg" = "sha256" ]; then alg_digits=64; fi
2023-06-30 16:33:09 +00:00
shift 2
2023-07-10 21:52:15 +00:00
replayed_pcr=$(extend_pcr_state $alg $(printf "%.${alg_digits}d" 0) \
2023-11-03 14:15:37 +00:00
$(echo "$log" | awk -v alg=$alg -v pcr=$pcr -f <(echo $AWK_PROG)) $@)
2023-07-10 21:52:15 +00:00
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
2023-10-23 15:52:44 +00:00
# (6: LUKS header, 7: user related cbfs files loaded from cbfs-init)
2023-03-08 17:39:06 +00:00
}
2022-08-25 18:43:31 +00:00
tpm2_extend() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
while true; do
case "$1" in
2023-11-03 14:15:37 +00:00
-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
;;
2022-08-25 18:43:31 +00:00
esac
done
tpm2 pcrextend "$index:sha256=$hash"
2024-08-24 16:49:10 +00:00
tpm2 pcrread "sha256:$index"
DEBUG "TPM: Extended PCR[$index] with $hash"
2022-08-25 18:43:31 +00:00
}
tpm2_counter_read() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
while true; do
case "$1" in
2023-11-03 14:15:37 +00:00
-ix)
index="$2"
shift 2
;;
*)
break
;;
2022-08-25 18:43:31 +00:00
esac
done
2023-11-03 14:15:37 +00:00
echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)"
2022-08-25 18:43:31 +00:00
}
tpm2_counter_inc() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
while true; do
case "$1" in
2023-11-03 14:15:37 +00:00
-ix)
index="$2"
shift 2
;;
-pwdc)
pwd="$2"
shift 2
;;
*)
break
;;
2022-08-25 18:43:31 +00:00
esac
done
2023-11-03 14:15:37 +00:00
tpm2 nvincrement "0x$index" >/dev/console
echo "$index: $(tpm2 nvread 0x$index | xxd -pc8)"
2022-08-25 18:43:31 +00:00
}
2023-11-06 15:31:50 +00:00
tpm1_counter_create() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-11-06 15:31:50 +00:00
# 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() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
while true; do
case "$1" in
2023-11-03 14:15:37 +00:00
-pwdc)
pwd="$2"
shift 2
;;
-la)
label="$2"
shift 2
;;
*)
break
;;
2022-08-25 18:43:31 +00:00
esac
done
2023-11-06 15:31:50 +00:00
prompt_tpm_owner_password
2023-11-03 14:15:37 +00:00
rand_index="1$(dd if=/dev/urandom bs=1 count=3 | xxd -pc3)"
2023-03-10 19:19:43 +00:00
tpm2 nvdefine -C o -s 8 -a "ownerread|authread|authwrite|nt=1" \
2023-11-06 15:31:50 +00:00
-P "$(tpm2_password_hex "$(cat "/tmp/secret/tpm_owner_password")")" "0x$rand_index" >/dev/console ||
2023-11-03 14:54:16 +00:00
{
2023-11-06 15:31:50 +00:00
DEBUG "Failed to create counter from tpm2_counter_create. Wiping /tmp/secret/tpm_owner_password"
2023-11-03 14:54:16 +00:00
shred -n 10 -z -u /tmp/secret/tpm_owner_password
2023-11-06 15:31:50 +00:00
die "Unable to create counter from tpm2_counter_create"
2023-11-03 14:54:16 +00:00
}
2022-08-25 18:43:31 +00:00
echo "$rand_index: (valid after an increment)"
}
tpm2_startsession() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
mkdir -p "$SECRET_DIR"
2023-02-24 20:52:10 +00:00
tpm2 flushcontext -Q \
2023-11-03 14:15:37 +00:00
--transient-object ||
die "tpm2_flushcontext: unable to flush transient handles"
2022-08-25 18:43:31 +00:00
2023-02-24 20:52:10 +00:00
tpm2 flushcontext -Q \
2023-11-03 14:15:37 +00:00
--loaded-session ||
die "tpm2_flushcontext: unable to flush sessions"
2022-08-25 18:43:31 +00:00
2023-02-24 20:52:10 +00:00
tpm2 flushcontext -Q \
2023-11-03 14:15:37 +00:00
--saved-session ||
die "tpm2_flushcontext: unable to flush saved session"
2023-11-02 15:30:59 +00:00
tpm2 readpublic -Q -c "$PRIMARY_HANDLE" -t "$PRIMARY_HANDLE_FILE"
2024-04-26 00:23:28 +00:00
#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
2024-08-24 16:49:10 +00:00
tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$ENC_SESSION_FILE" > /dev/null 2>&1
2024-04-26 00:23:28 +00:00
#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
2024-08-24 16:49:10 +00:00
tpm2 startauthsession -Q -c "$PRIMARY_HANDLE_FILE" --hmac-session -S "$DEC_SESSION_FILE" > /dev/null 2>&1
2023-11-02 15:30:59 +00:00
tpm2 sessionconfig -Q --disable-encrypt "$DEC_SESSION_FILE"
2022-08-25 18:43:31 +00:00
}
2023-03-10 22:50:43 +00:00
# Use cleanup_session() with at_exit to release a TPM2 session and delete the
2023-02-24 20:52:10 +00:00
# session file. E.g.:
2023-03-10 22:50:43 +00:00
# at_exit cleanup_session "$SESSION_FILE"
2023-02-24 20:52:10 +00:00
cleanup_session() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-02-24 20:52:10 +00:00
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
}
2023-02-28 18:36:11 +00:00
# Clean up a file by shredding it. No-op if the file wasn't created. Use with
2023-03-10 22:50:43 +00:00
# at_exit, e.g.:
# at_exit cleanup_shred "$FILE"
2023-02-28 18:36:11 +00:00
cleanup_shred() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-02-28 18:36:11 +00:00
shred -n 10 -z -u "$1" 2>/dev/null || true
}
2022-10-25 22:09:15 +00:00
# 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() {
2024-08-24 16:49:10 +00:00
TRACE_FUNC
2023-11-03 14:15:37 +00:00
index="$1" # Index of the sealed file
size="$2" # Size of zeroes to overwrite for TPM1 (unused in TPM2)
2022-10-25 22:09:15 +00:00
# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"
# remove possible data occupying this handle
2023-11-03 14:15:37 +00:00
tpm2 evictcontrol -Q -C p -c "$handle" 2>/dev/null ||
2023-11-03 15:07:36 +00:00
die "Unable to evict secret from TPM NVRAM"
2022-10-25 22:09:15 +00:00
}
# 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() {
2024-08-24 16:49:10 +00:00
TRACE_FUNC
2023-11-03 14:15:37 +00:00
index="$1" # Index of the sealed file
size="$2" # Size of zeroes to overwrite for TPM1
2022-10-25 22:09:15 +00:00
dd if=/dev/zero bs="$size" count=1 of=/tmp/wipe-totp-zero
2023-11-03 14:15:37 +00:00
tpm nv_writevalue -in "$index" -if /tmp/wipe-totp-zero ||
2023-11-03 15:07:36 +00:00
die "Unable to wipe sealed secret from TPM NVRAM"
2022-10-25 22:09:15 +00:00
}
2023-02-28 18:36:11 +00:00
# tpm2_seal: Seal a file against PCR values and, optionally, a password.
2023-02-24 20:52:10 +00:00
# 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.
2023-02-28 18:36:11 +00:00
tpm2_seal() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2022-08-25 18:43:31 +00:00
file="$1" #$KEY_FILE
2023-03-08 17:39:06 +00:00
index="$2"
pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
2022-08-25 18:43:31 +00:00
pcrf="$4"
2023-11-03 14:15:37 +00:00
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
2023-10-23 17:13:39 +00:00
# TPM Owner Password is always needed for TPM2.
2023-03-08 17:39:06 +00:00
2022-08-25 18:43:31 +00:00
mkdir -p "$SECRET_DIR"
2023-11-03 14:15:37 +00:00
bname="$(basename $file)"
2023-02-24 20:52:10 +00:00
2023-03-08 17:39:06 +00:00
# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"
2023-03-07 19:00:57 +00:00
DEBUG "tpm2_seal: file=$file handle=$handle pcrl=$pcrl pcrf=$pcrf pass=$(mask_param "$pass")"
2023-02-24 21:45:41 +00:00
2023-02-24 20:52:10 +00:00
# Create a policy requiring both PCRs and the object's authentication
# value using a trial session.
2023-11-02 15:30:59 +00:00
TRIAL_SESSION="$SECRET_DIR/sealfile_trial.session"
AUTH_POLICY="$SECRET_DIR/sealfile_auth.policy"
2023-02-24 20:52:10 +00:00
rm -f "$TRIAL_SESSION" "$AUTH_POLICY"
tpm2 startauthsession -g sha256 -S "$TRIAL_SESSION"
# We have to clean up the session
2023-03-10 22:50:43 +00:00
at_exit cleanup_session "$TRIAL_SESSION"
2023-02-24 20:52:10 +00:00
# Save the policy hash in case the password policy is not used (we have
# to get this from the last step, whichever it is).
2023-03-08 17:39:06 +00:00
tpm2 policypcr -Q -l "sha256:$pcrl" -f "$pcrf" -S "$TRIAL_SESSION" -L "$AUTH_POLICY"
2023-02-24 20:52:10 +00:00
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"
2023-02-27 15:09:52 +00:00
# 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.
2023-03-10 19:19:43 +00:00
CREATE_PASS_ARGS=(-p "$(tpm2_password_hex "$pass")")
2022-08-25 18:43:31 +00:00
fi
2023-02-24 20:52:10 +00:00
# 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.)
2023-11-02 15:30:59 +00:00
tpm2 create -Q -C "$PRIMARY_HANDLE_FILE" \
2023-02-24 20:52:10 +00:00
-i "$file" \
-u "$SECRET_DIR/$bname.priv" \
-r "$SECRET_DIR/$bname.pub" \
-L "$AUTH_POLICY" \
2023-11-02 15:30:59 +00:00
-S "$DEC_SESSION_FILE" \
2023-02-27 14:49:43 +00:00
-a "fixedtpm|fixedparent|adminwithpolicy" \
2023-02-24 20:52:10 +00:00
"${CREATE_PASS_ARGS[@]}"
2023-11-02 15:30:59 +00:00
tpm2 load -Q -C "$PRIMARY_HANDLE_FILE" \
2023-03-08 22:07:00 +00:00
-u "$SECRET_DIR/$bname.priv" -r "$SECRET_DIR/$bname.pub" \
-c "$SECRET_DIR/$bname.seal.ctx"
2023-10-24 15:19:49 +00:00
prompt_tpm_owner_password
2022-08-25 18:43:31 +00:00
# remove possible data occupying this handle
2023-10-24 15:19:49 +00:00
tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \
2023-03-10 19:19:43 +00:00
-c "$handle" 2>/dev/null || true
2023-03-07 19:00:57 +00:00
DO_WITH_DEBUG --mask-position 6 \
2023-10-24 15:19:49 +00:00
tpm2 evictcontrol -Q -C o -P "$(tpm2_password_hex "$tpm_owner_password")" \
2023-11-03 14:15:37 +00:00
-c "$SECRET_DIR/$bname.seal.ctx" "$handle" ||
2023-11-03 14:54:16 +00:00
{
2023-11-03 15:07:36 +00:00
DEBUG "Failed to write sealed secret to NVRAM from tpm2_seal. Wiping /tmp/secret/tpm_owner_password"
2023-11-03 14:54:16 +00:00
shred -n 10 -z -u /tmp/secret/tpm_owner_password
2023-11-03 15:07:36 +00:00
die "Unable to write sealed secret to TPM NVRAM"
2023-11-03 14:54:16 +00:00
}
2022-08-25 18:43:31 +00:00
}
2023-03-08 17:39:06 +00:00
tpm1_seal() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-03-08 17:39:06 +00:00
file="$1"
index="$2"
pcrl="$3" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
pcrf="$4"
sealed_size="$5"
2023-11-03 14:15:37 +00:00
pass="$6" # May be empty to seal with no password
2023-12-29 19:38:21 +00:00
tpm_owner_password="$7" # Owner password - will prompt if needed and not empty
2023-03-08 17:39:06 +00:00
sealed_file="$SECRET_DIR/tpm1_seal_sealed.bin"
2023-03-10 22:50:43 +00:00
at_exit cleanup_shred "$sealed_file"
2023-03-08 17:39:06 +00:00
POLICY_ARGS=()
2023-12-29 19:38:21 +00:00
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")"
2023-03-08 17:39:06 +00:00
# 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 ' ')"
2023-11-03 14:15:37 +00:00
)
pcr_file_index=$((pcr_file_index + 1))
2023-03-08 17:39:06 +00:00
done
tpm sealfile2 \
-if "$file" \
-of "$sealed_file" \
-hk 40000000 \
"${POLICY_ARGS[@]}"
2023-12-29 19:38:21 +00:00
2023-11-03 17:53:47 +00:00
# try it without the TPM Owner Password first
2023-03-08 17:39:06 +00:00
if ! tpm nv_writevalue -in "$index" -if "$sealed_file"; then
2023-10-23 17:13:39 +00:00
# to create an nvram space we need the TPM Owner Password
2023-03-08 17:39:06 +00:00
# and the TPM physical presence must be asserted.
#
# The permissions are 0 since there is nothing special
# about the sealed file
2023-11-03 14:15:37 +00:00
tpm physicalpresence -s ||
warn "Unable to assert physical presence"
2023-03-08 17:39:06 +00:00
2023-10-24 15:19:49 +00:00
prompt_tpm_owner_password
2023-03-08 17:39:06 +00:00
tpm nv_definespace -in "$index" -sz "$sealed_size" \
2023-12-29 19:38:21 +00:00
-pwdo "$tpm_owner_password" -per 0 ||
2023-11-03 17:53:47 +00:00
warn "Unable to define TPM NVRAM space; trying anyway"
2023-03-08 17:39:06 +00:00
2023-11-03 14:15:37 +00:00
tpm nv_writevalue -in "$index" -if "$sealed_file" ||
2023-11-03 14:54:16 +00:00
{
2023-11-03 15:07:36 +00:00
DEBUG "Failed to write sealed secret to NVRAM from tpm1_seal. Wiping /tmp/secret/tpm_owner_password"
2023-11-03 14:54:16 +00:00
shred -n 10 -z -u /tmp/secret/tpm_owner_password
2023-11-03 15:07:36 +00:00
die "Unable to write sealed secret to TPM NVRAM"
2023-11-03 14:54:16 +00:00
}
2023-03-08 17:39:06 +00:00
fi
}
2022-08-25 18:43:31 +00:00
2023-02-28 18:36:11 +00:00
# Unseal a file sealed by tpm2_seal. The PCR list must be provided, the
2023-02-24 20:52:10 +00:00
# password must be provided if one was used to seal (and cannot be provided if
# no password was used to seal).
2023-02-28 18:36:11 +00:00
tpm2_unseal() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-02-28 18:36:11 +00:00
index="$1"
2023-03-08 17:39:06 +00:00
pcrl="$2" #0,1,2,3,4,5,6,7 (does not include algorithm prefix)
2023-02-28 18:55:00 +00:00
sealed_size="$3"
2023-02-28 18:36:11 +00:00
file="$4"
pass="$5"
2023-02-24 20:52:10 +00:00
2023-02-28 18:55:00 +00:00
# TPM2 doesn't care about sealed_size, only TPM1 needs that. We don't
# have to separately read the sealed file on TPM2.
2023-02-28 18:36:11 +00:00
# Pad with up to 6 zeros, i.e. '0x81000001', '0x81001234', etc.
handle="$(printf "0x81%6s" "$index" | tr ' ' 0)"
2023-03-07 19:00:57 +00:00
DEBUG "tpm2_unseal: handle=$handle pcrl=$pcrl file=$file pass=$(mask_param "$pass")"
2023-02-24 21:45:41 +00:00
2023-03-07 16:18:47 +00:00
# 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.
2023-11-02 15:30:59 +00:00
if [ ! -f "$PRIMARY_HANDLE_FILE" ]; then
2023-03-07 16:18:47 +00:00
DEBUG "tpm2_unseal: No primary handle, cannot attempt to unseal"
2023-11-03 15:07:36 +00:00
warn "No TPM primary handle. You must reset TPM to seal secret to TPM NVRAM"
2023-03-07 16:18:47 +00:00
exit 1
fi
2023-11-02 15:30:59 +00:00
POLICY_SESSION="$SECRET_DIR/unsealfile_policy.session"
2023-02-24 20:52:10 +00:00
rm -f "$POLICY_SESSION"
2023-03-07 16:21:45 +00:00
tpm2 startauthsession -Q -g sha256 -S "$POLICY_SESSION" --policy-session
2023-03-10 22:50:43 +00:00
at_exit cleanup_session "$POLICY_SESSION"
2023-02-24 20:52:10 +00:00
# Check the PCR policy
2023-03-08 17:39:06 +00:00
tpm2 policypcr -Q -l "sha256:$pcrl" -S "$POLICY_SESSION"
2023-02-24 20:52:10 +00:00
UNSEAL_PASS_SUFFIX=""
2023-03-07 16:21:45 +00:00
2023-02-24 20:52:10 +00:00
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
2023-03-10 19:19:43 +00:00
UNSEAL_PASS_SUFFIX="+$(tpm2_password_hex "$pass")"
2022-08-25 18:43:31 +00:00
fi
2023-02-24 20:52:10 +00:00
2023-03-08 22:07:00 +00:00
tpm2 unseal -Q -c "$handle" -p "session:$POLICY_SESSION$UNSEAL_PASS_SUFFIX" \
2023-11-03 14:15:37 +00:00
-S "$ENC_SESSION_FILE" >"$file"
2022-08-25 18:43:31 +00:00
}
2023-02-28 18:36:11 +00:00
tpm1_unseal() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-02-28 18:36:11 +00:00
index="$1"
pcrl="$2"
2023-02-28 18:55:00 +00:00
sealed_size="$3"
2023-02-28 18:36:11 +00:00
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"
2023-03-10 22:50:43 +00:00
at_exit cleanup_shred "$sealed_file"
2023-02-28 18:36:11 +00:00
rm -f "$sealed_file"
tpm nv_readvalue \
-in "$index" \
2023-02-28 18:55:00 +00:00
-sz "$sealed_size" \
2023-11-03 14:15:37 +00:00
-of "$sealed_file" ||
die "Unable to read sealed file from TPM NVRAM"
2023-02-28 18:36:11 +00:00
PASS_ARGS=()
if [ "$pass" ]; then
PASS_ARGS=(-pwdd "$pass")
fi
tpm unsealfile \
-if "$sealed_file" \
-of "$file" \
"${PASS_ARGS[@]}" \
-hk 40000000
}
2023-03-08 21:44:47 +00:00
tpm2_reset() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-10-24 15:19:49 +00:00
tpm_owner_password="$1"
2023-03-08 21:44:47 +00:00
mkdir -p "$SECRET_DIR"
2023-11-02 15:30:59 +00:00
# output TPM Owner Password to a file to be reused in this boot session until recovery shell/reboot
2023-10-24 15:19:49 +00:00
DEBUG "Caching TPM Owner Password to $SECRET_DIR/tpm_owner_password"
2023-11-03 14:15:37 +00:00
echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password"
2023-08-22 18:34:29 +00:00
tpm2 clear -c platform || warn "Unable to clear TPM on platform hierarchy"
2023-10-24 15:19:49 +00:00
tpm2 changeauth -c owner "$(tpm2_password_hex "$tpm_owner_password")"
tpm2 changeauth -c endorsement "$(tpm2_password_hex "$tpm_owner_password")"
2023-03-10 19:19:43 +00:00
tpm2 createprimary -C owner -g sha256 -G "${CONFIG_PRIMARY_KEY_TYPE:-rsa}" \
2023-10-24 15:19:49 +00:00
-c "$SECRET_DIR/primary.ctx" -P "$(tpm2_password_hex "$tpm_owner_password")"
2023-03-10 19:19:43 +00:00
tpm2 evictcontrol -C owner -c "$SECRET_DIR/primary.ctx" "$PRIMARY_HANDLE" \
2023-10-24 15:19:49 +00:00
-P "$(tpm2_password_hex "$tpm_owner_password")"
2023-03-08 21:44:47 +00:00
shred -u "$SECRET_DIR/primary.ctx"
tpm2_startsession
2023-03-10 16:16:34 +00:00
# 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.
2023-11-03 17:53:47 +00:00
# Most TPM2 flows ask for the TPM Owner Password 2-4 times, so this allows
2023-03-10 16:16:34 +00:00
# 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 \
2023-11-02 15:30:59 +00:00
--auth="session:$ENC_SESSION_FILE"
2023-03-10 16:16:34 +00:00
# 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')"
2023-03-08 21:44:47 +00:00
}
2023-03-10 20:07:44 +00:00
tpm1_reset() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-10-24 15:19:49 +00:00
tpm_owner_password="$1"
2023-10-23 17:13:39 +00:00
mkdir -p "$SECRET_DIR"
2023-10-24 15:19:49 +00:00
# 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"
2023-11-03 14:15:37 +00:00
echo -n "$tpm_owner_password" >"$SECRET_DIR/tpm_owner_password"
2023-03-10 20:07:44 +00:00
# Make sure the TPM is ready to be reset
tpm physicalpresence -s
tpm physicalenable
tpm physicalsetdeactivated -c
tpm forceclear
tpm physicalenable
2023-10-24 15:19:49 +00:00
tpm takeown -pwdo "$tpm_owner_password"
2023-03-10 20:07:44 +00:00
# And now turn it all back on
tpm physicalpresence -s
tpm physicalenable
tpm physicalsetdeactivated -c
}
2023-03-08 21:44:47 +00:00
# Perform final cleanup before boot and lock the platform heirarchy.
tpm2_kexec_finalize() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-03-08 22:06:31 +00:00
2023-03-08 21:44:47 +00:00
# Flush sessions and transient objects
2023-11-03 14:15:37 +00:00
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"
2023-03-08 21:44:47 +00:00
# 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)
2023-11-03 14:15:37 +00:00
tpm2 changeauth -c platform "$randpass" ||
warn "Failed to lock platform hierarchy of TPM2"
2023-03-08 21:44:47 +00:00
}
2023-03-08 21:20:21 +00:00
tpm2_shutdown() {
2024-02-01 19:30:31 +00:00
TRACE_FUNC
2023-03-08 22:06:31 +00:00
2023-03-08 21:20:21 +00:00
# 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
}
2023-02-27 21:42:01 +00:00
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
2023-11-03 14:15:37 +00:00
PCR_SIZE=20 # TPM1 PCRs are always SHA-1
2023-02-27 21:42:01 +00:00
subcmd="$1"
# Don't shift yet, for most commands we will just forward to tpm.
case "$subcmd" in
2023-03-08 17:39:06 +00:00
pcrread)
2023-11-03 14:15:37 +00:00
shift
tpm1_pcrread "$@"
;;
2023-03-08 17:39:06 +00:00
pcrsize)
2023-11-03 14:15:37 +00:00
echo "$PCR_SIZE"
;;
2023-03-08 17:39:06 +00:00
calcfuturepcr)
2023-11-03 14:15:37 +00:00
shift
replay_pcr "sha1" "$@"
;;
2023-11-06 15:31:50 +00:00
counter_create)
shift
tpm1_counter_create "$@"
;;
2022-10-25 22:09:15 +00:00
destroy)
2023-11-03 14:15:37 +00:00
shift
tpm1_destroy "$@"
;;
2024-08-24 16:49:10 +00:00
extend)
DEBUG "TPM: Extending PCR[$3] with $5"
DO_WITH_DEBUG exec tpm "$@"
;;
2022-08-25 18:43:31 +00:00
seal)
2023-11-03 14:15:37 +00:00
shift
tpm1_seal "$@"
;;
startsession) ;; # Nothing on TPM1.
2022-08-25 18:43:31 +00:00
unseal)
2023-11-03 14:15:37 +00:00
shift
tpm1_unseal "$@"
;;
2022-08-25 18:43:31 +00:00
reset)
2023-11-03 14:15:37 +00:00
shift
tpm1_reset "$@"
;;
kexec_finalize) ;; # Nothing on TPM1.
shutdown) ;; # Nothing on TPM1.
2022-08-25 18:43:31 +00:00
*)
2023-11-03 14:15:37 +00:00
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)
2024-08-24 16:49:10 +00:00
DEBUG "TPM: Extending PCR[$2] with $4"
2023-11-03 14:15:37 +00:00
tpm2_extend "$@"
;;
counter_read)
tpm2_counter_read "$@"
;;
counter_increment)
tpm2_counter_inc "$@"
;;
counter_create)
2023-11-06 15:31:50 +00:00
tpm2_counter_create "$@"
2023-11-03 14:15:37 +00:00
;;
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
;;
2022-08-25 18:43:31 +00:00
esac