diff --git a/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build b/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build new file mode 100644 index 0000000000..917ad55bb7 --- /dev/null +++ b/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + # The test reboots to verify TPM state persists across boots, so keep storage around. + 'storage' : 'persistent', + 'configuration' : integration_test_template['configuration'] + { + 'wants' : '@0@ tpm2.target'.format(integration_test_template['configuration']['wants']), + 'after' : '@0@ tpm2.target'.format(integration_test_template['configuration']['after']), + }, + 'vm' : true, + # We need to boot in EFI mode (with an initrd) so that an ESP exists for the software TPM to + # store its state on and so that systemd-tpm2-generator runs in the initrd. + 'firmware' : 'auto', + # Crucially, do NOT attach a hardware/firmware TPM: that's what makes the generator fall back + # to the software TPM, which is what this test exercises. + 'tpm' : false, + 'cmdline' : integration_test_template['cmdline'] + [ + # Opt into the software TPM fallback (off by default). + 'systemd.tpm2_software_fallback=yes', + 'systemd.default_device_timeout_sec=300', + ], + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 867fdecda2..3991b11e79 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -104,6 +104,7 @@ foreach dirname : [ 'TEST-89-RESOLVED-MDNS', 'TEST-90-RESTRICT-FSACCESS', 'TEST-91-LIVEUPDATE', + 'TEST-92-TPM2-SWTPM', ] subdir(dirname) endforeach diff --git a/test/units/TEST-92-TPM2-SWTPM.sh b/test/units/TEST-92-TPM2-SWTPM.sh new file mode 100755 index 0000000000..7793a7f58c --- /dev/null +++ b/test/units/TEST-92-TPM2-SWTPM.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Exercises the software TPM fallback (systemd-tpm2-swtpm.service) across a reboot. The VM boots in EFI mode +# without a hardware/firmware TPM and with "systemd.tpm2_software_fallback=yes" (see the test's meson.build), +# so systemd-tpm2-generator manufactures a software TPM on the ESP in the initrd and chainloads swtpm. +# +# boot 0: the TPM is manufactured in the initrd; seal a secret to it and stash the blob. +# boot 1: the TPM state persisted on the ESP across the reboot, so the secret still unseals. +# +# See systemd-tpm2-swtpm.service(8). + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +CRED=/var/lib/systemd-tpm2-swtpm-test.cred +PLAINTEXT="swtpm round-trip" + +if [[ -n "${ASAN_OPTIONS:-}" ]]; then + # swtpm_setup is not built with sanitizers, but does NSS lookups that pull in the ASan-instrumented + # libnss_systemd.so, which aborts with "ASan runtime does not come first". Skip under sanitizers. + echo "swtpm_setup does not work under sanitizers, skipping the test" | tee --append /skipped + exit 77 +fi + +if [[ ! -x /usr/lib/systemd/systemd-tpm2-swtpm ]] || ! command -v swtpm >/dev/null || [[ ! -d /sys/firmware/efi ]]; then + echo "Software TPM prerequisites missing, skipping the test" | tee --append /skipped + exit 77 +fi + +assert_swtpm_up() { + systemctl is-active systemd-tpm2-swtpm.service + timeout 30 bash -c 'until [[ -c /dev/tpmrm0 ]]; do sleep 1; done' + test -c /dev/tpm0 + # No firmware TPM here, so has-tpm2 reports "partial"; assert the software driver is present. + assert_in '\+driver' "$(systemd-analyze has-tpm2 || :)" +} + +case "$REBOOT_COUNT" in + 0) + assert_swtpm_up + # Seal a secret to the software TPM and keep the blob for the next boot. + echo -n "$PLAINTEXT" >/tmp/swtpm-plaintext + systemd-creds encrypt --name= --with-key=tpm2 /tmp/swtpm-plaintext "$CRED" + systemd-creds decrypt --name= "$CRED" - | cmp /tmp/swtpm-plaintext - + systemctl_final reboot + exec sleep infinity + ;; + 1) + assert_swtpm_up + # Persistence: the TPM state survived the reboot on the ESP, so the blob still unseals. + echo -n "$PLAINTEXT" >/tmp/swtpm-plaintext + systemd-creds decrypt --name= "$CRED" - | cmp /tmp/swtpm-plaintext - + touch /testok + ;; + *) + assert_not_reached + ;; +esac