]> git.proxmox.com Git - pve-edk2-firmware.git/commitdiff
debian: sync tests with packaging upstream
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 7 Mar 2023 08:57:30 +0000 (09:57 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 7 Mar 2023 09:01:59 +0000 (10:01 +0100)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
debian/python/UEFI/Filesystems.py
debian/python/UEFI/Qemu.py
debian/python/UEFI/SignedBinary.py [new file with mode: 0644]
debian/tests/control
debian/tests/shell.py

index 0f47cbd1250ebcadb01ebc5c24e444103549e74b..923db30a19cde002eb4e08e697f56a80ae18b011 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright 2019-2021 Canonical Ltd.
+# Copyright 2019-2022 Canonical Ltd.
 # Authors:
 # - dann frazier <dann.frazier@canonical.com>
 #
@@ -87,35 +87,15 @@ class EfiBootableIsoImage:
 
 
 class GrubShellBootableIsoImage(EfiBootableIsoImage):
-    def __init__(self, efi_arch, use_signed):
-        EfiArchToGrubArch = {
-            'X64': "x86_64",
-            'AA64': "arm64",
-        }
+    def __init__(self, efi_arch, shim_path, grub_path):
         efi_img = FatFsImage(64)
         efi_img.makedirs(os.path.join('EFI', 'BOOT'))
         removable_media_path = os.path.join(
             'EFI', 'BOOT', 'BOOT%s.EFI' % (efi_arch.upper())
         )
-        efi_ext = 'efi'
-        grub_subdir = "%s-efi" % EfiArchToGrubArch[efi_arch.upper()]
-        if use_signed:
-            efi_ext = "%s.signed" % (efi_ext)
-            grub_subdir = "%s-signed" % (grub_subdir)
-
-        shim_src = os.path.join(
-            os.path.sep, 'usr', 'lib', 'shim',
-            'shim%s.%s' % (efi_arch.lower(), efi_ext)
-        )
-        grub_src = os.path.join(
-            os.path.sep, 'usr', 'lib', 'grub',
-            '%s' % (grub_subdir),
-            "" if use_signed else "monolithic",
-            'grub%s.%s' % (efi_arch.lower(), efi_ext)
-        )
         grub_dest = os.path.join(
             'EFI', 'BOOT', 'GRUB%s.EFI' % (efi_arch.upper())
         )
-        efi_img.insert_file(shim_src, removable_media_path)
-        efi_img.insert_file(grub_src, grub_dest)
+        efi_img.insert_file(shim_path, removable_media_path)
+        efi_img.insert_file(grub_path, grub_dest)
         super().__init__(efi_img)
index d8aaf23c01a295408e52440dde2883785ddb67ef..d4d8668480c2e8dca44d384caa7da68e0c0641e3 100644 (file)
@@ -43,11 +43,10 @@ class QemuEfiFlashSize(enum.Enum):
 
 
 class QemuCommand:
-    # Based on the args used by ovmf-vars-generator
     Qemu_Common_Params = [
         '-no-user-config', '-nodefaults',
         '-m', '256',
-        '-smp', '2,sockets=2,cores=1,threads=1',
+        '-smp', '1,sockets=1,cores=1,threads=1',
         '-display', 'none',
         '-serial', 'stdio',
     ]
diff --git a/debian/python/UEFI/SignedBinary.py b/debian/python/UEFI/SignedBinary.py
new file mode 100644 (file)
index 0000000..5bb33aa
--- /dev/null
@@ -0,0 +1,52 @@
+#
+# Copyright 2022 Canonical Ltd.
+# Authors:
+# - dann frazier <dann.frazier@canonical.com>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
+# SATISFACTORY QUALITY, 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
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import subprocess
+import tempfile
+
+
+class SignedBinary:
+    def __init__(self, binary_path, key_path, cert_path, password=None):
+        openssl_password_args = []
+        if password:
+            openssl_password_args = [
+                "-passin", f"pass:{password}"
+            ]
+        with tempfile.NamedTemporaryFile() as keytmp:
+            subprocess.check_call(
+                [
+                    "openssl", "rsa",
+                ] + openssl_password_args + [
+                    "-in", f"{key_path}",
+                    "-out", f"{keytmp.name}",
+                ]
+            )
+            with tempfile.NamedTemporaryFile(delete=False) as f:
+                self.path = f.name
+
+            subprocess.check_call(
+                [
+                    "sbsign", "--key", f"{keytmp.name}",
+                    "--cert", f"{cert_path}",
+                    binary_path, "--output", f"{self.path}"
+                ]
+            )
+
+    def __del__(self):
+        os.unlink(self.path)
index cc87fdeadfea1304df2ed9621b15c4bfe8f4f695..fbfa279f4c3d6b3d03cf6a16e85738c73533adb0 100644 (file)
@@ -5,6 +5,7 @@ Depends:
  grub-efi-amd64-signed [amd64],
  grub-efi-arm64-signed [arm64],
  mtools [amd64 arm64],
+ openssl [amd64 arm64],
  ovmf,
  ovmf-ia32,
  python3-pexpect,
@@ -12,5 +13,6 @@ Depends:
  qemu-efi-arm,
  qemu-system-arm,
  qemu-system-x86,
+ sbsigntool [amd64 arm64],
  shim-signed [amd64 arm64],
  xorriso [amd64 arm64],
index 391b7bf17e7bbd8382ee992ab63afda97d048da4..de4a699087c493235d6d0734d0c6093da1bb5d2a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-# Copyright 2019-2021 Canonical Ltd.
+# Copyright 2019-2022 Canonical Ltd.
 # Authors:
 # - dann frazier <dann.frazier@canonical.com>
 #
 #
 
 import enum
+import os
 import pexpect
 import subprocess
 import sys
+import time
 import unittest
 
 from UEFI.Filesystems import GrubShellBootableIsoImage
+from UEFI.SignedBinary import SignedBinary
 from UEFI.Qemu import QemuEfiMachine, QemuEfiVariant, QemuEfiFlashSize
 from UEFI import Qemu
 
@@ -31,15 +34,56 @@ DPKG_ARCH = subprocess.check_output(
     ['dpkg', '--print-architecture']
 ).decode().rstrip()
 
+EfiArchToGrubArch = {
+    'X64': "x86_64",
+    'AA64': "arm64",
+}
+
+TEST_TIMEOUT = 120
+
+
+def get_local_grub_path(efi_arch, signed=False):
+    grub_subdir = "%s-efi" % EfiArchToGrubArch[efi_arch.upper()]
+    ext = "efi"
+    if signed:
+        grub_subdir = f"{grub_subdir}-signed"
+        ext = f"{ext}.signed"
+
+    grub_path = os.path.join(
+        os.path.sep, 'usr', 'lib', 'grub',
+        '%s' % (grub_subdir),
+        "" if signed else "monolithic",
+        'grub%s.%s' % (efi_arch.lower(), ext)
+    )
+    return grub_path
+
+
+def get_local_shim_path(efi_arch, signed=False):
+    ext = 'efi'
+    if signed:
+        ext = f"{ext}.signed"
+    shim_path = os.path.join(
+        os.path.sep, 'usr', 'lib', 'shim',
+        'shim%s.%s' % (efi_arch.lower(), ext)
+    )
+    return shim_path
+
 
 class BootToShellTest(unittest.TestCase):
     debug = True
 
+    def setUp(self):
+        self.startTime = time.time()
+
+    def tearDown(self):
+        t = time.time() - self.startTime
+        sys.stdout.write("%s runtime: %.3fs\n" % (self.id(), t))
+
     def run_cmd_check_shell(self, cmd):
-        child = pexpect.spawn(' '.join(cmd))
+        child = pexpect.spawn(' '.join(cmd), encoding='UTF-8')
 
         if self.debug:
-            child.logfile = sys.stdout.buffer
+            child.logfile = sys.stdout
         try:
             while True:
                 i = child.expect(
@@ -47,7 +91,7 @@ class BootToShellTest(unittest.TestCase):
                         'Press .* or any other key to continue',
                         'Shell> '
                     ],
-                    timeout=60,
+                    timeout=TEST_TIMEOUT,
                 )
                 if i == 0:
                     child.sendline('\x1b')
