summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-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 0000000..4c059e2
--- /dev/null
+++ b/lib/oeqa/selftest/cases/secureboot.py
@@ -0,0 +1,176 @@
1#!/usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# Copyright (c) 2017, Intel Corporation.
6# All rights reserved.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20#
21# AUTHORS
22# Mikko Ylinen <mikko.ylinen@linux.intel.com>
23#
24# Based on meta/lib/oeqa/selftest/* and meta-refkit/lib/oeqa/selftest/*
25
26"""Test cases for secure boot with QEMU running OVMF."""
27
28import os
29import unittest
30import re
31import glob
32from shutil import rmtree, copy
33
34from oeqa.core.decorator.depends import OETestDepends
35from oeqa.selftest.case import OESelftestTestCase
36from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
37
38class SecureBootTests(OESelftestTestCase):
39 """Secure Boot test class."""
40
41 ovmf_keys_enrolled = False
42 ovmf_qemuparams = ''
43 ovmf_dir = ''
44 test_image_unsigned = 'secureboot-selftest-image-unsigned'
45 test_image_signed = 'secureboot-selftest-image-signed'
46 correct_key = 'refkit-db'
47 incorrect_key = 'incorrect'
48
49 @classmethod
50 def setUpLocal(self):
51
52 if not SecureBootTests.ovmf_keys_enrolled:
53 bitbake('ovmf ovmf-shell-image-enrollkeys', output_log=self.logger)
54
55 bb_vars = get_bb_vars(['TMPDIR', 'DEPLOY_DIR_IMAGE'])
56
57 SecureBootTests.ovmf_dir = os.path.join(bb_vars['TMPDIR'], 'oeselftest', 'secureboot', 'ovmf')
58 bb.utils.mkdirhier(SecureBootTests.ovmf_dir)
59
60 # Copy (all) OVMF in a temporary location
61 for src in glob.glob('%s/ovmf.*' % bb_vars['DEPLOY_DIR_IMAGE']):
62 copy(src, SecureBootTests.ovmf_dir)
63
64 SecureBootTests.ovmf_qemuparams = '-drive if=pflash,format=qcow2,file=%s/ovmf.secboot.qcow2' % SecureBootTests.ovmf_dir
65
66 cmd = ("runqemu "
67 "qemuparams='%s' "
68 "ovmf-shell-image-enrollkeys wic intel-corei7-64 "
69 "nographic slirp") % SecureBootTests.ovmf_qemuparams
70 print('Running "%s"' % cmd)
71 status = runCmd(cmd)
72
73 if not re.search('info: success', status.output, re.M):
74 self.fail('Failed to enroll keys. EFI shell log:\n%s' % status.output)
75 else:
76 # keys enrolled in ovmf.secboot.vars
77 SecureBootTests.ovmf_keys_enrolled = True
78
79 @classmethod
80 def tearDownLocal(self):
81 # Seems this is mandatory between the tests (a signed image is booted
82 # when running test_boot_unsigned_image after test_boot_signed_image).
83 # bitbake('-c clean %s' % test_image, output_log=self.logger)
84 #
85 # Whatever the problem was, it no longer seems to be necessary, so
86 # we can skip the time-consuming clean + full rebuild (5:04 min instead
87 # of 6:55min here).
88 pass
89
90 @classmethod
91 def tearDownClass(self):
92 bitbake('ovmf-shell-image-enrollkeys:do_cleanall', output_log=self.logger)
93 rmtree(self.ovmf_dir, ignore_errors=True)
94
95 def secureboot_with_image(self, boot_timeout=300, signing_key=None):
96 """Boot the image with UEFI SecureBoot enabled and see the result. """
97
98 config = ""
99
100 if signing_key:
101 test_image = self.test_image_signed
102 config += 'SECURE_BOOT_SIGNING_KEY = "${THISDIR}/files/%s.key"\n' % signing_key
103 config += 'SECURE_BOOT_SIGNING_CERT = "${THISDIR}/files/%s.crt"\n' % signing_key
104 else:
105 test_image = self.test_image_unsigned
106
107 self.write_config(config)
108 bitbake(test_image, output_log=self.logger)
109 self.remove_config(config)
110
111 # Some of the cases depend on the timeout to expire. Allow overrides
112 # so that we don't have to wait 1000s which is the default.
113 overrides = {
114 'TEST_QEMUBOOT_TIMEOUT': boot_timeout,
115 }
116
117 print('Booting %s' % test_image)
118
119 try:
120 with runqemu(test_image, ssh=False,
121 runqemuparams='nographic slirp',
122 qemuparams=self.ovmf_qemuparams,
123 overrides=overrides,
124 image_fstype='wic') as qemu:
125
126 cmd = 'uname -a'
127
128 status, output = qemu.run_serial(cmd)
129
130 self.assertTrue(status, 'Could not run \'uname -a\' (status=%s):\n%s' % (status, output))
131
132 # if we got this far without a correctly signed image, something went wrong
133 if signing_key != self.correct_key:
134 self.fail('The image not give a Security violation when expected. Boot log:\n%s' % output)
135
136
137 except Exception:
138
139 # Currently runqemu() fails if 'login:' prompt is not seen and it's
140 # not possible to login as 'root'. Those conditions aren't met when
141 # booting to EFI shell (See [YOCTO #11438]). We catch the failure
142 # and parse the boot log to determine the success. Note: the
143 # timeout triggers verbose bb.error() but that's normal with some
144 # of the test cases.
145
146 workdir = get_bb_var('WORKDIR', test_image)
147 bootlog = "%s/testimage/qemu_boot_log" % workdir
148
149 with open(bootlog, "r") as log:
150
151 # This isn't right but all we can do at this point. The right
152 # approach would run commands in the EFI shell to determine
153 # the BIOS rejects unsigned and/or images signed with keys in
154 # dbx key store but that needs changes in oeqa framework.
155
156 output = log.read()
157
158 # PASS if we see a security violation on unsigned or incorrectly signed images, otherwise fail
159 if signing_key == self.correct_key:
160 self.fail('Correctly signed image failed to boot. Boot log:\n%s' % output)
161 elif not re.search('Security Violation', output):
162 self.fail('The image not give a Security violation when expected. Boot log:\n%s' % output)
163
164 def test_boot_unsigned_image(self):
165 """ Boot unsigned image with secureboot enabled in UEFI."""
166 self.secureboot_with_image(boot_timeout=120, signing_key=None)
167
168 @OETestDepends(['secureboot.SecureBootTests.test_boot_unsigned_image'])
169 def test_boot_incorrectly_signed_image(self):
170 """ Boot (correctly) signed image with secureboot enabled in UEFI."""
171 self.secureboot_with_image(boot_timeout=120, signing_key=self.incorrect_key)
172
173 @OETestDepends(['secureboot.SecureBootTests.test_boot_incorrectly_signed_image'])
174 def test_boot_correctly_signed_image(self):
175 """ Boot (correctly) signed image with secureboot enabled in UEFI."""
176 self.secureboot_with_image(boot_timeout=150, signing_key=self.correct_key)