test: vboot: Parameterise the test
[oweals/u-boot.git] / test / py / tests / test_vboot.py
1 # SPDX-License-Identifier:      GPL-2.0+
2 # Copyright (c) 2016, Google Inc.
3 #
4 # U-Boot Verified Boot Test
5
6 """
7 This tests verified boot in the following ways:
8
9 For image verification:
10 - Create FIT (unsigned) with mkimage
11 - Check that verification shows that no keys are verified
12 - Sign image
13 - Check that verification shows that a key is now verified
14
15 For configuration verification:
16 - Corrupt signature and check for failure
17 - Create FIT (with unsigned configuration) with mkimage
18 - Check that image verification works
19 - Sign the FIT and mark the key as 'required' for verification
20 - Check that image verification works
21 - Corrupt the signature
22 - Check that image verification no-longer works
23
24 Tests run with both SHA1 and SHA256 hashing.
25 """
26
27 import pytest
28 import sys
29 import struct
30 import u_boot_utils as util
31 import vboot_forge
32
33 TESTDATA = [
34     ['sha1', '', False],
35     ['sha1', '-pss', False],
36     ['sha256', '', False],
37     ['sha256', '-pss', False],
38     ['sha256', '-pss', True],
39 ]
40
41 @pytest.mark.boardspec('sandbox')
42 @pytest.mark.buildconfigspec('fit_signature')
43 @pytest.mark.requiredtool('dtc')
44 @pytest.mark.requiredtool('fdtget')
45 @pytest.mark.requiredtool('fdtput')
46 @pytest.mark.requiredtool('openssl')
47 @pytest.mark.parametrize("sha_algo,padding,required", TESTDATA)
48 def test_vboot(u_boot_console, sha_algo, padding, required):
49     """Test verified boot signing with mkimage and verification with 'bootm'.
50
51     This works using sandbox only as it needs to update the device tree used
52     by U-Boot to hold public keys from the signing process.
53
54     The SHA1 and SHA256 tests are combined into a single test since the
55     key-generation process is quite slow and we want to avoid doing it twice.
56     """
57     def dtc(dts):
58         """Run the device tree compiler to compile a .dts file
59
60         The output file will be the same as the input file but with a .dtb
61         extension.
62
63         Args:
64             dts: Device tree file to compile.
65         """
66         dtb = dts.replace('.dts', '.dtb')
67         util.run_and_log(cons, 'dtc %s %s%s -O dtb '
68                          '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
69
70     def run_bootm(sha_algo, test_type, expect_string, boots):
71         """Run a 'bootm' command U-Boot.
72
73         This always starts a fresh U-Boot instance since the device tree may
74         contain a new public key.
75
76         Args:
77             test_type: A string identifying the test type.
78             expect_string: A string which is expected in the output.
79             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
80                     use.
81             boots: A boolean that is True if Linux should boot and False if
82                     we are expected to not boot
83         """
84         cons.restart_uboot()
85         with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
86             output = cons.run_command_list(
87                 ['host load hostfs - 100 %stest.fit' % tmpdir,
88                 'fdt addr 100',
89                 'bootm 100'])
90         assert(expect_string in ''.join(output))
91         if boots:
92             assert('sandbox: continuing, as we cannot run' in ''.join(output))
93         else:
94             assert('sandbox: continuing, as we cannot run' not in ''.join(output))
95
96     def make_fit(its):
97         """Make a new FIT from the .its source file.
98
99         This runs 'mkimage -f' to create a new FIT.
100
101         Args:
102             its: Filename containing .its source.
103         """
104         util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
105                                 '%s%s' % (datadir, its), fit])
106
107     def sign_fit(sha_algo):
108         """Sign the FIT
109
110         Signs the FIT and writes the signature into it. It also writes the
111         public key into the dtb.
112
113         Args:
114             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
115                     use.
116         """
117         cons.log.action('%s: Sign images' % sha_algo)
118         util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
119                                 '-r', fit])
120
121     def sign_fit_norequire(sha_algo):
122         """Sign the FIT
123
124         Signs the FIT and writes the signature into it. It also writes the
125         public key into the dtb.
126
127         Args:
128             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
129                     use.
130         """
131         cons.log.action('%s: Sign images' % sha_algo)
132         util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
133                                 fit])
134
135     def replace_fit_totalsize(size):
136         """Replace FIT header's totalsize with something greater.
137
138         The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
139         If the size is greater, the signature verification should return false.
140
141         Args:
142             size: The new totalsize of the header
143
144         Returns:
145             prev_size: The previous totalsize read from the header
146         """
147         total_size = 0
148         with open(fit, 'r+b') as handle:
149             handle.seek(4)
150             total_size = handle.read(4)
151             handle.seek(4)
152             handle.write(struct.pack(">I", size))
153         return struct.unpack(">I", total_size)[0]
154
155     def test_with_algo(sha_algo, padding):
156         """Test verified boot with the given hash algorithm.
157
158         This is the main part of the test code. The same procedure is followed
159         for both hashing algorithms.
160
161         Args:
162             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
163                     use.
164         """
165         # Compile our device tree files for kernel and U-Boot. These are
166         # regenerated here since mkimage will modify them (by adding a
167         # public key) below.
168         dtc('sandbox-kernel.dts')
169         dtc('sandbox-u-boot.dts')
170
171         # Build the FIT, but don't sign anything yet
172         cons.log.action('%s: Test FIT with signed images' % sha_algo)
173         make_fit('sign-images-%s%s.its' % (sha_algo , padding))
174         run_bootm(sha_algo, 'unsigned images', 'dev-', True)
175
176         # Sign images with our dev keys
177         sign_fit(sha_algo)
178         run_bootm(sha_algo, 'signed images', 'dev+', True)
179
180         # Create a fresh .dtb without the public keys
181         dtc('sandbox-u-boot.dts')
182
183         cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
184         make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
185         run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
186
187         # Sign images with our dev keys
188         sign_fit(sha_algo)
189         run_bootm(sha_algo, 'signed config', 'dev+', True)
190
191         cons.log.action('%s: Check signed config on the host' % sha_algo)
192
193         util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
194
195         # Make sure that U-Boot checks that the config is in the list of hashed
196         # nodes. If it isn't, a security bypass is possible.
197         with open(fit, 'rb') as fp:
198             root, strblock = vboot_forge.read_fdt(fp)
199         root, strblock = vboot_forge.manipulate(root, strblock)
200         with open(fit, 'w+b') as fp:
201             vboot_forge.write_fdt(root, strblock, fp)
202         util.run_and_log_expect_exception(cons,
203                 [fit_check_sign, '-f', fit, '-k', dtb],
204                 1, 'Failed to verify required signature')
205
206         run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
207
208         # Create a new properly signed fit and replace header bytes
209         make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
210         sign_fit(sha_algo)
211         bcfg = u_boot_console.config.buildconfig
212         max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
213         existing_size = replace_fit_totalsize(max_size + 1)
214         run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
215         cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
216
217         # Replace with existing header bytes
218         replace_fit_totalsize(existing_size)
219         run_bootm(sha_algo, 'signed config', 'dev+', True)
220         cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
221
222         # Increment the first byte of the signature, which should cause failure
223         sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
224                                (fit, sig_node))
225         byte_list = sig.split()
226         byte = int(byte_list[0], 16)
227         byte_list[0] = '%x' % (byte + 1)
228         sig = ' '.join(byte_list)
229         util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
230                          (fit, sig_node, sig))
231
232         run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
233
234         cons.log.action('%s: Check bad config on the host' % sha_algo)
235         util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
236                 '-k', dtb], 1, 'Failed to verify required signature')
237
238     def test_required_key(sha_algo, padding):
239         """Test verified boot with the given hash algorithm.
240
241         This function test if u-boot reject an image when a required
242         key isn't used to sign a FIT.
243
244         Args:
245             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
246                     use.
247         """
248         # Compile our device tree files for kernel and U-Boot. These are
249         # regenerated here since mkimage will modify them (by adding a
250         # public key) below.
251         dtc('sandbox-kernel.dts')
252         dtc('sandbox-u-boot.dts')
253
254         # Build the FIT with prod key (keys required)
255         # Build the FIT with dev key (keys NOT required)
256         # The dtb contain the key prod and dev and the key prod are set as required.
257         # Then try to boot the FIT with dev key
258         # This FIT should not be accepted by u-boot because the key prod is required
259         cons.log.action('%s: Test FIT with configs images' % sha_algo)
260         make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
261         sign_fit(sha_algo)
262         make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
263         sign_fit(sha_algo)
264
265         run_bootm(sha_algo, 'signed configs', '', False)
266
267     cons = u_boot_console
268     tmpdir = cons.config.result_dir + '/'
269     tmp = tmpdir + 'vboot.tmp'
270     datadir = cons.config.source_dir + '/test/py/tests/vboot/'
271     fit = '%stest.fit' % tmpdir
272     mkimage = cons.config.build_dir + '/tools/mkimage'
273     fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
274     dtc_args = '-I dts -O dtb -i %s' % tmpdir
275     dtb = '%ssandbox-u-boot.dtb' % tmpdir
276     sig_node = '/configurations/conf-1/signature'
277
278     # Create an RSA key pair
279     public_exponent = 65537
280     util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
281                      '-pkeyopt rsa_keygen_bits:2048 '
282                      '-pkeyopt rsa_keygen_pubexp:%d' %
283                      (tmpdir, public_exponent))
284
285     # Create a certificate containing the public key
286     util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
287                      '%sdev.crt' % (tmpdir, tmpdir))
288
289     # Create an RSA key pair (prod)
290     public_exponent = 65537
291     util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sprod.key '
292                      '-pkeyopt rsa_keygen_bits:2048 '
293                      '-pkeyopt rsa_keygen_pubexp:%d' %
294                      (tmpdir, public_exponent))
295
296     # Create a certificate containing the public key (prod)
297     util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sprod.key -out '
298                      '%sprod.crt' % (tmpdir, tmpdir))
299
300     # Create a number kernel image with zeroes
301     with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
302         fd.write(5000 * chr(0))
303
304     try:
305         # We need to use our own device tree file. Remember to restore it
306         # afterwards.
307         old_dtb = cons.config.dtb
308         cons.config.dtb = dtb
309         if required:
310             test_required_key(sha_algo, padding)
311         else:
312             test_with_algo(sha_algo, padding)
313     finally:
314         # Go back to the original U-Boot with the correct dtb.
315         cons.config.dtb = old_dtb
316         cons.restart_uboot()