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.
31 import u_boot_utils as util
35 # Only run the full suite on a few combinations, since it doesn't add any more
38 ['sha1-basic', 'sha1', '', None, False, True],
39 ['sha1-pad', 'sha1', '', '-E -p 0x10000', False, False],
40 ['sha1-pss', 'sha1', '-pss', None, False, False],
41 ['sha1-pss-pad', 'sha1', '-pss', '-E -p 0x10000', False, False],
42 ['sha256-basic', 'sha256', '', None, False, False],
43 ['sha256-pad', 'sha256', '', '-E -p 0x10000', False, False],
44 ['sha256-pss', 'sha256', '-pss', None, False, False],
45 ['sha256-pss-pad', 'sha256', '-pss', '-E -p 0x10000', False, False],
46 ['sha256-pss-required', 'sha256', '-pss', None, True, False],
47 ['sha256-pss-pad-required', 'sha256', '-pss', '-E -p 0x10000', True, True],
50 @pytest.mark.boardspec('sandbox')
51 @pytest.mark.buildconfigspec('fit_signature')
52 @pytest.mark.requiredtool('dtc')
53 @pytest.mark.requiredtool('fdtget')
54 @pytest.mark.requiredtool('fdtput')
55 @pytest.mark.requiredtool('openssl')
56 @pytest.mark.parametrize("name,sha_algo,padding,sign_options,required,full_test",
58 def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required,
60 """Test verified boot signing with mkimage and verification with 'bootm'.
62 This works using sandbox only as it needs to update the device tree used
63 by U-Boot to hold public keys from the signing process.
65 The SHA1 and SHA256 tests are combined into a single test since the
66 key-generation process is quite slow and we want to avoid doing it twice.
69 """Run the device tree compiler to compile a .dts file
71 The output file will be the same as the input file but with a .dtb
75 dts: Device tree file to compile.
77 dtb = dts.replace('.dts', '.dtb')
78 util.run_and_log(cons, 'dtc %s %s%s -O dtb '
79 '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
81 def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
82 """Run a 'bootm' command U-Boot.
84 This always starts a fresh U-Boot instance since the device tree may
85 contain a new public key.
88 test_type: A string identifying the test type.
89 expect_string: A string which is expected in the output.
90 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
92 boots: A boolean that is True if Linux should boot and False if
93 we are expected to not boot
94 fit: FIT filename to load and verify
97 fit = '%stest.fit' % tmpdir
99 with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
100 output = cons.run_command_list(
101 ['host load hostfs - 100 %s' % fit,
104 assert expect_string in ''.join(output)
106 assert 'sandbox: continuing, as we cannot run' in ''.join(output)
108 assert('sandbox: continuing, as we cannot run'
109 not in ''.join(output))
112 """Make a new FIT from the .its source file.
114 This runs 'mkimage -f' to create a new FIT.
117 its: Filename containing .its source.
119 util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
120 '%s%s' % (datadir, its), fit])
122 def sign_fit(sha_algo, options):
125 Signs the FIT and writes the signature into it. It also writes the
126 public key into the dtb.
129 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
131 options: Options to provide to mkimage.
133 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
135 args += options.split(' ')
136 cons.log.action('%s: Sign images' % sha_algo)
137 util.run_and_log(cons, args)
139 def sign_fit_norequire(sha_algo, options):
142 Signs the FIT and writes the signature into it. It also writes the
143 public key into the dtb. It does not mark key as 'required' in dtb.
146 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
148 options: Options to provide to mkimage.
150 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit]
152 args += options.split(' ')
153 cons.log.action('%s: Sign images' % sha_algo)
154 util.run_and_log(cons, args)
156 def replace_fit_totalsize(size):
157 """Replace FIT header's totalsize with something greater.
159 The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
160 If the size is greater, the signature verification should return false.
163 size: The new totalsize of the header
166 prev_size: The previous totalsize read from the header
169 with open(fit, 'r+b') as handle:
171 total_size = handle.read(4)
173 handle.write(struct.pack(">I", size))
174 return struct.unpack(">I", total_size)[0]
176 def create_rsa_pair(name):
177 """Generate a new RSA key paid and certificate
180 name: Name of of the key (e.g. 'dev')
182 public_exponent = 65537
183 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
184 '-pkeyopt rsa_keygen_bits:2048 '
185 '-pkeyopt rsa_keygen_pubexp:%d' %
186 (tmpdir, name, public_exponent))
188 # Create a certificate containing the public key
189 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
190 '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
192 def test_with_algo(sha_algo, padding, sign_options):
193 """Test verified boot with the given hash algorithm.
195 This is the main part of the test code. The same procedure is followed
196 for both hashing algorithms.
199 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
201 padding: Either '' or '-pss', to select the padding to use for the
202 rsa signature algorithm.
203 sign_options: Options to mkimage when signing a fit image.
205 # Compile our device tree files for kernel and U-Boot. These are
206 # regenerated here since mkimage will modify them (by adding a
208 dtc('sandbox-kernel.dts')
209 dtc('sandbox-u-boot.dts')
211 # Build the FIT, but don't sign anything yet
212 cons.log.action('%s: Test FIT with signed images' % sha_algo)
213 make_fit('sign-images-%s%s.its' % (sha_algo, padding))
214 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
216 # Sign images with our dev keys
217 sign_fit(sha_algo, sign_options)
218 run_bootm(sha_algo, 'signed images', 'dev+', True)
220 # Create a fresh .dtb without the public keys
221 dtc('sandbox-u-boot.dts')
223 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
224 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
225 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
227 # Sign images with our dev keys
228 sign_fit(sha_algo, sign_options)
229 run_bootm(sha_algo, 'signed config', 'dev+', True)
231 cons.log.action('%s: Check signed config on the host' % sha_algo)
233 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
236 # Make sure that U-Boot checks that the config is in the list of
237 # hashed nodes. If it isn't, a security bypass is possible.
238 ffit = '%stest.forged.fit' % tmpdir
239 shutil.copyfile(fit, ffit)
240 with open(ffit, 'rb') as fd:
241 root, strblock = vboot_forge.read_fdt(fd)
242 root, strblock = vboot_forge.manipulate(root, strblock)
243 with open(ffit, 'w+b') as fd:
244 vboot_forge.write_fdt(root, strblock, fd)
245 util.run_and_log_expect_exception(
246 cons, [fit_check_sign, '-f', ffit, '-k', dtb],
247 1, 'Failed to verify required signature')
249 run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit)
251 # Try adding an evil root node. This should be detected.
252 efit = '%stest.evilf.fit' % tmpdir
253 shutil.copyfile(fit, efit)
254 vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot')
256 util.run_and_log_expect_exception(
257 cons, [fit_check_sign, '-f', efit, '-k', dtb],
258 1, 'Failed to verify required signature')
259 run_bootm(sha_algo, 'evil fakeroot', 'Bad FIT kernel image format',
262 # Try adding an @ to the kernel node name. This should be detected.
263 efit = '%stest.evilk.fit' % tmpdir
264 shutil.copyfile(fit, efit)
265 vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
267 msg = 'Signature checking prevents use of unit addresses (@) in nodes'
268 util.run_and_log_expect_exception(
269 cons, [fit_check_sign, '-f', efit, '-k', dtb],
271 run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
273 # Create a new properly signed fit and replace header bytes
274 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
275 sign_fit(sha_algo, sign_options)
276 bcfg = u_boot_console.config.buildconfig
277 max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
278 existing_size = replace_fit_totalsize(max_size + 1)
279 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
281 cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
283 # Replace with existing header bytes
284 replace_fit_totalsize(existing_size)
285 run_bootm(sha_algo, 'signed config', 'dev+', True)
286 cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
288 # Increment the first byte of the signature, which should cause failure
289 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
291 byte_list = sig.split()
292 byte = int(byte_list[0], 16)
293 byte_list[0] = '%x' % (byte + 1)
294 sig = ' '.join(byte_list)
295 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
296 (fit, sig_node, sig))
298 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
301 cons.log.action('%s: Check bad config on the host' % sha_algo)
302 util.run_and_log_expect_exception(
303 cons, [fit_check_sign, '-f', fit, '-k', dtb],
304 1, 'Failed to verify required signature')
306 def test_required_key(sha_algo, padding, sign_options):
307 """Test verified boot with the given hash algorithm.
309 This function tests if U-Boot rejects an image when a required key isn't
313 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
314 padding: Either '' or '-pss', to select the padding to use for the
315 rsa signature algorithm.
316 sign_options: Options to mkimage when signing a fit image.
318 # Compile our device tree files for kernel and U-Boot. These are
319 # regenerated here since mkimage will modify them (by adding a
321 dtc('sandbox-kernel.dts')
322 dtc('sandbox-u-boot.dts')
324 cons.log.action('%s: Test FIT with configs images' % sha_algo)
326 # Build the FIT with prod key (keys required) and sign it. This puts the
327 # signature into sandbox-u-boot.dtb, marked 'required'
328 make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
329 sign_fit(sha_algo, sign_options)
331 # Build the FIT with dev key (keys NOT required). This adds the
332 # signature into sandbox-u-boot.dtb, NOT marked 'required'.
333 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
334 sign_fit_norequire(sha_algo, sign_options)
336 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
337 # Only the prod key is set as 'required'. But FIT we just built has
338 # a dev signature only (sign_fit_norequire() overwrites the FIT).
339 # Try to boot the FIT with dev key. This FIT should not be accepted by
340 # U-Boot because the prod key is required.
341 run_bootm(sha_algo, 'required key', '', False)
343 # Build the FIT with dev key (keys required) and sign it. This puts the
344 # signature into sandbox-u-boot.dtb, marked 'required'.
345 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
346 sign_fit(sha_algo, sign_options)
348 # Set the required-mode policy to "any".
349 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
350 # Both the dev and prod key are set as 'required'. But FIT we just built has
351 # a dev signature only (sign_fit() overwrites the FIT).
352 # Try to boot the FIT with dev key. This FIT should be accepted by
353 # U-Boot because the dev key is required and policy is "any" required key.
354 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' %
356 run_bootm(sha_algo, 'multi required key', 'dev+', True)
358 # Set the required-mode policy to "all".
359 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
360 # Both the dev and prod key are set as 'required'. But FIT we just built has
361 # a dev signature only (sign_fit() overwrites the FIT).
362 # Try to boot the FIT with dev key. This FIT should not be accepted by
363 # U-Boot because the prod key is required and policy is "all" required key
364 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' %
366 run_bootm(sha_algo, 'multi required key', '', False)
368 cons = u_boot_console
369 tmpdir = os.path.join(cons.config.result_dir, name) + '/'
370 if not os.path.exists(tmpdir):
372 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
373 fit = '%stest.fit' % tmpdir
374 mkimage = cons.config.build_dir + '/tools/mkimage'
375 fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
376 dtc_args = '-I dts -O dtb -i %s' % tmpdir
377 dtb = '%ssandbox-u-boot.dtb' % tmpdir
378 sig_node = '/configurations/conf-1/signature'
380 create_rsa_pair('dev')
381 create_rsa_pair('prod')
383 # Create a number kernel image with zeroes
384 with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
385 fd.write(500 * b'\0')
387 # Create a second kernel image with ones
388 evil_kernel = '%stest-kernel1.bin' % tmpdir
389 with open(evil_kernel, 'wb') as fd:
390 fd.write(500 * b'\x01')
393 # We need to use our own device tree file. Remember to restore it
395 old_dtb = cons.config.dtb
396 cons.config.dtb = dtb
398 test_required_key(sha_algo, padding, sign_options)
400 test_with_algo(sha_algo, padding, sign_options)
402 # Go back to the original U-Boot with the correct dtb.
403 cons.config.dtb = old_dtb