swtpm/tests/test_tpm2_libtpms_versions_profiles
Stefan Berger 0a1efc5b79 tests: Pass --disable-tests when building swtpm across versions
Older versions of swtpm require the expect package to be installed, but we
do not need it anymore and will get rid of it. Pass --disable-tests when
building older versions of swtpm to avoid the older build scripts to check
whether 'expect' is installed -- which may not be the case anymore.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
2026-05-05 17:19:48 -04:00

1089 lines
29 KiB
Bash
Executable File

#!/usr/bin/env bash
#set -x
# shellcheck disable=SC2090
# For the license, see the LICENSE file in the root directory.
# This test case builds various versions of libtpms + swtpm and generates
# TPM 2 state using swtpm_setup with profiles, if supported. After that
# it starts the built versions of swtpm and tries to use the state that
# was created possibly by another version of libtpms and runs tests with it.
# Currently this only works with the null profile whose state can be created
# and read either with libtpms v0.9 or v0.10. In the future tests can be
# covering the default-v1 profile as well that must be exchageable with
# libtpms >v0.10.
if [ "${SWTPM_TEST_EXPENSIVE:-0}" -eq 0 ]; then
echo "SWTPM_TEST_EXPENSIVE must be set to run this test."
exit 77
fi
if [ "${SWTPM_TEST_IBMTSS2:-0}" -eq 0 ]; then
echo "SWTPM_TEST_IBMTSS2 must be set to run this test."
exit 77
fi
ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:-$(dirname "$0")}
SRCDIR=${abs_top_srcdir:-$(dirname "$0")/..}
source "${TESTDIR}/common"
workdir="$(mktemp -d)" || exit 1
export TPM_PATH=${workdir}
SWTPM_SERVER_PORT=65478
SWTPM_CTRL_PORT=65479
SWTPM_SERVER_NAME=127.0.0.1
SWTPM_INTERFACE="socket+socket"
LIBTPMS_URL=https://github.com/stefanberger/libtpms
LIBTPMS_INITIAL_BRANCH=master
SWTPM_URL=https://github.com/stefanberger/swtpm
SWTPM_DEFAULT_BRANCH=master # during development change to local branch
cat <<_EOF_ > "${workdir}/swtpm-localca.options"
--tpm-manufacturer IBM
--tpm-model swtpm-libtpms
--tpm-version 2
--platform-manufacturer "Fedora XYZ"
--platform-version 2.1
--platform-model "QEMU A.B"
_EOF_
cat <<_EOF_ > "${workdir}/swtpm_setup.conf"
create_certs_tool=\${SWTPM_LOCALCA}
create_certs_tool_config=${workdir}/swtpm-localca.conf
create_certs_tool_options=${workdir}/swtpm-localca.options
_EOF_
# Copy swtpm source tree to workdir
pushd "${SRCDIR}" &>/dev/null || exit 1
if [ ! -d ".git" ]; then
echo "SKIP: This test must be run from a git checkout"
exit 77
fi
mkdir -p "${workdir}/swtpm"
cp -rp . "${workdir}/swtpm"
cd "${workdir}/swtpm" || exit 1
chmod -R 0755 . # when using 'distcheck'
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
# store all current modifications in a temp patch
git config --local user.name test
git config --local user.email test@test.test
git add --all . >/dev/null
git commit -m "temp" >/dev/null
fi
git clean -xdf &>/dev/null
popd &>/dev/null || exit 1
function cleanup()
{
rm -rf "${workdir}"
if kill_quiet -0 "${SWTPM_PID}"; then
kill_quiet -9 "${SWTPM_PID}"
fi
}
trap "cleanup" SIGTERM EXIT
function nv_storefile()
{
local nvindex="$1"
local filename="$2"
local sz
if ! sz=$(get_filesize "${filename}"); then
return 1
fi
if ! tssnvdefinespace -ha "${nvindex}" -hi o -pwdn nv -sz "${sz}" 1>/dev/null; then
return 1
fi
if ! tssnvwrite -ha "${nvindex}" -pwdn nv -if "${filename}"; then
return 1
fi
return 0
}
function nv_savetofile()
{
local nvindex="$1"
local filename="$2"
local sz
if ! tssnvread -ha "${nvindex}" -pwdn nv -of "${filename}" >/dev/null; then
return 1
fi
sz=$(get_filesize "${filename}")
if [ "${sz}" -eq 0 ]; then
# older versions required the -sz parameter
sz=$(tssnvreadpublic -ha "${nvindex}" | sed -n 's/.*data size //p')
if ! tssnvread -ha "${nvindex}" -pwdn nv -sz "${sz}" -of "${filename}" >/dev/null; then
return 1
fi
fi
return 0
}
function create_tpm_state()
{
local workdir="$1"
local inputfile="${workdir}/input.txt"
local encfile="${workdir}/enc"
local signature="${workdir}/signature.bin"
local contextfile="${workdir}/context.bin"
local aespub="${workdir}/aespub.bin"
local aespriv="${workdir}/aespriv.bin"
local cc rsasize ecdsaparam sz_hi sz_lo sz i j
echo "input" > "${inputfile}"
export TPM_COMMAND_PORT=${SWTPM_SERVER_PORT}
export TPM_PLATFORM_PORT=${SWTPM_CTRL_PORT}
export TPM_SERVER_NAME=${SWTPM_SERVER_NAME}
export TPM_INTERFACE_TYPE=socsim
export TPM_SERVER_TYPE=raw
if tsscreateprimary --help | grep rsa | grep -q keybits; then
rsasize="3072"
fi
# Test signing with RSA 3072 key
if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo -si 1>/dev/null; then
return 1
fi
if ! tsssign -hk 80000000 -pwdk ooo -if "${inputfile}" -os "${signature}"; then
return 1
fi
if ! nv_storefile 01000000 "${signature}"; then
return 1
fi
# Save the key as contextfile
if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null; then
return 1
fi
if ! nv_storefile 01000001 "${contextfile}"; then
return 1
fi
# Flush all keys
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Test HMAC
# tsssign -salg was added in a later version only; Ubuntu Jammy does not have it
if tsssign -h | grep -q "salg"; then
if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo 1>/dev/null; then
return 1
fi
if ! tsscreate -hp 80000000 -pwdp ooo -kh -kt f -kt p -opu "${aespub}" -opr "${aespriv}" -halg sha256; then
return 1
fi
if ! nv_storefile 01000002 "${aespub}" || ! nv_storefile 01000003 "${aespriv}" ; then
return 1
fi
if ! tssload -hp 80000000 -pwdp ooo -ipu "${aespub}" -ipr "${aespriv}" 1>/dev/null; then
return 1
fi
if ! tsssign -hk 80000001 -if "${inputfile}" -salg hmac -scheme hmac -os "${signature}"; then
return 1
fi
if ! nv_storefile 01000004 "${signature}"; then
return 1
fi
if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null || \
! nv_storefile 01000005 "${contextfile}" || \
! tsscontextsave -ha 80000001 -of "${contextfile}" 1>/dev/null || \
! nv_storefile 01000006 "${contextfile}"; then
return 1
fi
# Flush all keys
if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
return 1
fi
fi
# Encryption/Decryption
if ! tsscreateprimary -hi o -des 1>/dev/null; then
return 1
fi
if ! tssencryptdecrypt -hk 80000000 -if "${inputfile}" -of "${encfile}"; then
return 1
fi
if ! nv_storefile 01000007 "${encfile}"; then
return 1
fi
if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null || \
! nv_storefile 01000008 "${contextfile}"; then
return 1
fi
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Signing with a NIST P256 key
if ! tsscreateprimary -ecc nistp256 -hi o -pwdk ooo -si 1>/dev/null; then
return 1
fi
ecdsaparam="-scheme ecdsa"
# older tools had: [-ecc (ECDSA scheme)]
if tsssign --help | grep ecc | grep -q scheme; then
ecdsaparam="-ecc"
fi
if ! tsssign ${ecdsaparam:+${ecdsaparam}} -hk 80000000 -pwdk ooo -if "${inputfile}" -os "${signature}"; then
return 1
fi
if ! nv_storefile 01000009 "${signature}"; then
return 1
fi
# Save the key as contextfile
if ! tsscontextsave -ha 80000000 -of "${contextfile}" 1>/dev/null; then
return 1
fi
if ! nv_storefile 0100000a "${contextfile}"; then
return 1
fi
# Flush all keys
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Test setting command audit if available; Ubuntu Jammy does not have it
if type -P tsssetcommandcodeauditstatus >/dev/null; then
# Set a couple of commands to be audited
for cc in 11f 12a 133 13a 140 147 150 15c 16e 17a 187 190 197; do
if ! tsssetcommandcodeauditstatus -hi o -set $cc; then
return 1
fi
done
fi
# Add new test here before NVRAM fill-up
echo -n "INFO: Filling up NVRAM with persistent keys: 0x81001000 .. "
if ! nv_savetofile 01000001 "${contextfile}"; then
return 1
fi
if ! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
for ((i = 0x81001000; i < 0x81001050; i++)); do
tssevictcontrol \
-hi o \
-ho 80000000 \
-hp "$(printf "%x" "$i")" &>/dev/null || break
done
echo "0x$(printf "%08x" "$((i-1))")"
# Flush the key
if ! tssflushcontext -ha 80000000; then
return 1
fi
echo -n "INFO: Filling up NVRAM with 2kb indices: "
for ((i = 0x01000100; i < 0x01000200; i++)); do
tssnvdefinespace \
-ha "$(printf "0x%08x" "$i")" \
-hi o \
-sz 2048 1>/dev/null || break
done
if [ "${i}" != $((0x01000100)) ]; then
echo "0x01000100 .. 0x$(printf "%08x" "$((i-1))")"
else
echo "(no space for 2kb indices)"
fi
echo -n "INFO: Filling up last NVRAM index: "
# bisect to quickly find approximate size; max. 12 steps for 2048 bytes
sz_hi=2048
sz_lo=0
for ((j = 0; j < 12; j++)); do
sz=$(((sz_hi + sz_lo) / 2))
if ! tssnvdefinespace -ha "$(printf "0x%08x" "$i")" -hi o -sz "${sz}" 1>/dev/null; then
sz_hi=${sz}
else
sz_lo=${sz}
tssnvundefinespace -ha "$(printf "0x%08x" "$i")" -hi o
fi
done
# ... this should find it quickly now
for ((sz = $((sz_hi+1)); sz > 0; sz--)); do
tssnvdefinespace \
-ha "$(printf "0x%08x" "$i")" \
-hi o \
-sz "${sz}" 1>/dev/null && break
done
if [ "${sz}" -gt 0 ]; then
echo "0x$(printf "%08x" "$i") size: ${sz}"
else
echo "(no space to create it)"
fi
return 0
}
# Find the last handle in 'a sequence of handles' given the first one
# @first: First handle; must start with '0x'
function find_last_handle()
{
local first="$1"
local last tmp
first=$(tssgetcapability -cap 1 -pr "${first}" |
grep "${first:2}" |
head -n1 |
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
last="0x${first}"
while :; do
tmp=$(tssgetcapability -cap 1 -pr "${last}" -pc 128 | \
tail -n1 |
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
[[ "0x${tmp}" = "${last}" ]] && break
last="0x${tmp}"
done
echo "${last}"
return 0
}
function check_tpm_state()
{
local workdir="$1"
local is_fullresume="$2"
local inputfile="${workdir}/input.txt"
local encfile="${workdir}/enc"
local decfile="${workdir}/dec"
local signature="${workdir}/signature.bin"
local contextfile="${workdir}/context.bin"
local aespub="${workdir}/aespub.bin"
local aespriv="${workdir}/aespriv.bin"
local cc rsasize first last tmp
echo "input" > "${inputfile}"
export TPM_COMMAND_PORT=${SWTPM_SERVER_PORT}
export TPM_PLATFORM_PORT=${SWTPM_CTRL_PORT}
export TPM_SERVER_NAME=${SWTPM_SERVER_NAME}
export TPM_INTERFACE_TYPE=socsim
export TPM_SERVER_TYPE=raw
if tsscreateprimary --help | grep rsa | grep -q keybits; then
rsasize="3072"
fi
# Test RSA 3072 signing key
if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo -si 1>/dev/null; then
return 1
fi
if ! nv_savetofile 01000000 "${signature}"; then
return 1
fi
if ! tssverifysignature -hk 80000000 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified signature with RSA key"
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Test with the key stored in context; this only works with save/restore of all state
if [ "${is_fullresume}" -ne 0 ]; then
if ! nv_savetofile 01000001 "${contextfile}"; then
return 1
fi
if ! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
if ! tssverifysignature -hk 80000000 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified signature with RSA key and restored key context"
if ! tssflushcontext -ha 80000000; then
return 1
fi
fi
# HMAC test
# tsssign -salg was added in a later version only
if tsssign -h | grep -q "salg"; then
if ! tsscreateprimary -rsa ${rsasize:+${rsasize}} -hi o -pwdk ooo 1>/dev/null; then
return 1
fi
if ! nv_savetofile 01000002 "${aespub}" \
|| ! nv_savetofile 01000003 "${aespriv}" \
|| ! nv_savetofile 01000004 "${signature}"; then
return 1
fi
if ! tssload -hp 80000000 -pwdp ooo -ipu "${aespub}" -ipr "${aespriv}" 1>/dev/null; then
return 1
fi
if ! tssverifysignature -hk 80000001 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified HMAC"
if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
return 1
fi
# Test with the keys stored in contexts; this only works with save/restore of all state
if [ "${is_fullresume}" -ne 0 ]; then
if ! nv_savetofile 01000005 "${contextfile}" || \
! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
if ! nv_savetofile 01000006 "${contextfile}" || \
! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
if ! tssverifysignature -hk 80000001 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified HMAC with restored key context"
if ! tssflushcontext -ha 80000000 || ! tssflushcontext -ha 80000001; then
return 1
fi
fi
fi
# Encryption/Decryption
if ! tsscreateprimary -hi o -des 1>/dev/null; then
return 1
fi
if ! nv_savetofile 01000007 "${encfile}"; then
return 1
fi
if ! tssencryptdecrypt -hk 80000000 -d -if "${encfile}" -of "${decfile}"; then
return 1
fi
if [ "$(get_sha1_file "${inputfile}")" != "$(get_sha1_file "${decfile}")" ]; then
return 1
fi
echo "INFO: Verified decryption"
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Test with the key stored in context; this only works with save/restore of all state
if [ "${is_fullresume}" -ne 0 ]; then
if ! nv_savetofile 01000008 "${contextfile}" || \
! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
if ! tssencryptdecrypt -hk 80000000 -d -if "${encfile}" -of "${decfile}"; then
return 1
fi
if [ "$(get_sha1_file "${inputfile}")" != "$(get_sha1_file "${decfile}")" ]; then
return 1
fi
echo "INFO: Verified decryption with restored key context"
if ! tssflushcontext -ha 80000000; then
return 1
fi
fi
# Test NIST p256 signing key
if ! tsscreateprimary -ecc nistp256 -hi o -pwdk ooo -si 1>/dev/null; then
return 1
fi
if ! nv_savetofile 01000009 "${signature}"; then
return 1
fi
if ! tssverifysignature -ecc -hk 80000000 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified signature with RSA key"
if ! tssflushcontext -ha 80000000; then
return 1
fi
# Test with the key stored in context; this only works with save/restore of all state
if [ "${is_fullresume}" -ne 0 ]; then
if ! nv_savetofile 0100000a "${contextfile}"; then
return 1
fi
if ! tsscontextload -if "${contextfile}" 1>/dev/null; then
return 1
fi
if ! tssverifysignature -ecc -hk 80000000 -if "${inputfile}" -is "${signature}"; then
return 1
fi
echo "INFO: Verified signature with RSA key and restored key context"
if ! tssflushcontext -ha 80000000; then
return 1
fi
fi
# Test the audited commands is command was available to set audited commands
if [ "${is_fullresume}" -ne 0 ]; then
if type -P tsssetcommandcodeauditstatus >/dev/null; then
for cc in 11f 12a 133 13a 140 147 150 15c 16e 17a 187 190 197; do
if ! tssgetcapability -cap 4 | grep -q 00000${cc}; then
echo "Error: Audit not set for command 0000${cc}"
tssgetcapability -cap 4
return 1
fi
done
echo "INFO: Verified audited commands"
fi
fi
# Find which persisted keys there are:
first="0x81001000"
last="$(find_last_handle "${first}")"
echo "INFO: Found persistent keys indices: ${first} .. ${last}"
# Check signature with persisted keys
if ! nv_savetofile 01000000 "${signature}"; then
return 1
fi
for ((i="${first}"; i <= "${last}"; i++)); do
if ! tssverifysignature -hk "$(printf "0x%08x" "${i}")" -if "${inputfile}" -is "${signature}"; then
echo "Error: Key at 0x$(printf "%08x" "$i") failed to verify signature"
return 1
fi
done
echo "INFO: All persisted keys verified signature successfully"
first="0x01000100"
last="$(find_last_handle "$first")"
echo "INFO: Found NVRAM indices: ${first} ... ${last}"
tmp=$(tssnvreadpublic -ha "${last}" | sed -n 's/.*data size //p')
echo "INFO: Last NVRAM index size: ${tmp}"
return 0
}
# Build swtpm
#
# @param1: The directory where to do the git checkout into
# @param2: CFLAGS to use for the build
# @param3: Where to copy the executables to
# @param4: The git version to tag to build; if it does not exist (yet), fall
# back to using 'master'
function build_swtpm()
{
local srcdir="$1"
local cflags="$2"
local destdir="$3"
local swtpm_git_version="$4"
echo -e "\nBuilding swtpm ${swtpm_git_version} with CFLAGS=${cflags} ..."
pushd "${srcdir}" &>/dev/null || exit 1
git clean -xdf &>/dev/null
git reset --hard HEAD
if ! git checkout "${swtpm_git_version}"; then
# Travis needs this
git remote add upstream "${SWTPM_URL}"
git fetch upstream "${swtpm_git_version}"
if ! git checkout "upstream/${swtpm_git_version}"; then
echo "WARN: Could not checkout swtpm's ${swtpm_git_version}. Falling back to 'master'."
git checkout upstream/master
fi
fi
PKG_CONFIG_PATH="${destdir}" CFLAGS="${cflags}" \
./autogen.sh --without-cuse --without-selinux --without-seccomp --disable-tests
make -j "$(nproc)" || return 1
cp \
src/swtpm/.libs/swtpm src/swtpm/.libs/libswtpm*.so* \
src/swtpm_setup/swtpm_setup \
src/swtpm_ioctl/swtpm_ioctl \
"${destdir}"
popd &>/dev/null || exit 1
}
# Copy a compiled libtpms version to its destination directory; the destination
# directory will be created
# @param1: Destination directory's parent
# @param2: The version of libtpms, e.g. '0.9' or 'master'
function copy_libtpms()
{
local dest="$1"
local version="$2"
local destdir="${dest}/libtpms-${version}"
mkdir -p "${destdir}"
cp libtpms.pc src/.libs/libtpms.so* "${destdir}"
return 0
}
# Build various versions of libtpms starting with libtpms 0.9 up to 'master'
# Build swtpm for each one of these versions as well.
function build_libtpms_and_swtpm()
{
local workdir="$1"
local tmp major maj min swtpm_branch
local libtpmsdir="${workdir}/libtpms"
pushd "${workdir}" 2>&1 || return 1
git clone "${LIBTPMS_URL}" libtpms
pushd libtpms 2>&1 || return 1
git checkout "${LIBTPMS_INITIAL_BRANCH}"
echo -e "\nBuilding libtpms ${LIBTPMS_INITIAL_BRANCH} ..."
CFLAGS="-g -O2" ./autogen.sh --with-tpm2 --without-tpm1
make -j "$(nproc)" || return 1
copy_libtpms "${workdir}" "master"
# Determine major version of what master built
tmp=$(ls src/.libs/libtpms.so.*.*)
major=$(echo "${tmp}" | sed -n 's/.*.so\.\([^\.]\+\)\.\([^\.]\+\)\..*/\1/p')
if ! build_swtpm \
"${workdir}/swtpm" \
"-I${libtpmsdir}/include -L${workdir}/libtpms-master" \
"${workdir}/libtpms-master" \
"${SWTPM_DEFAULT_BRANCH}"; then
echo "Error: Could not build swtpm"
return 1
fi
for ((maj=0; maj<=major; maj++)) {
min=0
[ "${maj}" = 0 ] && min=9 # start with v0.9
for ((;; min++)) {
git checkout "origin/stable-${maj}.${min}" &>/dev/null || break
# clean directory containing libtpms.so.*
rm -rf src/.libs
echo -e "\nBuilding libtpms ${maj}.${min}..."
make -j "$(nproc)" || return 1
copy_libtpms "${workdir}" "${maj}.${min}"
# Based on libtpms version select swtpm branch
case "${maj}.${min}" in
"0.9") swtpm_branch="stable-0.9";;
"0.10") swtpm_branch="stable-0.10";;
"0.11") swtpm_branch="stable-0.11";; # will fall back to master
*) swtpm_branch="master";;
esac
if ! build_swtpm \
"${workdir}/swtpm" \
"-I${libtpmsdir}/include -L${workdir}/libtpms-${maj}.${min}" \
"${workdir}/libtpms-${maj}.${min}" \
"${swtpm_branch}"; then
echo "Error: Could not build swtpm"
return 1
fi
}
}
popd &>/dev/null || return 1
#ls -l libtpms-*
#PATH="${workdir}/libtpms-master" LD_LIBRARY_PATH=${PATH} ./libtpms-master/swtpm_setup --help
#PATH="${workdir}/libtpms-0.9" LD_LIBRARY_PATH=${PATH} ./libtpms-0.9/swtpm_setup socket --help
popd &>/dev/null || return 1
return 0
}
# Run swtpm_setup on all versions >= v0.10 of libtpms. The state is written
# into directories with the name patterns of state--${branch}--${profile},
# which leads to names like state--0.10--null for null profile and libtpms v0.10
function swtpm_setup_create_profile_state()
{
local workdir="$1"
local branch profiles libtpmsdir statedir err=0 has_option
echo -e "INFO: Creating state with various profiles\n"
pushd "${workdir}" &>/dev/null || return 1
# Setup swtpm-0.10 and later with null profile and check that swtpm-0.9 works with it
for libtpmsdir in libtpms-master libtpms-*.*; do
branch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')
export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
export SWTPM_IOCTL=./${libtpmsdir}/swtpm_ioctl
echo
has_option=$(PATH="${workdir}/${libtpmsdir}" \
swtpm socket --help | grep -E "\-profile")
if [ -n "${has_option}" ]; then
# get a space-separated list of profile names
profiles=$(PATH="${workdir}/${libtpmsdir}" \
swtpm socket --tpm2 --print-capabilities \
| sed -n \
-e "s/.* \"profiles\": { \"names\": \[ \(.*\) \].*/\1/" \
-e 's/[ "]//g' -e 's/,/ /gp')
else
profiles="<none>"
fi
for profile in ${profiles}; do
local profileopt=""
if [ "${profile}" = "<none>" ]; then
profile=null
else
# shellcheck disable=SC2089
profileopt="--profile {\"Name\":\"${profile}\"}"
fi
statedir="state--${branch}--${profile}"
mkdir "${statedir}"
echo
PATH="${workdir}/${libtpmsdir}" \
swtpm -v
printf "state dir:%-28s libtpms dir:%-15s profile: %s\n" \
"${statedir}" "${libtpmsdir}" "${profile}"
if ! PATH="${workdir}/${libtpmsdir}" \
"./${libtpmsdir}/swtpm_setup" \
--tpm2 \
--tpmstate "${statedir}" \
--config "${workdir}/swtpm_setup.conf" \
--log "${statedir}/swtpm_setup.log" \
--tpm "${workdir}/${libtpmsdir}/swtpm socket" \
${profileopt:+${profileopt}}; then
echo "Error: Could not run swtpm_setup with libtpms from branch ${branch} with profile ${profile}"
err=1
break
fi
SWTPM_PID=""
if ! SWTPM_SERVER_NO_DISCONNECT=1 \
SWTPM_EXE=./${libtpmsdir}/swtpm \
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--tpmstate "dir=${workdir}/${statedir}" \
--flags not-need-init,startup-clear; then
echo "Error: Could not start swtpm with null profile state and libtpms version '${libtpmsbranch}'."
err=1
break
fi
if ! create_tpm_state "${workdir}"; then
echo "Error: Creating TPM-internal state failed"
err=1
break
fi
echo "INFO: Successfully created TPM-internal state"
if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --save permanent "${workdir}/${statedir}/permanent" \
|| ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --save volatile "${workdir}/${statedir}/volatile"; then
echo "Error: Could not save permanent or volatile state"
err=1
break
fi
echo "INFO: Successfully saved permanent and volatile state to ${workdir}/${statedir}/"
if ! check_tpm_state "${workdir}" 1; then
echo "Error: Checking TPM-internal state failed"
err=1
break
fi
echo "INFO: Successfully checked proper functioning of TPM-internal state"
# Use kill here rather than a shutdown to be able to compare the
# size of the state later on. A shutdown would generate a bigger
# tpm2-00.permall file that cannot be compare with while the TPM 2
# is running.
kill_quiet -9 "${SWTPM_PID}"
SWTPM_PID=""
echo "INFO: Sizes of state in ${workdir}/${statedir}"
ls -l "${workdir}/${statedir}"
done
if [ -n "${SWTPM_PID}" ]; then
kill_quiet -9 "${SWTPM_PID}"
fi
if [ "${err}" -ne 0 ]; then
break
fi
done
unset LD_LIBRARY_PATH
popd &>/dev/null || return 1
echo -e "INFO: Done creating state with various profiles\n\n"
return "${err}"
}
# This function tests that a profile state can be used by swtpm with other
# versions of libtpms. A given regular expression allows to skip versions
# of libtpms that did not support this profile.
#
# @param1: The workdir
# @param2: The name of the profile
# @param3: A regular expression for libtpms versions to skip since this libtpms
# did not support the given profile
function swtpm_run_with_profile_state()
{
local workdir="$1"
local profile="$2"
local skip_regex="$3"
local statedir branch libtpmsbranch resp exp fsize err=0
echo "INFO: Running with ${profile} profile states using different version of libtpms"
pushd "${workdir}" &>/dev/null || return 1
for statedir in state--*--"${profile}"; do
branch=$(echo "${statedir}" | sed -n 's/.*--\(.*\)--.*/\1/p')
for libtpmsdir in libtpms-master libtpms-*.*; do
libtpmsbranch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')
[[ ${libtpmsbranch} =~ ${skip_regex} ]] && continue
fsize=$(get_filesize "${workdir}/${statedir}/tpm2-00.permall")
export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
export SWTPM_IOCTL="./${libtpmsdir}/swtpm_ioctl"
echo -e "\nINFO: Using swtpm in ${libtpmsdir} with TPM state in ${workdir}/${statedir}"
if ! SWTPM_SERVER_NO_DISCONNECT=1 \
SWTPM_EXE=./${libtpmsdir}/swtpm \
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--tpmstate "dir=${workdir}/${statedir}" \
--flags not-need-init,startup-clear; then
echo "Error: Could not start swtpm with ${profile} profile state and libtpms version '${libtpmsbranch}'."
err=1
break
fi
# Read PCR 10
exp=' 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 '
resp=$(tsspcrread -ha 10 -halg sha256 | grep " 00" | tr -d "\n" | tr -s " ")
if [ "${resp}" != "${exp}" ]; then
echo "Error: Did not get expected result from TPM2_PCRRead(10)"
echo "expected: ${exp}"
echo "received: ${resp}"
err=1
break
fi
printf " PCR 10 is good: profile: %-11s libtpms: %-8s state written by: %s\n" \
"${profile}" "${libtpmsbranch}" "${branch}"
if [ "$(get_filesize "${workdir}/${statedir}/tpm2-00.permall")" != "${fsize}" ]; then
echo "Error: Size of tpm2 state file has changed"
echo "expected: ${fsize}"
echo "actual : $(get_filesize "${workdir}/${statedir}/tpm2-00.permall")"
echo "State in ${workdir}/${statedir}:"
ls -l "${workdir}/${statedir}/"
err=1
break
fi
if ! check_tpm_state "${workdir}" 0; then
echo "Error: Checking TPM-internal state failed: profile: ${profile} libtpms: ${libtpmsbranch} state written by: ${branch}"
err=1
break
fi
echo "INFO: Successfully checked proper functioning of TPM-internal state"
# restore permanent and volatile state and run tests again
if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --stop \
|| ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --load permanent "${workdir}/${statedir}/permanent" \
|| ! run_swtpm_ioctl "${SWTPM_INTERFACE}" --load volatile "${workdir}/${statedir}/volatile" \
|| ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -i; then
echo "Error: Could not load permanent or volatile state"
err=1
break
fi
echo "INFO: Successfully restored permanent and volatile state from ${workdir}/${statedir}/"
if ! check_tpm_state "${workdir}" 1; then
echo "Error: Checking TPM-internal state failed (after resume): profile: ${profile} libtpms: ${libtpmsbranch} state written by: ${branch}"
err=1
break
fi
echo "INFO: Successfully checked proper functioning of TPM-internal state after state resume"
kill_quiet -9 "${SWTPM_PID}"
echo " INFO: Passed with libtpms ${libtpmsbranch}"
done
if [ ${err} -ne 0 ]; then
kill_quiet -9 "${SWTPM_PID}"
break
fi
done
unset LD_LIBRARY_PATH
popd &>/dev/null || return 1
echo -e "INFO: Done running with ${profile} profile states using different version of libtpms\n\n"
return "${err}"
}
# This function tests that a default profile state with 'stateFormatLevel = 2' can be used
# and remains at this level.
function swtpm_run_with_default_profile_state_sfl2()
{
local workdir="$1"
local statefile="$2"
local libtpmsdir libtpmsbranch output err=0 exp
mkdir "${workdir}/tmp"
cp "${statefile}" "${workdir}/tmp/"
echo -e "\n\nINFO: Testing with previously created default profile state"
pushd "${workdir}" &>/dev/null || return 1
for libtpmsdir in libtpms-master libtpms-*.*; do
libtpmsbranch=$(echo "${libtpmsdir}" | sed -n 's/.*-//p')
[ "${libtpmsbranch}" = "0.9" ] && continue
export LD_LIBRARY_PATH="${workdir}/${libtpmsdir}"
export SWTPM_IOCTL="./${libtpmsdir}/swtpm_ioctl"
if ! SWTPM_EXE="./${libtpmsdir}/swtpm" \
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--tpmstate "dir=${workdir}/tmp" \
--flags not-need-init,startup-clear; then
echo "Error: Could not start swtpm with 'default' profile state and libtpms version '${libtpmsbranch}'."
err=1
break
fi
if ! output=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x20); then
echo "Error: Could not run swtpm_ioctl: ${output}"
err=1
break
fi
exp=',"StateFormatLevel":2,'
if ! echo "${output}" | grep -q "${exp}"; then
echo "Error: Could not find '${exp}' in swtpm_ioctl output."
echo " output: ${output}"
err=1
break
fi
echo " INFO: Found proper default state in libtpms ${libtpmsbranch}"
run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
done
if [ ${err} -ne 0 ]; then
run_swtpm_ioctl "${SWTPM_INTERFACE}" -s
fi
unset LD_LIBRARY_PATH
popd &>/dev/null || return 1
echo -e "INFO: Done testing with previously created default profile state\n\n"
rm -rf "${workdir}/tmp/"
return "${err}"
}
if ! build_libtpms_and_swtpm "${workdir}"; then
echo "Error: Building libtpms and/or swtpm failed"
exit 1
fi
if ! swtpm_setup_create_profile_state "${workdir}"; then
exit 1
fi
if ! swtpm_run_with_profile_state "${workdir}" "null" "^()$"; then
exit 1
fi
if ! swtpm_run_with_profile_state "${workdir}" "default-v1" "^(0\.9)$"; then
exit 1
fi
if ! swtpm_run_with_profile_state "${workdir}" "default-v2" "^(0\.9|0\.10)$"; then
exit 1
fi
if ! swtpm_run_with_default_profile_state_sfl2 "${workdir}" "${TESTDIR}/data/tpm2state7/tpm2-00.permall"; then
exit 1
fi
exit 0