blob: e45800d94c02b62dcba092a5614b64640ef061a2 [file] [log] [blame]
Simon Glassd977ecd2016-07-03 09:40:46 -06001# SPDX-License-Identifier: GPL-2.0+
Tom Rini10e47792018-05-06 17:58:06 -04002# Copyright (c) 2016, Google Inc.
Simon Glassd977ecd2016-07-03 09:40:46 -06003#
4# U-Boot Verified Boot Test
5
6"""
7This tests verified boot in the following ways:
8
9For 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
15For configuration verification:
16- Corrupt signature and check for failure
17- Create FIT (with unsigned configuration) with mkimage
Simon Glassd5deca02016-07-31 17:35:04 -060018- Check that image verification works
Simon Glassd977ecd2016-07-03 09:40:46 -060019- 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
24Tests run with both SHA1 and SHA256 hashing.
25"""
26
Teddy Reede6a47832018-06-09 11:38:05 -040027import struct
Simon Glass861b5042020-03-18 11:44:05 -060028import pytest
Simon Glassd977ecd2016-07-03 09:40:46 -060029import u_boot_utils as util
Simon Glassc35df8f2020-03-18 11:43:59 -060030import vboot_forge
Simon Glassd977ecd2016-07-03 09:40:46 -060031
Simon Glassa0ba39d2020-03-18 11:44:00 -060032TESTDATA = [
Philippe Reynes2fbd17c2020-04-29 15:26:16 +020033 ['sha1', '', None, False],
34 ['sha1', '', '-E -p 0x10000', False],
35 ['sha1', '-pss', None, False],
36 ['sha1', '-pss', '-E -p 0x10000', False],
37 ['sha256', '', None, False],
38 ['sha256', '', '-E -p 0x10000', False],
39 ['sha256', '-pss', None, False],
40 ['sha256', '-pss', '-E -p 0x10000', False],
41 ['sha256', '-pss', None, True],
42 ['sha256', '-pss', '-E -p 0x10000', True],
Simon Glassa0ba39d2020-03-18 11:44:00 -060043]
44
Michal Simek6e035ab2016-07-18 08:49:08 +020045@pytest.mark.boardspec('sandbox')
Simon Glassd977ecd2016-07-03 09:40:46 -060046@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2079db32017-09-18 11:11:49 -060047@pytest.mark.requiredtool('dtc')
48@pytest.mark.requiredtool('fdtget')
49@pytest.mark.requiredtool('fdtput')
50@pytest.mark.requiredtool('openssl')
Philippe Reynes2fbd17c2020-04-29 15:26:16 +020051@pytest.mark.parametrize("sha_algo,padding,sign_options,required", TESTDATA)
52def test_vboot(u_boot_console, sha_algo, padding, sign_options, required):
Simon Glassd977ecd2016-07-03 09:40:46 -060053 """Test verified boot signing with mkimage and verification with 'bootm'.
54
55 This works using sandbox only as it needs to update the device tree used
56 by U-Boot to hold public keys from the signing process.
57
58 The SHA1 and SHA256 tests are combined into a single test since the
59 key-generation process is quite slow and we want to avoid doing it twice.
60 """
61 def dtc(dts):
Simon Glassd5deca02016-07-31 17:35:04 -060062 """Run the device tree compiler to compile a .dts file
Simon Glassd977ecd2016-07-03 09:40:46 -060063
64 The output file will be the same as the input file but with a .dtb
65 extension.
66
67 Args:
68 dts: Device tree file to compile.
69 """
70 dtb = dts.replace('.dts', '.dtb')
Simon Glassba8116c2016-07-31 17:35:05 -060071 util.run_and_log(cons, 'dtc %s %s%s -O dtb '
72 '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
Simon Glassd977ecd2016-07-03 09:40:46 -060073
Tom Rinib65ce462016-09-18 09:46:58 -040074 def run_bootm(sha_algo, test_type, expect_string, boots):
Simon Glassd977ecd2016-07-03 09:40:46 -060075 """Run a 'bootm' command U-Boot.
76
77 This always starts a fresh U-Boot instance since the device tree may
78 contain a new public key.
79
80 Args:
Simon Glassf223c732016-07-31 17:35:06 -060081 test_type: A string identifying the test type.
82 expect_string: A string which is expected in the output.
83 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
84 use.
Tom Rinib65ce462016-09-18 09:46:58 -040085 boots: A boolean that is True if Linux should boot and False if
86 we are expected to not boot
Simon Glassd977ecd2016-07-03 09:40:46 -060087 """
Simon Glass37c2ce12016-07-31 17:35:08 -060088 cons.restart_uboot()
Simon Glass2a40d832016-07-31 17:35:07 -060089 with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
90 output = cons.run_command_list(
Simon Glassf250d472018-11-15 18:44:02 -070091 ['host load hostfs - 100 %stest.fit' % tmpdir,
Simon Glass861b5042020-03-18 11:44:05 -060092 'fdt addr 100',
93 'bootm 100'])
94 assert expect_string in ''.join(output)
Tom Rinib65ce462016-09-18 09:46:58 -040095 if boots:
Simon Glass861b5042020-03-18 11:44:05 -060096 assert 'sandbox: continuing, as we cannot run' in ''.join(output)
Philippe Reynes1d5ef522019-09-18 16:04:53 +020097 else:
Simon Glass724c03b2020-03-18 11:44:04 -060098 assert('sandbox: continuing, as we cannot run'
99 not in ''.join(output))
Simon Glassd977ecd2016-07-03 09:40:46 -0600100
101 def make_fit(its):
Simon Glassd5deca02016-07-31 17:35:04 -0600102 """Make a new FIT from the .its source file.
Simon Glassd977ecd2016-07-03 09:40:46 -0600103
104 This runs 'mkimage -f' to create a new FIT.
105
106 Args:
Simon Glassd5deca02016-07-31 17:35:04 -0600107 its: Filename containing .its source.
Simon Glassd977ecd2016-07-03 09:40:46 -0600108 """
109 util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
110 '%s%s' % (datadir, its), fit])
111
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200112 def sign_fit(sha_algo, options):
Simon Glassd977ecd2016-07-03 09:40:46 -0600113 """Sign the FIT
114
115 Signs the FIT and writes the signature into it. It also writes the
116 public key into the dtb.
Simon Glassf223c732016-07-31 17:35:06 -0600117
118 Args:
119 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
120 use.
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200121 options: Options to provide to mkimage.
Simon Glassd977ecd2016-07-03 09:40:46 -0600122 """
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200123 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
124 if options:
125 args += options.split(' ')
Simon Glassf223c732016-07-31 17:35:06 -0600126 cons.log.action('%s: Sign images' % sha_algo)
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200127 util.run_and_log(cons, args)
Simon Glassd977ecd2016-07-03 09:40:46 -0600128
Thirupathaiah Annapureddy7e703f72020-08-16 23:01:10 -0700129 def sign_fit_norequire(sha_algo, options):
130 """Sign the FIT
131
132 Signs the FIT and writes the signature into it. It also writes the
133 public key into the dtb. It does not mark key as 'required' in dtb.
134
135 Args:
136 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
137 use.
138 options: Options to provide to mkimage.
139 """
140 args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit]
141 if options:
142 args += options.split(' ')
143 cons.log.action('%s: Sign images' % sha_algo)
144 util.run_and_log(cons, args)
145
Teddy Reede6a47832018-06-09 11:38:05 -0400146 def replace_fit_totalsize(size):
147 """Replace FIT header's totalsize with something greater.
148
149 The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
150 If the size is greater, the signature verification should return false.
151
152 Args:
153 size: The new totalsize of the header
154
155 Returns:
156 prev_size: The previous totalsize read from the header
157 """
158 total_size = 0
159 with open(fit, 'r+b') as handle:
160 handle.seek(4)
161 total_size = handle.read(4)
162 handle.seek(4)
163 handle.write(struct.pack(">I", size))
164 return struct.unpack(">I", total_size)[0]
165
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600166 def create_rsa_pair(name):
167 """Generate a new RSA key paid and certificate
168
169 Args:
170 name: Name of of the key (e.g. 'dev')
171 """
172 public_exponent = 65537
173 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
174 '-pkeyopt rsa_keygen_bits:2048 '
175 '-pkeyopt rsa_keygen_pubexp:%d' %
176 (tmpdir, name, public_exponent))
177
178 # Create a certificate containing the public key
179 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
180 '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
181
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200182 def test_with_algo(sha_algo, padding, sign_options):
Simon Glassd5deca02016-07-31 17:35:04 -0600183 """Test verified boot with the given hash algorithm.
Simon Glassd977ecd2016-07-03 09:40:46 -0600184
185 This is the main part of the test code. The same procedure is followed
186 for both hashing algorithms.
187
188 Args:
Simon Glassf223c732016-07-31 17:35:06 -0600189 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
190 use.
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200191 padding: Either '' or '-pss', to select the padding to use for the
192 rsa signature algorithm.
193 sign_options: Options to mkimage when signing a fit image.
Simon Glassd977ecd2016-07-03 09:40:46 -0600194 """
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600195 # Compile our device tree files for kernel and U-Boot. These are
196 # regenerated here since mkimage will modify them (by adding a
197 # public key) below.
Simon Glassd977ecd2016-07-03 09:40:46 -0600198 dtc('sandbox-kernel.dts')
199 dtc('sandbox-u-boot.dts')
200
201 # Build the FIT, but don't sign anything yet
Simon Glassf223c732016-07-31 17:35:06 -0600202 cons.log.action('%s: Test FIT with signed images' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600203 make_fit('sign-images-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400204 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600205
206 # Sign images with our dev keys
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200207 sign_fit(sha_algo, sign_options)
Tom Rinib65ce462016-09-18 09:46:58 -0400208 run_bootm(sha_algo, 'signed images', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600209
210 # Create a fresh .dtb without the public keys
211 dtc('sandbox-u-boot.dts')
212
Simon Glassf223c732016-07-31 17:35:06 -0600213 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600214 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400215 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600216
217 # Sign images with our dev keys
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200218 sign_fit(sha_algo, sign_options)
Tom Rinib65ce462016-09-18 09:46:58 -0400219 run_bootm(sha_algo, 'signed config', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600220
Simon Glassf223c732016-07-31 17:35:06 -0600221 cons.log.action('%s: Check signed config on the host' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600222
Simon Glassf411a892020-03-18 11:43:58 -0600223 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
Simon Glassd977ecd2016-07-03 09:40:46 -0600224
Simon Glassc35df8f2020-03-18 11:43:59 -0600225 # Make sure that U-Boot checks that the config is in the list of hashed
226 # nodes. If it isn't, a security bypass is possible.
Simon Glass861b5042020-03-18 11:44:05 -0600227 with open(fit, 'rb') as fd:
228 root, strblock = vboot_forge.read_fdt(fd)
Simon Glassc35df8f2020-03-18 11:43:59 -0600229 root, strblock = vboot_forge.manipulate(root, strblock)
Simon Glass861b5042020-03-18 11:44:05 -0600230 with open(fit, 'w+b') as fd:
231 vboot_forge.write_fdt(root, strblock, fd)
232 util.run_and_log_expect_exception(
233 cons, [fit_check_sign, '-f', fit, '-k', dtb],
234 1, 'Failed to verify required signature')
Simon Glassc35df8f2020-03-18 11:43:59 -0600235
236 run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
237
238 # Create a new properly signed fit and replace header bytes
239 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200240 sign_fit(sha_algo, sign_options)
Teddy Reede6a47832018-06-09 11:38:05 -0400241 bcfg = u_boot_console.config.buildconfig
242 max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
243 existing_size = replace_fit_totalsize(max_size + 1)
Simon Glass724c03b2020-03-18 11:44:04 -0600244 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
245 False)
Teddy Reede6a47832018-06-09 11:38:05 -0400246 cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
247
248 # Replace with existing header bytes
249 replace_fit_totalsize(existing_size)
250 run_bootm(sha_algo, 'signed config', 'dev+', True)
251 cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
252
Simon Glassd977ecd2016-07-03 09:40:46 -0600253 # Increment the first byte of the signature, which should cause failure
Simon Glassba8116c2016-07-31 17:35:05 -0600254 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
255 (fit, sig_node))
Simon Glassd977ecd2016-07-03 09:40:46 -0600256 byte_list = sig.split()
257 byte = int(byte_list[0], 16)
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600258 byte_list[0] = '%x' % (byte + 1)
Simon Glassd977ecd2016-07-03 09:40:46 -0600259 sig = ' '.join(byte_list)
Simon Glassba8116c2016-07-31 17:35:05 -0600260 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
261 (fit, sig_node, sig))
Simon Glassd977ecd2016-07-03 09:40:46 -0600262
Simon Glass724c03b2020-03-18 11:44:04 -0600263 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
264 False)
Simon Glassd977ecd2016-07-03 09:40:46 -0600265
Simon Glassf223c732016-07-31 17:35:06 -0600266 cons.log.action('%s: Check bad config on the host' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600267 util.run_and_log_expect_exception(
268 cons, [fit_check_sign, '-f', fit, '-k', dtb],
269 1, 'Failed to verify required signature')
Simon Glassd977ecd2016-07-03 09:40:46 -0600270
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200271 def test_required_key(sha_algo, padding, sign_options):
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200272 """Test verified boot with the given hash algorithm.
273
Simon Glass724c03b2020-03-18 11:44:04 -0600274 This function tests if U-Boot rejects an image when a required key isn't
275 used to sign a FIT.
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200276
277 Args:
Simon Glass724c03b2020-03-18 11:44:04 -0600278 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200279 padding: Either '' or '-pss', to select the padding to use for the
280 rsa signature algorithm.
281 sign_options: Options to mkimage when signing a fit image.
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200282 """
283 # Compile our device tree files for kernel and U-Boot. These are
284 # regenerated here since mkimage will modify them (by adding a
285 # public key) below.
286 dtc('sandbox-kernel.dts')
287 dtc('sandbox-u-boot.dts')
288
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200289 cons.log.action('%s: Test FIT with configs images' % sha_algo)
Simon Glass724c03b2020-03-18 11:44:04 -0600290
291 # Build the FIT with prod key (keys required) and sign it. This puts the
292 # signature into sandbox-u-boot.dtb, marked 'required'
Simon Glass861b5042020-03-18 11:44:05 -0600293 make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200294 sign_fit(sha_algo, sign_options)
Simon Glass724c03b2020-03-18 11:44:04 -0600295
296 # Build the FIT with dev key (keys NOT required). This adds the
297 # signature into sandbox-u-boot.dtb, NOT marked 'required'.
Simon Glass861b5042020-03-18 11:44:05 -0600298 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Thirupathaiah Annapureddy7e703f72020-08-16 23:01:10 -0700299 sign_fit_norequire(sha_algo, sign_options)
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200300
Simon Glass724c03b2020-03-18 11:44:04 -0600301 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
302 # Only the prod key is set as 'required'. But FIT we just built has
Thirupathaiah Annapureddy7e703f72020-08-16 23:01:10 -0700303 # a dev signature only (sign_fit_norequire() overwrites the FIT).
Simon Glass724c03b2020-03-18 11:44:04 -0600304 # Try to boot the FIT with dev key. This FIT should not be accepted by
305 # U-Boot because the prod key is required.
306 run_bootm(sha_algo, 'required key', '', False)
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200307
Thirupathaiah Annapureddy7e703f72020-08-16 23:01:10 -0700308 # Build the FIT with dev key (keys required) and sign it. This puts the
309 # signature into sandbox-u-boot.dtb, marked 'required'.
310 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
311 sign_fit(sha_algo, sign_options)
312
313 # Set the required-mode policy to "any".
314 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
315 # Both the dev and prod key are set as 'required'. But FIT we just built has
316 # a dev signature only (sign_fit() overwrites the FIT).
317 # Try to boot the FIT with dev key. This FIT should be accepted by
318 # U-Boot because the dev key is required and policy is "any" required key.
319 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' %
320 (dtb))
321 run_bootm(sha_algo, 'multi required key', 'dev+', True)
322
323 # Set the required-mode policy to "all".
324 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
325 # Both the dev and prod key are set as 'required'. But FIT we just built has
326 # a dev signature only (sign_fit() overwrites the FIT).
327 # Try to boot the FIT with dev key. This FIT should not be accepted by
328 # U-Boot because the prod key is required and policy is "all" required key
329 util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' %
330 (dtb))
331 run_bootm(sha_algo, 'multi required key', '', False)
332
Simon Glassd977ecd2016-07-03 09:40:46 -0600333 cons = u_boot_console
334 tmpdir = cons.config.result_dir + '/'
Stephen Warren7047d952016-07-18 10:07:25 -0600335 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
Simon Glassd977ecd2016-07-03 09:40:46 -0600336 fit = '%stest.fit' % tmpdir
337 mkimage = cons.config.build_dir + '/tools/mkimage'
338 fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
339 dtc_args = '-I dts -O dtb -i %s' % tmpdir
340 dtb = '%ssandbox-u-boot.dtb' % tmpdir
Philippe Reynesa28e9222018-11-14 13:51:05 +0100341 sig_node = '/configurations/conf-1/signature'
Simon Glassd977ecd2016-07-03 09:40:46 -0600342
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600343 create_rsa_pair('dev')
344 create_rsa_pair('prod')
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200345
Simon Glassd977ecd2016-07-03 09:40:46 -0600346 # Create a number kernel image with zeroes
347 with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
Simon Glass9be531f2020-03-18 11:44:08 -0600348 fd.write(500 * chr(0))
Simon Glassd977ecd2016-07-03 09:40:46 -0600349
350 try:
351 # We need to use our own device tree file. Remember to restore it
352 # afterwards.
353 old_dtb = cons.config.dtb
354 cons.config.dtb = dtb
Simon Glassa0ba39d2020-03-18 11:44:00 -0600355 if required:
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200356 test_required_key(sha_algo, padding, sign_options)
Simon Glassa0ba39d2020-03-18 11:44:00 -0600357 else:
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200358 test_with_algo(sha_algo, padding, sign_options)
Simon Glassd977ecd2016-07-03 09:40:46 -0600359 finally:
Simon Glass37c2ce12016-07-31 17:35:08 -0600360 # Go back to the original U-Boot with the correct dtb.
Simon Glassd977ecd2016-07-03 09:40:46 -0600361 cons.config.dtb = old_dtb
Simon Glass37c2ce12016-07-31 17:35:08 -0600362 cons.restart_uboot()