blob: e67f2b3d0f609c148da75a52627c4518a61c7ae2 [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 = [
33 ['sha1', '', False],
34 ['sha1', '-pss', False],
35 ['sha256', '', False],
36 ['sha256', '-pss', False],
37 ['sha256', '-pss', True],
38]
39
Michal Simek6e035ab2016-07-18 08:49:08 +020040@pytest.mark.boardspec('sandbox')
Simon Glassd977ecd2016-07-03 09:40:46 -060041@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2079db32017-09-18 11:11:49 -060042@pytest.mark.requiredtool('dtc')
43@pytest.mark.requiredtool('fdtget')
44@pytest.mark.requiredtool('fdtput')
45@pytest.mark.requiredtool('openssl')
Simon Glassa0ba39d2020-03-18 11:44:00 -060046@pytest.mark.parametrize("sha_algo,padding,required", TESTDATA)
47def test_vboot(u_boot_console, sha_algo, padding, required):
Simon Glassd977ecd2016-07-03 09:40:46 -060048 """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):
Simon Glassd5deca02016-07-31 17:35:04 -060057 """Run the device tree compiler to compile a .dts file
Simon Glassd977ecd2016-07-03 09:40:46 -060058
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')
Simon Glassba8116c2016-07-31 17:35:05 -060066 util.run_and_log(cons, 'dtc %s %s%s -O dtb '
67 '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
Simon Glassd977ecd2016-07-03 09:40:46 -060068
Tom Rinib65ce462016-09-18 09:46:58 -040069 def run_bootm(sha_algo, test_type, expect_string, boots):
Simon Glassd977ecd2016-07-03 09:40:46 -060070 """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:
Simon Glassf223c732016-07-31 17:35:06 -060076 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.
Tom Rinib65ce462016-09-18 09:46:58 -040080 boots: A boolean that is True if Linux should boot and False if
81 we are expected to not boot
Simon Glassd977ecd2016-07-03 09:40:46 -060082 """
Simon Glass37c2ce12016-07-31 17:35:08 -060083 cons.restart_uboot()
Simon Glass2a40d832016-07-31 17:35:07 -060084 with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
85 output = cons.run_command_list(
Simon Glassf250d472018-11-15 18:44:02 -070086 ['host load hostfs - 100 %stest.fit' % tmpdir,
Simon Glass861b5042020-03-18 11:44:05 -060087 'fdt addr 100',
88 'bootm 100'])
89 assert expect_string in ''.join(output)
Tom Rinib65ce462016-09-18 09:46:58 -040090 if boots:
Simon Glass861b5042020-03-18 11:44:05 -060091 assert 'sandbox: continuing, as we cannot run' in ''.join(output)
Philippe Reynes1d5ef522019-09-18 16:04:53 +020092 else:
Simon Glass724c03b2020-03-18 11:44:04 -060093 assert('sandbox: continuing, as we cannot run'
94 not in ''.join(output))
Simon Glassd977ecd2016-07-03 09:40:46 -060095
96 def make_fit(its):
Simon Glassd5deca02016-07-31 17:35:04 -060097 """Make a new FIT from the .its source file.
Simon Glassd977ecd2016-07-03 09:40:46 -060098
99 This runs 'mkimage -f' to create a new FIT.
100
101 Args:
Simon Glassd5deca02016-07-31 17:35:04 -0600102 its: Filename containing .its source.
Simon Glassd977ecd2016-07-03 09:40:46 -0600103 """
104 util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
105 '%s%s' % (datadir, its), fit])
106
Simon Glassf223c732016-07-31 17:35:06 -0600107 def sign_fit(sha_algo):
Simon Glassd977ecd2016-07-03 09:40:46 -0600108 """Sign the FIT
109
110 Signs the FIT and writes the signature into it. It also writes the
111 public key into the dtb.
Simon Glassf223c732016-07-31 17:35:06 -0600112
113 Args:
114 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
115 use.
Simon Glassd977ecd2016-07-03 09:40:46 -0600116 """
Simon Glassf223c732016-07-31 17:35:06 -0600117 cons.log.action('%s: Sign images' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600118 util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
119 '-r', fit])
120
Teddy Reede6a47832018-06-09 11:38:05 -0400121 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
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600141 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
Philippe Reynesafdbcdb2018-11-14 13:51:04 +0100157 def test_with_algo(sha_algo, padding):
Simon Glassd5deca02016-07-31 17:35:04 -0600158 """Test verified boot with the given hash algorithm.
Simon Glassd977ecd2016-07-03 09:40:46 -0600159
160 This is the main part of the test code. The same procedure is followed
161 for both hashing algorithms.
162
163 Args:
Simon Glassf223c732016-07-31 17:35:06 -0600164 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
165 use.
Simon Glassd977ecd2016-07-03 09:40:46 -0600166 """
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600167 # 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.
Simon Glassd977ecd2016-07-03 09:40:46 -0600170 dtc('sandbox-kernel.dts')
171 dtc('sandbox-u-boot.dts')
172
173 # Build the FIT, but don't sign anything yet
Simon Glassf223c732016-07-31 17:35:06 -0600174 cons.log.action('%s: Test FIT with signed images' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600175 make_fit('sign-images-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400176 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600177
178 # Sign images with our dev keys
Simon Glassf223c732016-07-31 17:35:06 -0600179 sign_fit(sha_algo)
Tom Rinib65ce462016-09-18 09:46:58 -0400180 run_bootm(sha_algo, 'signed images', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600181
182 # Create a fresh .dtb without the public keys
183 dtc('sandbox-u-boot.dts')
184
Simon Glassf223c732016-07-31 17:35:06 -0600185 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600186 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400187 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600188
189 # Sign images with our dev keys
Simon Glassf223c732016-07-31 17:35:06 -0600190 sign_fit(sha_algo)
Tom Rinib65ce462016-09-18 09:46:58 -0400191 run_bootm(sha_algo, 'signed config', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600192
Simon Glassf223c732016-07-31 17:35:06 -0600193 cons.log.action('%s: Check signed config on the host' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600194
Simon Glassf411a892020-03-18 11:43:58 -0600195 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
Simon Glassd977ecd2016-07-03 09:40:46 -0600196
Simon Glassc35df8f2020-03-18 11:43:59 -0600197 # 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.
Simon Glass861b5042020-03-18 11:44:05 -0600199 with open(fit, 'rb') as fd:
200 root, strblock = vboot_forge.read_fdt(fd)
Simon Glassc35df8f2020-03-18 11:43:59 -0600201 root, strblock = vboot_forge.manipulate(root, strblock)
Simon Glass861b5042020-03-18 11:44:05 -0600202 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')
Simon Glassc35df8f2020-03-18 11:43:59 -0600207
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)
Teddy Reede6a47832018-06-09 11:38:05 -0400213 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)
Simon Glass724c03b2020-03-18 11:44:04 -0600216 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
217 False)
Teddy Reede6a47832018-06-09 11:38:05 -0400218 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
Simon Glassd977ecd2016-07-03 09:40:46 -0600225 # Increment the first byte of the signature, which should cause failure
Simon Glassba8116c2016-07-31 17:35:05 -0600226 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
227 (fit, sig_node))
Simon Glassd977ecd2016-07-03 09:40:46 -0600228 byte_list = sig.split()
229 byte = int(byte_list[0], 16)
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600230 byte_list[0] = '%x' % (byte + 1)
Simon Glassd977ecd2016-07-03 09:40:46 -0600231 sig = ' '.join(byte_list)
Simon Glassba8116c2016-07-31 17:35:05 -0600232 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
233 (fit, sig_node, sig))
Simon Glassd977ecd2016-07-03 09:40:46 -0600234
Simon Glass724c03b2020-03-18 11:44:04 -0600235 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
236 False)
Simon Glassd977ecd2016-07-03 09:40:46 -0600237
Simon Glassf223c732016-07-31 17:35:06 -0600238 cons.log.action('%s: Check bad config on the host' % sha_algo)
Simon Glass861b5042020-03-18 11:44:05 -0600239 util.run_and_log_expect_exception(
240 cons, [fit_check_sign, '-f', fit, '-k', dtb],
241 1, 'Failed to verify required signature')
Simon Glassd977ecd2016-07-03 09:40:46 -0600242
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200243 def test_required_key(sha_algo, padding):
244 """Test verified boot with the given hash algorithm.
245
Simon Glass724c03b2020-03-18 11:44:04 -0600246 This function tests if U-Boot rejects an image when a required key isn't
247 used to sign a FIT.
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200248
249 Args:
Simon Glass724c03b2020-03-18 11:44:04 -0600250 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200251 """
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
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200258 cons.log.action('%s: Test FIT with configs images' % sha_algo)
Simon Glass724c03b2020-03-18 11:44:04 -0600259
260 # Build the FIT with prod key (keys required) and sign it. This puts the
261 # signature into sandbox-u-boot.dtb, marked 'required'
Simon Glass861b5042020-03-18 11:44:05 -0600262 make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200263 sign_fit(sha_algo)
Simon Glass724c03b2020-03-18 11:44:04 -0600264
265 # Build the FIT with dev key (keys NOT required). This adds the
266 # signature into sandbox-u-boot.dtb, NOT marked 'required'.
Simon Glass861b5042020-03-18 11:44:05 -0600267 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200268 sign_fit(sha_algo)
269
Simon Glass724c03b2020-03-18 11:44:04 -0600270 # 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)
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200276
Simon Glassd977ecd2016-07-03 09:40:46 -0600277 cons = u_boot_console
278 tmpdir = cons.config.result_dir + '/'
Stephen Warren7047d952016-07-18 10:07:25 -0600279 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
Simon Glassd977ecd2016-07-03 09:40:46 -0600280 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
Philippe Reynesa28e9222018-11-14 13:51:05 +0100285 sig_node = '/configurations/conf-1/signature'
Simon Glassd977ecd2016-07-03 09:40:46 -0600286
Simon Glassb4a2f6a2020-03-18 11:44:07 -0600287 create_rsa_pair('dev')
288 create_rsa_pair('prod')
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200289
Simon Glassd977ecd2016-07-03 09:40:46 -0600290 # Create a number kernel image with zeroes
291 with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
Simon Glass9be531f2020-03-18 11:44:08 -0600292 fd.write(500 * chr(0))
Simon Glassd977ecd2016-07-03 09:40:46 -0600293
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
Simon Glassa0ba39d2020-03-18 11:44:00 -0600299 if required:
300 test_required_key(sha_algo, padding)
301 else:
302 test_with_algo(sha_algo, padding)
Simon Glassd977ecd2016-07-03 09:40:46 -0600303 finally:
Simon Glass37c2ce12016-07-31 17:35:08 -0600304 # Go back to the original U-Boot with the correct dtb.
Simon Glassd977ecd2016-07-03 09:40:46 -0600305 cons.config.dtb = old_dtb
Simon Glass37c2ce12016-07-31 17:35:08 -0600306 cons.restart_uboot()