1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016, Google Inc.
4 # U-Boot Verified Boot Test
7 This tests verified boot in the following ways:
9 For image verification:
10 - Create FIT (unsigned) with mkimage
11 - Check that verification shows that no keys are verified
13 - Check that verification shows that a key is now verified
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
24 Tests run with both SHA1 and SHA256 hashing.
30 import u_boot_utils as util
34 # Only run the full suite on a few combinations, since it doesn't add any more
37 ['sha1', '', None, False, True],
38 ['sha1', '', '-E -p 0x10000', False, False],
39 ['sha1', '-pss', None, False, False],
40 ['sha1', '-pss', '-E -p 0x10000', False, False],
41 ['sha256', '', None, False, False],
42 ['sha256', '', '-E -p 0x10000', False, False],
43 ['sha256', '-pss', None, False, False],
44 ['sha256', '-pss', '-E -p 0x10000', False, False],
45 ['sha256', '-pss', None, True, False],
46 ['sha256', '-pss', '-E -p 0x10000', True, True],
49 @pytest.mark.boardspec('sandbox')
50 @pytest.mark.buildconfigspec('fit_signature')
51 @pytest.mark.requiredtool('dtc')
52 @pytest.mark.requiredtool('fdtget')
53 @pytest.mark.requiredtool('fdtput')
54 @pytest.mark.requiredtool('openssl')
55 @pytest.mark.parametrize("sha_algo,padding,sign_options,required,full_test",
57 def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
59 """Test verified boot signing with mkimage and verification with 'bootm'.
61 This works using sandbox only as it needs to update the device tree used
62 by U-Boot to hold public keys from the signing process.
64 The SHA1 and SHA256 tests are combined into a single test since the
65 key-generation process is quite slow and we want to avoid doing it twice.
68 """Run the device tree compiler to compile a .dts file
70 The output file will be the same as the input file but with a .dtb
74 dts: Device tree file to compile.
76 dtb = dts.replace('.dts', '.dtb')
77 util.run_and_log(cons, 'dtc %s %s%s -O dtb '
78 '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
80 def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
81 """Run a 'bootm' command U-Boot.
83 This always starts a fresh U-Boot instance since the device tree may
84 contain a new public key.
87 test_type: A string identifying the test type.
88 expect_string: A string which is expected in the output.
89 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
91 boots: A boolean that is True if Linux should boot and False if
92 we are expected to not boot
93 fit: FIT filename to load and verify
96 fit = '%stest.fit' % tmpdir
98 with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
99 output = cons.run_command_list(
100 ['host load hostfs - 100 %s' % fit,
103 assert expect_string in ''.join(output)
105 assert 'sandbox: continuing, as we cannot run' in ''.join(output)
107 assert('sandbox: continuing, as we cannot run'
108 not in ''.join(output))
111 """Make a new FIT from the .its source file.
113 This runs 'mkimage -f' to create a new FIT.
116 its: Filename containing .its source.
118 util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
119 '%s%s' % (datadir, its), fit])
121 def sign_fit(sha_algo, options):
124 Signs the FIT and writes the signature into it. It also writes the
125 public key into the dtb.
128 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
130 options: Options to provide to mkimage.
132 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
134 args += options.split(' ')
135 cons.log.action('%s: Sign images' % sha_algo)
136 util.run_and_log(cons, args)
138 def sign_fit_norequire(sha_algo, options):
141 Signs the FIT and writes the signature into it. It also writes the
142 public key into the dtb. It does not mark key as 'required' in dtb.
145 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
147 options: Options to provide to mkimage.
149 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit]
151 args += options.split(' ')
152 cons.log.action('%s: Sign images' % sha_algo)
153 util.run_and_log(cons, args)
155 def replace_fit_totalsize(size):
156 """Replace FIT header's totalsize with something greater.
158 The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
159 If the size is greater, the signature verification should return false.
162 size: The new totalsize of the header
165 prev_size: The previous totalsize read from the header
168 with open(fit, 'r+b') as handle:
170 total_size = handle.read(4)
172 handle.write(struct.pack(">I", size))
173 return struct.unpack(">I", total_size)[0]
175 def create_rsa_pair(name):
176 """Generate a new RSA key paid and certificate
179 name: Name of of the key (e.g. 'dev')
181 public_exponent = 65537
182 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
183 '-pkeyopt rsa_keygen_bits:2048 '
184 '-pkeyopt rsa_keygen_pubexp:%d' %
185 (tmpdir, name, public_exponent))
187 # Create a certificate containing the public key
188 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
189 '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
191 def test_with_algo(sha_algo, padding, sign_options):
192 """Test verified boot with the given hash algorithm.
194 This is the main part of the test code. The same procedure is followed
195 for both hashing algorithms.
198 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
200 padding: Either '' or '-pss', to select the padding to use for the
201 rsa signature algorithm.
202 sign_options: Options to mkimage when signing a fit image.
204 # Compile our device tree files for kernel and U-Boot. These are
205 # regenerated here since mkimage will modify them (by adding a
207 dtc('sandbox-kernel.dts')
208 dtc('sandbox-u-boot.dts')
210 # Build the FIT, but don't sign anything yet
211 cons.log.action('%s: Test FIT with signed images' % sha_algo)
212 make_fit('sign-images-%s%s.its' % (sha_algo, padding))
213 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
215 # Sign images with our dev keys
216 sign_fit(sha_algo, sign_options)
217 run_bootm(sha_algo, 'signed images', 'dev+', True)
219 # Create a fresh .dtb without the public keys
220 dtc('sandbox-u-boot.dts')
222 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
223 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
224 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
226 # Sign images with our dev keys
227 sign_fit(sha_algo, sign_options)
228 run_bootm(sha_algo, 'signed config', 'dev+', True)
230 cons.log.action('%s: Check signed config on the host' % sha_algo)
232 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
235 # Make sure that U-Boot checks that the config is in the list of
236 # hashed nodes. If it isn't, a security bypass is possible.
237 ffit = '%stest.forged.fit' % tmpdir
238 shutil.copyfile(fit, ffit)
239 with open(ffit, 'rb') as fd:
240 root, strblock = vboot_forge.read_fdt(fd)
241 root, strblock = vboot_forge.manipulate(root, strblock)
242 with open(ffit, 'w+b') as fd:
243 vboot_forge.write_fdt(root, strblock, fd)
244 util.run_and_log_expect_exception(
245 cons, [fit_check_sign, '-f', ffit, '-k', dtb],
246 1, 'Failed to verify required signature')
248 run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit)
250 # Try adding an evil root node. This should be detected.
251 efit = '%stest.evilf.fit' % tmpdir
252 shutil.copyfile(fit, efit)
253 vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot')
255 util.run_and_log_expect_exception(
256 cons, [fit_check_sign, '-f', efit, '-k', dtb],
257 1, 'Failed to verify required signature')
258 run_bootm(sha_algo, 'evil fakeroot', 'Bad FIT kernel image format',
261 # Try adding an @ to the kernel node name. This should be detected.
262 efit = '%stest.evilk.fit' % tmpdir
263 shutil.copyfile(fit, efit)
264 vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
266 msg = 'Signature checking prevents use of unit addresses (@) in nodes'
267 util.run_and_log_expect_exception(
268 cons, [fit_check_sign, '-f', efit, '-k', dtb],
270 run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
272 # Create a new properly signed fit and replace header bytes
273 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
274 sign_fit(sha_algo, sign_options)
275 bcfg = u_boot_console.config.buildconfig
276 max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
277 existing_size = replace_fit_totalsize(max_size + 1)
278 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
280 cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
282 # Replace with existing header bytes
283 replace_fit_totalsize(existing_size)
284 run_bootm(sha_algo, 'signed config', 'dev+', True)
285 cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
287 # Increment the first byte of the signature, which should cause failure
288 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
290 byte_list = sig.split()
291 byte = int(byte_list[0], 16)
292 byte_list[0] = '%x' % (byte + 1)
293 sig = ' '.join(byte_list)
294 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
295 (fit, sig_node, sig))
297 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
300 cons.log.action('%s: Check bad config on the host' % sha_algo)
301 util.run_and_log_expect_exception(
302 cons, [fit_check_sign, '-f', fit, '-k', dtb],
303 1, 'Failed to verify required signature')
305 def test_required_key(sha_algo, padding, sign_options):
306 """Test verified boot with the given hash algorithm.
308 This function tests if U-Boot rejects an image when a required key isn't
312 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
313 padding: Either '' or '-pss', to select the padding to use for the
314 rsa signature algorithm.
315 sign_options: Options to mkimage when signing a fit image.
317 # Compile our device tree files for kernel and U-Boot. These are
318 # regenerated here since mkimage will modify them (by adding a
320 dtc('sandbox-kernel.dts')
321 dtc('sandbox-u-boot.dts')
323 cons.log.action('%s: Test FIT with configs images' % sha_algo)
325 # Build the FIT with prod key (keys required) and sign it. This puts the
326 # signature into sandbox-u-boot.dtb, marked 'required'
327 make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
328 sign_fit(sha_algo, sign_options)
330 # Build the FIT with dev key (keys NOT required). This adds the
331 # signature into sandbox-u-boot.dtb, NOT marked 'required'.
332 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
333 sign_fit_norequire(sha_algo, sign_options)
335 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
336 # Only the prod key is set as 'required'. But FIT we just built has
337 # a dev signature only (sign_fit_norequire() overwrites the FIT).
338 # Try to boot the FIT with dev key. This FIT should not be accepted by
339 # U-Boot because the prod key is required.
340 run_bootm(sha_algo, 'required key', '', False)
342 # Build the FIT with dev key (keys required) and sign it. This puts the
343 # signature into sandbox-u-boot.dtb, marked 'required'.
344 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
345 sign_fit(sha_algo, sign_options)
347 # Set the required-mode policy to "any".
348 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
349 # Both the dev and prod key are set as 'required'. But FIT we just built has
350 # a dev signature only (sign_fit() overwrites the FIT).
351 # Try to boot the FIT with dev key. This FIT should be accepted by
352 # U-Boot because the dev key is required and policy is "any" required key.
353 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' %
355 run_bootm(sha_algo, 'multi required key', 'dev+', True)
357 # Set the required-mode policy to "all".
358 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
359 # Both the dev and prod key are set as 'required'. But FIT we just built has
360 # a dev signature only (sign_fit() overwrites the FIT).
361 # Try to boot the FIT with dev key. This FIT should not be accepted by
362 # U-Boot because the prod key is required and policy is "all" required key
363 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' %
365 run_bootm(sha_algo, 'multi required key', '', False)
367 cons = u_boot_console
368 tmpdir = cons.config.result_dir + '/'
369 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
370 fit = '%stest.fit' % tmpdir
371 mkimage = cons.config.build_dir + '/tools/mkimage'
372 fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
373 dtc_args = '-I dts -O dtb -i %s' % tmpdir
374 dtb = '%ssandbox-u-boot.dtb' % tmpdir
375 sig_node = '/configurations/conf-1/signature'
377 create_rsa_pair('dev')
378 create_rsa_pair('prod')
380 # Create a number kernel image with zeroes
381 with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
382 fd.write(500 * b'\0')
384 # Create a second kernel image with ones
385 evil_kernel = '%stest-kernel1.bin' % tmpdir
386 with open(evil_kernel, 'wb') as fd:
387 fd.write(500 * b'\x01')
390 # We need to use our own device tree file. Remember to restore it
392 old_dtb = cons.config.dtb
393 cons.config.dtb = dtb
395 test_required_key(sha_algo, padding, sign_options)
397 test_with_algo(sha_algo, padding, sign_options)
399 # Go back to the original U-Boot with the correct dtb.
400 cons.config.dtb = old_dtb