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