wolfTPM/scripts/tpm2_tools_test.sh

2148 lines
78 KiB
Bash
Executable File

#!/bin/bash
# tpm2_tools_test.sh
# Run tpm2-tools commands against a TPM simulator to verify compatibility.
# Works with fwtpm_server, swtpm, or any mssim-protocol TPM simulator.
#
# Usage:
# scripts/tpm2_tools_test.sh [--no-start] [--verbose] [--tcti=mssim|swtpm]
#
# Environment:
# TPM2_SWTPM_PORT TCP port the running TPM server listens on (default: 2321).
# Used when this script is invoked from `make check` to
# target the random port picked by tests/fwtpm_check.sh.
#
# Requirements: tpm2-tools >= 5.0, libtss2-tcti-mssim (or libtss2-tcti-swtpm)
#
# Exit: 0 if all tests pass, 77 if tpm2-tools not installed (SKIP), 1 on failure
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WOLFTPM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# ----------------------------------------------------------------
# Config
# ----------------------------------------------------------------
# wolfTPM fwtpm_server auto-detects the TCTI protocol (mssim or swtpm).
# Both are supported. Default to mssim for backward compatibility.
TCTI_TYPE="${TCTI_TYPE:-mssim}"
export TPM2TOOLS_TCTI="${TCTI_TYPE}:host=localhost,port=${TPM2_SWTPM_PORT:-2321}"
TEST_TMPDIR=/tmp/fwtpm_tpm2tools
VERBOSE=0
NO_START=0
PASS=0; FAIL=0; SKIP=0
SERVER_PID=""
SERVER_BIN="$WOLFTPM_ROOT/src/fwtpm/fwtpm_server"
NV_FILE="$WOLFTPM_ROOT/fwtpm_nv.bin"
SRV_LOG=/tmp/fwtpm_tpm2tools_srv.log
# ----------------------------------------------------------------
# Argument parsing
# ----------------------------------------------------------------
for arg in "$@"; do
case "$arg" in
--verbose|-v) VERBOSE=1 ;;
--no-start) NO_START=1 ;;
--tcti=*)
TCTI_TYPE="${arg#--tcti=}"
export TPM2TOOLS_TCTI="${TCTI_TYPE}:host=localhost,port=${TPM2_SWTPM_PORT:-2321}"
;;
--help|-h)
grep '^#' "$0" | sed 's/^# \{0,1\}//' | head -12; exit 0 ;;
esac
done
# ----------------------------------------------------------------
# Helpers
# ----------------------------------------------------------------
check_tool() {
command -v "$1" >/dev/null 2>&1
}
run_test() {
local name="$1"; shift
local out rc
if [ $VERBOSE -eq 1 ]; then
echo " >>> $*"
"$@" 2>&1
rc=$?
else
out=$("$@" 2>&1)
rc=$?
fi
if [ $rc -eq 0 ]; then
echo " [PASS] $name"
PASS=$((PASS+1))
else
echo " [FAIL] $name (exit $rc)"
[ $VERBOSE -eq 0 ] && echo "$out" | sed 's/^/ /'
FAIL=$((FAIL+1))
fi
return $rc
}
skip_test() {
echo " [SKIP] $1$2"
SKIP=$((SKIP+1))
}
# run_test_fail: expect the command to fail (non-zero exit)
run_test_fail() {
local name="$1"; shift
local out rc
if [ $VERBOSE -eq 1 ]; then
echo " >>> $* (expect fail)"
"$@" 2>&1
rc=$?
else
out=$("$@" 2>&1)
rc=$?
fi
if [ $rc -ne 0 ]; then
echo " [PASS] $name (failed as expected, exit $rc)"
PASS=$((PASS+1))
else
echo " [FAIL] $name (expected failure but got exit 0)"
[ $VERBOSE -eq 0 ] && echo "$out" | sed 's/^/ /'
FAIL=$((FAIL+1))
fi
return 0
}
hdr() {
printf "\n\033[36m--- %s ---\033[0m\n" "$1"
}
# Flush all transient handles (objects loaded in TPM).
# Real hardware TPMs typically have only 3 transient slots, so tests must
# clean up handles between sections to avoid TPM_RC_OBJECT_MEMORY (0x902).
flush_transient() {
for h in $(tpm2_getcap handles-transient 2>/dev/null | \
grep "^-" | sed 's/- //'); do
tpm2_flushcontext "$h" 2>/dev/null || true
done
# Also flush any stale saved sessions
tpm2_flushcontext -s 2>/dev/null || true
}
# ----------------------------------------------------------------
# Server lifecycle (self-contained — no external scripts)
# ----------------------------------------------------------------
server_start() {
if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
kill "$SERVER_PID" 2>/dev/null
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=""
fi
killall fwtpm_server 2>/dev/null || true
sleep 0.2
rm -f "$NV_FILE"
"$SERVER_BIN" > "$SRV_LOG" 2>&1 &
SERVER_PID=$!
disown "$SERVER_PID" 2>/dev/null || true
# Wait for server process to be ready (up to 2 seconds)
sleep 1
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "ERROR: fwtpm_server process died"
cat "$SRV_LOG"
exit 1
fi
sleep 1
echo "[fwtpm] Server started (PID $SERVER_PID)"
}
server_stop() {
if [ -n "$SERVER_PID" ]; then
kill "$SERVER_PID" 2>/dev/null || true
# Don't wait — avoid signal propagation issues in CI
sleep 0.3
echo "[fwtpm] Server stopped"
fi
SERVER_PID=""
}
# Restart server preserving NV state (for determinism tests)
server_restart() {
# Send tpm2_shutdown to flush volatile state before stopping server.
# Use explicit TCTI in case environment is not set.
TPM2TOOLS_TCTI="${TCTI_TYPE}:host=localhost,port=${TPM2_SWTPM_PORT:-2321}" \
tpm2_shutdown 2>/dev/null || true
sleep 0.2
if [ -n "$SERVER_PID" ]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=""
fi
killall fwtpm_server 2>/dev/null || true
sleep 0.3
"$SERVER_BIN" >> "$SRV_LOG" 2>&1 &
SERVER_PID=$!
sleep 1
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "ERROR: fwtpm_server restart failed"
tail -10 "$SRV_LOG"
exit 1
fi
sleep 1
# mssim TCTI auto-startup only works on first connection to a fresh
# server. After restart with preserved NV, we need explicit startup.
tpm2_startup -c 2>/dev/null || true
echo "[fwtpm] Server restarted (PID $SERVER_PID, NV preserved)"
}
if [ $NO_START -eq 0 ]; then
trap 'server_stop; rm -rf "$TEST_TMPDIR"' EXIT
fi
# ----------------------------------------------------------------
# Pre-flight: check tpm2-tools present
# ----------------------------------------------------------------
for t in tpm2_startup tpm2_getcap tpm2_getrandom tpm2_pcrread \
tpm2_pcrextend tpm2_createprimary tpm2_create tpm2_load \
tpm2_sign tpm2_verifysignature tpm2_nvdefine tpm2_nvwrite \
tpm2_nvread tpm2_nvundefine tpm2_testparms tpm2_changeauth \
tpm2_hierarchycontrol tpm2_nvsetbits tpm2_hash \
tpm2_policylocality tpm2_rsaencrypt tpm2_rsadecrypt \
tpm2_encryptdecrypt tpm2_evictcontrol tpm2_readpublic \
tpm2_clear tpm2_nvincrement tpm2_nvwritelock \
tpm2_stirrandom tpm2_selftest tpm2_flushcontext tpm2_quote \
tpm2_pcrreset tpm2_readclock tpm2_setclock tpm2_hmac \
tpm2_certifycreation tpm2_loadexternal tpm2_duplicate \
tpm2_policypassword tpm2_policyauthvalue tpm2_policycommandcode \
tpm2_policyor tpm2_policysecret tpm2_policyauthorize \
tpm2_changepps tpm2_changeeps; do
if ! check_tool "$t"; then
echo "SKIP: tpm2-tools not installed ($t not found)"
exit 77
fi
done
# ----------------------------------------------------------------
# Start server
# ----------------------------------------------------------------
mkdir -p "$TEST_TMPDIR"
if [ $NO_START -eq 0 ]; then
[ -x "$SERVER_BIN" ] || { echo "ERROR: $SERVER_BIN not found"; exit 1; }
server_start
fi
# ----------------------------------------------------------------
# Tests
# ----------------------------------------------------------------
hdr "Startup & Self-Test"
run_test "tpm2_startup (clear)" \
tpm2_startup -c
run_test "tpm2_selftest" \
tpm2_selftest
# ----------------------------------------------------------------
hdr "GetCapability"
run_test "getcap properties-fixed" \
tpm2_getcap properties-fixed
run_test "getcap properties-variable" \
tpm2_getcap properties-variable
run_test "getcap algorithms" \
tpm2_getcap algorithms
run_test "getcap commands" \
tpm2_getcap commands
run_test "getcap pcrs" \
tpm2_getcap pcrs
# ----------------------------------------------------------------
hdr "GetRandom"
run_test "getrandom 32 bytes" \
tpm2_getrandom --hex 32
run_test "getrandom 16 bytes" \
tpm2_getrandom --hex 16
# ----------------------------------------------------------------
hdr "PCR Operations"
run_test "pcrread sha256:0,1,2,3" \
tpm2_pcrread sha256:0,1,2,3
run_test "pcrextend pcr0" \
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
run_test "pcrread after extend" \
tpm2_pcrread sha256:0
# ----------------------------------------------------------------
hdr "CreatePrimary"
run_test "createprimary RSA (owner hierarchy)" \
tpm2_createprimary -C o -g sha256 -G rsa -c "$TEST_TMPDIR/primary_rsa.ctx"
run_test "createprimary ECC (owner hierarchy)" \
tpm2_createprimary -C o -g sha256 -G ecc -c "$TEST_TMPDIR/primary_ecc.ctx"
# Flush both primaries — each section below recreates what it needs.
# Real hardware TPMs only have 3 transient slots; primary keys are
# deterministic (same seed + template = same key) so recreating is free.
flush_transient
# ----------------------------------------------------------------
hdr "Create & Load — RSA"
# Recreate RSA primary for this section
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/primary_rsa.ctx" > /dev/null 2>&1
run_test "create RSA key under RSA primary" \
tpm2_create -C "$TEST_TMPDIR/primary_rsa.ctx" \
-g sha256 -G rsa \
-u "$TEST_TMPDIR/rsa.pub" -r "$TEST_TMPDIR/rsa.priv"
run_test "load RSA key" \
tpm2_load -C "$TEST_TMPDIR/primary_rsa.ctx" \
-u "$TEST_TMPDIR/rsa.pub" -r "$TEST_TMPDIR/rsa.priv" \
-c "$TEST_TMPDIR/rsa.ctx"
# ----------------------------------------------------------------
hdr "Sign & Verify — RSA"
echo "hello tpm2-tools test" > "$TEST_TMPDIR/msg.txt"
run_test "sign RSA (sha256)" \
tpm2_sign -c "$TEST_TMPDIR/rsa.ctx" -g sha256 \
-o "$TEST_TMPDIR/sig.rsa" "$TEST_TMPDIR/msg.txt"
run_test "verifysignature RSA" \
tpm2_verifysignature -c "$TEST_TMPDIR/rsa.ctx" -g sha256 \
-m "$TEST_TMPDIR/msg.txt" -s "$TEST_TMPDIR/sig.rsa"
# Flush transient handles to free slots for ECC tests
flush_transient
# ----------------------------------------------------------------
hdr "Create & Load — ECC"
# Re-create ECC primary (flushed above; deterministic — same key)
tpm2_createprimary -C o -g sha256 -G ecc \
-c "$TEST_TMPDIR/primary_ecc.ctx" > /dev/null 2>&1
run_test "create ECC key under ECC primary" \
tpm2_create -C "$TEST_TMPDIR/primary_ecc.ctx" \
-g sha256 -G ecc \
-u "$TEST_TMPDIR/ecc.pub" -r "$TEST_TMPDIR/ecc.priv"
run_test "load ECC key" \
tpm2_load -C "$TEST_TMPDIR/primary_ecc.ctx" \
-u "$TEST_TMPDIR/ecc.pub" -r "$TEST_TMPDIR/ecc.priv" \
-c "$TEST_TMPDIR/ecc.ctx"
# ----------------------------------------------------------------
hdr "Sign & Verify — ECC"
run_test "sign ECC (sha256)" \
tpm2_sign -c "$TEST_TMPDIR/ecc.ctx" -g sha256 \
-o "$TEST_TMPDIR/sig.ecc" "$TEST_TMPDIR/msg.txt"
run_test "verifysignature ECC" \
tpm2_verifysignature -c "$TEST_TMPDIR/ecc.ctx" -g sha256 \
-m "$TEST_TMPDIR/msg.txt" -s "$TEST_TMPDIR/sig.ecc"
# Flush ECC handles
flush_transient
# ----------------------------------------------------------------
hdr "NV RAM"
NV_IDX=0x01500000
run_test "nvdefine 32-byte index" \
tpm2_nvdefine "$NV_IDX" -C o -s 32 -a "ownerread|ownerwrite"
run_test "nvwrite 32 bytes" \
bash -c 'printf "%-32s" "fwtpm-nv-test-data" | \
tpm2_nvwrite '"$NV_IDX"' -C o --input=-'
run_test "nvread 32 bytes" \
tpm2_nvread "$NV_IDX" -C o -s 32
run_test "nvundefine" \
tpm2_nvundefine "$NV_IDX" -C o
# ----------------------------------------------------------------
hdr "Policy Engine"
# PolicyPCR: create a sealed key locked to current PCR 0 value
run_test "createprimary for policy tests" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/pol_primary.ctx"
run_test "startauthsession (trial)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/trial_session.ctx"
run_test "policypcr (trial — lock to PCR 0)" \
tpm2_policypcr -S "$TEST_TMPDIR/trial_session.ctx" \
-l sha256:0 -L "$TEST_TMPDIR/pcr_policy.bin"
run_test "flushcontext (trial session)" \
tpm2_flushcontext "$TEST_TMPDIR/trial_session.ctx"
run_test "create sealed key with PolicyPCR" \
bash -c 'echo -n "secretdata" | tpm2_create \
-C "'"$TEST_TMPDIR"'/pol_primary.ctx" \
-i- -L "'"$TEST_TMPDIR"'/pcr_policy.bin" \
-u "'"$TEST_TMPDIR"'/sealed.pub" -r "'"$TEST_TMPDIR"'/sealed.priv"'
run_test "load sealed key" \
tpm2_load -C "$TEST_TMPDIR/pol_primary.ctx" \
-u "$TEST_TMPDIR/sealed.pub" -r "$TEST_TMPDIR/sealed.priv" \
-c "$TEST_TMPDIR/sealed.ctx"
run_test "startauthsession (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/policy_session.ctx"
run_test "policypcr (satisfy — PCR 0)" \
tpm2_policypcr -S "$TEST_TMPDIR/policy_session.ctx" -l sha256:0
run_test "unseal with PolicyPCR" \
tpm2_unseal -c "$TEST_TMPDIR/sealed.ctx" \
-p "session:$TEST_TMPDIR/policy_session.ctx"
run_test "flushcontext (policy session)" \
tpm2_flushcontext "$TEST_TMPDIR/policy_session.ctx"
# Flush policy objects
flush_transient
# NV index with index password (authread|authwrite lets NV index auth access)
NV_POLICY_IDX=0x01500001
run_test "nvdefine with index password" \
tpm2_nvdefine "$NV_POLICY_IDX" -C o -s 16 \
-a "ownerread|ownerwrite|authread|authwrite" \
-p "testpass"
run_test "nvwrite via owner (no password)" \
bash -c 'echo -n "policy-test-data" | \
tpm2_nvwrite '"$NV_POLICY_IDX"' -C o --input=-'
run_test "nvread via owner (no password)" \
tpm2_nvread "$NV_POLICY_IDX" -C o -s 16
run_test "nvundefine policy nv index" \
tpm2_nvundefine "$NV_POLICY_IDX" -C o
# ----------------------------------------------------------------
hdr "TestParms"
run_test "testparms RSA" \
tpm2_testparms rsa2048
run_test "testparms ECC (P-256)" \
tpm2_testparms ecc256
run_test "testparms AES-128-CFB" \
tpm2_testparms aes128cfb
# ----------------------------------------------------------------
hdr "Hierarchy Control & Auth"
# Change the owner hierarchy auth, then change it back
run_test "hierarchychangeauth (set owner password)" \
tpm2_changeauth -c o newpass
run_test "hierarchychangeauth (clear owner password)" \
tpm2_changeauth -c o -p newpass
# HierarchyControl: disable/re-enable endorsement hierarchy
# Note: re-enabling requires platform auth
run_test "hierarchycontrol (disable endorsement)" \
tpm2_hierarchycontrol -C p ehEnable clear
run_test "hierarchycontrol (enable endorsement)" \
tpm2_hierarchycontrol -C p ehEnable set
# ----------------------------------------------------------------
hdr "NV SetBits & ChangeAuth"
NV_BITS_IDX=0x01500010
run_test "nvdefine bit field index" \
tpm2_nvdefine "$NV_BITS_IDX" -C o -s 8 \
-a "ownerread|ownerwrite|nt=bits"
run_test "nvsetbits (set bit 0)" \
tpm2_nvsetbits "$NV_BITS_IDX" -C o -i 0x01
run_test "nvsetbits (set bit 3)" \
tpm2_nvsetbits "$NV_BITS_IDX" -C o -i 0x08
run_test "nvread bit field" \
tpm2_nvread "$NV_BITS_IDX" -C o -s 8
run_test "nvundefine bit field" \
tpm2_nvundefine "$NV_BITS_IDX" -C o
# NV ChangeAuth: define index with auth, change it, verify access
NV_CHAUTH_IDX=0x01500011
run_test "nvdefine for changeauth test" \
tpm2_nvdefine "$NV_CHAUTH_IDX" -C o -s 16 \
-a "ownerread|authwrite" -p "oldpass"
run_test "nvwrite with original auth" \
bash -c 'echo -n "changeauth-test!" | \
tpm2_nvwrite '"$NV_CHAUTH_IDX"' -P "oldpass" --input=-'
# Note: tpm2-tools doesn't have a direct nvchangeauth command;
# the fwTPM NV_ChangeAuth handler is tested via the wolfTPM wrapper API.
run_test "nvundefine changeauth test index" \
tpm2_nvundefine "$NV_CHAUTH_IDX" -C o
# ----------------------------------------------------------------
hdr "Hash"
# tpm2_hash uses TPM2_Hash (single command). EventSequenceComplete
# is tested via the wolfTPM wrapper examples (hash sequence API).
echo -n "hash-test-data" > "$TEST_TMPDIR/hash_data.bin"
run_test "tpm2_hash (sha256)" \
tpm2_hash -g sha256 -o "$TEST_TMPDIR/hash_out.bin" \
-t "$TEST_TMPDIR/hash_ticket.bin" "$TEST_TMPDIR/hash_data.bin"
# ----------------------------------------------------------------
hdr "PolicyLocality"
run_test "startauthsession for policylocality (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/loc_session.ctx"
run_test "policylocality (locality 1)" \
tpm2_policylocality -S "$TEST_TMPDIR/loc_session.ctx" 1
run_test "flushcontext (locality session)" \
tpm2_flushcontext "$TEST_TMPDIR/loc_session.ctx"
# ----------------------------------------------------------------
hdr "Attestation"
# Flush remaining handles before attestation (needs 3 slots)
flush_transient
# Create a primary key for signing attestations
run_test "createprimary for attestation" \
tpm2_createprimary -C o -g sha256 -G rsa -c "$TEST_TMPDIR/att_primary.ctx"
# Create child AIK for signing. Quote requires a restricted signing key
# (TPM 2.0 Part 3 Sec.18.4), so set the canonical attestation-key attributes.
run_test "create AIK (RSA signing key)" \
tpm2_create -C "$TEST_TMPDIR/att_primary.ctx" \
-g sha256 -G rsa:rsassa:null \
-a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign" \
-u "$TEST_TMPDIR/aik.pub" -r "$TEST_TMPDIR/aik.priv"
run_test "load AIK" \
tpm2_load -C "$TEST_TMPDIR/att_primary.ctx" \
-u "$TEST_TMPDIR/aik.pub" -r "$TEST_TMPDIR/aik.priv" \
-c "$TEST_TMPDIR/aik.ctx"
# PCR Quote: sign current PCR values with AIK
run_test "tpm2_quote (sha256:0,1,2)" \
tpm2_quote -c "$TEST_TMPDIR/aik.ctx" -l sha256:0,1,2 \
-q "cafebabe" -m "$TEST_TMPDIR/quote.msg" \
-s "$TEST_TMPDIR/quote.sig" -o "$TEST_TMPDIR/quote.pcrs" \
-g sha256
# GetTime
if check_tool tpm2_gettime; then
run_test "tpm2_gettime (signed timestamp)" \
tpm2_gettime -c "$TEST_TMPDIR/aik.ctx" \
-q "deadbeef" \
--attestation="$TEST_TMPDIR/time_attest.bin" \
-o "$TEST_TMPDIR/time_sig.bin" \
-g sha256 -s rsassa
else
skip_test "tpm2_gettime" "tpm2_gettime not available in this version"
fi
# Flush before certify to free object slots
flush_transient
# Re-create attestation primary and reload AIK for certify
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/att_primary.ctx" > /dev/null 2>&1
tpm2_load -C "$TEST_TMPDIR/att_primary.ctx" \
-u "$TEST_TMPDIR/aik.pub" -r "$TEST_TMPDIR/aik.priv" \
-c "$TEST_TMPDIR/aik.ctx" > /dev/null 2>&1
# Create a second key to certify using the AIK
run_test "create key to certify" \
tpm2_create -C "$TEST_TMPDIR/att_primary.ctx" \
-g sha256 -G ecc \
-u "$TEST_TMPDIR/certify_key.pub" -r "$TEST_TMPDIR/certify_key.priv"
run_test "load key to certify" \
tpm2_load -C "$TEST_TMPDIR/att_primary.ctx" \
-u "$TEST_TMPDIR/certify_key.pub" -r "$TEST_TMPDIR/certify_key.priv" \
-c "$TEST_TMPDIR/certify_key.ctx"
# Flush to free slots — certify only needs certify_key + aik
flush_transient
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/att_primary.ctx" > /dev/null 2>&1
tpm2_load -C "$TEST_TMPDIR/att_primary.ctx" \
-u "$TEST_TMPDIR/aik.pub" -r "$TEST_TMPDIR/aik.priv" \
-c "$TEST_TMPDIR/aik.ctx" > /dev/null 2>&1
tpm2_load -C "$TEST_TMPDIR/att_primary.ctx" \
-u "$TEST_TMPDIR/certify_key.pub" -r "$TEST_TMPDIR/certify_key.priv" \
-c "$TEST_TMPDIR/certify_key.ctx" > /dev/null 2>&1
run_test "tpm2_certify (certify key with AIK)" \
tpm2_certify -c "$TEST_TMPDIR/certify_key.ctx" \
-C "$TEST_TMPDIR/aik.ctx" \
-g sha256 -o "$TEST_TMPDIR/certify_attest.bin" \
-s "$TEST_TMPDIR/certify_sig.bin"
# Flush attestation handles
flush_transient
# ----------------------------------------------------------------
hdr "RSA Encrypt/Decrypt"
# Re-create RSA primary (flushed before attestation)
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/primary_rsa.ctx" > /dev/null 2>&1
# Create RSA encryption key (restricted=no, decrypt)
run_test "create RSA decrypt key" \
tpm2_create -C "$TEST_TMPDIR/primary_rsa.ctx" \
-g sha256 -G rsa:null:null \
-u "$TEST_TMPDIR/rsa_enc.pub" -r "$TEST_TMPDIR/rsa_enc.priv" \
-a "decrypt|userwithauth|sensitivedataorigin"
run_test "load RSA decrypt key" \
tpm2_load -C "$TEST_TMPDIR/primary_rsa.ctx" \
-u "$TEST_TMPDIR/rsa_enc.pub" -r "$TEST_TMPDIR/rsa_enc.priv" \
-c "$TEST_TMPDIR/rsa_enc.ctx"
echo -n "RSA encrypt test data!" > "$TEST_TMPDIR/rsa_plain.bin"
run_test "rsaencrypt" \
tpm2_rsaencrypt -c "$TEST_TMPDIR/rsa_enc.ctx" \
-o "$TEST_TMPDIR/rsa_cipher.bin" "$TEST_TMPDIR/rsa_plain.bin"
run_test "rsadecrypt" \
tpm2_rsadecrypt -c "$TEST_TMPDIR/rsa_enc.ctx" \
-o "$TEST_TMPDIR/rsa_dec.bin" "$TEST_TMPDIR/rsa_cipher.bin"
run_test "rsadecrypt verify plaintext" \
diff "$TEST_TMPDIR/rsa_plain.bin" "$TEST_TMPDIR/rsa_dec.bin"
# Flush RSA encrypt handles
flush_transient
# ----------------------------------------------------------------
hdr "AES Encrypt/Decrypt"
# Re-create RSA primary (flushed above)
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/primary_rsa.ctx" > /dev/null 2>&1
# Create AES symmetric key
run_test "create AES-128 key" \
tpm2_create -C "$TEST_TMPDIR/primary_rsa.ctx" \
-g sha256 -G aes128cfb \
-u "$TEST_TMPDIR/aes.pub" -r "$TEST_TMPDIR/aes.priv"
run_test "load AES key" \
tpm2_load -C "$TEST_TMPDIR/primary_rsa.ctx" \
-u "$TEST_TMPDIR/aes.pub" -r "$TEST_TMPDIR/aes.priv" \
-c "$TEST_TMPDIR/aes.ctx"
echo -n "AES encrypt test data for fwTPM!!" > "$TEST_TMPDIR/aes_plain.bin"
run_test "encryptdecrypt (encrypt)" \
tpm2_encryptdecrypt -c "$TEST_TMPDIR/aes.ctx" \
-o "$TEST_TMPDIR/aes_cipher.bin" "$TEST_TMPDIR/aes_plain.bin"
run_test "encryptdecrypt (decrypt)" \
tpm2_encryptdecrypt -d -c "$TEST_TMPDIR/aes.ctx" \
-o "$TEST_TMPDIR/aes_dec.bin" "$TEST_TMPDIR/aes_cipher.bin"
run_test "aes decrypt verify plaintext" \
diff "$TEST_TMPDIR/aes_plain.bin" "$TEST_TMPDIR/aes_dec.bin"
# Flush AES handles
flush_transient
# ----------------------------------------------------------------
hdr "EvictControl (Persistent Handles)"
run_test "createprimary for evict test" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/evict_primary.ctx"
run_test "evictcontrol (make persistent 0x81000100)" \
tpm2_evictcontrol -C o -c "$TEST_TMPDIR/evict_primary.ctx" 0x81000100
run_test "readpublic (persistent handle)" \
tpm2_readpublic -c 0x81000100
run_test "evictcontrol (evict persistent)" \
tpm2_evictcontrol -C o -c 0x81000100
# Flush evict handles
flush_transient
# ----------------------------------------------------------------
hdr "Import/Duplicate"
# Import an external RSA key
if check_tool tpm2_import; then
run_test "createprimary for import" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/import_parent.ctx"
# Note: tpm2_import -i file.pem (direct) fails with TPM_RC_INTEGRITY due
# to a wrapping format difference in tpm2-tools' internal import path.
# Using tpm2_duplicate --tcti none + tpm2_import works correctly.
if check_tool tpm2_duplicate; then
openssl genrsa -out "$TEST_TMPDIR/import_rsa.pem" 2048 2>/dev/null
tpm2_readpublic -c "$TEST_TMPDIR/import_parent.ctx" \
-o "$TEST_TMPDIR/import_parent.pub" -f tss > /dev/null 2>&1
run_test "duplicate wrap (offline)" \
tpm2_duplicate --tcti none \
-U "$TEST_TMPDIR/import_parent.pub" -G rsa \
-k "$TEST_TMPDIR/import_rsa.pem" \
-u "$TEST_TMPDIR/import_key.pub" \
-r "$TEST_TMPDIR/import_key.dpriv" \
-s "$TEST_TMPDIR/import_key.seed"
run_test "import wrapped RSA key" \
tpm2_import -C "$TEST_TMPDIR/import_parent.ctx" -G rsa \
-i "$TEST_TMPDIR/import_key.dpriv" \
-s "$TEST_TMPDIR/import_key.seed" \
-u "$TEST_TMPDIR/import_key.pub" \
-r "$TEST_TMPDIR/import_key.priv"
# Load and use the imported key
run_test "load imported RSA key" \
tpm2_load -C "$TEST_TMPDIR/import_parent.ctx" \
-u "$TEST_TMPDIR/import_key.pub" \
-r "$TEST_TMPDIR/import_key.priv" \
-c "$TEST_TMPDIR/import_key.ctx"
echo "import-sign-test" > "$TEST_TMPDIR/import_msg.txt"
run_test "sign with imported key" \
tpm2_sign -c "$TEST_TMPDIR/import_key.ctx" -g sha256 \
-o "$TEST_TMPDIR/import_sig.bin" "$TEST_TMPDIR/import_msg.txt"
run_test "verify imported key signature" \
tpm2_verifysignature -c "$TEST_TMPDIR/import_key.ctx" -g sha256 \
-m "$TEST_TMPDIR/import_msg.txt" \
-s "$TEST_TMPDIR/import_sig.bin"
flush_transient
else
skip_test "import external RSA key" "tpm2_duplicate not available"
fi
fi
# ----------------------------------------------------------------
hdr "MakeCredential / ActivateCredential"
CRED_EK_HANDLE=0x81010009
if check_tool tpm2_makecredential && check_tool tpm2_activatecredential; then
flush_transient
# Evict any stale persistent EK from prior runs
tpm2_evictcontrol -Q -C o -c "$CRED_EK_HANDLE" 2>/dev/null || true
# Create persistent EK (doesn't consume transient slot)
run_test "createek (persistent $CRED_EK_HANDLE)" \
tpm2_createek -Q -c "$CRED_EK_HANDLE" -G rsa -u "$TEST_TMPDIR/ek.pub"
# Create AK under persistent EK
run_test "createak" \
tpm2_createak -C "$CRED_EK_HANDLE" -c "$TEST_TMPDIR/ak.ctx" \
-G rsa -g sha256 -s rsassa \
-u "$TEST_TMPDIR/ak.pub" -n "$TEST_TMPDIR/ak.name"
# Read EK public in PEM format for makecredential
tpm2_readpublic -c "$CRED_EK_HANDLE" \
-o "$TEST_TMPDIR/ek.pem" -f pem -Q 2>/dev/null
# MakeCredential (challenge) using EK public
echo -n "secret-credential!" > "$TEST_TMPDIR/cred_secret.bin"
run_test "makecredential" \
tpm2_makecredential -u "$TEST_TMPDIR/ek.pem" -G rsa \
-s "$TEST_TMPDIR/cred_secret.bin" \
-n "$(xxd -p -c 256 "$TEST_TMPDIR/ak.name")" \
-o "$TEST_TMPDIR/cred_blob.bin"
# ActivateCredential requires EK auth via PolicySecret(endorsement)
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/cred_session.ctx" > /dev/null 2>&1
tpm2_policysecret -S "$TEST_TMPDIR/cred_session.ctx" \
-c e > /dev/null 2>&1
run_test "activatecredential" \
tpm2_activatecredential -c "$TEST_TMPDIR/ak.ctx" \
-C "$CRED_EK_HANDLE" \
-i "$TEST_TMPDIR/cred_blob.bin" \
-o "$TEST_TMPDIR/cred_recovered.bin" \
-P "session:$TEST_TMPDIR/cred_session.ctx"
tpm2_flushcontext "$TEST_TMPDIR/cred_session.ctx" 2>/dev/null || true
run_test "credential verify recovered secret" \
diff "$TEST_TMPDIR/cred_secret.bin" "$TEST_TMPDIR/cred_recovered.bin"
# Clean up persistent EK
tpm2_evictcontrol -Q -C o -c "$CRED_EK_HANDLE" 2>/dev/null || true
else
skip_test "MakeCredential/ActivateCredential" "tpm2_makecredential not available"
fi
# ----------------------------------------------------------------
hdr "NV Counter, Extend & Locks"
NV_CTR_IDX=0x01500020
run_test "nvdefine counter index" \
tpm2_nvdefine "$NV_CTR_IDX" -C o -s 8 \
-a "ownerread|ownerwrite|nt=counter"
run_test "nvincrement (first)" \
tpm2_nvincrement "$NV_CTR_IDX" -C o
run_test "nvincrement (second)" \
tpm2_nvincrement "$NV_CTR_IDX" -C o
run_test "nvread counter value" \
tpm2_nvread "$NV_CTR_IDX" -C o -s 8
run_test "nvundefine counter" \
tpm2_nvundefine "$NV_CTR_IDX" -C o
# NV Extend
NV_EXT_IDX=0x01500021
run_test "nvdefine extend index" \
tpm2_nvdefine "$NV_EXT_IDX" -C o -s 32 \
-a "ownerread|ownerwrite|nt=extend"
if check_tool tpm2_nvextend; then
run_test "nvextend" \
bash -c 'echo -n "extend-data-test" | \
tpm2_nvextend '"$NV_EXT_IDX"' -C o --input=-'
run_test "nvread extended value" \
tpm2_nvread "$NV_EXT_IDX" -C o -s 32
else
skip_test "nvextend" "tpm2_nvextend not available"
fi
run_test "nvundefine extend index" \
tpm2_nvundefine "$NV_EXT_IDX" -C o
# NV Write Lock
NV_LOCK_IDX=0x01500022
run_test "nvdefine for writelock test" \
tpm2_nvdefine "$NV_LOCK_IDX" -C o -s 16 \
-a "ownerread|ownerwrite|writedefine"
run_test "nvwrite before lock" \
bash -c 'echo -n "lock-test-data!!" | \
tpm2_nvwrite '"$NV_LOCK_IDX"' -C o --input=-'
run_test "nvwritelock" \
tpm2_nvwritelock "$NV_LOCK_IDX" -C o
run_test "nvread after lock (should work)" \
tpm2_nvread "$NV_LOCK_IDX" -C o -s 16
run_test "nvundefine locked index" \
tpm2_nvundefine "$NV_LOCK_IDX" -C o
# ----------------------------------------------------------------
hdr "StirRandom"
run_test "stirrandom" \
bash -c 'echo -n "additional-entropy-for-drbg" | tpm2_stirrandom'
# ----------------------------------------------------------------
hdr "Clear"
# Clear resets owner hierarchy — run near end to not break other tests
run_test "tpm2_clear (owner)" \
tpm2_clear -c p
# Re-create primary after clear for any subsequent tests
run_test "createprimary after clear" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/post_clear_primary.ctx"
# ----------------------------------------------------------------
hdr "ReadPublic"
run_test "readpublic" \
tpm2_readpublic -c "$TEST_TMPDIR/post_clear_primary.ctx"
# ----------------------------------------------------------------
hdr "ContextSave / ContextLoad"
if check_tool tpm2_contextsave && check_tool tpm2_contextload; then
run_test "contextsave" \
tpm2_contextsave -c "$TEST_TMPDIR/post_clear_primary.ctx" \
-o "$TEST_TMPDIR/saved_ctx.bin"
run_test "contextload" \
tpm2_contextload -c "$TEST_TMPDIR/saved_ctx.bin"
else
skip_test "ContextSave/ContextLoad" "tpm2_contextsave not available"
fi
# ================================================================
# NEW TESTS: Commands supported by fwTPM but not previously tested
# ================================================================
# ----------------------------------------------------------------
hdr "PCR Reset"
# PCR 23 is resettable from locality 0
run_test "pcrreset PCR 23" \
tpm2_pcrreset 23
run_test "pcrextend PCR 23 then reset" \
bash -c 'tpm2_pcrextend 23:sha256=0000000000000000000000000000000000000000000000000000000000000001 && \
tpm2_pcrreset 23'
# ----------------------------------------------------------------
# ----------------------------------------------------------------
hdr "PCR Event"
echo "event data for test" > "$TEST_TMPDIR/event.bin"
run_test "pcrevent PCR 16" \
tpm2_pcrevent 16 "$TEST_TMPDIR/event.bin"
# ----------------------------------------------------------------
hdr "ClearControl"
run_test "clearcontrol disable" \
tpm2_clearcontrol -C p s
run_test "clearcontrol re-enable" \
tpm2_clearcontrol -C p c
# ----------------------------------------------------------------
hdr "Primary Key Determinism (across restart)"
if [ $NO_START -eq 0 ]; then
# Extract object name from tpm2_readpublic output.
# Name = nameAlg || H(publicArea) — deterministic for same key.
get_obj_name() {
tpm2_readpublic -c "$1" 2>/dev/null | grep "^name:" | awk '{print $2}'
}
# Create fixed unique data so hashUnique is identical across calls.
# tpm2-tools randomizes unique by default, which changes the derived key.
dd if=/dev/zero of="$TEST_TMPDIR/unique_rsa.bin" bs=256 count=1 2>/dev/null
dd if=/dev/zero of="$TEST_TMPDIR/unique_ecc.bin" bs=64 count=1 2>/dev/null
# Save pre-restart key names (RSA + ECC under owner hierarchy)
run_test "createprimary rsa (pre-restart)" \
tpm2_createprimary -C o -g sha256 -G rsa \
-u "$TEST_TMPDIR/unique_rsa.bin" \
-c "$TEST_TMPDIR/det_rsa_pre.ctx"
DET_RSA_PRE=$(get_obj_name "$TEST_TMPDIR/det_rsa_pre.ctx")
tpm2_flushcontext "$TEST_TMPDIR/det_rsa_pre.ctx" 2>/dev/null
run_test "createprimary ecc (pre-restart)" \
tpm2_createprimary -C o -g sha256 -G ecc \
-u "$TEST_TMPDIR/unique_ecc.bin" \
-c "$TEST_TMPDIR/det_ecc_pre.ctx"
DET_ECC_PRE=$(get_obj_name "$TEST_TMPDIR/det_ecc_pre.ctx")
tpm2_flushcontext "$TEST_TMPDIR/det_ecc_pre.ctx" 2>/dev/null
# Send tpm2_shutdown to flush volatile state before restart
run_test "shutdown (flush NV before restart)" \
tpm2_shutdown
# Restart server (preserves NV/seeds, clears transient state + cache)
server_restart
# Recreate with same templates + unique — names MUST match
run_test "createprimary rsa (post-restart)" \
tpm2_createprimary -C o -g sha256 -G rsa \
-u "$TEST_TMPDIR/unique_rsa.bin" \
-c "$TEST_TMPDIR/det_rsa_post.ctx"
DET_RSA_POST=$(get_obj_name "$TEST_TMPDIR/det_rsa_post.ctx")
tpm2_flushcontext "$TEST_TMPDIR/det_rsa_post.ctx" 2>/dev/null
run_test "createprimary ecc (post-restart)" \
tpm2_createprimary -C o -g sha256 -G ecc \
-u "$TEST_TMPDIR/unique_ecc.bin" \
-c "$TEST_TMPDIR/det_ecc_post.ctx"
DET_ECC_POST=$(get_obj_name "$TEST_TMPDIR/det_ecc_post.ctx")
tpm2_flushcontext "$TEST_TMPDIR/det_ecc_post.ctx" 2>/dev/null
# Verify identical names (= identical public areas) across restart
if [ -n "$DET_RSA_PRE" ] && [ "$DET_RSA_PRE" = "$DET_RSA_POST" ]; then
echo " [PASS] rsa primary deterministic across restart"
PASS=$((PASS+1))
else
echo " [FAIL] rsa primary deterministic across restart"
echo " pre: $DET_RSA_PRE"
echo " post: $DET_RSA_POST"
FAIL=$((FAIL+1))
fi
if [ -n "$DET_ECC_PRE" ] && [ "$DET_ECC_PRE" = "$DET_ECC_POST" ]; then
echo " [PASS] ecc primary deterministic across restart"
PASS=$((PASS+1))
else
echo " [FAIL] ecc primary deterministic across restart"
echo " pre: $DET_ECC_PRE"
echo " post: $DET_ECC_POST"
FAIL=$((FAIL+1))
fi
else
skip_test "Determinism tests" "requires server lifecycle (--no-start mode)"
fi
# ----------------------------------------------------------------
hdr "ChangePPS / ChangeEPS"
# ChangePPS: platform seed change should produce different primary key
run_test "createprimary under platform (before changepps)" \
tpm2_createprimary -C p -g sha256 -G ecc \
-c "$TEST_TMPDIR/plat_pre.ctx"
tpm2_readpublic -c "$TEST_TMPDIR/plat_pre.ctx" \
-o "$TEST_TMPDIR/plat_pre.pub" 2>/dev/null
tpm2_flushcontext "$TEST_TMPDIR/plat_pre.ctx" 2>/dev/null
run_test "tpm2_changepps" \
tpm2_changepps
run_test "createprimary under platform (after changepps)" \
tpm2_createprimary -C p -g sha256 -G ecc \
-c "$TEST_TMPDIR/plat_post.ctx"
tpm2_readpublic -c "$TEST_TMPDIR/plat_post.ctx" \
-o "$TEST_TMPDIR/plat_post.pub" 2>/dev/null
tpm2_flushcontext "$TEST_TMPDIR/plat_post.ctx" 2>/dev/null
run_test "changepps produced different key" \
bash -c '! cmp -s "$TEST_TMPDIR/plat_pre.pub" "$TEST_TMPDIR/plat_post.pub"'
# ChangeEPS: endorsement seed change should produce different primary key
run_test "createprimary under endorsement (before changeeps)" \
tpm2_createprimary -C e -g sha256 -G ecc \
-c "$TEST_TMPDIR/ek_pre.ctx"
tpm2_readpublic -c "$TEST_TMPDIR/ek_pre.ctx" \
-o "$TEST_TMPDIR/ek_pre.pub" 2>/dev/null
tpm2_flushcontext "$TEST_TMPDIR/ek_pre.ctx" 2>/dev/null
run_test "tpm2_changeeps" \
tpm2_changeeps
run_test "createprimary under endorsement (after changeeps)" \
tpm2_createprimary -C e -g sha256 -G ecc \
-c "$TEST_TMPDIR/ek_post.ctx"
tpm2_readpublic -c "$TEST_TMPDIR/ek_post.ctx" \
-o "$TEST_TMPDIR/ek_post.pub" 2>/dev/null
tpm2_flushcontext "$TEST_TMPDIR/ek_post.ctx" 2>/dev/null
run_test "changeeps produced different key" \
bash -c '! cmp -s "$TEST_TMPDIR/ek_pre.pub" "$TEST_TMPDIR/ek_post.pub"'
# Owner hierarchy should be unaffected
run_test "createprimary under owner (still works after seed changes)" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/owner_after_change.ctx"
tpm2_flushcontext "$TEST_TMPDIR/owner_after_change.ctx" 2>/dev/null
# ----------------------------------------------------------------
hdr "Dictionary Attack Lockout"
if check_tool tpm2_dictionarylockout; then
run_test "DA set parameters" \
tpm2_dictionarylockout --setup-parameters \
--max-tries=5 --recovery-time=10 --lockout-recovery=60
run_test "DA clear lockout" \
tpm2_dictionarylockout --clear-lockout
# Restore defaults
run_test "DA restore defaults" \
tpm2_dictionarylockout --setup-parameters \
--max-tries=32 --recovery-time=600 --lockout-recovery=86400
else
skip_test "DictionaryAttack" "tpm2_dictionarylockout not available"
fi
# ----------------------------------------------------------------
hdr "PolicyCounterTimer"
if check_tool tpm2_policycountertimer; then
run_test "policycountertimer trial (unsigned-lt)" \
bash -c 'tpm2_startauthsession -S '"$TEST_TMPDIR"'/ct.ctx && \
tpm2_policycountertimer -S '"$TEST_TMPDIR"'/ct.ctx --ult 99999999999 && \
tpm2_flushcontext '"$TEST_TMPDIR"'/ct.ctx'
else
skip_test "PolicyCounterTimer" "tpm2_policycountertimer not available"
fi
# ----------------------------------------------------------------
hdr "PCR Allocate"
if check_tool tpm2_pcrallocate; then
run_test "pcrallocate sha256+sha384" \
tpm2_pcrallocate sha256:all+sha384:all
else
skip_test "PCR_Allocate" "tpm2_pcrallocate not available"
fi
# ----------------------------------------------------------------
hdr "ReadClock & SetClock"
run_test "readclock" \
tpm2_readclock
# SetClock: advance clock forward
run_test "setclock (advance)" \
bash -c 'clock_val=$(tpm2_readclock 2>/dev/null | grep "^ clock:" | awk "{print \$2}"); \
new_clock=$((clock_val + 100000)); \
tpm2_setclock $new_clock'
# ----------------------------------------------------------------
hdr "GetTestResult"
if check_tool tpm2_gettestresult; then
run_test "gettestresult" \
tpm2_gettestresult
else
skip_test "gettestresult" "tpm2_gettestresult not available"
fi
# ----------------------------------------------------------------
hdr "IncrementalSelfTest"
if check_tool tpm2_incrementalselftest; then
run_test "incrementalselftest (sha256)" \
tpm2_incrementalselftest sha256
run_test "incrementalselftest (rsa)" \
tpm2_incrementalselftest rsa
else
skip_test "incrementalselftest" "tpm2_incrementalselftest not available"
fi
# ----------------------------------------------------------------
hdr "HMAC"
# Flush transient objects from prior tests
flush_transient
run_test "createprimary for HMAC" \
tpm2_createprimary -C e -g sha256 -G rsa \
-c "$TEST_TMPDIR/hmac_primary.ctx"
run_test "create HMAC key" \
tpm2_create -C "$TEST_TMPDIR/hmac_primary.ctx" \
-G hmac -u "$TEST_TMPDIR/hmac_key.pub" -r "$TEST_TMPDIR/hmac_key.priv"
run_test "load HMAC key" \
tpm2_load -C "$TEST_TMPDIR/hmac_primary.ctx" \
-u "$TEST_TMPDIR/hmac_key.pub" -r "$TEST_TMPDIR/hmac_key.priv" \
-c "$TEST_TMPDIR/hmac_key.ctx"
echo -n "hmac-test-data" > "$TEST_TMPDIR/hmac_data.bin"
run_test "tpm2_hmac (stdin)" \
bash -c 'cat "'"$TEST_TMPDIR"'/hmac_data.bin" | \
tpm2_hmac -c "'"$TEST_TMPDIR"'/hmac_key.ctx" \
-o "'"$TEST_TMPDIR"'/hmac_out.bin"'
run_test "tpm2_hmac (file arg)" \
tpm2_hmac -c "$TEST_TMPDIR/hmac_key.ctx" \
-o "$TEST_TMPDIR/hmac_out2.bin" "$TEST_TMPDIR/hmac_data.bin"
# ----------------------------------------------------------------
hdr "LoadExternal"
# Flush transient objects
flush_transient
# Load public part of a TPM-created key externally
run_test "createprimary for loadexternal" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/le_primary.ctx"
run_test "create key for loadexternal" \
tpm2_create -C "$TEST_TMPDIR/le_primary.ctx" \
-g sha256 -G rsa \
-u "$TEST_TMPDIR/le_key.pub" -r "$TEST_TMPDIR/le_key.priv"
run_test "loadexternal (public only, null hierarchy)" \
tpm2_loadexternal -C n -u "$TEST_TMPDIR/le_key.pub" \
-c "$TEST_TMPDIR/le_ext.ctx"
run_test "readpublic of externally loaded key" \
tpm2_readpublic -c "$TEST_TMPDIR/le_ext.ctx"
tpm2_flushcontext "$TEST_TMPDIR/le_ext.ctx" 2>/dev/null || true
# Load an OpenSSL-generated RSA key
if check_tool openssl; then
openssl genrsa -out "$TEST_TMPDIR/le_rsa.pem" 2048 2>/dev/null
run_test "loadexternal (OpenSSL RSA private key)" \
tpm2_loadexternal -G rsa -C n \
-r "$TEST_TMPDIR/le_rsa.pem" -c "$TEST_TMPDIR/le_rsa.ctx"
tpm2_flushcontext "$TEST_TMPDIR/le_rsa.ctx" 2>/dev/null || true
fi
# ----------------------------------------------------------------
hdr "CertifyCreation"
# Flush transient objects
flush_transient
if check_tool tpm2_certifycreation; then
run_test "createprimary with creation data" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/cc_primary.ctx" \
--creation-data "$TEST_TMPDIR/cc_creation.data" \
-d "$TEST_TMPDIR/cc_creation.digest" \
-t "$TEST_TMPDIR/cc_creation.ticket"
run_test "create signing key for certifycreation" \
tpm2_create -G rsa -C "$TEST_TMPDIR/cc_primary.ctx" \
-u "$TEST_TMPDIR/cc_sign.pub" -r "$TEST_TMPDIR/cc_sign.priv" \
-c "$TEST_TMPDIR/cc_sign.ctx"
run_test "certifycreation" \
tpm2_certifycreation -C "$TEST_TMPDIR/cc_sign.ctx" \
-c "$TEST_TMPDIR/cc_primary.ctx" \
-d "$TEST_TMPDIR/cc_creation.digest" \
-t "$TEST_TMPDIR/cc_creation.ticket" \
-g sha256 -o "$TEST_TMPDIR/cc_sig.bin" \
--attestation "$TEST_TMPDIR/cc_attest.bin" \
-f plain -s rsassa
# Negative: tamper with the creation ticket and confirm the TPM rejects
# it with TPM_RC_TICKET (HMAC verification fails). tpm2-tools may save
# the ticket with a leading marshaling header, so tampering a single
# byte at a fixed offset isn't reliable across tool versions. Overwrite
# the trailing half of the file with 0xAA — the HMAC digest lives at
# the end of the TPMT_TK_CREATION structure and is always included in
# verification, so corrupting it guarantees TPM_RC_TICKET regardless
# of any wrapper framing.
cp "$TEST_TMPDIR/cc_creation.ticket" "$TEST_TMPDIR/cc_creation.ticket.bad"
TICKET_SIZE=$(wc -c < "$TEST_TMPDIR/cc_creation.ticket.bad")
TAMPER_OFFSET=$((TICKET_SIZE / 2))
TAMPER_LEN=$((TICKET_SIZE - TAMPER_OFFSET))
dd if=/dev/zero bs=1 count="$TAMPER_LEN" 2>/dev/null \
| tr '\000' '\252' \
| dd of="$TEST_TMPDIR/cc_creation.ticket.bad" \
bs=1 seek="$TAMPER_OFFSET" conv=notrunc 2>/dev/null
run_test_fail "certifycreation rejects tampered ticket (TPM_RC_TICKET)" \
tpm2_certifycreation -C "$TEST_TMPDIR/cc_sign.ctx" \
-c "$TEST_TMPDIR/cc_primary.ctx" \
-d "$TEST_TMPDIR/cc_creation.digest" \
-t "$TEST_TMPDIR/cc_creation.ticket.bad" \
-g sha256 -o "$TEST_TMPDIR/cc_sig_bad.bin" \
--attestation "$TEST_TMPDIR/cc_attest_bad.bin" \
-f plain -s rsassa
else
skip_test "certifycreation" "tpm2_certifycreation not available"
fi
# ----------------------------------------------------------------
hdr "Hash + Sign (TK_HASHCHECK ticket)"
# Generate a hash with ticket via TPM2_Hash, then sign the hashed digest
# using the ticket as proof the TPM produced the hash. Exercises the
# TK_HASHCHECK ticket generate→consume flow that PolicyAuthorize/Sign rely on.
flush_transient
run_test "createprimary for hash+sign" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/hs_primary.ctx"
run_test "create signing key for hash+sign" \
tpm2_create -G rsa -C "$TEST_TMPDIR/hs_primary.ctx" \
-u "$TEST_TMPDIR/hs_sign.pub" -r "$TEST_TMPDIR/hs_sign.priv" \
-c "$TEST_TMPDIR/hs_sign.ctx"
echo -n "ticket-data-to-sign" > "$TEST_TMPDIR/hs_data.bin"
run_test "tpm2_hash with TK_HASHCHECK output" \
tpm2_hash -C o -g sha256 \
-o "$TEST_TMPDIR/hs_digest.bin" \
-t "$TEST_TMPDIR/hs_ticket.bin" \
"$TEST_TMPDIR/hs_data.bin"
run_test "tpm2_sign consumes TK_HASHCHECK ticket" \
tpm2_sign -c "$TEST_TMPDIR/hs_sign.ctx" \
-g sha256 -d -t "$TEST_TMPDIR/hs_ticket.bin" \
-o "$TEST_TMPDIR/hs_sig.bin" \
"$TEST_TMPDIR/hs_digest.bin"
# Negative: tamper ticket bytes; sign should reject (TPM_RC_TICKET).
# Overwrite the trailing half with 0xAA rather than forcing a single fixed
# byte: the HMAC digest lives at the end of the ticket and a fixed-value
# single-byte write is a no-op whenever that byte already holds the chosen
# value (~1/256 per run, since the hierarchy proof is randomized per server
# start) — the source of the intermittent CI failures.
cp "$TEST_TMPDIR/hs_ticket.bin" "$TEST_TMPDIR/hs_ticket.bad"
HS_TICKET_SIZE=$(wc -c < "$TEST_TMPDIR/hs_ticket.bad")
HS_TAMPER_OFFSET=$((HS_TICKET_SIZE / 2))
HS_TAMPER_LEN=$((HS_TICKET_SIZE - HS_TAMPER_OFFSET))
dd if=/dev/zero bs=1 count="$HS_TAMPER_LEN" 2>/dev/null \
| tr '\000' '\252' \
| dd of="$TEST_TMPDIR/hs_ticket.bad" \
bs=1 seek="$HS_TAMPER_OFFSET" conv=notrunc 2>/dev/null
run_test_fail "tpm2_sign rejects tampered TK_HASHCHECK (TPM_RC_TICKET)" \
tpm2_sign -c "$TEST_TMPDIR/hs_sign.ctx" \
-g sha256 -d -t "$TEST_TMPDIR/hs_ticket.bad" \
-o "$TEST_TMPDIR/hs_sig_bad.bin" \
"$TEST_TMPDIR/hs_digest.bin"
# ----------------------------------------------------------------
hdr "Duplicate"
# Flush transient objects
flush_transient
run_test "createprimary for duplicate" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/dup_primary.ctx"
# Create duplication policy (PolicyCommandCode for TPM2_CC_Duplicate)
run_test "startauthsession for duplication policy (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/dup_trial.ctx"
run_test "policycommandcode (Duplicate)" \
tpm2_policycommandcode -S "$TEST_TMPDIR/dup_trial.ctx" \
-L "$TEST_TMPDIR/dup_policy.dat" TPM2_CC_Duplicate
run_test "flushcontext (duplication trial)" \
tpm2_flushcontext "$TEST_TMPDIR/dup_trial.ctx"
# Create key with duplication policy
run_test "create key with duplication policy" \
tpm2_create -C "$TEST_TMPDIR/dup_primary.ctx" \
-g sha256 -G rsa \
-r "$TEST_TMPDIR/dup_key.priv" -u "$TEST_TMPDIR/dup_key.pub" \
-L "$TEST_TMPDIR/dup_policy.dat" \
-a "sensitivedataorigin|sign|decrypt"
run_test "load key for duplicate" \
tpm2_load -C "$TEST_TMPDIR/dup_primary.ctx" \
-r "$TEST_TMPDIR/dup_key.priv" -u "$TEST_TMPDIR/dup_key.pub" \
-c "$TEST_TMPDIR/dup_key.ctx"
# Duplicate with null parent, null sym alg
run_test "startauthsession for duplication (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/dup_session.ctx"
run_test "policycommandcode (satisfy Duplicate)" \
tpm2_policycommandcode -S "$TEST_TMPDIR/dup_session.ctx" \
-L "$TEST_TMPDIR/dup_policy.dat" TPM2_CC_Duplicate
run_test "duplicate (null parent, null sym)" \
tpm2_duplicate -C null -c "$TEST_TMPDIR/dup_key.ctx" -G null \
-p "session:$TEST_TMPDIR/dup_session.ctx" \
-r "$TEST_TMPDIR/dup_priv.bin" -s "$TEST_TMPDIR/dup_seed.dat"
run_test "flushcontext (duplication session)" \
tpm2_flushcontext "$TEST_TMPDIR/dup_session.ctx"
# ----------------------------------------------------------------
hdr "ECC Operations (ECDH_KeyGen, ECDH_ZGen, ECC_Parameters)"
# Flush transient objects
flush_transient
if check_tool tpm2_geteccparameters; then
run_test "geteccparameters (P-256)" \
tpm2_geteccparameters ecc256 -o "$TEST_TMPDIR/ecc_params.bin"
else
skip_test "geteccparameters" "tpm2_geteccparameters not available"
fi
if check_tool tpm2_ecdhkeygen && check_tool tpm2_ecdhzgen; then
run_test "createprimary for ECDH" \
tpm2_createprimary -C o -g sha256 -G ecc \
-c "$TEST_TMPDIR/ecdh_primary.ctx"
run_test "create ECDH key" \
tpm2_create -C "$TEST_TMPDIR/ecdh_primary.ctx" \
-g sha256 -G ecc256:ecdh \
-u "$TEST_TMPDIR/ecdh_key.pub" -r "$TEST_TMPDIR/ecdh_key.priv"
run_test "load ECDH key" \
tpm2_load -C "$TEST_TMPDIR/ecdh_primary.ctx" \
-u "$TEST_TMPDIR/ecdh_key.pub" -r "$TEST_TMPDIR/ecdh_key.priv" \
-c "$TEST_TMPDIR/ecdh_key.ctx"
run_test "ecdhkeygen (generate ephemeral)" \
tpm2_ecdhkeygen -u "$TEST_TMPDIR/ecdh_pub.bin" \
-o "$TEST_TMPDIR/ecdh_secret.bin" \
-c "$TEST_TMPDIR/ecdh_key.ctx"
run_test "ecdhzgen (recover shared secret)" \
tpm2_ecdhzgen -u "$TEST_TMPDIR/ecdh_pub.bin" \
-o "$TEST_TMPDIR/ecdh_zpoint.bin" \
-c "$TEST_TMPDIR/ecdh_key.ctx"
# Verify ECDH roundtrip: ecdhkeygen produces shared secret,
# ecdhzgen with the ephemeral public point recovers the same secret.
# Note: tpm2_ecdhzgen -u with .pub files sends TPM2B_PUBLIC format
# which may not match the expected TPM2B_ECC_POINT wire format in
# all tpm2-tools versions. Use ecdhkeygen+ecdhzgen roundtrip instead.
run_test "ecdh shared secrets match" \
diff "$TEST_TMPDIR/ecdh_secret.bin" "$TEST_TMPDIR/ecdh_zpoint.bin"
else
skip_test "ECDH operations" "tpm2_ecdhkeygen/tpm2_ecdhzgen not available"
fi
# ----------------------------------------------------------------
hdr "NV Certify"
# Flush transient objects
flush_transient
if check_tool tpm2_nvcertify; then
NV_CERT_IDX=0x01500030
run_test "createprimary for nvcertify" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/nvc_primary.ctx"
run_test "create signing key for nvcertify" \
tpm2_create -G rsa -C "$TEST_TMPDIR/nvc_primary.ctx" \
-u "$TEST_TMPDIR/nvc_sign.pub" -r "$TEST_TMPDIR/nvc_sign.priv" \
-c "$TEST_TMPDIR/nvc_sign.ctx"
run_test "nvdefine for nvcertify" \
tpm2_nvdefine -s 32 -a "authread|authwrite" "$NV_CERT_IDX"
run_test "nvwrite for nvcertify" \
bash -c 'dd if=/dev/urandom bs=1 count=32 status=none | \
tpm2_nvwrite '"$NV_CERT_IDX"' -i-'
run_test "nvcertify" \
tpm2_nvcertify -C "$TEST_TMPDIR/nvc_sign.ctx" -g sha256 \
-f plain -s rsassa \
-o "$TEST_TMPDIR/nvc_sig.bin" \
--attestation "$TEST_TMPDIR/nvc_attest.bin" \
--size 32 "$NV_CERT_IDX"
run_test "nvundefine nvcertify index" \
tpm2_nvundefine "$NV_CERT_IDX" -C o
else
skip_test "nvcertify" "tpm2_nvcertify not available"
fi
# ================================================================
# NEW TESTS: Advanced Policy Commands
# ================================================================
# ----------------------------------------------------------------
hdr "PolicyPassword (sign with policy + password)"
# Flush transient objects
flush_transient
run_test "startauthsession for policypassword (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pp_trial.ctx"
run_test "policypassword (trial)" \
tpm2_policypassword -S "$TEST_TMPDIR/pp_trial.ctx" \
-L "$TEST_TMPDIR/pp_policy.dat"
run_test "flushcontext (policypassword trial)" \
tpm2_flushcontext "$TEST_TMPDIR/pp_trial.ctx"
run_test "createprimary for policypassword" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/pp_primary.ctx"
run_test "create ECC key with policypassword + password" \
tpm2_create -g sha256 -G ecc \
-u "$TEST_TMPDIR/pp_key.pub" -r "$TEST_TMPDIR/pp_key.priv" \
-C "$TEST_TMPDIR/pp_primary.ctx" \
-L "$TEST_TMPDIR/pp_policy.dat" -p testpswd
run_test "load policypassword key" \
tpm2_load -C "$TEST_TMPDIR/pp_primary.ctx" \
-u "$TEST_TMPDIR/pp_key.pub" -r "$TEST_TMPDIR/pp_key.priv" \
-c "$TEST_TMPDIR/pp_key.ctx"
echo "plaintext" > "$TEST_TMPDIR/pp_plain.txt"
# Sign with plain password (should work)
run_test "sign with plain password" \
tpm2_sign -c "$TEST_TMPDIR/pp_key.ctx" -p testpswd \
-o "$TEST_TMPDIR/pp_sig.bin" "$TEST_TMPDIR/pp_plain.txt"
run_test "verify signature (plain password)" \
tpm2_verifysignature -c "$TEST_TMPDIR/pp_key.ctx" \
-m "$TEST_TMPDIR/pp_plain.txt" -s "$TEST_TMPDIR/pp_sig.bin"
# Sign using policy session with PolicyPassword + password
run_test "startauthsession for policypassword sign (policy)" \
tpm2_startauthsession --policy-session -S "$TEST_TMPDIR/pp_sign_session.ctx"
run_test "policypassword (satisfy for sign)" \
tpm2_policypassword -S "$TEST_TMPDIR/pp_sign_session.ctx"
run_test "sign with policy session + password" \
tpm2_sign -c "$TEST_TMPDIR/pp_key.ctx" \
-p session:"$TEST_TMPDIR/pp_sign_session.ctx"+testpswd \
-o "$TEST_TMPDIR/pp_sig2.bin" "$TEST_TMPDIR/pp_plain.txt"
# Flush objects — verifysignature only needs the key
flush_transient
tpm2_createprimary -C o -c "$TEST_TMPDIR/pp_primary.ctx" > /dev/null 2>&1
tpm2_load -C "$TEST_TMPDIR/pp_primary.ctx" \
-u "$TEST_TMPDIR/pp_key.pub" -r "$TEST_TMPDIR/pp_key.priv" \
-c "$TEST_TMPDIR/pp_key.ctx" > /dev/null 2>&1
run_test "verify signature (policy + password)" \
tpm2_verifysignature -c "$TEST_TMPDIR/pp_key.ctx" \
-m "$TEST_TMPDIR/pp_plain.txt" -s "$TEST_TMPDIR/pp_sig2.bin"
# ----------------------------------------------------------------
hdr "PolicyAuthValue (sign with policy + authvalue)"
# Flush transient objects
flush_transient
run_test "startauthsession for policyauthvalue (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pa_trial.ctx"
run_test "policyauthvalue (trial)" \
tpm2_policyauthvalue -S "$TEST_TMPDIR/pa_trial.ctx" \
-L "$TEST_TMPDIR/pa_policy.dat"
run_test "flushcontext (policyauthvalue trial)" \
tpm2_flushcontext "$TEST_TMPDIR/pa_trial.ctx"
run_test "createprimary for policyauthvalue" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/pa_primary.ctx"
run_test "create ECC key with policyauthvalue + password" \
tpm2_create -g sha256 -G ecc \
-u "$TEST_TMPDIR/pa_key.pub" -r "$TEST_TMPDIR/pa_key.priv" \
-C "$TEST_TMPDIR/pa_primary.ctx" \
-L "$TEST_TMPDIR/pa_policy.dat" -p authpswd
run_test "load policyauthvalue key" \
tpm2_load -C "$TEST_TMPDIR/pa_primary.ctx" \
-u "$TEST_TMPDIR/pa_key.pub" -r "$TEST_TMPDIR/pa_key.priv" \
-c "$TEST_TMPDIR/pa_key.ctx"
echo "plaintext" > "$TEST_TMPDIR/pa_plain.txt"
# Sign using policy session with PolicyAuthValue + authvalue
run_test "startauthsession for policyauthvalue sign (policy)" \
tpm2_startauthsession --policy-session -S "$TEST_TMPDIR/pa_sign_session.ctx"
run_test "policyauthvalue (satisfy for sign)" \
tpm2_policyauthvalue -S "$TEST_TMPDIR/pa_sign_session.ctx"
run_test "sign with policyauthvalue session + authvalue" \
tpm2_sign -c "$TEST_TMPDIR/pa_key.ctx" \
-p session:"$TEST_TMPDIR/pa_sign_session.ctx"+authpswd \
-o "$TEST_TMPDIR/pa_sig.bin" "$TEST_TMPDIR/pa_plain.txt"
run_test "verify signature (policyauthvalue)" \
tpm2_verifysignature -c "$TEST_TMPDIR/pa_key.ctx" \
-m "$TEST_TMPDIR/pa_plain.txt" -s "$TEST_TMPDIR/pa_sig.bin"
# ----------------------------------------------------------------
hdr "PolicyCommandCode (unseal with command code policy)"
# Flush transient objects
flush_transient
run_test "createprimary for policycommandcode" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/pcc_primary.ctx"
run_test "startauthsession for policycommandcode (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pcc_trial.ctx"
run_test "policycommandcode (trial, Unseal)" \
tpm2_policycommandcode -S "$TEST_TMPDIR/pcc_trial.ctx" \
-L "$TEST_TMPDIR/pcc_policy.dat" TPM2_CC_Unseal
run_test "flushcontext (pcc trial)" \
tpm2_flushcontext "$TEST_TMPDIR/pcc_trial.ctx"
run_test "create sealed object with policycommandcode" \
bash -c 'echo -n "cc-sealed-data" | tpm2_create \
-C "'"$TEST_TMPDIR"'/pcc_primary.ctx" \
-i- -L "'"$TEST_TMPDIR"'/pcc_policy.dat" \
-u "'"$TEST_TMPDIR"'/pcc_sealed.pub" -r "'"$TEST_TMPDIR"'/pcc_sealed.priv"'
run_test "load policycommandcode sealed object" \
tpm2_load -C "$TEST_TMPDIR/pcc_primary.ctx" \
-u "$TEST_TMPDIR/pcc_sealed.pub" -r "$TEST_TMPDIR/pcc_sealed.priv" \
-c "$TEST_TMPDIR/pcc_sealed.ctx"
run_test "startauthsession for policycommandcode (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/pcc_session.ctx"
run_test "policycommandcode (satisfy, Unseal)" \
tpm2_policycommandcode -S "$TEST_TMPDIR/pcc_session.ctx" \
-L "$TEST_TMPDIR/pcc_policy.dat" TPM2_CC_Unseal
run_test "unseal with policycommandcode" \
tpm2_unseal -p "session:$TEST_TMPDIR/pcc_session.ctx" \
-c "$TEST_TMPDIR/pcc_sealed.ctx"
run_test "flushcontext (pcc policy session)" \
tpm2_flushcontext "$TEST_TMPDIR/pcc_session.ctx"
# ----------------------------------------------------------------
hdr "PolicyOR (unseal with OR of two PCR policies)"
# Flush transient objects
flush_transient
# Capture PolicyPCR digest for current PCR 23 state (set 1)
tpm2_pcrreset 23 2>/dev/null || true
run_test "startauthsession for policyor set1 (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/por_trial1.ctx"
run_test "policypcr set1 (PCR 23 = zeros)" \
tpm2_policypcr -S "$TEST_TMPDIR/por_trial1.ctx" \
-l sha256:23 -L "$TEST_TMPDIR/por_set1.policy"
run_test "flushcontext (por trial1)" \
tpm2_flushcontext "$TEST_TMPDIR/por_trial1.ctx"
# Extend PCR 23 and capture second policy
run_test "pcrextend PCR 23 for policyor set2" \
tpm2_pcrextend 23:sha256=e7011b851ee967e2d24e035ae41b0ada2decb182e4f7ad8411f2bf564c56fd6f
run_test "startauthsession for policyor set2 (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/por_trial2.ctx"
run_test "policypcr set2 (PCR 23 = extended)" \
tpm2_policypcr -S "$TEST_TMPDIR/por_trial2.ctx" \
-l sha256:23 -L "$TEST_TMPDIR/por_set2.policy"
run_test "flushcontext (por trial2)" \
tpm2_flushcontext "$TEST_TMPDIR/por_trial2.ctx"
# Build PolicyOR from the two PCR policies
run_test "startauthsession for policyor compound (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/por_trial3.ctx"
run_test "policyor (compound: set1 OR set2)" \
tpm2_policyor -S "$TEST_TMPDIR/por_trial3.ctx" \
-L "$TEST_TMPDIR/por_or.policy" \
"sha256:$TEST_TMPDIR/por_set1.policy,$TEST_TMPDIR/por_set2.policy"
run_test "flushcontext (por trial3)" \
tpm2_flushcontext "$TEST_TMPDIR/por_trial3.ctx"
# Create sealed object with PolicyOR
run_test "createprimary for policyor" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/por_primary.ctx"
run_test "create sealed object with PolicyOR" \
bash -c 'echo -n "or-secret" | tpm2_create \
-C "'"$TEST_TMPDIR"'/por_primary.ctx" \
-g sha256 -i- -L "'"$TEST_TMPDIR"'/por_or.policy" \
-u "'"$TEST_TMPDIR"'/por_sealed.pub" -r "'"$TEST_TMPDIR"'/por_sealed.priv"'
run_test "load policyor sealed object" \
tpm2_load -C "$TEST_TMPDIR/por_primary.ctx" \
-u "$TEST_TMPDIR/por_sealed.pub" -r "$TEST_TMPDIR/por_sealed.priv" \
-c "$TEST_TMPDIR/por_sealed.ctx"
# Unseal using set2 (current PCR state matches set2)
run_test "startauthsession for policyor unseal (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/por_session.ctx"
run_test "policypcr (satisfy set2)" \
tpm2_policypcr -S "$TEST_TMPDIR/por_session.ctx" -l sha256:23
run_test "policyor (satisfy compound)" \
tpm2_policyor -S "$TEST_TMPDIR/por_session.ctx" \
"sha256:$TEST_TMPDIR/por_set1.policy,$TEST_TMPDIR/por_set2.policy"
run_test "unseal with PolicyOR" \
tpm2_unseal -p "session:$TEST_TMPDIR/por_session.ctx" \
-c "$TEST_TMPDIR/por_sealed.ctx"
run_test "flushcontext (por policy session)" \
tpm2_flushcontext "$TEST_TMPDIR/por_session.ctx"
# ----------------------------------------------------------------
hdr "PolicySecret (unseal with hierarchy secret)"
# Flush transient objects and saved sessions
flush_transient
tpm2_flushcontext -s 2>/dev/null || true
if check_tool tpm2_policysecret; then
# Clear and set up fresh
tpm2_clear -c p 2>/dev/null || true
# Create policy: PolicySecret referencing owner hierarchy
run_test "startauthsession for policysecret (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/ps_trial.ctx"
run_test "policysecret (trial, owner hierarchy)" \
tpm2_policysecret -S "$TEST_TMPDIR/ps_trial.ctx" \
-c 0x40000001 -L "$TEST_TMPDIR/ps_policy.dat"
run_test "flushcontext (ps trial)" \
tpm2_flushcontext "$TEST_TMPDIR/ps_trial.ctx"
# Create sealed object protected by PolicySecret
run_test "createprimary for policysecret" \
tpm2_createprimary -C o -c "$TEST_TMPDIR/ps_primary.ctx"
run_test "create sealed object with policysecret" \
bash -c 'echo -n "secret-data" | tpm2_create \
-C "'"$TEST_TMPDIR"'/ps_primary.ctx" \
-g sha256 -i- -L "'"$TEST_TMPDIR"'/ps_policy.dat" \
-u "'"$TEST_TMPDIR"'/ps_sealed.pub" -r "'"$TEST_TMPDIR"'/ps_sealed.priv"'
run_test "load policysecret sealed object" \
tpm2_load -C "$TEST_TMPDIR/ps_primary.ctx" \
-u "$TEST_TMPDIR/ps_sealed.pub" -r "$TEST_TMPDIR/ps_sealed.priv" \
-c "$TEST_TMPDIR/ps_sealed.ctx"
# Satisfy PolicySecret and unseal
run_test "startauthsession for policysecret (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/ps_session.ctx"
run_test "policysecret (satisfy, owner hierarchy)" \
tpm2_policysecret -S "$TEST_TMPDIR/ps_session.ctx" \
-c 0x40000001
run_test "unseal with policysecret" \
tpm2_unseal -p "session:$TEST_TMPDIR/ps_session.ctx" \
-c "$TEST_TMPDIR/ps_sealed.ctx"
run_test "flushcontext (ps session)" \
tpm2_flushcontext "$TEST_TMPDIR/ps_session.ctx"
else
skip_test "PolicySecret" "tpm2_policysecret not available"
fi
# ----------------------------------------------------------------
hdr "PolicyAuthorize"
# Flush transient objects
flush_transient
if check_tool tpm2_policyauthorize && check_tool openssl; then
# Generate RSA signing key for authorization
openssl genrsa -out "$TEST_TMPDIR/pauth_priv.pem" 2048 2>/dev/null
openssl rsa -in "$TEST_TMPDIR/pauth_priv.pem" \
-out "$TEST_TMPDIR/pauth_pub.pem" -pubout 2>/dev/null
run_test "loadexternal (verifying key for policyauthorize)" \
tpm2_loadexternal -G rsa -C n \
-u "$TEST_TMPDIR/pauth_pub.pem" \
-c "$TEST_TMPDIR/pauth_vk.ctx" \
-n "$TEST_TMPDIR/pauth_vk.name"
# Create a PCR policy that we'll authorize
run_test "startauthsession for policyauthorize sub-policy (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pauth_sub.ctx"
run_test "policypcr (sub-policy for authorize)" \
tpm2_policypcr -S "$TEST_TMPDIR/pauth_sub.ctx" \
-l sha256:23 -L "$TEST_TMPDIR/pauth_pcr.policy"
run_test "flushcontext (pauth sub)" \
tpm2_flushcontext "$TEST_TMPDIR/pauth_sub.ctx"
# Generate PolicyAuthorize (trial)
run_test "startauthsession for policyauthorize (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pauth_trial.ctx"
run_test "policyauthorize (trial)" \
tpm2_policyauthorize -S "$TEST_TMPDIR/pauth_trial.ctx" \
-L "$TEST_TMPDIR/pauth_auth.policy" \
-n "$TEST_TMPDIR/pauth_vk.name"
run_test "flushcontext (pauth trial)" \
tpm2_flushcontext "$TEST_TMPDIR/pauth_trial.ctx"
tpm2_flushcontext "$TEST_TMPDIR/pauth_vk.ctx" 2>/dev/null || true
else
skip_test "PolicyAuthorize" "tpm2_policyauthorize or openssl not available"
fi
# ----------------------------------------------------------------
hdr "PolicyNV"
# Flush transient objects and saved sessions
flush_transient
tpm2_flushcontext -s 2>/dev/null || true
if check_tool tpm2_policynv; then
NV_POLICYNV_IDX=0x01500040
tpm2_clear -c p 2>/dev/null || true
# Define NV index, write a value, then test PolicyNV comparison
run_test "nvdefine for policynv" \
tpm2_nvdefine -C o -p nvpass "$NV_POLICYNV_IDX" \
-a "authread|authwrite" -s 1
run_test "nvwrite for policynv" \
bash -c 'echo -ne "\x81" | \
tpm2_nvwrite -P nvpass -i- '"$NV_POLICYNV_IDX"
# Test "eq" comparison (operandB == 0x81, should match)
run_test "startauthsession for policynv eq (policy)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pnv_session.ctx" \
--policy-session
run_test "policynv (eq comparison, 0x81 == 0x81)" \
bash -c 'echo -ne "\x81" | \
tpm2_policynv -S "'"$TEST_TMPDIR"'/pnv_session.ctx" \
-i- -P nvpass '"$NV_POLICYNV_IDX"' eq'
run_test "flushcontext (pnv session)" \
tpm2_flushcontext "$TEST_TMPDIR/pnv_session.ctx"
# Test "neq" comparison (operandB == 0x80, should pass since != 0x81)
run_test "startauthsession for policynv neq (policy)" \
tpm2_startauthsession -S "$TEST_TMPDIR/pnv_session2.ctx" \
--policy-session
run_test "policynv (neq comparison, 0x80 != 0x81)" \
bash -c 'echo -ne "\x80" | \
tpm2_policynv -S "'"$TEST_TMPDIR"'/pnv_session2.ctx" \
-i- -P nvpass '"$NV_POLICYNV_IDX"' neq'
run_test "flushcontext (pnv session2)" \
tpm2_flushcontext "$TEST_TMPDIR/pnv_session2.ctx"
run_test "nvundefine policynv index" \
tpm2_nvundefine "$NV_POLICYNV_IDX" -C o
else
skip_test "PolicyNV" "tpm2_policynv not available"
fi
# ================================================================
# NEGATIVE / FAILURE TEST CASES
# ================================================================
hdr "Negative Tests — ChangePPS/ChangeEPS Auth"
if check_tool tpm2_changepps && check_tool tpm2_changeeps; then
# Wrong platform password should fail
run_test_fail "changepps with wrong platform auth" \
tpm2_changepps -p wrongpass
run_test_fail "changeeps with wrong platform auth" \
tpm2_changeeps -p wrongpass
# ChangeEPS resets endorsement auth — set password then verify reset
tpm2_clear -c p 2>/dev/null || true
tpm2_changeauth -c e endorsepass 2>/dev/null || true
run_test "changeeps (resets endorsement auth)" \
tpm2_changeeps
run_test_fail "endorsement old auth rejected after changeeps" \
tpm2_createprimary -C e -P endorsepass -g sha256 -G ecc \
-c "$TEST_TMPDIR/ek_fail.ctx"
run_test "endorsement no-auth works after changeeps" \
tpm2_createprimary -C e -g sha256 -G ecc \
-c "$TEST_TMPDIR/ek_noauth.ctx"
tpm2_flushcontext "$TEST_TMPDIR/ek_noauth.ctx" 2>/dev/null
fi
hdr "Negative Tests — Auth Failures"
# Flush all transient/loaded/saved objects and clear for fresh state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
tpm2_clear -c p 2>/dev/null || true
# Wrong owner password
tpm2_changeauth -c o correctpass 2>/dev/null || true
run_test_fail "createprimary with wrong owner password" \
tpm2_createprimary -C o -P wrongpass -g sha256 -G rsa \
-c "$TEST_TMPDIR/neg_auth.ctx"
# Flush sessions and reset owner auth
tpm2_flushcontext -s 2>/dev/null || true
tpm2_changeauth -c o -p correctpass 2>/dev/null || true
# Wrong key password
run_test "createprimary for neg auth tests" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/neg_primary.ctx"
run_test "create key with password for neg test" \
tpm2_create -C "$TEST_TMPDIR/neg_primary.ctx" \
-g sha256 -G ecc -p mypassword \
-u "$TEST_TMPDIR/neg_key.pub" -r "$TEST_TMPDIR/neg_key.priv"
run_test "load key for neg auth test" \
tpm2_load -C "$TEST_TMPDIR/neg_primary.ctx" \
-u "$TEST_TMPDIR/neg_key.pub" -r "$TEST_TMPDIR/neg_key.priv" \
-c "$TEST_TMPDIR/neg_key.ctx"
echo "test" > "$TEST_TMPDIR/neg_msg.txt"
run_test_fail "sign with wrong key password" \
tpm2_sign -c "$TEST_TMPDIR/neg_key.ctx" -p wrongpassword \
-o "$TEST_TMPDIR/neg_sig.bin" "$TEST_TMPDIR/neg_msg.txt"
# Flush sessions leaked by failed auth
tpm2_flushcontext -s 2>/dev/null || true
run_test_fail "sign with no password (key has password)" \
tpm2_sign -c "$TEST_TMPDIR/neg_key.ctx" \
-o "$TEST_TMPDIR/neg_sig.bin" "$TEST_TMPDIR/neg_msg.txt"
# Flush sessions leaked by failed auth
tpm2_flushcontext -s 2>/dev/null || true
# ----------------------------------------------------------------
hdr "Negative Tests — Handle Errors"
# Use a flushed/invalid handle
run_test_fail "readpublic on invalid handle" \
tpm2_readpublic -c 0x80000099
# ----------------------------------------------------------------
hdr "Negative Tests — PCR Locality"
# PCR 0 is not resettable from locality 0
run_test_fail "pcrreset PCR 0 (wrong locality)" \
tpm2_pcrreset 0
# ----------------------------------------------------------------
hdr "Negative Tests — NV Errors"
# Flush all and clear to ensure clean NV state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
tpm2_clear -c p 2>/dev/null || true
NV_NEG_IDX=0x01500050
# Read undefined NV index
run_test_fail "nvread undefined index" \
tpm2_nvread "$NV_NEG_IDX" -C o -s 16
# Define, write-lock, then try to write
run_test "nvdefine for neg writelock test" \
tpm2_nvdefine "$NV_NEG_IDX" -C o -s 16 \
-a "ownerread|ownerwrite|writedefine"
run_test "nvwrite before neg writelock" \
bash -c 'echo -n "neg-lock-test!!!!" | head -c 16 | \
tpm2_nvwrite '"$NV_NEG_IDX"' -C o --input=-'
run_test "nvwritelock for neg test" \
tpm2_nvwritelock "$NV_NEG_IDX" -C o
run_test_fail "nvwrite after writelock (should fail)" \
bash -c 'echo -n "should-fail!!!!!" | head -c 16 | \
tpm2_nvwrite '"$NV_NEG_IDX"' -C o --input=-'
run_test "nvundefine neg writelock index" \
tpm2_nvundefine "$NV_NEG_IDX" -C o
# NV wrong auth
NV_NEG_AUTH_IDX=0x01500051
run_test "nvdefine with auth for neg test" \
tpm2_nvdefine "$NV_NEG_AUTH_IDX" -C o -s 16 \
-a "authread|authwrite" -p "correct"
run_test_fail "nvwrite with wrong NV auth" \
bash -c 'echo -n "wrong-auth-data!" | \
tpm2_nvwrite '"$NV_NEG_AUTH_IDX"' -P "wrong" --input=-'
# Flush sessions leaked by failed auth attempts
tpm2_flushcontext -s 2>/dev/null || true
run_test_fail "nvread with wrong NV auth" \
tpm2_nvread "$NV_NEG_AUTH_IDX" -P "wrong" -s 16
# Flush sessions leaked by failed auth attempts
tpm2_flushcontext -s 2>/dev/null || true
run_test "nvundefine neg auth index" \
tpm2_nvundefine "$NV_NEG_AUTH_IDX" -C o
# NV wrong type operation (increment on non-counter)
NV_NEG_TYPE_IDX=0x01500052
run_test "nvdefine ordinary for neg type test" \
tpm2_nvdefine "$NV_NEG_TYPE_IDX" -C o -s 16 \
-a "ownerread|ownerwrite"
run_test_fail "nvincrement on non-counter index (wrong type)" \
tpm2_nvincrement "$NV_NEG_TYPE_IDX" -C o
run_test "nvundefine neg type index" \
tpm2_nvundefine "$NV_NEG_TYPE_IDX" -C o
# ----------------------------------------------------------------
hdr "Negative Tests — Key Type Mismatches"
# Flush all to ensure clean state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
run_test "createprimary for neg key tests" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/neg_kp.ctx"
# Create a sign-only key, try to use for decrypt
run_test "create sign-only RSA key" \
tpm2_create -C "$TEST_TMPDIR/neg_kp.ctx" \
-g sha256 -G rsa:rsassa:null \
-u "$TEST_TMPDIR/neg_sign.pub" -r "$TEST_TMPDIR/neg_sign.priv" \
-a "sign|sensitivedataorigin|userwithauth"
run_test "load sign-only RSA key" \
tpm2_load -C "$TEST_TMPDIR/neg_kp.ctx" \
-u "$TEST_TMPDIR/neg_sign.pub" -r "$TEST_TMPDIR/neg_sign.priv" \
-c "$TEST_TMPDIR/neg_sign.ctx"
echo -n "test data" > "$TEST_TMPDIR/neg_plain.bin"
run_test_fail "rsadecrypt with sign-only key (wrong key usage)" \
tpm2_rsadecrypt -c "$TEST_TMPDIR/neg_sign.ctx" \
-o "$TEST_TMPDIR/neg_dec.bin" "$TEST_TMPDIR/neg_plain.bin"
tpm2_flushcontext -s 2>/dev/null || true
tpm2_flushcontext -t 2>/dev/null || true
# Create decrypt-only key, try to use for sign
# Re-create primary since we flushed transient objects
run_test "createprimary for neg decrypt test" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/neg_kp.ctx"
run_test "create decrypt-only RSA key" \
tpm2_create -C "$TEST_TMPDIR/neg_kp.ctx" \
-g sha256 -G rsa:null:null \
-u "$TEST_TMPDIR/neg_dec_key.pub" -r "$TEST_TMPDIR/neg_dec_key.priv" \
-a "decrypt|sensitivedataorigin|userwithauth"
run_test "load decrypt-only RSA key" \
tpm2_load -C "$TEST_TMPDIR/neg_kp.ctx" \
-u "$TEST_TMPDIR/neg_dec_key.pub" -r "$TEST_TMPDIR/neg_dec_key.priv" \
-c "$TEST_TMPDIR/neg_dec_key.ctx"
run_test_fail "sign with decrypt-only key (wrong key usage)" \
tpm2_sign -c "$TEST_TMPDIR/neg_dec_key.ctx" -g sha256 \
-o "$TEST_TMPDIR/neg_badsig.bin" "$TEST_TMPDIR/neg_msg.txt"
tpm2_flushcontext -s 2>/dev/null || true
# ----------------------------------------------------------------
hdr "Negative Tests — Policy Failures"
# Flush all for clean state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
# Create sealed object locked to PCR 23, extend PCR, try unseal (wrong PCR)
run_test "createprimary for neg policy tests" \
tpm2_createprimary -C o -g sha256 -G rsa \
-c "$TEST_TMPDIR/negpol_primary.ctx"
tpm2_pcrreset 23 2>/dev/null || true
run_test "startauthsession for neg policypcr (trial)" \
tpm2_startauthsession -S "$TEST_TMPDIR/negpol_trial.ctx"
run_test "policypcr neg (trial, PCR 23 current)" \
tpm2_policypcr -S "$TEST_TMPDIR/negpol_trial.ctx" \
-l sha256:23 -L "$TEST_TMPDIR/negpol_policy.bin"
run_test "flushcontext (negpol trial)" \
tpm2_flushcontext "$TEST_TMPDIR/negpol_trial.ctx"
run_test "create sealed key locked to current PCR 23" \
bash -c 'echo -n "pcr-locked-secret" | tpm2_create \
-C "'"$TEST_TMPDIR"'/negpol_primary.ctx" \
-i- -L "'"$TEST_TMPDIR"'/negpol_policy.bin" \
-u "'"$TEST_TMPDIR"'/negpol_sealed.pub" -r "'"$TEST_TMPDIR"'/negpol_sealed.priv"'
run_test "load neg policy sealed key" \
tpm2_load -C "$TEST_TMPDIR/negpol_primary.ctx" \
-u "$TEST_TMPDIR/negpol_sealed.pub" -r "$TEST_TMPDIR/negpol_sealed.priv" \
-c "$TEST_TMPDIR/negpol_sealed.ctx"
# Extend PCR 23 to change its value (policy will no longer match)
run_test "pcrextend PCR 23 (invalidate policy)" \
tpm2_pcrextend 23:sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
run_test "startauthsession for neg policypcr (policy)" \
tpm2_startauthsession --policy-session \
-S "$TEST_TMPDIR/negpol_session.ctx"
# PolicyPCR will succeed (it just records current PCR), but the
# resulting policy digest won't match the sealed object's policy
run_test "policypcr neg (satisfy with wrong PCR)" \
tpm2_policypcr -S "$TEST_TMPDIR/negpol_session.ctx" -l sha256:23
run_test_fail "unseal with wrong PCR value (policy mismatch)" \
tpm2_unseal -p "session:$TEST_TMPDIR/negpol_session.ctx" \
-c "$TEST_TMPDIR/negpol_sealed.ctx"
tpm2_flushcontext "$TEST_TMPDIR/negpol_session.ctx" 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
# Try unseal without any policy session (no auth at all)
run_test_fail "unseal without policy session (no auth)" \
tpm2_unseal -c "$TEST_TMPDIR/negpol_sealed.ctx"
tpm2_flushcontext -s 2>/dev/null || true
# ----------------------------------------------------------------
hdr "Negative Tests — State Violations"
# Note: tpm2_startup via mssim TCTI always sends a platform POWER_ON first,
# which resets the TPM boot state. Cannot test double-startup via tpm2-tools.
# fwTPM correctly returns TPM_RC_INITIALIZE for actual double startup
# (tested via wolfTPM wrapper examples).
run_test "double startup (mssim power-cycles — always succeeds)" \
tpm2_startup -c
# ----------------------------------------------------------------
hdr "Negative Tests — Unsupported Algorithms"
run_test_fail "testparms unsupported algorithm (rsa512)" \
tpm2_testparms rsa512
run_test_fail "createprimary with unsupported key size" \
tpm2_createprimary -C o -g sha256 -G rsa512 \
-c "$TEST_TMPDIR/neg_unsup.ctx"
# ----------------------------------------------------------------
hdr "Negative Tests — Disabled Hierarchy"
# Flush all for clean state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
# Disable owner hierarchy, try to create under it
run_test "hierarchycontrol (disable owner for neg test)" \
tpm2_hierarchycontrol -C p shEnable clear
# Note: fwTPM allows operations even when hierarchy is disabled
# (it only tracks the enable flag but doesn't enforce on all commands).
# Test disabling and re-enabling to verify the command works.
run_test "hierarchycontrol (re-enable owner)" \
tpm2_hierarchycontrol -C p shEnable set
# ----------------------------------------------------------------
hdr "Negative Tests — NV Read Lock"
# Flush all and clear for clean NV state
tpm2_flushcontext -t 2>/dev/null || true
tpm2_flushcontext -l 2>/dev/null || true
tpm2_flushcontext -s 2>/dev/null || true
tpm2_clear -c p 2>/dev/null || true
NV_RLOCK_IDX=0x01500060
run_test "nvdefine for neg readlock test" \
tpm2_nvdefine "$NV_RLOCK_IDX" -C o -s 16 \
-a "ownerread|ownerwrite|read_stclear"
run_test "nvwrite for neg readlock" \
bash -c 'echo -n "readlock-test!!!" | head -c 16 | \
tpm2_nvwrite '"$NV_RLOCK_IDX"' -C o --input=-'
run_test "nvread before readlock (should work)" \
tpm2_nvread "$NV_RLOCK_IDX" -C o -s 16
if check_tool tpm2_nvreadlock; then
run_test "nvreadlock" \
tpm2_nvreadlock "$NV_RLOCK_IDX" -C o
run_test_fail "nvread after readlock (should fail)" \
tpm2_nvread "$NV_RLOCK_IDX" -C o -s 16
else
skip_test "nvreadlock" "tpm2_nvreadlock not available"
fi
run_test "nvundefine neg readlock index" \
tpm2_nvundefine "$NV_RLOCK_IDX" -C o
# ----------------------------------------------------------------
# Results
# ----------------------------------------------------------------
printf "\n========================================\n"
printf " tpm2-tools Compatibility Results\n"
printf "========================================\n"
printf " PASS %4d\n" $PASS
printf " FAIL %4d\n" $FAIL
printf " SKIP %4d\n" $SKIP
printf "========================================\n"
[ $FAIL -eq 0 ]