As explained in commit a21618c8a (tests: Test aborts due to missing requirements should be marked as error instead of skipped) and in the Automake manual[1], skipped tests are tests that should not be run, e.g. running the ohci test on the powerpc-ieee1275 as there are no native ohci drivers for that platform. Test that fail for reasons other than there is a bug in GRUB code that is causing the test to fail are hard errors. Commonly this is because the test is run in an improperly configured environment, like required programs are missing. If a hard error condition is identified with a SKIP return code, the person running the tests can not know without investigating every skip if a SKIP in the tests was because the test does not apply to the target being tested or because the user had a misconfigured environment that was causing the test not to run. By ensuring that a test is skipped only when it should not run, the person running the test can be sure that there is no need to investigate why the test was skipped. This reverts commit bf13fed5f (tests: Skip tests if required tools are not available). [1] https://www.gnu.org/software/automake/manual/automake.html#Generalities-about-Testing Signed-off-by: Glenn Washburn <development@efficientek.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
426 lines
11 KiB
Plaintext
426 lines
11 KiB
Plaintext
#! @BUILD_SHEBANG@ -e
|
|
|
|
# Test GRUBs ability to unseal a LUKS key with TPM 2.0
|
|
# Copyright (C) 2024 Free Software Foundation, Inc.
|
|
#
|
|
# GRUB is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# GRUB is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
grubshell=@builddir@/grub-shell
|
|
|
|
. "@builddir@/grub-core/modinfo.sh"
|
|
|
|
if [ x${grub_modinfo_platform} != xemu ]; then
|
|
exit 77
|
|
fi
|
|
|
|
builddir="@builddir@"
|
|
|
|
# Force build directory components
|
|
PATH="${builddir}:${PATH}"
|
|
export PATH
|
|
|
|
if [ "x${EUID}" = "x" ] ; then
|
|
EUID=`id -u`
|
|
fi
|
|
|
|
if [ "${EUID}" != 0 ] ; then
|
|
echo "not root; cannot test tpm2."
|
|
exit 99
|
|
fi
|
|
|
|
if ! command -v cryptsetup >/dev/null 2>&1; then
|
|
echo "cryptsetup not installed; cannot test tpm2."
|
|
exit 99
|
|
fi
|
|
|
|
if ! grep -q tpm_vtpm_proxy /proc/modules && ! modprobe tpm_vtpm_proxy; then
|
|
echo "no tpm_vtpm_proxy support; cannot test tpm2."
|
|
exit 99
|
|
fi
|
|
|
|
if ! command -v swtpm >/dev/null 2>&1; then
|
|
echo "swtpm not installed; cannot test tpm2."
|
|
exit 99
|
|
fi
|
|
|
|
if ! command -v tpm2_startup >/dev/null 2>&1; then
|
|
echo "tpm2-tools not installed; cannot test tpm2."
|
|
exit 99
|
|
fi
|
|
|
|
tpm2testdir="`mktemp -d "${TMPDIR:-/tmp}/$(basename "$0").XXXXXXXXXX"`" || exit 99
|
|
|
|
disksize=20M
|
|
|
|
luksfile=${tpm2testdir}/luks.disk
|
|
lukskeyfile=${tpm2testdir}/password.txt
|
|
|
|
# Choose a low iteration number to reduce the time to decrypt the disk
|
|
csopt="--type luks2 --pbkdf pbkdf2 --iter-time 1000"
|
|
|
|
tpm2statedir=${tpm2testdir}/tpm
|
|
tpm2ctrl=${tpm2statedir}/ctrl
|
|
tpm2log=${tpm2statedir}/logfile
|
|
|
|
sealedkey=${tpm2testdir}/sealed.tpm
|
|
|
|
timeout=20
|
|
|
|
testoutput=${tpm2testdir}/testoutput
|
|
|
|
vtext="TEST VERIFIED"
|
|
|
|
ret=0
|
|
|
|
# Create the password file
|
|
echo -n "top secret" > "${lukskeyfile}"
|
|
|
|
# Setup LUKS2 image
|
|
truncate -s ${disksize} "${luksfile}" || exit 99
|
|
cryptsetup luksFormat -q ${csopt} "${luksfile}" "${lukskeyfile}" || exit 99
|
|
|
|
# Write vtext into the first block of the LUKS2 image
|
|
luksdev=/dev/mapper/`basename "${tpm2testdir}"`
|
|
cryptsetup open --key-file "${lukskeyfile}" "${luksfile}" `basename "${luksdev}"` || exit 99
|
|
echo "${vtext}" > "${luksdev}"
|
|
cryptsetup close "${luksdev}"
|
|
|
|
# Shutdown the swtpm instance on exit
|
|
cleanup() {
|
|
RET=$?
|
|
if [ -e "${tpm2ctrl}" ]; then
|
|
swtpm_ioctl -s --unix "${tpm2ctrl}"
|
|
fi
|
|
if [ "${RET}" -eq 0 ]; then
|
|
rm -rf "$tpm2testdir" || :
|
|
fi
|
|
}
|
|
trap cleanup EXIT INT TERM KILL QUIT
|
|
|
|
mkdir -p "${tpm2statedir}"
|
|
|
|
# Create the swtpm chardev instance
|
|
swtpm chardev --vtpm-proxy --tpmstate dir="${tpm2statedir}" \
|
|
--tpm2 --ctrl type=unixio,path="${tpm2ctrl}" \
|
|
--flags startup-clear --daemon > "${tpm2log}" || ret=$?
|
|
if [ "${ret}" -ne 0 ]; then
|
|
echo "Failed to start swtpm chardev: ${ret}" >&2
|
|
exit 99
|
|
fi
|
|
|
|
# Wait for tpm2 chardev
|
|
tpm2timeout=${GRUB_TEST_SWTPM_DEFAULT_TIMEOUT:-3}
|
|
for count in `seq 1 ${tpm2timeout}`; do
|
|
sleep 1
|
|
|
|
tpm2dev=$(grep "New TPM device" "${tpm2log}" | cut -d' ' -f 4)
|
|
if [ -c "${tpm2dev}" ]; then
|
|
break
|
|
elif [ "${count}" -eq "${tpm2timeout}" ]; then
|
|
echo "TPM device did not appear." >&2
|
|
exit 99
|
|
fi
|
|
done
|
|
|
|
# Export the TCTI variable for tpm2-tools
|
|
export TPM2TOOLS_TCTI="device:${tpm2dev}"
|
|
|
|
# Check if the sha384 bank is available
|
|
if [ "$(tpm2_getcap pcrs | grep sha384)" != "" ]; then
|
|
with_sha384=true
|
|
fi
|
|
|
|
# Extend PCR 0
|
|
tpm2_pcrextend 0:sha256=$(echo "test0" | sha256sum | cut -d ' ' -f 1) || exit 99
|
|
if [ "${with_sha384}" = "true" ]; then
|
|
tpm2_pcrextend 0:sha384=$(echo "test0" | sha384sum | cut -d ' ' -f 1) || exit 99
|
|
fi
|
|
|
|
# Extend PCR 1
|
|
tpm2_pcrextend 1:sha256=$(echo "test1" | sha256sum | cut -d ' ' -f 1) || exit 99
|
|
if [ "${with_sha384}" = "true" ]; then
|
|
tpm2_pcrextend 1:sha384=$(echo "test1" | sha384sum | cut -d ' ' -f 1) || exit 99
|
|
fi
|
|
|
|
tpm2_seal_unseal() {
|
|
srk_alg="$1"
|
|
handle_type="$2"
|
|
srk_test="$3"
|
|
pcr_bank="$4"
|
|
|
|
grub_srk_alg=${srk_alg}
|
|
|
|
extra_opt=""
|
|
extra_grub_opt=""
|
|
|
|
persistent_handle="0x81000000"
|
|
|
|
grub_cfg=${tpm2testdir}/testcase.cfg
|
|
|
|
if [ "${handle_type}" = "persistent" ]; then
|
|
extra_opt="--tpm2-srk=${persistent_handle}"
|
|
fi
|
|
|
|
if [ "${srk_alg}" != "default" ]; then
|
|
extra_opt="${extra_opt} --tpm2-asymmetric=${srk_alg}"
|
|
fi
|
|
|
|
# Seal the password with grub-protect
|
|
grub-protect ${extra_opt} \
|
|
--tpm2-device="${tpm2dev}" \
|
|
--action=add \
|
|
--protector=tpm2 \
|
|
--tpm2key \
|
|
--tpm2-bank="${pcr_bank}" \
|
|
--tpm2-pcrs=0,1 \
|
|
--tpm2-keyfile="${lukskeyfile}" \
|
|
--tpm2-outfile="${sealedkey}" || ret=$?
|
|
if [ "${ret}" -ne 0 ]; then
|
|
echo "Failed to seal the secret key: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
|
|
# Flip the asymmetric algorithm in grub.cfg to trigger fallback SRKs
|
|
if [ "${srk_test}" = "fallback_srk" ]; then
|
|
if [ -z "${srk_alg##RSA*}" ]; then
|
|
grub_srk_alg="ECC"
|
|
elif [ -z "${srk_alg##ECC*}" ]; then
|
|
grub_srk_alg="RSA"
|
|
fi
|
|
fi
|
|
|
|
if [ "${grub_srk_alg}" != "default" ] && [ "${handle_type}" != "persistent" ]; then
|
|
extra_grub_opt="-a ${grub_srk_alg}"
|
|
fi
|
|
|
|
# Write the TPM unsealing script
|
|
cat > "${grub_cfg}" <<EOF
|
|
loopback luks (host)${luksfile}
|
|
tpm2_key_protector_init -T (host)${sealedkey} ${extra_grub_opt}
|
|
if cryptomount -a --protector tpm2; then
|
|
cat (crypto0)+1
|
|
fi
|
|
EOF
|
|
|
|
# Test TPM unsealing with the same PCR
|
|
${grubshell} --timeout=${timeout} --emu-opts="-t ${tpm2dev}" < "${grub_cfg}" > "${testoutput}" || ret=$?
|
|
|
|
# Remove the persistent handle
|
|
if [ "${handle_type}" = "persistent" ]; then
|
|
grub-protect \
|
|
--tpm2-device="${tpm2dev}" \
|
|
--protector=tpm2 \
|
|
--action=remove \
|
|
--tpm2-srk=${persistent_handle} \
|
|
--tpm2-evict || :
|
|
fi
|
|
|
|
if [ "${ret}" -eq 0 ]; then
|
|
if ! grep -q "^${vtext}$" "${testoutput}"; then
|
|
echo "error: test not verified [`cat ${testoutput}`]" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
echo "grub-emu exited with error: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
}
|
|
|
|
tpm2_seal_unseal_nv() {
|
|
handle_type="$1"
|
|
key_type="$2"
|
|
pcr_bank="$3"
|
|
|
|
extra_opt=""
|
|
extra_grub_opt=""
|
|
|
|
if [ "$handle_type" = "nvindex" ]; then
|
|
nv_index="0x1000000"
|
|
else
|
|
nv_index="0x81000000"
|
|
fi
|
|
|
|
if [ "$key_type" = "tpm2key" ]; then
|
|
extra_opt="--tpm2key"
|
|
else
|
|
extra_grub_opt="--pcrs=0,1 -b ${pcr_bank}"
|
|
fi
|
|
|
|
grub_cfg=${tpm2testdir}/testcase.cfg
|
|
|
|
# Seal the key into a NV index guarded by PCR 0 and 1
|
|
grub-protect ${extra_opt} \
|
|
--tpm2-device="${tpm2dev}" \
|
|
--action=add \
|
|
--protector=tpm2 \
|
|
--tpm2-bank="${pcr_bank}" \
|
|
--tpm2-pcrs=0,1 \
|
|
--tpm2-keyfile="${lukskeyfile}" \
|
|
--tpm2-nvindex="${nv_index}" || ret=$?
|
|
if [ "${ret}" -ne 0 ]; then
|
|
echo "Failed to seal the secret key into ${nv_index}: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
|
|
# Write the TPM unsealing script
|
|
cat > ${grub_cfg} <<EOF
|
|
loopback luks (host)${luksfile}
|
|
tpm2_key_protector_init --mode=nv --nvindex=${nv_index} ${extra_grub_opt}
|
|
if cryptomount -a --protector tpm2; then
|
|
cat (crypto0)+1
|
|
fi
|
|
EOF
|
|
|
|
# Test TPM unsealing with the same PCR
|
|
${grubshell} --timeout=${timeout} --emu-opts="-t ${tpm2dev}" < "${grub_cfg}" > "${testoutput}" || ret=$?
|
|
|
|
# Remove the object from the NV index
|
|
grub-protect \
|
|
--tpm2-device="${tpm2dev}" \
|
|
--protector=tpm2 \
|
|
--action=remove \
|
|
--tpm2-nvindex=${nv_index} \
|
|
--tpm2-evict || :
|
|
|
|
if [ "${ret}" -eq 0 ]; then
|
|
if ! grep -q "^${vtext}$" "${testoutput}"; then
|
|
echo "error: test not verified [`cat ${testoutput}`]" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
echo "grub-emu exited with error: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
}
|
|
|
|
tpm2_seal_unseal_cap() {
|
|
pcr_bank="sha256"
|
|
|
|
original_pcr1="$(tpm2_pcrread ${pcr_bank}:1) | tail -1 | cut -d' ' -f7"
|
|
|
|
grub_cfg=${tpm2testdir}/testcase.cfg
|
|
|
|
# Seal the password with grub-protect
|
|
grub-protect \
|
|
--tpm2-device="${tpm2dev}" \
|
|
--action=add \
|
|
--protector=tpm2 \
|
|
--tpm2key \
|
|
--tpm2-bank="${pcr_bank}" \
|
|
--tpm2-pcrs=0,1 \
|
|
--tpm2-keyfile="${lukskeyfile}" \
|
|
--tpm2-outfile="${sealedkey}" || ret=$?
|
|
if [ "${ret}" -ne 0 ]; then
|
|
echo "Failed to seal the secret key: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
|
|
# Write the TPM unsealing script and cap PCR 1
|
|
cat > "${grub_cfg}" <<EOF
|
|
loopback luks (host)${luksfile}
|
|
tpm2_key_protector_init -T (host)${sealedkey} -c 1
|
|
if cryptomount -a --protector tpm2; then
|
|
cat (crypto0)+1
|
|
fi
|
|
EOF
|
|
|
|
# Test TPM unsealing with the same PCR
|
|
${grubshell} --timeout=${timeout} --emu-opts="-t ${tpm2dev}" < "${grub_cfg}" > "${testoutput}" || ret=$?
|
|
|
|
if [ "${ret}" -eq 0 ]; then
|
|
if ! grep -q "^${vtext}$" "${testoutput}"; then
|
|
echo "error: test not verified [`cat ${testoutput}`]" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
echo "grub-emu exited with error: ${ret}" >&2
|
|
return 99
|
|
fi
|
|
|
|
capped_pcr1="$(tpm2_pcrread ${pcr_bank}:1) | tail -1 | cut -d' ' -f7"
|
|
|
|
if [ "${original_pcr1}" = "${capped_pcr1}" ]; then
|
|
echo "error: PCR 1 not capped" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Testcases for SRK mode
|
|
declare -a srktests=()
|
|
srktests+=("default transient no_fallback_srk sha256")
|
|
srktests+=("RSA transient no_fallback_srk sha256")
|
|
srktests+=("ECC transient no_fallback_srk sha256")
|
|
srktests+=("RSA persistent no_fallback_srk sha256")
|
|
srktests+=("ECC persistent no_fallback_srk sha256")
|
|
srktests+=("RSA transient fallback_srk sha256")
|
|
srktests+=("ECC transient fallback_srk sha256")
|
|
if [ "${with_sha384}" = "true" ]; then
|
|
srktests+=("default transient no_fallback_srk sha384")
|
|
fi
|
|
|
|
exit_status=0
|
|
|
|
for i in "${!srktests[@]}"; do
|
|
tpm2_seal_unseal ${srktests[$i]} || ret=$?
|
|
if [ "${ret}" -eq 0 ]; then
|
|
echo "TPM2 [SRK][${srktests[$i]}]: PASS"
|
|
elif [ "${ret}" -eq 1 ]; then
|
|
echo "TPM2 [SRK][${srktests[$i]}]: FAIL"
|
|
ret=0
|
|
exit_status=1
|
|
else
|
|
echo "Unexpected failure [SRK][${srktests[$i]}]" >&2
|
|
exit ${ret}
|
|
fi
|
|
done
|
|
|
|
# Testcases for NV index mode
|
|
declare -a nvtests=()
|
|
nvtests+=("persistent raw sha256")
|
|
nvtests+=("nvindex raw sha256")
|
|
nvtests+=("nvindex tpm2key sha256")
|
|
if [ "${with_sha384}" = "true" ]; then
|
|
nvtests+=("persistent raw sha384")
|
|
nvtests+=("nvindex tpm2key sha384")
|
|
fi
|
|
|
|
for i in "${!nvtests[@]}"; do
|
|
tpm2_seal_unseal_nv ${nvtests[$i]} || ret=$?
|
|
if [ "${ret}" -eq 0 ]; then
|
|
echo "TPM2 [NV Index][${nvtests[$i]}]: PASS"
|
|
elif [ "${ret}" -eq 1 ]; then
|
|
echo "TPM2 [NV Index][${nvtests[$i]}]: FAIL"
|
|
ret=0
|
|
exit_status=1
|
|
else
|
|
echo "Unexpected failure [NV index][${nvtests[$i]}]" >&2
|
|
exit ${ret}
|
|
fi
|
|
done
|
|
|
|
# Testcase for PCR Capping
|
|
tpm2_seal_unseal_cap || ret=$?
|
|
if [ "${ret}" -eq 0 ]; then
|
|
echo "TPM2 [PCR Capping]: PASS"
|
|
elif [ "${ret}" -eq 1 ]; then
|
|
echo "TPM2 [PCR Capping]: FAIL"
|
|
ret=0
|
|
exit_status=1
|
|
else
|
|
echo "Unexpected failure [PCR Capping]" >&2
|
|
exit ${ret}
|
|
fi
|
|
|
|
exit ${exit_status}
|