blob: b1badaad7372c9dc4544eb3e11d3e710be73d978 [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
27import pytest
28import sys
Teddy Reede6a47832018-06-09 11:38:05 -040029import struct
Simon Glassd977ecd2016-07-03 09:40:46 -060030import u_boot_utils as util
Simon Glassc35df8f2020-03-18 11:43:59 -060031import vboot_forge
Simon Glassd977ecd2016-07-03 09:40:46 -060032
Simon Glassa0ba39d2020-03-18 11:44:00 -060033TESTDATA = [
34 ['sha1', '', False],
35 ['sha1', '-pss', False],
36 ['sha256', '', False],
37 ['sha256', '-pss', False],
38 ['sha256', '-pss', True],
39]
40
Michal Simek6e035ab2016-07-18 08:49:08 +020041@pytest.mark.boardspec('sandbox')
Simon Glassd977ecd2016-07-03 09:40:46 -060042@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2079db32017-09-18 11:11:49 -060043@pytest.mark.requiredtool('dtc')
44@pytest.mark.requiredtool('fdtget')
45@pytest.mark.requiredtool('fdtput')
46@pytest.mark.requiredtool('openssl')
Simon Glassa0ba39d2020-03-18 11:44:00 -060047@pytest.mark.parametrize("sha_algo,padding,required", TESTDATA)
48def test_vboot(u_boot_console, sha_algo, padding, required):
Simon Glassd977ecd2016-07-03 09:40:46 -060049 """Test verified boot signing with mkimage and verification with 'bootm'.
50
51 This works using sandbox only as it needs to update the device tree used
52 by U-Boot to hold public keys from the signing process.
53
54 The SHA1 and SHA256 tests are combined into a single test since the
55 key-generation process is quite slow and we want to avoid doing it twice.
56 """
57 def dtc(dts):
Simon Glassd5deca02016-07-31 17:35:04 -060058 """Run the device tree compiler to compile a .dts file
Simon Glassd977ecd2016-07-03 09:40:46 -060059
60 The output file will be the same as the input file but with a .dtb
61 extension.
62
63 Args:
64 dts: Device tree file to compile.
65 """
66 dtb = dts.replace('.dts', '.dtb')
Simon Glassba8116c2016-07-31 17:35:05 -060067 util.run_and_log(cons, 'dtc %s %s%s -O dtb '
68 '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
Simon Glassd977ecd2016-07-03 09:40:46 -060069
Tom Rinib65ce462016-09-18 09:46:58 -040070 def run_bootm(sha_algo, test_type, expect_string, boots):
Simon Glassd977ecd2016-07-03 09:40:46 -060071 """Run a 'bootm' command U-Boot.
72
73 This always starts a fresh U-Boot instance since the device tree may
74 contain a new public key.
75
76 Args:
Simon Glassf223c732016-07-31 17:35:06 -060077 test_type: A string identifying the test type.
78 expect_string: A string which is expected in the output.
79 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
80 use.
Tom Rinib65ce462016-09-18 09:46:58 -040081 boots: A boolean that is True if Linux should boot and False if
82 we are expected to not boot
Simon Glassd977ecd2016-07-03 09:40:46 -060083 """
Simon Glass37c2ce12016-07-31 17:35:08 -060084 cons.restart_uboot()
Simon Glass2a40d832016-07-31 17:35:07 -060085 with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
86 output = cons.run_command_list(
Simon Glassf250d472018-11-15 18:44:02 -070087 ['host load hostfs - 100 %stest.fit' % tmpdir,
Simon Glass2a40d832016-07-31 17:35:07 -060088 'fdt addr 100',
89 'bootm 100'])
Simon Glass2ca73112016-07-31 17:35:09 -060090 assert(expect_string in ''.join(output))
Tom Rinib65ce462016-09-18 09:46:58 -040091 if boots:
92 assert('sandbox: continuing, as we cannot run' in ''.join(output))
Philippe Reynes1d5ef522019-09-18 16:04:53 +020093 else:
94 assert('sandbox: continuing, as we cannot run' 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
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200121 def sign_fit_norequire(sha_algo):
122 """Sign the FIT
123
124 Signs the FIT and writes the signature into it. It also writes the
125 public key into the dtb.
126
127 Args:
128 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
129 use.
130 """
131 cons.log.action('%s: Sign images' % sha_algo)
132 util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
133 fit])
134
Teddy Reede6a47832018-06-09 11:38:05 -0400135 def replace_fit_totalsize(size):
136 """Replace FIT header's totalsize with something greater.
137
138 The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
139 If the size is greater, the signature verification should return false.
140
141 Args:
142 size: The new totalsize of the header
143
144 Returns:
145 prev_size: The previous totalsize read from the header
146 """
147 total_size = 0
148 with open(fit, 'r+b') as handle:
149 handle.seek(4)
150 total_size = handle.read(4)
151 handle.seek(4)
152 handle.write(struct.pack(">I", size))
153 return struct.unpack(">I", total_size)[0]
154
Philippe Reynesafdbcdb2018-11-14 13:51:04 +0100155 def test_with_algo(sha_algo, padding):
Simon Glassd5deca02016-07-31 17:35:04 -0600156 """Test verified boot with the given hash algorithm.
Simon Glassd977ecd2016-07-03 09:40:46 -0600157
158 This is the main part of the test code. The same procedure is followed
159 for both hashing algorithms.
160
161 Args:
Simon Glassf223c732016-07-31 17:35:06 -0600162 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
163 use.
Simon Glassd977ecd2016-07-03 09:40:46 -0600164 """
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600165 # Compile our device tree files for kernel and U-Boot. These are
166 # regenerated here since mkimage will modify them (by adding a
167 # public key) below.
Simon Glassd977ecd2016-07-03 09:40:46 -0600168 dtc('sandbox-kernel.dts')
169 dtc('sandbox-u-boot.dts')
170
171 # Build the FIT, but don't sign anything yet
Simon Glassf223c732016-07-31 17:35:06 -0600172 cons.log.action('%s: Test FIT with signed images' % sha_algo)
Philippe Reynesafdbcdb2018-11-14 13:51:04 +0100173 make_fit('sign-images-%s%s.its' % (sha_algo , padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400174 run_bootm(sha_algo, 'unsigned images', 'dev-', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600175
176 # Sign images with our dev keys
Simon Glassf223c732016-07-31 17:35:06 -0600177 sign_fit(sha_algo)
Tom Rinib65ce462016-09-18 09:46:58 -0400178 run_bootm(sha_algo, 'signed images', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600179
180 # Create a fresh .dtb without the public keys
181 dtc('sandbox-u-boot.dts')
182
Simon Glassf223c732016-07-31 17:35:06 -0600183 cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
Philippe Reynesafdbcdb2018-11-14 13:51:04 +0100184 make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
Tom Rinib65ce462016-09-18 09:46:58 -0400185 run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600186
187 # Sign images with our dev keys
Simon Glassf223c732016-07-31 17:35:06 -0600188 sign_fit(sha_algo)
Tom Rinib65ce462016-09-18 09:46:58 -0400189 run_bootm(sha_algo, 'signed config', 'dev+', True)
Simon Glassd977ecd2016-07-03 09:40:46 -0600190
Simon Glassf223c732016-07-31 17:35:06 -0600191 cons.log.action('%s: Check signed config on the host' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600192
Simon Glassf411a892020-03-18 11:43:58 -0600193 util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
Simon Glassd977ecd2016-07-03 09:40:46 -0600194
Simon Glassc35df8f2020-03-18 11:43:59 -0600195 # Make sure that U-Boot checks that the config is in the list of hashed
196 # nodes. If it isn't, a security bypass is possible.
197 with open(fit, 'rb') as fp:
198 root, strblock = vboot_forge.read_fdt(fp)
199 root, strblock = vboot_forge.manipulate(root, strblock)
200 with open(fit, 'w+b') as fp:
201 vboot_forge.write_fdt(root, strblock, fp)
202 util.run_and_log_expect_exception(cons,
203 [fit_check_sign, '-f', fit, '-k', dtb],
204 1, 'Failed to verify required signature')
205
206 run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
207
208 # Create a new properly signed fit and replace header bytes
209 make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
210 sign_fit(sha_algo)
Teddy Reede6a47832018-06-09 11:38:05 -0400211 bcfg = u_boot_console.config.buildconfig
212 max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
213 existing_size = replace_fit_totalsize(max_size + 1)
214 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
215 cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
216
217 # Replace with existing header bytes
218 replace_fit_totalsize(existing_size)
219 run_bootm(sha_algo, 'signed config', 'dev+', True)
220 cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
221
Simon Glassd977ecd2016-07-03 09:40:46 -0600222 # Increment the first byte of the signature, which should cause failure
Simon Glassba8116c2016-07-31 17:35:05 -0600223 sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
224 (fit, sig_node))
Simon Glassd977ecd2016-07-03 09:40:46 -0600225 byte_list = sig.split()
226 byte = int(byte_list[0], 16)
Simon Glassdc3ab7e2016-07-31 17:35:02 -0600227 byte_list[0] = '%x' % (byte + 1)
Simon Glassd977ecd2016-07-03 09:40:46 -0600228 sig = ' '.join(byte_list)
Simon Glassba8116c2016-07-31 17:35:05 -0600229 util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
230 (fit, sig_node, sig))
Simon Glassd977ecd2016-07-03 09:40:46 -0600231
Tom Rinib65ce462016-09-18 09:46:58 -0400232 run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
Simon Glassd977ecd2016-07-03 09:40:46 -0600233
Simon Glassf223c732016-07-31 17:35:06 -0600234 cons.log.action('%s: Check bad config on the host' % sha_algo)
Simon Glassd977ecd2016-07-03 09:40:46 -0600235 util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
236 '-k', dtb], 1, 'Failed to verify required signature')
237
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200238 def test_required_key(sha_algo, padding):
239 """Test verified boot with the given hash algorithm.
240
241 This function test if u-boot reject an image when a required
242 key isn't used to sign a FIT.
243
244 Args:
245 sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
246 use.
247 """
248 # Compile our device tree files for kernel and U-Boot. These are
249 # regenerated here since mkimage will modify them (by adding a
250 # public key) below.
251 dtc('sandbox-kernel.dts')
252 dtc('sandbox-u-boot.dts')
253
254 # Build the FIT with prod key (keys required)
255 # Build the FIT with dev key (keys NOT required)
256 # The dtb contain the key prod and dev and the key prod are set as required.
257 # Then try to boot the FIT with dev key
258 # This FIT should not be accepted by u-boot because the key prod is required
259 cons.log.action('%s: Test FIT with configs images' % sha_algo)
260 make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
261 sign_fit(sha_algo)
262 make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
263 sign_fit(sha_algo)
264
265 run_bootm(sha_algo, 'signed configs', '', False)
266
Simon Glassd977ecd2016-07-03 09:40:46 -0600267 cons = u_boot_console
268 tmpdir = cons.config.result_dir + '/'
269 tmp = tmpdir + 'vboot.tmp'
Stephen Warren7047d952016-07-18 10:07:25 -0600270 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
Simon Glassd977ecd2016-07-03 09:40:46 -0600271 fit = '%stest.fit' % tmpdir
272 mkimage = cons.config.build_dir + '/tools/mkimage'
273 fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
274 dtc_args = '-I dts -O dtb -i %s' % tmpdir
275 dtb = '%ssandbox-u-boot.dtb' % tmpdir
Philippe Reynesa28e9222018-11-14 13:51:05 +0100276 sig_node = '/configurations/conf-1/signature'
Simon Glassd977ecd2016-07-03 09:40:46 -0600277
278 # Create an RSA key pair
279 public_exponent = 65537
Simon Glassba8116c2016-07-31 17:35:05 -0600280 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
281 '-pkeyopt rsa_keygen_bits:2048 '
Paul Burton2662c822017-09-14 14:34:50 -0700282 '-pkeyopt rsa_keygen_pubexp:%d' %
283 (tmpdir, public_exponent))
Simon Glassd977ecd2016-07-03 09:40:46 -0600284
285 # Create a certificate containing the public key
Simon Glassba8116c2016-07-31 17:35:05 -0600286 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
287 '%sdev.crt' % (tmpdir, tmpdir))
Simon Glassd977ecd2016-07-03 09:40:46 -0600288
Philippe Reynes1d5ef522019-09-18 16:04:53 +0200289 # Create an RSA key pair (prod)
290 public_exponent = 65537
291 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sprod.key '
292 '-pkeyopt rsa_keygen_bits:2048 '
293 '-pkeyopt rsa_keygen_pubexp:%d' %
294 (tmpdir, public_exponent))
295
296 # Create a certificate containing the public key (prod)
297 util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sprod.key -out '
298 '%sprod.crt' % (tmpdir, tmpdir))
299
Simon Glassd977ecd2016-07-03 09:40:46 -0600300 # Create a number kernel image with zeroes
301 with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
302 fd.write(5000 * chr(0))
303
304 try:
305 # We need to use our own device tree file. Remember to restore it
306 # afterwards.
307 old_dtb = cons.config.dtb
308 cons.config.dtb = dtb
Simon Glassa0ba39d2020-03-18 11:44:00 -0600309 if required:
310 test_required_key(sha_algo, padding)
311 else:
312 test_with_algo(sha_algo, padding)
Simon Glassd977ecd2016-07-03 09:40:46 -0600313 finally:
Simon Glass37c2ce12016-07-31 17:35:08 -0600314 # Go back to the original U-Boot with the correct dtb.
Simon Glassd977ecd2016-07-03 09:40:46 -0600315 cons.config.dtb = old_dtb
Simon Glass37c2ce12016-07-31 17:35:08 -0600316 cons.restart_uboot()