@@ -56,7 +100,9 @@ class BootToShellTest(unittest.TestCase):
                     child.sendline('reset -s\r')
                     continue
         except pexpect.EOF:
-            return
+            child.close()
+            if child.exitstatus != 0:
+                self.fail("ERROR: exit code %d\n" % (child.exitstatus))
         except pexpect.TIMEOUT as err:
             self.fail("%s\n" % (err))
 
@@ -65,10 +111,10 @@ class BootToShellTest(unittest.TestCase):
             PRE_EXEC = 1
             POST_EXEC = 2
 
-        child = pexpect.spawn(' '.join(cmd))
+        child = pexpect.spawn(' '.join(cmd), encoding='UTF-8')
 
         if self.debug:
-            child.logfile = sys.stdout.buffer
+            child.logfile = sys.stdout
         try:
             state = State.PRE_EXEC
             while True:
@@ -80,7 +126,7 @@ class BootToShellTest(unittest.TestCase):
                         'grub> ',
                         'Command Error Status: Access Denied',
                     ],
-                    timeout=60,
+                    timeout=TEST_TIMEOUT,
                 )
                 if i == 0:
                     child.sendline('\x1b')
@@ -102,10 +148,12 @@ class BootToShellTest(unittest.TestCase):
                 if i == 4:
                     verified = False
                     continue
+        except pexpect.EOF:
+            child.close()
+            if child.exitstatus != 0:
+                self.fail("ERROR: exit code %d\n" % (child.exitstatus))
         except pexpect.TIMEOUT as err:
             self.fail("%s\n" % (err))
-        except pexpect.EOF:
-            pass
         self.assertEqual(should_verify, verified)
 
     def test_aavmf(self):
@@ -113,12 +161,20 @@ class BootToShellTest(unittest.TestCase):
         self.run_cmd_check_shell(q.command)
 
     @unittest.skipUnless(DPKG_ARCH == 'arm64', "Requires grub-efi-arm64")
