From ccc2dc2781b77f79c892afabbb4fb55693eed9b6 Mon Sep 17 00:00:00 2001 From: Thierry Escande Date: Fri, 18 Aug 2023 17:27:31 +0200 Subject: [PATCH] Add vTPM basic tests This change adds tests for vTPM functionalities requiring XCP-ng 8.3 and a Unix UEFI VM. The first test creates and destroys a vtpm device. A second test does basic TPM tests (like message signing) using tpm2-tools. Nested fixtures are defined for the second test, starting a snapshotted UEFI VM with a vTPM device attached. Signed-off-by: Thierry Escande --- jobs.py | 14 ++++ tests/vtpm/__init__.py | 0 tests/vtpm/conftest.py | 62 ++++++++++++++++++ tests/vtpm/test_vtpm_basic_operations.py | 81 ++++++++++++++++++++++++ vm_data.py-dist | 2 + 5 files changed, 159 insertions(+) create mode 100644 tests/vtpm/__init__.py create mode 100644 tests/vtpm/conftest.py create mode 100644 tests/vtpm/test_vtpm_basic_operations.py diff --git a/jobs.py b/jobs.py index af5679e38..249b5f669 100755 --- a/jobs.py +++ b/jobs.py @@ -221,6 +221,20 @@ "params": {}, "paths": ["tests/xen"], }, + "vtpm": { + "description": "Testing vTPM functionalities", + "requirements": [ + "A XCP-ng host >= 8.3 and a Unix RPM-based or DEB-based UEFI VM with " + "tpm2-tools installable from default repositories.", + ], + "nb_pools": 1, + "params": { + # The test also works on CentOS, for example, but in this job definition + # we settle for a debian VM + "--vm": "single/debian_uefi_vm", + }, + "paths": ["tests/vtpm"], + }, "flaky": { "description": "tests that usually pass, but sometimes fail unexpectedly", "requirements": [ diff --git a/tests/vtpm/__init__.py b/tests/vtpm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/vtpm/conftest.py b/tests/vtpm/conftest.py new file mode 100644 index 000000000..09e2c26fe --- /dev/null +++ b/tests/vtpm/conftest.py @@ -0,0 +1,62 @@ +import pytest +import logging + +from lib.common import PackageManagerEnum + +@pytest.fixture(scope='module') +def halted_uefi_unix_vm(uefi_vm, unix_vm): + assert uefi_vm.is_halted(), "The VM must be halted for these tests" + yield uefi_vm + +@pytest.fixture(scope='module') +def snapshotted_halted_uefi_unix_vm(halted_uefi_unix_vm): + vm = halted_uefi_unix_vm + snapshot = vm.snapshot() + + yield vm + + try: + snapshot.revert() + finally: + snapshot.destroy() + +@pytest.fixture(scope='module') +def unix_vm_with_vtpm(snapshotted_halted_uefi_unix_vm): + vm = snapshotted_halted_uefi_unix_vm + + vm.create_vtpm() + yield vm + # Tear down + vm.destroy_vtpm() + +@pytest.fixture(scope='module') +def started_unix_vm_with_vtpm(unix_vm_with_vtpm): + vm = unix_vm_with_vtpm + + try: + vm.start() + vm.wait_for_os_booted() + except Exception: + vm.shutdown(force=True, verify=True) + raise Exception(f"Failed to start VM") + + yield vm + # Tear down + vm.shutdown(verify=True) + +@pytest.fixture(scope='module') +def unix_vm_with_tpm2_tools(started_unix_vm_with_vtpm): + vm = started_unix_vm_with_vtpm + + pkg_mgr = vm.detect_package_manager() + if pkg_mgr == PackageManagerEnum.APT_GET: + pkg_mgr = 'apt-get' + elif pkg_mgr == PackageManagerEnum.RPM: + pkg_mgr = 'yum' + else: + pytest.fail("Unsupported package manager for this test. Cannot install tpm2-tools") + + logging.info("Installing tpm2-tools package using '%s'" % pkg_mgr) + vm.ssh([pkg_mgr, 'install', '-y', 'tpm2-tools']) + + yield vm diff --git a/tests/vtpm/test_vtpm_basic_operations.py b/tests/vtpm/test_vtpm_basic_operations.py new file mode 100644 index 000000000..b8e917a14 --- /dev/null +++ b/tests/vtpm/test_vtpm_basic_operations.py @@ -0,0 +1,81 @@ +import logging +import pytest +import lib.commands as commands + +# These tests are basic tests for vTPM devices. +# - Create / Destroy a vTPM device on a VM +# - Do some basic encryption tests using tpm2-tools package +# +# Requirements: +# - an XCP-ng host >= 8.3 +# - a RPM-based or DEB-based UEFI VM with `tpm2-tools` installable +# from default repositories + +vtpm_signing_test_script = """#!/bin/env bash + +set -ex + +tpm2_selftest --fulltest + +tpm2_getrandom 32 > /dev/null + +TMPDIR=`mktemp -d` + +# Create an Endorsement primary key +tpm2_createprimary --hierarchy e --key-context ${TMPDIR}/primary.ctx > /dev/null + +# Create key objects +tpm2_create --key-algorithm rsa --public ${TMPDIR}/rsa.pub --private ${TMPDIR}/rsa.priv --parent-context \ + ${TMPDIR}/primary.ctx > /dev/null + +# Load keys into the TPM +tpm2_load --parent-context ${TMPDIR}/primary.ctx --public ${TMPDIR}/rsa.pub --private ${TMPDIR}/rsa.priv \ + --key-context ${TMPDIR}/rsa.ctx > /dev/null + +# Delete loaded key files +rm -f ${TMPDIR}/rsa.pub ${TMPDIR}/rsa.priv + +# Message to sign +echo 'XCP-ng Rulez' > ${TMPDIR}/message.dat + +# Sign the message +tpm2_sign --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --signature ${TMPDIR}/sig.rssa \ + ${TMPDIR}/message.dat > /dev/null + +# Verify signature +tpm2_verifysignature --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --message ${TMPDIR}/message.dat \ + --signature ${TMPDIR}/sig.rssa > /dev/null + +# Verify with another message +echo "XCP-ng Still Rulez" > ${TMPDIR}/message.dat + +# Verify signature !!!!! THIS MUST FAIL !!!!! +if tpm2_verifysignature --key-context ${TMPDIR}/rsa.ctx --hash-algorithm sha256 --message ${TMPDIR}/message.dat \ + --signature ${TMPDIR}/sig.rssa > /dev/null 2>&1; then + echo "Should not succeed" + exit 1 +fi + +rm -rf ${TMPDIR} +""" + +@pytest.mark.small_vm +@pytest.mark.usefixtures("host_at_least_8_3") +def test_create_and_destroy_vtpm(halted_uefi_unix_vm): + vm = halted_uefi_unix_vm + assert not vm.get_vtpm_uuid(), "there must be no vTPM before we create it" + vtpm_uuid = vm.create_vtpm() + assert vtpm_uuid + assert vm.get_vtpm_uuid(), "a vTPM must be present after creation" + logging.info("vTPM created with uuid: %s" % vtpm_uuid) + vm.destroy_vtpm() + assert not vm.get_vtpm_uuid(), "there must be no vTPM after we deleted it" + +@pytest.mark.small_vm +@pytest.mark.usefixtures("host_at_least_8_3") +def test_vtpm(unix_vm_with_tpm2_tools): + global vtpm_signing_test_script + vm = unix_vm_with_tpm2_tools + # Basic TPM2 tests with tpm2-tools + logging.info("Running TPM2 test script on the VM") + vm.execute_script(vtpm_signing_test_script) diff --git a/vm_data.py-dist b/vm_data.py-dist index b54e9d857..5964cd726 100644 --- a/vm_data.py-dist +++ b/vm_data.py-dist @@ -13,6 +13,8 @@ VMS = { "small_vm_efitools": "", # "small" Windows VM (UEFI) "small_vm_windows": "", + # Debian VM (UEFI, no GUI) + "debian_uefi_vm": "", }, "multi": { # all VMs we want to run "multi_vms" tests on