aboutsummaryrefslogtreecommitdiffstats
path: root/lib/oeqa
diff options
context:
space:
mode:
authorCalifornia Sullivan <california.l.sullivan@intel.com>2017-08-28 15:19:01 -0700
committerSaul Wold <sgw@linux.intel.com>2017-08-30 14:25:25 -0700
commitfbee56b78623808f9c6ed6f92b085ee7c504cf70 (patch)
treefba43e35e6ac703cc023159719ba00fe07394dd1 /lib/oeqa
parent7549a93e9985da8453e683265ad0c65694765be0 (diff)
downloadmeta-intel-fbee56b78623808f9c6ed6f92b085ee7c504cf70.tar.gz
meta-intel-fbee56b78623808f9c6ed6f92b085ee7c504cf70.tar.bz2
meta-intel-fbee56b78623808f9c6ed6f92b085ee7c504cf70.zip
Add secureboot selftests
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@intel.com> Signed-off-by: Saul Wold <sgw@linux.intel.com>
Diffstat (limited to 'lib/oeqa')
-rw-r--r--lib/oeqa/selftest/cases/secureboot.py176
1 files changed, 176 insertions, 0 deletions
diff --git a/lib/oeqa/selftest/cases/secureboot.py b/lib/oeqa/selftest/cases/secureboot.py
new file mode 100644
index 00000000..4c059e25
--- /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@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)