swtpm/tests/test_tpm2_swtpm_setup_profile
Stefan Berger a72da2dfac tests: Extend regex's with optional match for Attributes in profiles
The default-v1 profile may soon also set Attributes in the JSON and
therefore extend the regular expressions matching profiles to optionally
match for Attributes.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
2024-10-17 17:56:11 -04:00

456 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# For the license, see the LICENSE file in the root directory.
ROOT=${abs_top_builddir:-$(dirname "$0")/..}
TESTDIR=${abs_top_testdir:-$(dirname "$0")}
source "${TESTDIR}/common"
skip_test_no_tpm20 "${SWTPM_EXE}"
workdir="$(mktemp -d)" || exit 1
export TPM_PATH=${workdir}
SWTPM_SERVER_PORT=65480
SWTPM_CTRL_PORT=65481
SWTPM_SERVER_NAME=127.0.0.1
SWTPM_INTERFACE="socket+socket"
trap "cleanup" SIGTERM EXIT
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_
function cleanup()
{
rm -rf "${workdir}"
if kill_quiet -0 "${SWTPM_PID}"; then
kill_quiet -9 "${SWTPM_PID}"
fi
}
test_swtpm_setup_profile()
{
local workdir="${1}"
local profile="${2}"
local exp_response="${3}" # expected 'ActiveProfile' response; optional
local disabled_algos="${4}" # disabled algorithms; optional
local tpm2request="${5}" # TPM 2 request to send; optional
local exp_tpm2response="${6}" # expected response; optional
local exp_fail="${7}" # swtpm_setup is expexted to fail when '1'
local response
rm -f "${workdir}/logfile"
if ! $SWTPM_SETUP \
--tpm2 \
--tpmstate "${workdir}" \
--config "${workdir}/swtpm_setup.conf" \
--log "${workdir}/logfile" \
--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
--overwrite \
${profile:+--profile ${profile}}
then
if [ "${exp_fail}" -eq 1 ]; then
# swtpm_setup failed as expected
return 0
fi
echo "Test failed: Error: Could not run $SWTPM_SETUP."
echo "Setup Logfile:"
cat "${workdir}/logfile"
exit 1
fi
if [ -n "${exp_fail}" ] && [ "${exp_fail}" -eq 1 ]; then
echo "Error: swtpm_setup did not fail as expected"
exit 1
fi
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--flags not-need-init,startup-clear
if ! kill_quiet -0 "${SWTPM_PID}"; then
echo "Error: ${SWTPM_INTERFACE} TPM did not start."
exit 1
fi
if [ -n "${exp_response}" ]; then
response=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x20)
if ! [[ "${response}" =~ ${exp_response} ]]; then
echo "Error: Response does not match expected response regular expression"
echo "Actual : ${response}"
echo "Expected : ${exp_response}"
exit 1
fi
fi
if [ -n "${disabled_algos}" ]; then
response=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 |
sed -n 's/.*"Disabled":"\([^"]*\).*/\1/p')
if [ "${response}" != "${disabled_algos}" ]; then
echo "Error: Response for disabled algorithms does not match expected response"
echo "Actual : ${response}"
echo "Expected : ${disabled_algos}"
exit 1
fi
fi
if [ -n "${tpm2request}" ]; then
response=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" "${tpm2request}")
if [ "${response}" != "${exp_tpm2response}" ]; then
echo "Error: TPM 2 response does not match expected response"
echo "Actual : ${response}"
echo "Expected : ${exp_tpm2response}"
exit 1
fi
fi
if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then
echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
exit 1
fi
if wait_process_gone "${SWTPM_PID}" 4; then
echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
exit 1
fi
# Expecting same results from swtpm directly
if [ -n "${exp_response}" ]; then
response=$(${SWTPM_EXE} socket \
--tpmstate "dir=${workdir}" \
--tpm2 \
--print-info 0x20)
if ! [[ "${response}" =~ ${exp_response} ]]; then
echo "Error: Response does not match expected response regular expression (swtpm)"
echo "Actual : ${response}"
echo "Expected : ${exp_response}"
exit 1
fi
fi
}
# Get available profiles and algorithms that are implemented and those that can be disabled
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--flags not-need-init
if ! kill_quiet -0 "${SWTPM_PID}"; then
echo "Error: ${SWTPM_INTERFACE} TPM did not start."
exit 1
fi
profiles=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x40 |
sed $'s/{"Name/\\\n{"Name/g' |
sed -n 's/^{"Name":"\([^"]*\)".*/\1/p')
canbedisabled_algos_str=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 |
sed -n 's/.*"CanBeDisabled":"\([^"]*\).*/\1/p')
implemented_algos_str=$(run_swtpm_ioctl "${SWTPM_INTERFACE}" --info 0x8 |
sed -n 's/.*"Implemented":"\([^"]*\).*/\1/p')
if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then
echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
exit 1
fi
if wait_process_gone "${SWTPM_PID}" 4; then
echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
exit 1
fi
# Test with no profile
exp_response=$(echo "^\{\"ActiveProfile\":\{" \
"\"Name\":\"null\"," \
"\"StateFormatLevel\":[0-9]+,"\
"\"Commands\":\"[,x[:xdigit:]-]+\","\
"\"Algorithms\":\"[,=[:alnum:]-]+\","\
"\"Description\":\"[[:print:]]+\""\
"\}\}\$"| tr -d " ")
test_swtpm_setup_profile \
"${workdir}" "" "${exp_response}" \
"" "" "" ""
echo "Test without profile passed"
# Test-setup each profile that is implemented
for profile in ${profiles}; do
exp_response=$(echo "^\{\"ActiveProfile\":\{" \
"\"Name\":\"${profile}\"," \
"\"StateFormatLevel\":[0-9]+,"\
"\"Commands\":\"[,x[:xdigit:]-]+\","\
"\"Algorithms\":\"[,=[:alnum:]-]+\","\
"(\"Attributes\":\"[,=[:alnum:]-]+\",)?"\
"\"Description\":\"[[:print:]]+\""\
"\}\}\$"| tr -d " ")
test_swtpm_setup_profile \
"${workdir}" "{\"Name\":\"${profile}\"}" "${exp_response}" "" "" "" ""
echo "Test with profile '${profile}' passed"
done
# Setup the custom profile with an increasing set of disabled algorithms
# that swtpm_ioctl has to show then.
IFS="," read -r -a canbedisabled_algos <<< "${canbedisabled_algos_str}"
IFS="," read -r -a implemented_algos <<< "${implemented_algos_str}"
algos="${implemented_algos[*]}"
disabled=()
for todisable in "${canbedisabled_algos[@]}"; do
# Remove algorithm ${todisable} from list of implemented algorithms
algos=$(echo "${algos}" | sed "s/${todisable}//" | tr -s ' ')
disabled+=("${todisable}")
# Format profile
a=$(echo "${algos}" | sed -e 's/ /,/g' -e 's/,$//')
profile="{\"Name\":\"custom\",\"Algorithms\":\"${a}\"}"
d=${disabled[*]}
d=${d// /,}
test_swtpm_setup_profile \
"${workdir}" "${profile}" "" "${d}" "" "" ""
echo "Test with custom profile and disabled algorithms '${d}' passed"
done
# Test the custom profiles with disabled commands and various StateFormatLevels
test_custom_profile_sfls()
{
local cmds="$1"
local sfls_list="$2"
local msg="$3"
local sfl exp_sfl exp_fail profile exp_response
for sfls in ${sfls_list}; do
sfl=$(cut -d":" -f1 <<< "${sfls}")
exp_sfl=$(cut -d":" -f2 <<< "${sfls}")
exp_fail=$(cut -d":" -f3 <<< "${sfls}")
profile="{\"Name\":\"custom:test-sfl-${sfl}\",\"Commands\":\"${cmds}\",\"StateFormatLevel\":${sfl}}"
exp_response=".*,\"StateFormatLevel\":${exp_sfl},.*"
test_swtpm_setup_profile \
"${workdir}" "${profile}" "${exp_response}" "" "" "" "${exp_fail}"
echo "Test with custom profile and commands${msg} passed with StateFormatLevel ${sfl}."
done
}
# because of 0x199-0x19a we must have StateFormatLevel >=3 for it to be acceptable
cmds="0x11f-0x122,0x124-0x12e,0x130-0x140,0x142-0x159,0x15b-0x15e,0x160-0x165,0x167-0x174,0x176-0x178,0x17a-0x193,0x199-0x19a"
test_custom_profile_sfls "${cmds}" "1:f:1 2:f:1 3:3:0 4:4:0 5:5:0 6:6:0 7:7:0" " (with 0x199-0x19a)"
# because of 0x19b we must have StateFormatLevel >=5 for it to be acceptable
cmds="0x11f-0x122,0x124-0x12e,0x130-0x140,0x142-0x159,0x15b-0x15e,0x160-0x165,0x167-0x174,0x176-0x178,0x17a-0x193,0x199-0x19a,0x19b"
test_custom_profile_sfls "${cmds}" "1:f:1 2:f:1 3:f:1 4:f:1 5:5:0 6:6:0 7:7:0" " (with 0x199-0x19b)"
# Without given StateFormatLevel it should go up to StateFormatLevel '4' (due to AES-192 being selected)
profile="{\"Name\":\"custom\",\"Commands\":\"${cmds}\"}"
exp_response=".*,\"StateFormatLevel\":[[:digit:]]+,.*"
test_swtpm_setup_profile \
"${workdir}" "${profile}" "${exp_response}" "" "" "" "0"
# with cmds 0x199-0x19a missing it can have many StateFormatLevels >= 2
cmds="0x11f-0x122,0x124-0x12e,0x130-0x140,0x142-0x159,0x15b-0x15e,0x160-0x165,0x167-0x174,0x176-0x178,0x17a-0x193"
# StateFormatLevel 3 enables 'nothing' additional but will be kept
# StateFormatLevel 4 enables AES-192 and will remain at '4'
test_custom_profile_sfls "${cmds}" "1:f:1 2:2:0 3:3:0 4:4:0 5:5:0 6:6:0 7:7:0" ""
# Without given StateFormatLevel it should go up to StateFormatLevel '4' (due to AES-192 being selected)
profile="{\"Name\":\"custom\",\"Commands\":\"${cmds}\"}"
exp_response=".*,\"StateFormatLevel\":[[:digit:]]+,.*"
test_swtpm_setup_profile \
"${workdir}" "${profile}" "${exp_response}" "" "" "" "${exp_fail}"
# Setup the TPM 2 with custom profile and sha1 removed
# Check that the GetCapability command returns the proper set of PCR banks
algos="${implemented_algos[*]}"
algos=$(echo "${algos}" | sed "s/sha1//" | tr -s ' ')
# Format profile
a=$(echo "${algos}" | sed -e 's/ /,/g' -e 's/,$//')
profile="{\"Name\":\"custom:no-sha1\",\"Algorithms\":\"${a}\"}"
# Check PCR bank sha1 is not available: tssgetcapability -cap 5
test_swtpm_setup_profile \
"${workdir}" \
"${profile}" \
"" \
"" \
"\x80\x01\x00\x00\x00\x16\x00\x00\x01\x7a\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x40" \
" 80 01 00 00 00 25 00 00 00 00 00 00 00 00 05 00 00 00 03 00 0b 03 ff ff ff 00 0c 03 00 00 00 00 0d 03 00 00 00" \
""
echo "Test with custom profile and 'sha1' disabled algorithms passed"
# Check that swtpm adjusts key sizes and sets 'Attributes' when
# '--profile-remove-disabled fips-host' is passed
profile="{\"Name\":\"custom\",\"Description\":\"Test\"} --profile-remove-disabled fips-host"
exp_response="\{\"ActiveProfile\":\{\"Name\":\"custom\",.*,\"Algorithms\":\".*,rsa-min-size=2048,.*,ecc-min-size=224,.*\",\"Attributes\":\"no-sha1-signing,no-sha1-verification,no-unpadded-encryption\",\"Description\":\"Test\"\}\}"
test_swtpm_setup_profile "${workdir}" "${profile}" "${exp_response}" "" "" "" "0"
profile="{\"Name\":\"custom\",\"Attributes\":\"no-sha1-signing\",\"Description\":\"Test\"} --profile-remove-disabled fips-host"
test_swtpm_setup_profile "${workdir}" "${profile}" "${exp_response}" "" "" "" "0"
profile="{\"Name\":\"custom:test\",\"Attributes\":\"fips-host\",\"Description\":\"Test\"} --profile-remove-disabled fips-host"
exp_response="\{\"ActiveProfile\":\{\"Name\":\"custom:test\",.*,\"Algorithms\":\".*,rsa-min-size=2048,.*,ecc-min-size=224,.*\",\"Attributes\":\"fips-host\",\"Description\":\"Test\"\}\}"
test_swtpm_setup_profile "${workdir}" "${profile}" "${exp_response}" "" "" "" "0"
# --profile-remove-disabled must not have any effect on other profiles
for p in null default-v1; do
profile="{\"Name\":\"${p}\"} --profile-remove-disabled fips-host"
exp_response="\{\"ActiveProfile\":\{\"Name\":\"${p}\",.*,\"Algorithms\":\".*,rsa-min-size=1024,.*,ecc-min-size=192,.*\",.*\}\}"
test_swtpm_setup_profile "${workdir}" "${profile}" "${exp_response}" "" "" "" "0"
done
echo "Tests with custom profile and --profile-remove-disabled option passed"
# Copy TPM 2 state written by libtpms v0.9 and have the TPM read and re-write the state.
# The size of the state file must not change since we don't want the profile to be
# written into it.
cp "${TESTDIR}/data/tpm2state6/tpm2-00.permall" "${workdir}"
before=$(get_filesize "${workdir}/tpm2-00.permall")
# Avoid swptm sending TPM2_Shutdown(SU_STATE) and adding savestate to the state file
rm -f "${workdir}/logfile"
run_swtpm "${SWTPM_INTERFACE}" \
--tpm2 \
--flags not-need-init,startup-clear,disable-auto-shutdown \
--log "file=${workdir}/logfile"
if ! kill_quiet -0 "${SWTPM_PID}"; then
echo "Error: ${SWTPM_INTERFACE} TPM did not start."
exit 1
fi
# Run ChangeEPS so the state is re-written: tsschangeeps
cmd='\x80\x02\x00\x00\x00\x1b\x00\x00\x01\x24\x40\x00\x00\x0c\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00'
RES=$(swtpm_cmd_tx "${SWTPM_INTERFACE}" "${cmd}")
exp=' 80 02 00 00 00 13 00 00 00 00 00 00 00 00 00 00 01 00 00'
if [ "$RES" != "$exp" ]; then
echo "Error: Did not get expected result from TPM2_ChangeEPS"
echo "expected: $exp"
echo "received: $RES"
exit 1
fi
if ! run_swtpm_ioctl "${SWTPM_INTERFACE}" -s; then
echo "Error: Could not shut down the ${SWTPM_INTERFACE} TPM."
exit 1
fi
if wait_process_gone "${SWTPM_PID}" 4; then
echo "Error: ${SWTPM_INTERFACE} TPM should not be running anymore."
exit 1
fi
after=$(get_filesize "${workdir}/tpm2-00.permall")
if [ "${after}" -ne "${before}" ]; then
echo "Error: The size of the TPM 2 state file has changed"
echo "Actual : ${after}"
echo "Expected : ${before}"
exit 1
fi
echo "Test with state written by libtpms v0.9 passed"
# Check that swtpm emitted log message with OPENSSL_ENABLE_SHA1_SIGNATURES=1 on
# RHEL and CentOS; libtpms 0.9 supported SHA1 signatures
case $(get_distro_name) in
RHEL) [ "$(get_rhel_version)" -ge 904 ] && exp=1;;
CentOS) [ "$(get_centos_version)" -ge 900 ] && exp=1;;
*) exp=0;;
esac
if [ "${exp}" -eq 1 ]; then
if ! grep -q OPENSSL_ENABLE_SHA1_SIGNATURES "${workdir}/logfile"; then
echo "Missing reference to OPENSSL_ENABLE_SHA1_SIGNATURES in logfile"
exit 1
fi
echo "Test checking for reference to OPENSSL_ENABLE_SHA1_SIGNATURES in logfile passed"
fi
# If the user passes the null profile in then libtpms has to write state
# at the level of libtpms v0.9 and the size of the state file has to be
# the same as the one created with libtpms v0.9
rm -f "${workdir}"/tpm2-00.*
if ! $SWTPM_SETUP \
--tpm2 \
--tpmstate "${workdir}" \
--config "${workdir}/swtpm_setup.conf" \
--log "${workdir}/logfile" \
--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
--profile-file <(echo '{"Name":"null"}')
then
echo "Test failed: Error: Could not run $SWTPM_SETUP and set null-profile."
echo "Setup Logfile:"
cat "${workdir}/logfile"
exit 1
fi
after=$(get_filesize "${workdir}/tpm2-00.permall")
if [ "${after}" -ne "${before}" ]; then
echo "Error: The size of the TPM 2 state file is different than if created by libtpms v0.9"
echo "Actual : ${after}"
echo "Expected : ${before}"
exit 1
fi
echo "Test of writing state at the level of libtpms v0.9 passed"
# Test enablement of PCR banks
rm -f "${workdir}/logfile"
algorithms="rsa,rsa-min-size=1024,hmac,aes,aes-min-size=128,mgf1,keyedhash,xor,sha256,sha384,null,rsassa,rsapss,oaep,ecdsa,kdf1-sp800-56a,kdf2,kdf1-sp800-108,ecc,ecc-nist,symcipher,cmac,ctr,ofb,cbc,cfb,ecb,ecdh"
profile="{\"Name\":\"custom\",\"Algorithms\":\"${algorithms}\"}"
if ! $SWTPM_SETUP \
--tpm2 \
--tpmstate "${workdir}" \
--config "${workdir}/swtpm_setup.conf" \
--log "${workdir}/logfile" \
--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
--profile "${profile}" \
--pcr-banks sha256 \
--overwrite; then
echo "Error: swtpm_setup failed to run:"
cat "${workdir}/logfile"
exit 1
fi
if ! grep -q "PCR banks sha256 among sha256,sha384." "${workdir}/logfile"; then
echo "Error: Did not get expected output from activation of SHA256 PCR bank."
cat "${workdir}/logfile"
exit 1
fi
# sha512 enablement must fail
rm -f "${workdir}/logfile"
if $SWTPM_SETUP \
--tpm2 \
--tpmstate "${workdir}" \
--config "${workdir}/swtpm_setup.conf" \
--log "${workdir}/logfile" \
--tpm "${SWTPM_EXE} socket ${SWTPM_TEST_SECCOMP_OPT}" \
--profile "${profile}" \
--pcr-banks sha512 \
--overwrite; then
echo "Error: Enablement of SHA512 bank should have failed."
cat "${workdir}/logfile"
exit 1
fi
echo "Test of activation of sha256 bank when only sha256 & sha384 banks are available passed"
exit 0