[meta-intel] [PATCH 3/3] Add secureboot selftests

California Sullivan california.l.sullivan at intel.com
Mon Aug 28 15:19:01 PDT 2017


This was based on the secureboot selftests in meta-refkit:

https://github.com/intel/intel-iot-refkit/blob/3bf04941a3a150ed86d8ae61366ae3a19443a600/meta-refkit/lib/oeqa/selftest/cases/secureboot.py

It had to be modified a bit to work in meta-intel, as we can't depend on
efivar which resides in meta-openembedded. Instead, in order to test
that secureboot is enabled, we first try to boot with an unsigned, then
image signed with incorrect keys, and search for a "Security Violation"
error message in each log. If the image booted successfully or that
error did not occur, something went wrong and the third test becomes
invalid. The third test is simply booting an image that is signed with
the enrolled keys, getting to a login screen and running a simple
command.

Note that these tests can be quite time consuming, as we have to wait
for the first two tests to timeout, and the timeout values have to be
somewhat high as it sometimes takes a while for the ovmf firmware to
come up.

Original work by Mikko Ylinen and Patrick Ohly.

Signed-off-by: California Sullivan <california.l.sullivan at intel.com>
---
 lib/oeqa/selftest/cases/secureboot.py | 176 ++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)
 create mode 100644 lib/oeqa/selftest/cases/secureboot.py

