blob: 6b998cfd70e8c6997d6d921e0439ac94f67ff1e0 [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
Teddy Reede6a47832018-06-09 11:38:05 -0400129 def replace_fit_totalsize(size):
130 """Replace FIT header's totalsize with something greater.
131
132 The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
133 If the size is greater, the signature verification should return false.
134
135 Args:
136 size: The new totalsize of the header
137
138 Returns:
139 prev_size: The previous totalsize read from the header
140 """
141 total_size = 0
142 with open(fit, 'r+b') as handle:
143 handle.seek(4)
144 total_size = handle.read(4)
145 handle.seek(4)
146 handle.write(struct.pack(">I", size))
147 return struct.unpack(">I", total_size)[0]
148
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600149 def create_rsa_pair(name):
150 """Generate a new RSA key paid and certificate
151
152 Args:
153 name: Name of of the key (e.g. 'dev')
154 """
155 public_exponent = 65537
156 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
157 '-pkeyopt rsa_keygen_bits:2048 '
158 '-pkeyopt rsa_keygen_pubexp:%d' %
159 (tmpdir, name, public_exponent))
160
161 # Create a certificate containing the public key
162 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
163 '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
164
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200165 def test_with_algo(sha_algo, padding, sign_options):
Simon Glassd5deca02016-07-31 17:35:04 -0600166 """Test verified boot with the given hash algorithm.
Simon Glassd977ecd2016-07-03 09:40:46 -0600167
168 This is the main part of the test code. The same procedure is followed
169 for both hashing algorithms.
170
171 Args:
Simon Glassf223c732016-07-31 17:35:06 -0600172 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
173 use.
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200174 padding: Either '' or '-pss', to select the padding to use for the
175 rsa signature algorithm.
176 sign_options: Options to mkimage when signing a fit image.
Simon Glassd977ecd2016-07-03 09:40:46 -0600177 """
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600178 # Compile our device tree files for kernel and U-Boot. These are
179 # regenerated here since mkimage will modify them (by adding a
180 # public key) below.
Simon Glassd977ecd2016-07-03 09:40:46 -0600181 dtc('sandbox-kernel.dts')
182 dtc('sandbox-u-boot.dts')
183
184 # Build the FIT, but don't sign anything yet
Simon Glassf223c732016-07-31 17:35:06 -0600185 cons.log.action('%s: Test FIT with signed images' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600186 make_fit('sign-images-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400187 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600188
189 # Sign images with our dev keys
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200190 sign_fit(sha_algo, sign_options)
Tom Rinib65ce462016-09-18 09:46:58 -0400191 run_bootm(sha_algo, 'signed images', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600192
193 # Create a fresh .dtb without the public keys
194 dtc('sandbox-u-boot.dts')
195
Simon Glassf223c732016-07-31 17:35:06 -0600196 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600197 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400198 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600199
200 # Sign images with our dev keys
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200201 sign_fit(sha_algo, sign_options)
Tom Rinib65ce462016-09-18 09:46:58 -0400202 run_bootm(sha_algo, 'signed config', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600203
Simon Glassf223c732016-07-31 17:35:06 -0600204 cons.log.action('%s: Check signed config on the host' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600205
Simon Glassf411a892020-03-18 11:43:58 -0600206 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
Simon Glassd977ecd2016-07-03 09:40:46 -0600207
Simon Glassc35df8f2020-03-18 11:43:59 -0600208 # Make sure that U-Boot checks that the config is in the list of hashed
209 # nodes. If it isn't, a security bypass is possible.
Simon Glass861b5042020-03-18 11:44:05 -0600210 with open(fit, 'rb') as fd:
211 root, strblock = vboot_forge.read_fdt(fd)
Simon Glassc35df8f2020-03-18 11:43:59 -0600212 root, strblock = vboot_forge.manipulate(root, strblock)
Simon Glass861b5042020-03-18 11:44:05 -0600213 with open(fit, 'w+b') as fd:
214 vboot_forge.write_fdt(root, strblock, fd)
215 util.run_and_log_expect_exception(
216 cons, [fit_check_sign, '-f', fit, '-k', dtb],
217 1, 'Failed to verify required signature')
Simon Glassc35df8f2020-03-18 11:43:59 -0600218
219 run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
220
221 # Create a new properly signed fit and replace header bytes
222 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200223 sign_fit(sha_algo, sign_options)
Teddy Reede6a47832018-06-09 11:38:05 -0400224 bcfg = u_boot_console.config.buildconfig
225 max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
226 existing_size = replace_fit_totalsize(max_size + 1)
Simon Glass724c03b2020-03-18 11:44:04 -0600227 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
228 False)
Teddy Reede6a47832018-06-09 11:38:05 -0400229 cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
230
231 # Replace with existing header bytes
232 replace_fit_totalsize(existing_size)
233 run_bootm(sha_algo, 'signed config', 'dev+', True)
234 cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
235
Simon Glassd977ecd2016-07-03 09:40:46 -0600236 # Increment the first byte of the signature, which should cause failure
Simon Glassba8116c2016-07-31 17:35:05 -0600237 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
238 (fit, sig_node))
Simon Glassd977ecd2016-07-03 09:40:46 -0600239 byte_list = sig.split()
240 byte = int(byte_list[0], 16)
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600241 byte_list[0] = '%x' % (byte + 1)
Simon Glassd977ecd2016-07-03 09:40:46 -0600242 sig = ' '.join(byte_list)
Simon Glassba8116c2016-07-31 17:35:05 -0600243 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
244 (fit, sig_node, sig))
Simon Glassd977ecd2016-07-03 09:40:46 -0600245
Simon Glass724c03b2020-03-18 11:44:04 -0600246 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
247 False)
Simon Glassd977ecd2016-07-03 09:40:46 -0600248
Simon Glassf223c732016-07-31 17:35:06 -0600249 cons.log.action('%s: Check bad config on the host' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600250 util.run_and_log_expect_exception(
251 cons, [fit_check_sign, '-f', fit, '-k', dtb],
252 1, 'Failed to verify required signature')
Simon Glassd977ecd2016-07-03 09:40:46 -0600253
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200254 def test_required_key(sha_algo, padding, sign_options):
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200255 """Test verified boot with the given hash algorithm.
256
Simon Glass724c03b2020-03-18 11:44:04 -0600257 This function tests if U-Boot rejects an image when a required key isn't
258 used to sign a FIT.
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200259
260 Args:
Simon Glass724c03b2020-03-18 11:44:04 -0600261 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200262 padding: Either '' or '-pss', to select the padding to use for the
263 rsa signature algorithm.
264 sign_options: Options to mkimage when signing a fit image.
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200265 """
266 # Compile our device tree files for kernel and U-Boot. These are
267 # regenerated here since mkimage will modify them (by adding a
268 # public key) below.
269 dtc('sandbox-kernel.dts')
270 dtc('sandbox-u-boot.dts')
271
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200272 cons.log.action('%s: Test FIT with configs images' % sha_algo)
Simon Glass724c03b2020-03-18 11:44:04 -0600273
274 # Build the FIT with prod key (keys required) and sign it. This puts the
275 # signature into sandbox-u-boot.dtb, marked 'required'
Simon Glass861b5042020-03-18 11:44:05 -0600276 make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200277 sign_fit(sha_algo, sign_options)
Simon Glass724c03b2020-03-18 11:44:04 -0600278
279 # Build the FIT with dev key (keys NOT required). This adds the
280 # signature into sandbox-u-boot.dtb, NOT marked 'required'.
Simon Glass861b5042020-03-18 11:44:05 -0600281 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200282 sign_fit(sha_algo, sign_options)
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200283
Simon Glass724c03b2020-03-18 11:44:04 -0600284 # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
285 # Only the prod key is set as 'required'. But FIT we just built has
286 # a dev signature only (sign_fit() overwrites the FIT).
287 # Try to boot the FIT with dev key. This FIT should not be accepted by
288 # U-Boot because the prod key is required.
289 run_bootm(sha_algo, 'required key', '', False)
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200290
Simon Glassd977ecd2016-07-03 09:40:46 -0600291 cons = u_boot_console
292 tmpdir = cons.config.result_dir + '/'
Stephen Warren7047d952016-07-18 10:07:25 -0600293 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
Simon Glassd977ecd2016-07-03 09:40:46 -0600294 fit = '%stest.fit' % tmpdir
295 mkimage = cons.config.build_dir + '/tools/mkimage'
296 fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
297 dtc_args = '-I dts -O dtb -i %s' % tmpdir
298 dtb = '%ssandbox-u-boot.dtb' % tmpdir
Philippe Reynesa28e9222018-11-14 13:51:05 +0100299 sig_node = '/configurations/conf-1/signature'
Simon Glassd977ecd2016-07-03 09:40:46 -0600300
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600301 create_rsa_pair('dev')
302 create_rsa_pair('prod')
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200303
Simon Glassd977ecd2016-07-03 09:40:46 -0600304 # Create a number kernel image with zeroes
305 with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
Simon Glass9be531f2020-03-18 11:44:08 -0600306 fd.write(500 * chr(0))
Simon Glassd977ecd2016-07-03 09:40:46 -0600307
308 try:
309 # We need to use our own device tree file. Remember to restore it
310 # afterwards.
311 old_dtb = cons.config.dtb
312 cons.config.dtb = dtb
Simon Glassa0ba39d2020-03-18 11:44:00 -0600313 if required:
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200314 test_required_key(sha_algo, padding, sign_options)
Simon Glassa0ba39d2020-03-18 11:44:00 -0600315 else:
Philippe Reynes2fbd17c2020-04-29 15:26:16 +0200316 test_with_algo(sha_algo, padding, sign_options)
Simon Glassd977ecd2016-07-03 09:40:46 -0600317 finally:
Simon Glass37c2ce12016-07-31 17:35:08 -0600318 # Go back to the original U-Boot with the correct dtb.
Simon Glassd977ecd2016-07-03 09:40:46 -0600319 cons.config.dtb = old_dtb
Simon Glass37c2ce12016-07-31 17:35:08 -0600320 cons.restart_uboot()