From 039235e327f7d4485bf15985f92f9df751f8ff62 Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Thu, 20 Feb 2025 16:05:30 +0100 Subject: [PATCH] tests/uefi_sb: Add Windows key upgrade tests Signed-off-by: Tu Dinh --- .gitmodules | 3 + contrib/secureboot_objects | 1 + lib/efi.py | 31 +++++++++- pytest.ini | 2 +- tests/uefi_sb/test_varstored_sb.py | 94 ++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 160000 contrib/secureboot_objects diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..ae62ae7ff --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "contrib/secureboot_objects"] + path = contrib/secureboot_objects + url = https://github.com/microsoft/secureboot_objects.git diff --git a/contrib/secureboot_objects b/contrib/secureboot_objects new file mode 160000 index 000000000..058c7e668 --- /dev/null +++ b/contrib/secureboot_objects @@ -0,0 +1 @@ +Subproject commit 058c7e6680ca195ac15e3b59b3880b4fc29fe2a8 diff --git a/lib/efi.py b/lib/efi.py index dd3aebb62..4182839e4 100755 --- a/lib/efi.py +++ b/lib/efi.py @@ -47,6 +47,34 @@ def getfile(self, suffix=None, prefix=None): _tempdir = _EfiGlobalTempdir() +class _SecureBootCertList: + _prefix = Path(__file__).parent / '../contrib/secureboot_objects/PreSignedObjects' + + def kek_ms_2011(self): + return str(self._prefix / "KEK/Certificates/MicCorKEKCA2011_2011-06-24.der") + + def kek_ms_2023(self): + return str(self._prefix / "KEK/Certificates/microsoft corporation kek 2k ca 2023.der") + + def db_win_2011(self): + return str(self._prefix / "DB/Certificates/MicWinProPCA2011_2011-10-19.der") + + def db_uefi_2011(self): + return str(self._prefix / "DB/Certificates/MicCorUEFCA2011_2011-06-27.der") + + def db_win_2023(self): + return str(self._prefix / "DB/Certificates/windows uefi ca 2023.der") + + def db_uefi_2023(self): + return str(self._prefix / "DB/Certificates/microsoft uefi ca 2023.der") + + def db_oprom_2023(self): + return str(self._prefix / "DB/Certificates/microsoft option rom uefi ca 2023.der") + + +ms_certs = _SecureBootCertList() + + class GUID(UUID): def as_bytes(self): return self.bytes_le @@ -371,8 +399,7 @@ def __init__( owner_cert: Optional[Certificate] = None, other_certs: Iterable[Union[Certificate, str]] = None): assert name in SECURE_BOOT_VARIABLES - # No point having an owner cert without a matching private key - assert owner_cert is None or owner_cert.key is not None + assert owner_cert is None or owner_cert.key is not None, "owner cert must have private key" self.name = name self.guid = get_secure_boot_guid(self.name) self._owner_cert = owner_cert diff --git a/pytest.ini b/pytest.ini index fe93b353b..7f692ac86 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -ra --maxfail=1 +addopts = -ra --maxfail=1 --ignore=contrib/ markers = # *** Markers that change test behaviour *** default_vm: mark a test with a default VM in case no --vm parameter was given. diff --git a/tests/uefi_sb/test_varstored_sb.py b/tests/uefi_sb/test_varstored_sb.py index 75ef7563c..b54cf798c 100644 --- a/tests/uefi_sb/test_varstored_sb.py +++ b/tests/uefi_sb/test_varstored_sb.py @@ -1,6 +1,9 @@ import logging import pytest +from lib.efi import EFIAuth, ms_certs +from lib.vm import VM + from .utils import _test_key_exchanges, boot_and_check_no_sb_errors, boot_and_check_sb_failed, \ boot_and_check_sb_succeeded, generate_keys, revert_vm_state, sign_efi_bins @@ -153,3 +156,94 @@ def test_key_exchanges(self, uefi_vm): vm.set_uefi_setup_mode() _test_key_exchanges(vm) + +@pytest.mark.small_vm +@pytest.mark.usefixtures("host_at_least_8_3") +@pytest.mark.usefixtures("windows_vm") +class TestGuestWindowsUEFIKeyUpgrade: + @pytest.fixture(autouse=True) + def setup_and_cleanup(self, uefi_vm_and_snapshot): + vm, snapshot = uefi_vm_and_snapshot + yield + revert_vm_state(vm, snapshot) + + def install_old_certs(self, vm: VM): + """Populate a key set that looks like the old defaults.""" + + PK = EFIAuth.self_signed("PK") + KEK = EFIAuth.self_signed("KEK", other_certs=[ms_certs.kek_ms_2011()]) + db = EFIAuth("db", other_certs=[ms_certs.db_uefi_2011(), ms_certs.db_win_2011()]) + # Some test VMs don't like an empty dbx when their own dbx is empty, so just put whatever in there + dbx = EFIAuth.self_signed("dbx") + + PK.sign_auth(PK) + PK.sign_auth(KEK) + KEK.sign_auth(db) + KEK.sign_auth(dbx) + + vm.install_uefi_certs([PK, KEK, db, dbx]) + return [PK, KEK, db, dbx] + + def install_new_certs(self, vm: VM, signer: EFIAuth): + """Populate a key set that looks like the new defaults with 2023 MS keys.""" + + newPK = EFIAuth.self_signed("PK") + newKEK = EFIAuth("KEK", other_certs=[ms_certs.kek_ms_2011(), ms_certs.kek_ms_2023()]) + newdb = EFIAuth( + "db", + other_certs=[ + ms_certs.db_win_2011(), + ms_certs.db_win_2023(), + ms_certs.db_uefi_2011(), + ms_certs.db_uefi_2023(), + ms_certs.db_oprom_2023(), + ], + ) + newdbx = EFIAuth("dbx") + + newPK.sign_auth(newPK) + # Technically, there's no need to sign the other databases since we're setting them from Dom0. + # If signing with the old PK works, there'd be no need to test signing with the new PK. + # We use an invalid signer to test scenarios where the user mixes and matches default and custom keys. + signer.sign_auth(newKEK) + signer.sign_auth(newdb) + signer.sign_auth(newdbx) + + vm.install_uefi_certs([newPK, newKEK, newdb, newdbx]) + + def test_key_upgrade(self, uefi_vm: VM): + vm = uefi_vm + vm.param_set("platform", True, key="secureboot") + assert not vm.get_vtpm_uuid() + vm.create_vtpm() + + PK, _, _, _ = self.install_old_certs(vm) + boot_and_check_sb_succeeded(vm) + + vm.shutdown(verify=True) + + self.install_new_certs(vm, PK) + boot_and_check_sb_succeeded(vm) + + def test_key_upgrade_bitlocker(self, uefi_vm: VM): + vm = uefi_vm + vm.param_set("platform", True, key="secureboot") + assert not vm.get_vtpm_uuid() + vm.create_vtpm() + + PK, _, _, _ = self.install_old_certs(vm) + boot_and_check_sb_succeeded(vm) + + vm.execute_powershell_script("Add-WindowsFeature BitLocker,EnhancedStorage") + vm.reboot(verify=True) + + vm.execute_powershell_script( + "Enable-BitLocker $Env:SystemDrive -TpmProtector -UsedSpaceOnly; Suspend-BitLocker $Env:SystemDrive" + ) + vm.shutdown(verify=True) + + self.install_new_certs(vm, PK) + boot_and_check_sb_succeeded(vm) + # After Enable-BitLocker, Windows would boot into encryption test. + # If the test failed, Windows would cancel the encryption and give the status FullyDecrypted. + assert vm.execute_powershell_script("(Get-BitLockerVolume $Env:SystemDrive).VolumeStatus") != "FullyDecrypted"