rockchip: rk3399: Add Nanopi M4 2GB board support
[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 create_rsa_pair(name):
142         """Generate a new RSA key paid and certificate
143
144         Args:
145             name: Name of of the key (e.g. 'dev')
146         """
147         public_exponent = 65537
148         util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
149                      '-pkeyopt rsa_keygen_bits:2048 '
150                      '-pkeyopt rsa_keygen_pubexp:%d' %
151                      (tmpdir, name, public_exponent))
152
153         # Create a certificate containing the public key
154         util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
155                          '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
156
157     def test_with_algo(sha_algo, padding):
158         """Test verified boot with the given hash algorithm.
159
160         This is the main part of the test code. The same procedure is followed
161         for both hashing algorithms.
162
163         Args:
164             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
165                     use.
166         """
167         # Compile our device tree files for kernel and U-Boot. These are
168         # regenerated here since mkimage will modify them (by adding a
169         # public key) below.
170         dtc('sandbox-kernel.dts')
171         dtc('sandbox-u-boot.dts')
172
173         # Build the FIT, but don't sign anything yet
174         cons.log.action('%s: Test FIT with signed images' % sha_algo)
175         make_fit('sign-images-%s%s.its' % (sha_algo, padding))
176         run_bootm(sha_algo, 'unsigned images', 'dev-', True)
177
178         # Sign images with our dev keys
179         sign_fit(sha_algo)
180         run_bootm(sha_algo, 'signed images', 'dev+', True)
181
182         # Create a fresh .dtb without the public keys
183         dtc('sandbox-u-boot.dts')
184
185         cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
186         make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
187         run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
188
189         # Sign images with our dev keys
190         sign_fit(sha_algo)
191         run_bootm(sha_algo, 'signed config', 'dev+', True)
192
193         cons.log.action('%s: Check signed config on the host' % sha_algo)
194
195         util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
196
197         # Make sure that U-Boot checks that the config is in the list of hashed
198         # nodes. If it isn't, a security bypass is possible.
199         with open(fit, 'rb') as fd:
200             root, strblock = vboot_forge.read_fdt(fd)
201         root, strblock = vboot_forge.manipulate(root, strblock)
202         with open(fit, 'w+b') as fd:
203             vboot_forge.write_fdt(root, strblock, fd)
204         util.run_and_log_expect_exception(
205             cons, [fit_check_sign, '-f', fit, '-k', dtb],
206             1, 'Failed to verify required signature')
207
208         run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
209
210         # Create a new properly signed fit and replace header bytes
211         make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
212         sign_fit(sha_algo)
213         bcfg = u_boot_console.config.buildconfig
214         max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
215         existing_size = replace_fit_totalsize(max_size + 1)
216         run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
217                   False)
218         cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
219
220         # Replace with existing header bytes
221         replace_fit_totalsize(existing_size)
222         run_bootm(sha_algo, 'signed config', 'dev+', True)
223         cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
224
225         # Increment the first byte of the signature, which should cause failure
226         sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
227                                (fit, sig_node))
228         byte_list = sig.split()
229         byte = int(byte_list[0], 16)
230         byte_list[0] = '%x' % (byte + 1)
231         sig = ' '.join(byte_list)
232         util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
233                          (fit, sig_node, sig))
234
235         run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
236                   False)
237
238         cons.log.action('%s: Check bad config on the host' % sha_algo)
239         util.run_and_log_expect_exception(
240             cons, [fit_check_sign, '-f', fit, '-k', dtb],
241             1, 'Failed to verify required signature')
242
243     def test_required_key(sha_algo, padding):
244         """Test verified boot with the given hash algorithm.
245
246         This function tests if U-Boot rejects an image when a required key isn't
247         used to sign a FIT.
248
249         Args:
250             sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
251         """
252         # Compile our device tree files for kernel and U-Boot. These are
253         # regenerated here since mkimage will modify them (by adding a
254         # public key) below.
255         dtc('sandbox-kernel.dts')
256         dtc('sandbox-u-boot.dts')
257
258         cons.log.action('%s: Test FIT with configs images' % sha_algo)
259
260         # Build the FIT with prod key (keys required) and sign it. This puts the
261         # signature into sandbox-u-boot.dtb, marked 'required'
262         make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
263         sign_fit(sha_algo)
264
265         # Build the FIT with dev key (keys NOT required). This adds the
266         # signature into sandbox-u-boot.dtb, NOT marked 'required'.
267         make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
268         sign_fit(sha_algo)
269
270         # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
271         # Only the prod key is set as 'required'. But FIT we just built has
272         # a dev signature only (sign_fit() overwrites the FIT).
273         # Try to boot the FIT with dev key. This FIT should not be accepted by
274         # U-Boot because the prod key is required.
275         run_bootm(sha_algo, 'required key', '', False)
276
277     cons = u_boot_console
278     tmpdir = cons.config.result_dir + '/'
279     datadir = cons.config.source_dir + '/test/py/tests/vboot/'
280     fit = '%stest.fit' % tmpdir
281     mkimage = cons.config.build_dir + '/tools/mkimage'
282     fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
283     dtc_args = '-I dts -O dtb -i %s' % tmpdir
284     dtb = '%ssandbox-u-boot.dtb' % tmpdir
285     sig_node = '/configurations/conf-1/signature'
286
287     create_rsa_pair('dev')
288     create_rsa_pair('prod')
289
290     # Create a number kernel image with zeroes
291     with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
292         fd.write(500 * chr(0))
293
294     try:
295         # We need to use our own device tree file. Remember to restore it
296         # afterwards.
297         old_dtb = cons.config.dtb
298         cons.config.dtb = dtb
299         if required:
300             test_required_key(sha_algo, padding)
301         else:
302             test_with_algo(sha_algo, padding)
303     finally:
304         # Go back to the original U-Boot with the correct dtb.
305         cons.config.dtb = old_dtb
306         cons.restart_uboot()