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