diff --git a/lib/oeqa/selftest/cases/secureboot.py b/lib/oeqa/selftest/cases/secureboot.py
new file mode 100644
index 0000000..4c059e2
--- /dev/null
+++ b/lib/oeqa/selftest/cases/secureboot.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (c) 2017, Intel Corporation.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 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 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# AUTHORS
+# Mikko Ylinen <mikko.ylinen at linux.intel.com>
+#
+# Based on meta/lib/oeqa/selftest/* and meta-refkit/lib/oeqa/selftest/*
+
+"""Test cases for secure boot with QEMU running OVMF."""
+
+import os
+import unittest
+import re
+import glob
+from shutil import rmtree, copy
+
+from oeqa.core.decorator.depends import OETestDepends
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
+
+class SecureBootTests(OESelftestTestCase):
+    """Secure Boot test class."""
+
+    ovmf_keys_enrolled = False
+    ovmf_qemuparams = ''
+    ovmf_dir = ''
+    test_image_unsigned = 'secureboot-selftest-image-unsigned'
+    test_image_signed = 'secureboot-selftest-image-signed'
+    correct_key = 'refkit-db'
+    incorrect_key = 'incorrect'
+
+    @classmethod
+    def setUpLocal(self):
+
+        if not SecureBootTests.ovmf_keys_enrolled:
+            bitbake('ovmf ovmf-shell-image-enrollkeys', output_log=self.logger)
+
+            bb_vars = get_bb_vars(['TMPDIR', 'DEPLOY_DIR_IMAGE'])
+
+            SecureBootTests.ovmf_dir = os.path.join(bb_vars['TMPDIR'], 'oeselftest', 'secureboot', 'ovmf')
+            bb.utils.mkdirhier(SecureBootTests.ovmf_dir)
+
+            # Copy (all) OVMF in a temporary location
+            for src in glob.glob('%s/ovmf.*' % bb_vars['DEPLOY_DIR_IMAGE']):
+                copy(src, SecureBootTests.ovmf_dir)
+
+            SecureBootTests.ovmf_qemuparams = '-drive if=pflash,format=qcow2,file=%s/ovmf.secboot.qcow2' % SecureBootTests.ovmf_dir
+
+            cmd = ("runqemu "
+                   "qemuparams='%s' "
+                   "ovmf-shell-image-enrollkeys wic intel-corei7-64 "
+                   "nographic slirp") % SecureBootTests.ovmf_qemuparams
+            print('Running "%s"' % cmd)
+            status = runCmd(cmd)
+
+            if not re.search('info: success', status.output, re.M):
+                self.fail('Failed to enroll keys. EFI shell log:\n%s' % status.output)
+            else:
+                # keys enrolled in ovmf.secboot.vars
+                SecureBootTests.ovmf_keys_enrolled = True
+
+    @classmethod
+    def tearDownLocal(self):
+        # Seems this is mandatory between the tests (a signed image is booted
+        # when running test_boot_unsigned_image after test_boot_signed_image).
+        # bitbake('-c clean %s' % test_image, output_log=self.logger)
+        #
+        # Whatever the problem was, it no longer seems to be necessary, so
+        # we can skip the time-consuming clean + full rebuild (5:04 min instead
+        # of 6:55min here).
+        pass
+
+    @classmethod
+    def tearDownClass(self):
+        bitbake('ovmf-shell-image-enrollkeys:do_cleanall', output_log=self.logger)
+        rmtree(self.ovmf_dir, ignore_errors=True)
+
+    def secureboot_with_image(self, boot_timeout=300, signing_key=None):
+        """Boot the image with UEFI SecureBoot enabled and see the result. """
+
+        config = ""
+
+        if signing_key:
+            test_image = self.test_image_signed
+            config += 'SECURE_BOOT_SIGNING_KEY = "${THISDIR}/files/%s.key"\n' % signing_key
+            config += 'SECURE_BOOT_SIGNING_CERT = "${THISDIR}/files/%s.crt"\n' % signing_key
+        else:
+            test_image = self.test_image_unsigned
+
+        self.write_config(config)
+        bitbake(test_image, output_log=self.logger)
+        self.remove_config(config)
+
+        # Some of the cases depend on the timeout to expire. Allow overrides
+        # so that we don't have to wait 1000s which is the default.
+        overrides = {
+            'TEST_QEMUBOOT_TIMEOUT': boot_timeout,
+            }
+
+        print('Booting %s' % test_image)
+
+        try:
+            with runqemu(test_image, ssh=False,
+                          runqemuparams='nographic slirp',
+                          qemuparams=self.ovmf_qemuparams,
+                          overrides=overrides,
+                          image_fstype='wic') as qemu:
+
+                cmd  = 'uname -a'
+
+                status, output = qemu.run_serial(cmd)
+
+                self.assertTrue(status, 'Could not run \'uname -a\' (status=%s):\n%s' % (status, output))
+
+                # if we got this far without a correctly signed image, something went wrong
+                if signing_key != self.correct_key:
+                    self.fail('The image not give a Security violation when expected. Boot log:\n%s' % output)
+
+
+        except Exception:
+
+            # Currently runqemu() fails if 'login:' prompt is not seen and it's
+            # not possible to login as 'root'. Those conditions aren't met when
+            # booting to EFI shell (See [YOCTO #11438]). We catch the failure
+            # and parse the boot log to determine the success. Note: the
+            # timeout triggers verbose bb.error() but that's normal with some
+            # of the test cases.
+
+            workdir = get_bb_var('WORKDIR', test_image)
+            bootlog = "%s/testimage/qemu_boot_log" % workdir
+
+            with open(bootlog, "r") as log:
+
+                # This isn't right but all we can do at this point. The right
+                # approach would run commands in the EFI shell to determine
+                # the BIOS rejects unsigned and/or images signed with keys in
+                # dbx key store but that needs changes in oeqa framework.
+
+                output = log.read()
+
+                # PASS if we see a security violation on unsigned or incorrectly signed images, otherwise fail
+                if signing_key == self.correct_key:
+                    self.fail('Correctly signed image failed to boot. Boot log:\n%s' % output)
+                elif not re.search('Security Violation', output):
+                    self.fail('The image not give a Security violation when expected. Boot log:\n%s' % output)
+
+    def test_boot_unsigned_image(self):
+        """ Boot unsigned image with secureboot enabled in UEFI."""
+        self.secureboot_with_image(boot_timeout=120, signing_key=None)
+
+    @OETestDepends(['secureboot.SecureBootTests.test_boot_unsigned_image'])
+    def test_boot_incorrectly_signed_image(self):
+        """ Boot (correctly) signed image with secureboot enabled in UEFI."""
+        self.secureboot_with_image(boot_timeout=120, signing_key=self.incorrect_key)
+
+    @OETestDepends(['secureboot.SecureBootTests.test_boot_incorrectly_signed_image'])
+    def test_boot_correctly_signed_image(self):
+        """ Boot (correctly) signed image with secureboot enabled in UEFI."""
+        self.secureboot_with_image(boot_timeout=150, signing_key=self.correct_key)
-- 
2.9.5



More information about the meta-intel mailing list