+    @unittest.skipUnless(
+        subprocess.run(
+            ['dpkg-vendor', '--derives-from', 'Ubuntu']
+        ).returncode == 0,
+        "Debian does not provide a signed shim for arm64, see #992073"
+    )
     def test_aavmf_ms_secure_boot_signed(self):
         q = Qemu.QemuCommand(
             QemuEfiMachine.AAVMF,
             variant=QemuEfiVariant.MS,
         )
-        iso = GrubShellBootableIsoImage('AA64', use_signed=True)
+        grub = get_local_grub_path('AA64', signed=True)
+        shim = get_local_shim_path('AA64', signed=True)
+        iso = GrubShellBootableIsoImage('AA64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'aa64', True)
 
@@ -128,7 +184,9 @@ class BootToShellTest(unittest.TestCase):
             QemuEfiMachine.AAVMF,
             variant=QemuEfiVariant.MS,
         )
-        iso = GrubShellBootableIsoImage('AA64', use_signed=False)
+        grub = get_local_grub_path('AA64', signed=False)
+        shim = get_local_shim_path('AA64', signed=False)
+        iso = GrubShellBootableIsoImage('AA64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'aa64', False)
 
@@ -178,7 +236,9 @@ class BootToShellTest(unittest.TestCase):
             variant=QemuEfiVariant.MS,
             flash_size=QemuEfiFlashSize.SIZE_2MB,
         )
-        iso = GrubShellBootableIsoImage('X64', use_signed=True)
+        grub = get_local_grub_path('X64', signed=True)
+        shim = get_local_shim_path('X64', signed=True)
+        iso = GrubShellBootableIsoImage('X64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'x64', True)
 
@@ -189,7 +249,9 @@ class BootToShellTest(unittest.TestCase):
             variant=QemuEfiVariant.MS,
             flash_size=QemuEfiFlashSize.SIZE_2MB,
         )
-        iso = GrubShellBootableIsoImage('X64', use_signed=False)
+        grub = get_local_grub_path('X64', signed=False)
+        shim = get_local_shim_path('X64', signed=False)
+        iso = GrubShellBootableIsoImage('X64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'x64', False)
 
@@ -230,7 +292,9 @@ class BootToShellTest(unittest.TestCase):
             variant=QemuEfiVariant.MS,
             flash_size=QemuEfiFlashSize.SIZE_4MB,
         )
-        iso = GrubShellBootableIsoImage('X64', use_signed=True)
+        grub = get_local_grub_path('X64', signed=True)
+        shim = get_local_shim_path('X64', signed=True)
+        iso = GrubShellBootableIsoImage('X64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'x64', True)
 
@@ -241,7 +305,44 @@ class BootToShellTest(unittest.TestCase):
             variant=QemuEfiVariant.MS,
             flash_size=QemuEfiFlashSize.SIZE_4MB,
         )
-        iso = GrubShellBootableIsoImage('X64', use_signed=False)
+        grub = get_local_grub_path('X64', signed=False)
+        shim = get_local_shim_path('X64', signed=False)
+        iso = GrubShellBootableIsoImage('X64', shim, grub)
+        q.add_disk(iso.path)
+        self.run_cmd_check_secure_boot(q.command, 'x64', False)
+
+    @unittest.skipUnless(DPKG_ARCH == 'amd64', "amd64-only")
+    def test_ovmf_snakeoil_secure_boot_signed(self):
+        q = Qemu.QemuCommand(
+            QemuEfiMachine.OVMF_Q35,
+            variant=QemuEfiVariant.SNAKEOIL,
+        )
+        shim = SignedBinary(
+            get_local_shim_path('X64', signed=False),
+            "/usr/share/ovmf/PkKek-1-snakeoil.key",
+            "/usr/share/ovmf/PkKek-1-snakeoil.pem",
+            "snakeoil",
+        )
+        grub = SignedBinary(
+            get_local_grub_path('X64', signed=False),
+            "/usr/share/ovmf/PkKek-1-snakeoil.key",
+            "/usr/share/ovmf/PkKek-1-snakeoil.pem",
+            "snakeoil",
+        )
+        iso = GrubShellBootableIsoImage('X64', shim.path, grub.path)
+        q.add_disk(iso.path)
+        self.run_cmd_check_secure_boot(q.command, 'x64', True)
+
+    @unittest.skipUnless(DPKG_ARCH == 'amd64', "amd64-only")
+    def test_ovmf_snakeoil_secure_boot_unsigned(self):
+        q = Qemu.QemuCommand(
+            QemuEfiMachine.OVMF_Q35,
+            variant=QemuEfiVariant.SNAKEOIL,
+            flash_size=QemuEfiFlashSize.DEFAULT,
+        )
+        grub = get_local_grub_path('X64', signed=False)
+        shim = get_local_shim_path('X64', signed=False)
+        iso = GrubShellBootableIsoImage('X64', shim, grub)
         q.add_disk(iso.path)
         self.run_cmd_check_secure_boot(q.command, 'x64', False)