]>
Commit | Line | Data |
---|---|---|
8729d582 | 1 | # SPDX-License-Identifier: GPL-2.0+ |
83d290c5 | 2 | # Copyright (c) 2016, Google Inc. |
8729d582 SG |
3 | # |
4 | # U-Boot Verified Boot Test | |
5 | ||
6 | """ | |
7 | This tests verified boot in the following ways: | |
8 | ||
9 | For 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 | ||
15 | For configuration verification: | |
16 | - Corrupt signature and check for failure | |
17 | - Create FIT (with unsigned configuration) with mkimage | |
72f52268 | 18 | - Check that image verification works |
8729d582 SG |
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 | |
23 | ||
776db4fa PR |
24 | For pre-load header verification: |
25 | - Create FIT image with a pre-load header | |
26 | - Check that signature verification succeeds | |
27 | - Corrupt the FIT image | |
28 | - Check that signature verification fails | |
29 | - Launch an FIT image without a pre-load header | |
30 | - Check that image verification fails | |
31 | ||
8729d582 | 32 | Tests run with both SHA1 and SHA256 hashing. |
90999b45 RK |
33 | |
34 | This also tests fdt_add_pubkey utility in the simple way: | |
35 | - Create DTB and FIT files | |
36 | - Add keys with fdt_add_pubkey to DTB | |
37 | - Sign FIT image | |
38 | - Check with fit_check_sign that keys properly added to DTB file | |
8729d582 SG |
39 | """ |
40 | ||
cfb83f36 | 41 | import os |
d5f3aada | 42 | import shutil |
72239fc8 | 43 | import struct |
b008677d | 44 | import pytest |
8729d582 | 45 | import u_boot_utils as util |
c021971e | 46 | import vboot_forge |
d5f3aada | 47 | import vboot_evil |
8729d582 | 48 | |
90999b45 RK |
49 | # Common helper functions |
50 | def dtc(dts, cons, dtc_args, datadir, tmpdir, dtb): | |
51 | """Run the device tree compiler to compile a .dts file | |
52 | ||
53 | The output file will be the same as the input file but with a .dtb | |
54 | extension. | |
55 | ||
56 | Args: | |
57 | dts: Device tree file to compile. | |
58 | cons: U-Boot console. | |
59 | dtc_args: DTC arguments. | |
60 | datadir: Path to data directory. | |
61 | tmpdir: Path to temp directory. | |
62 | dtb: Resulting DTB file. | |
63 | """ | |
64 | dtb = dts.replace('.dts', '.dtb') | |
65 | util.run_and_log(cons, 'dtc %s %s%s -O dtb ' | |
66 | '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) | |
67 | ||
68 | def make_fit(its, cons, mkimage, dtc_args, datadir, fit): | |
69 | """Make a new FIT from the .its source file. | |
70 | ||
71 | This runs 'mkimage -f' to create a new FIT. | |
72 | ||
73 | Args: | |
74 | its: Filename containing .its source. | |
75 | cons: U-Boot console. | |
76 | mkimage: Path to mkimage utility. | |
77 | dtc_args: DTC arguments. | |
78 | datadir: Path to data directory. | |
79 | fit: Resulting FIT file. | |
80 | """ | |
81 | util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', | |
82 | '%s%s' % (datadir, its), fit]) | |
83 | ||
d5f3aada SG |
84 | # Only run the full suite on a few combinations, since it doesn't add any more |
85 | # test coverage. | |
c7c113dc | 86 | TESTDATA_IN = [ |
776db4fa PR |
87 | ['sha1-basic', 'sha1', '', None, False, True, False, False], |
88 | ['sha1-pad', 'sha1', '', '-E -p 0x10000', False, False, False, False], | |
89 | ['sha1-pss', 'sha1', '-pss', None, False, False, False, False], | |
90 | ['sha1-pss-pad', 'sha1', '-pss', '-E -p 0x10000', False, False, False, False], | |
91 | ['sha256-basic', 'sha256', '', None, False, False, False, False], | |
92 | ['sha256-pad', 'sha256', '', '-E -p 0x10000', False, False, False, False], | |
93 | ['sha256-pss', 'sha256', '-pss', None, False, False, False, False], | |
94 | ['sha256-pss-pad', 'sha256', '-pss', '-E -p 0x10000', False, False, False, False], | |
95 | ['sha256-pss-required', 'sha256', '-pss', None, True, False, False, False], | |
96 | ['sha256-pss-pad-required', 'sha256', '-pss', '-E -p 0x10000', True, True, False, False], | |
97 | ['sha384-basic', 'sha384', '', None, False, False, False, False], | |
98 | ['sha384-pad', 'sha384', '', '-E -p 0x10000', False, False, False, False], | |
99 | ['algo-arg', 'algo-arg', '', '-o sha256,rsa2048', False, False, True, False], | |
100 | ['sha256-global-sign', 'sha256', '', '', False, False, False, True], | |
101 | ['sha256-global-sign-pss', 'sha256', '-pss', '', False, False, False, True], | |
1b090032 SG |
102 | ] |
103 | ||
c7c113dc SG |
104 | # Mark all but the first test as slow, so they are not run with '-k not slow' |
105 | TESTDATA = [TESTDATA_IN[0]] | |
106 | TESTDATA += [pytest.param(*v, marks=pytest.mark.slow) for v in TESTDATA_IN[1:]] | |
107 | ||
04a4786c | 108 | @pytest.mark.boardspec('sandbox') |
8729d582 | 109 | @pytest.mark.buildconfigspec('fit_signature') |
2d26bf6c SW |
110 | @pytest.mark.requiredtool('dtc') |
111 | @pytest.mark.requiredtool('fdtget') | |
112 | @pytest.mark.requiredtool('fdtput') | |
113 | @pytest.mark.requiredtool('openssl') | |
776db4fa | 114 | @pytest.mark.parametrize("name,sha_algo,padding,sign_options,required,full_test,algo_arg,global_sign", |
d5f3aada | 115 | TESTDATA) |
cfb83f36 | 116 | def test_vboot(u_boot_console, name, sha_algo, padding, sign_options, required, |
776db4fa | 117 | full_test, algo_arg, global_sign): |
8729d582 SG |
118 | """Test verified boot signing with mkimage and verification with 'bootm'. |
119 | ||
120 | This works using sandbox only as it needs to update the device tree used | |
121 | by U-Boot to hold public keys from the signing process. | |
122 | ||
123 | The SHA1 and SHA256 tests are combined into a single test since the | |
124 | key-generation process is quite slow and we want to avoid doing it twice. | |
125 | """ | |
776db4fa PR |
126 | def dtc_options(dts, options): |
127 | """Run the device tree compiler to compile a .dts file | |
128 | ||
129 | The output file will be the same as the input file but with a .dtb | |
130 | extension. | |
131 | ||
132 | Args: | |
133 | dts: Device tree file to compile. | |
134 | options: Options provided to the compiler. | |
135 | """ | |
136 | dtb = dts.replace('.dts', '.dtb') | |
137 | util.run_and_log(cons, 'dtc %s %s%s -O dtb ' | |
138 | '-o %s%s %s' % (dtc_args, datadir, dts, tmpdir, dtb, options)) | |
139 | ||
140 | def run_binman(dtb): | |
141 | """Run binman to build an image | |
142 | ||
143 | Args: | |
144 | dtb: Device tree file used as input file. | |
145 | """ | |
146 | pythonpath = os.environ.get('PYTHONPATH', '') | |
147 | os.environ['PYTHONPATH'] = pythonpath + ':' + '%s/../scripts/dtc/pylibfdt' % tmpdir | |
148 | util.run_and_log(cons, [binman, 'build', '-d', "%s/%s" % (tmpdir,dtb), | |
149 | '-a', "pre-load-key-path=%s" % tmpdir, '-O', | |
150 | tmpdir, '-I', tmpdir]) | |
151 | os.environ['PYTHONPATH'] = pythonpath | |
152 | ||
d5f3aada | 153 | def run_bootm(sha_algo, test_type, expect_string, boots, fit=None): |
8729d582 SG |
154 | """Run a 'bootm' command U-Boot. |
155 | ||
156 | This always starts a fresh U-Boot instance since the device tree may | |
157 | contain a new public key. | |
158 | ||
159 | Args: | |
ac9a23cf SG |
160 | test_type: A string identifying the test type. |
161 | expect_string: A string which is expected in the output. | |
162 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to | |
163 | use. | |
de4be9ec TR |
164 | boots: A boolean that is True if Linux should boot and False if |
165 | we are expected to not boot | |
d5f3aada | 166 | fit: FIT filename to load and verify |
8729d582 | 167 | """ |
d5f3aada SG |
168 | if not fit: |
169 | fit = '%stest.fit' % tmpdir | |
27c087d5 | 170 | cons.restart_uboot() |
851271a7 SG |
171 | with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)): |
172 | output = cons.run_command_list( | |
d5f3aada | 173 | ['host load hostfs - 100 %s' % fit, |
b008677d SG |
174 | 'fdt addr 100', |
175 | 'bootm 100']) | |
176 | assert expect_string in ''.join(output) | |
de4be9ec | 177 | if boots: |
b008677d | 178 | assert 'sandbox: continuing, as we cannot run' in ''.join(output) |
ce5172cf | 179 | else: |
3156ee35 SG |
180 | assert('sandbox: continuing, as we cannot run' |
181 | not in ''.join(output)) | |
8729d582 | 182 | |
eb7690e8 | 183 | def sign_fit(sha_algo, options): |
8729d582 SG |
184 | """Sign the FIT |
185 | ||
186 | Signs the FIT and writes the signature into it. It also writes the | |
187 | public key into the dtb. | |
ac9a23cf SG |
188 | |
189 | Args: | |
190 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to | |
191 | use. | |
eb7690e8 | 192 | options: Options to provide to mkimage. |
8729d582 | 193 | """ |
eb7690e8 PR |
194 | args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit] |
195 | if options: | |
196 | args += options.split(' ') | |
ac9a23cf | 197 | cons.log.action('%s: Sign images' % sha_algo) |
eb7690e8 | 198 | util.run_and_log(cons, args) |
8729d582 | 199 | |
776db4fa PR |
200 | def sign_fit_dtb(sha_algo, options, dtb): |
201 | """Sign the FIT | |
202 | ||
203 | Signs the FIT and writes the signature into it. It also writes the | |
204 | public key into the dtb. | |
205 | ||
206 | Args: | |
207 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to | |
208 | use. | |
209 | options: Options to provide to mkimage. | |
210 | """ | |
211 | args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit] | |
212 | if options: | |
213 | args += options.split(' ') | |
214 | cons.log.action('%s: Sign images' % sha_algo) | |
215 | util.run_and_log(cons, args) | |
216 | ||
feaeee8b TA |
217 | def sign_fit_norequire(sha_algo, options): |
218 | """Sign the FIT | |
219 | ||
220 | Signs the FIT and writes the signature into it. It also writes the | |
221 | public key into the dtb. It does not mark key as 'required' in dtb. | |
222 | ||
223 | Args: | |
224 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to | |
225 | use. | |
226 | options: Options to provide to mkimage. | |
227 | """ | |
228 | args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit] | |
229 | if options: | |
230 | args += options.split(' ') | |
231 | cons.log.action('%s: Sign images' % sha_algo) | |
232 | util.run_and_log(cons, args) | |
233 | ||
72239fc8 TR |
234 | def replace_fit_totalsize(size): |
235 | """Replace FIT header's totalsize with something greater. | |
236 | ||
237 | The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE. | |
238 | If the size is greater, the signature verification should return false. | |
239 | ||
240 | Args: | |
241 | size: The new totalsize of the header | |
242 | ||
243 | Returns: | |
244 | prev_size: The previous totalsize read from the header | |
245 | """ | |
246 | total_size = 0 | |
247 | with open(fit, 'r+b') as handle: | |
248 | handle.seek(4) | |
249 | total_size = handle.read(4) | |
250 | handle.seek(4) | |
251 | handle.write(struct.pack(">I", size)) | |
252 | return struct.unpack(">I", total_size)[0] | |
253 | ||
776db4fa PR |
254 | def corrupt_file(fit, offset, value): |
255 | """Corrupt a file | |
256 | ||
257 | To corrupt a file, a value is written at the specified offset | |
258 | ||
259 | Args: | |
260 | fit: The file to corrupt | |
261 | offset: Offset to write | |
262 | value: Value written | |
263 | """ | |
264 | with open(fit, 'r+b') as handle: | |
265 | handle.seek(offset) | |
266 | handle.write(struct.pack(">I", value)) | |
267 | ||
da76ed27 SG |
268 | def create_rsa_pair(name): |
269 | """Generate a new RSA key paid and certificate | |
270 | ||
271 | Args: | |
272 | name: Name of of the key (e.g. 'dev') | |
273 | """ | |
274 | public_exponent = 65537 | |
2a4b0d58 JL |
275 | |
276 | if sha_algo == "sha384": | |
277 | rsa_keygen_bits = 3072 | |
278 | else: | |
279 | rsa_keygen_bits = 2048 | |
280 | ||
da76ed27 | 281 | util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key ' |
2a4b0d58 | 282 | '-pkeyopt rsa_keygen_bits:%d ' |
da76ed27 | 283 | '-pkeyopt rsa_keygen_pubexp:%d' % |
2a4b0d58 | 284 | (tmpdir, name, rsa_keygen_bits, public_exponent)) |
da76ed27 SG |
285 | |
286 | # Create a certificate containing the public key | |
287 | util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key ' | |
288 | '-out %s%s.crt' % (tmpdir, name, tmpdir, name)) | |
289 | ||
eb7690e8 | 290 | def test_with_algo(sha_algo, padding, sign_options): |
72f52268 | 291 | """Test verified boot with the given hash algorithm. |
8729d582 SG |
292 | |
293 | This is the main part of the test code. The same procedure is followed | |
294 | for both hashing algorithms. | |
295 | ||
296 | Args: | |
ac9a23cf SG |
297 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to |
298 | use. | |
eb7690e8 PR |
299 | padding: Either '' or '-pss', to select the padding to use for the |
300 | rsa signature algorithm. | |
301 | sign_options: Options to mkimage when signing a fit image. | |
8729d582 | 302 | """ |
bcbd0c8f SG |
303 | # Compile our device tree files for kernel and U-Boot. These are |
304 | # regenerated here since mkimage will modify them (by adding a | |
305 | # public key) below. | |
90999b45 RK |
306 | dtc('sandbox-kernel.dts', cons, dtc_args, datadir, tmpdir, dtb) |
307 | dtc('sandbox-u-boot.dts', cons, dtc_args, datadir, tmpdir, dtb) | |
8729d582 SG |
308 | |
309 | # Build the FIT, but don't sign anything yet | |
ac9a23cf | 310 | cons.log.action('%s: Test FIT with signed images' % sha_algo) |
90999b45 | 311 | make_fit('sign-images-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
7ace56ae | 312 | run_bootm(sha_algo, 'unsigned images', ' - OK' if algo_arg else 'dev-', True) |
8729d582 SG |
313 | |
314 | # Sign images with our dev keys | |
eb7690e8 | 315 | sign_fit(sha_algo, sign_options) |
de4be9ec | 316 | run_bootm(sha_algo, 'signed images', 'dev+', True) |
8729d582 SG |
317 | |
318 | # Create a fresh .dtb without the public keys | |
90999b45 | 319 | dtc('sandbox-u-boot.dts', cons, dtc_args, datadir, tmpdir, dtb) |
8729d582 | 320 | |
ac9a23cf | 321 | cons.log.action('%s: Test FIT with signed configuration' % sha_algo) |
90999b45 | 322 | make_fit('sign-configs-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
7ace56ae | 323 | run_bootm(sha_algo, 'unsigned config', '%s+ OK' % ('sha256' if algo_arg else sha_algo), True) |
8729d582 SG |
324 | |
325 | # Sign images with our dev keys | |
eb7690e8 | 326 | sign_fit(sha_algo, sign_options) |
de4be9ec | 327 | run_bootm(sha_algo, 'signed config', 'dev+', True) |
8729d582 | 328 | |
ac9a23cf | 329 | cons.log.action('%s: Check signed config on the host' % sha_algo) |
8729d582 | 330 | |
477f559e | 331 | util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb]) |
8729d582 | 332 | |
d5f3aada | 333 | if full_test: |
3f04db89 SG |
334 | # Make sure that U-Boot checks that the config is in the list of |
335 | # hashed nodes. If it isn't, a security bypass is possible. | |
d5f3aada SG |
336 | ffit = '%stest.forged.fit' % tmpdir |
337 | shutil.copyfile(fit, ffit) | |
338 | with open(ffit, 'rb') as fd: | |
339 | root, strblock = vboot_forge.read_fdt(fd) | |
340 | root, strblock = vboot_forge.manipulate(root, strblock) | |
341 | with open(ffit, 'w+b') as fd: | |
342 | vboot_forge.write_fdt(root, strblock, fd) | |
343 | util.run_and_log_expect_exception( | |
344 | cons, [fit_check_sign, '-f', ffit, '-k', dtb], | |
345 | 1, 'Failed to verify required signature') | |
346 | ||
347 | run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit) | |
348 | ||
349 | # Try adding an evil root node. This should be detected. | |
350 | efit = '%stest.evilf.fit' % tmpdir | |
351 | shutil.copyfile(fit, efit) | |
352 | vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot') | |
353 | ||
354 | util.run_and_log_expect_exception( | |
355 | cons, [fit_check_sign, '-f', efit, '-k', dtb], | |
356 | 1, 'Failed to verify required signature') | |
124c2557 SG |
357 | run_bootm(sha_algo, 'evil fakeroot', 'Bad FIT kernel image format', |
358 | False, efit) | |
d5f3aada SG |
359 | |
360 | # Try adding an @ to the kernel node name. This should be detected. | |
361 | efit = '%stest.evilk.fit' % tmpdir | |
362 | shutil.copyfile(fit, efit) | |
363 | vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@') | |
364 | ||
3f04db89 | 365 | msg = 'Signature checking prevents use of unit addresses (@) in nodes' |
d5f3aada SG |
366 | util.run_and_log_expect_exception( |
367 | cons, [fit_check_sign, '-f', efit, '-k', dtb], | |
3f04db89 SG |
368 | 1, msg) |
369 | run_bootm(sha_algo, 'evil kernel@', msg, False, efit) | |
c021971e SG |
370 | |
371 | # Create a new properly signed fit and replace header bytes | |
90999b45 | 372 | make_fit('sign-configs-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
eb7690e8 | 373 | sign_fit(sha_algo, sign_options) |
72239fc8 TR |
374 | bcfg = u_boot_console.config.buildconfig |
375 | max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0) | |
376 | existing_size = replace_fit_totalsize(max_size + 1) | |
3156ee35 SG |
377 | run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', |
378 | False) | |
72239fc8 TR |
379 | cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo) |
380 | ||
381 | # Replace with existing header bytes | |
382 | replace_fit_totalsize(existing_size) | |
383 | run_bootm(sha_algo, 'signed config', 'dev+', True) | |
384 | cons.log.action('%s: Check default FIT header totalsize' % sha_algo) | |
385 | ||
8729d582 | 386 | # Increment the first byte of the signature, which should cause failure |
ec70f8a9 SG |
387 | sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % |
388 | (fit, sig_node)) | |
8729d582 SG |
389 | byte_list = sig.split() |
390 | byte = int(byte_list[0], 16) | |
bcbd0c8f | 391 | byte_list[0] = '%x' % (byte + 1) |
8729d582 | 392 | sig = ' '.join(byte_list) |
ec70f8a9 SG |
393 | util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % |
394 | (fit, sig_node, sig)) | |
8729d582 | 395 | |
3156ee35 SG |
396 | run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', |
397 | False) | |
8729d582 | 398 | |
ac9a23cf | 399 | cons.log.action('%s: Check bad config on the host' % sha_algo) |
b008677d SG |
400 | util.run_and_log_expect_exception( |
401 | cons, [fit_check_sign, '-f', fit, '-k', dtb], | |
402 | 1, 'Failed to verify required signature') | |
8729d582 | 403 | |
eb7690e8 | 404 | def test_required_key(sha_algo, padding, sign_options): |
ce5172cf PR |
405 | """Test verified boot with the given hash algorithm. |
406 | ||
3156ee35 SG |
407 | This function tests if U-Boot rejects an image when a required key isn't |
408 | used to sign a FIT. | |
ce5172cf PR |
409 | |
410 | Args: | |
3156ee35 | 411 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use |
eb7690e8 PR |
412 | padding: Either '' or '-pss', to select the padding to use for the |
413 | rsa signature algorithm. | |
414 | sign_options: Options to mkimage when signing a fit image. | |
ce5172cf PR |
415 | """ |
416 | # Compile our device tree files for kernel and U-Boot. These are | |
417 | # regenerated here since mkimage will modify them (by adding a | |
418 | # public key) below. | |
90999b45 RK |
419 | dtc('sandbox-kernel.dts', cons, dtc_args, datadir, tmpdir, dtb) |
420 | dtc('sandbox-u-boot.dts', cons, dtc_args, datadir, tmpdir, dtb) | |
ce5172cf | 421 | |
ce5172cf | 422 | cons.log.action('%s: Test FIT with configs images' % sha_algo) |
3156ee35 SG |
423 | |
424 | # Build the FIT with prod key (keys required) and sign it. This puts the | |
425 | # signature into sandbox-u-boot.dtb, marked 'required' | |
90999b45 | 426 | make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
eb7690e8 | 427 | sign_fit(sha_algo, sign_options) |
3156ee35 SG |
428 | |
429 | # Build the FIT with dev key (keys NOT required). This adds the | |
430 | # signature into sandbox-u-boot.dtb, NOT marked 'required'. | |
90999b45 | 431 | make_fit('sign-configs-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
feaeee8b | 432 | sign_fit_norequire(sha_algo, sign_options) |
ce5172cf | 433 | |
3156ee35 SG |
434 | # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. |
435 | # Only the prod key is set as 'required'. But FIT we just built has | |
feaeee8b | 436 | # a dev signature only (sign_fit_norequire() overwrites the FIT). |
3156ee35 SG |
437 | # Try to boot the FIT with dev key. This FIT should not be accepted by |
438 | # U-Boot because the prod key is required. | |
439 | run_bootm(sha_algo, 'required key', '', False) | |
ce5172cf | 440 | |
feaeee8b TA |
441 | # Build the FIT with dev key (keys required) and sign it. This puts the |
442 | # signature into sandbox-u-boot.dtb, marked 'required'. | |
90999b45 | 443 | make_fit('sign-configs-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) |
feaeee8b TA |
444 | sign_fit(sha_algo, sign_options) |
445 | ||
446 | # Set the required-mode policy to "any". | |
447 | # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. | |
448 | # Both the dev and prod key are set as 'required'. But FIT we just built has | |
449 | # a dev signature only (sign_fit() overwrites the FIT). | |
450 | # Try to boot the FIT with dev key. This FIT should be accepted by | |
451 | # U-Boot because the dev key is required and policy is "any" required key. | |
452 | util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' % | |
453 | (dtb)) | |
454 | run_bootm(sha_algo, 'multi required key', 'dev+', True) | |
455 | ||
456 | # Set the required-mode policy to "all". | |
457 | # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. | |
458 | # Both the dev and prod key are set as 'required'. But FIT we just built has | |
459 | # a dev signature only (sign_fit() overwrites the FIT). | |
460 | # Try to boot the FIT with dev key. This FIT should not be accepted by | |
461 | # U-Boot because the prod key is required and policy is "all" required key | |
462 | util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' % | |
463 | (dtb)) | |
464 | run_bootm(sha_algo, 'multi required key', '', False) | |
465 | ||
776db4fa PR |
466 | def test_global_sign(sha_algo, padding, sign_options): |
467 | """Test global image signature with the given hash algorithm and padding. | |
468 | ||
469 | Args: | |
470 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use | |
471 | padding: Either '' or '-pss', to select the padding to use for the | |
472 | rsa signature algorithm. | |
473 | """ | |
474 | ||
475 | dtb = '%ssandbox-u-boot-global%s.dtb' % (tmpdir, padding) | |
476 | cons.config.dtb = dtb | |
477 | ||
478 | # Compile our device tree files for kernel and U-Boot. These are | |
479 | # regenerated here since mkimage will modify them (by adding a | |
480 | # public key) below. | |
90999b45 | 481 | dtc('sandbox-kernel.dts', cons, dtc_args, datadir, tmpdir, dtb) |
776db4fa PR |
482 | dtc_options('sandbox-u-boot-global%s.dts' % padding, '-p 1024') |
483 | ||
484 | # Build the FIT with dev key (keys NOT required). This adds the | |
485 | # signature into sandbox-u-boot.dtb, NOT marked 'required'. | |
90999b45 | 486 | make_fit('simple-images.its', cons, mkimage, dtc_args, datadir, fit) |
776db4fa PR |
487 | sign_fit_dtb(sha_algo, '', dtb) |
488 | ||
489 | # Build the dtb for binman that define the pre-load header | |
490 | # with the global sigature. | |
90999b45 | 491 | dtc('sandbox-binman%s.dts' % padding, cons, dtc_args, datadir, tmpdir, dtb) |
776db4fa PR |
492 | |
493 | # Run binman to create the final image with the not signed fit | |
494 | # and the pre-load header that contains the global signature. | |
495 | run_binman('sandbox-binman%s.dtb' % padding) | |
496 | ||
497 | # Check that the signature is correctly verified by u-boot | |
498 | run_bootm(sha_algo, 'global image signature', | |
499 | 'signature check has succeed', True, "%ssandbox.img" % tmpdir) | |
500 | ||
501 | # Corrupt the image (just one byte after the pre-load header) | |
502 | corrupt_file("%ssandbox.img" % tmpdir, 4096, 255); | |
503 | ||
504 | # Check that the signature verification fails | |
505 | run_bootm(sha_algo, 'global image signature', | |
506 | 'signature check has failed', False, "%ssandbox.img" % tmpdir) | |
507 | ||
508 | # Check that the boot fails if the global signature is not provided | |
509 | run_bootm(sha_algo, 'global image signature', 'signature is mandatory', False) | |
510 | ||
8729d582 | 511 | cons = u_boot_console |
cfb83f36 SG |
512 | tmpdir = os.path.join(cons.config.result_dir, name) + '/' |
513 | if not os.path.exists(tmpdir): | |
514 | os.mkdir(tmpdir) | |
c9ba60c4 | 515 | datadir = cons.config.source_dir + '/test/py/tests/vboot/' |
8729d582 SG |
516 | fit = '%stest.fit' % tmpdir |
517 | mkimage = cons.config.build_dir + '/tools/mkimage' | |
776db4fa | 518 | binman = cons.config.source_dir + '/tools/binman/binman' |
8729d582 SG |
519 | fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' |
520 | dtc_args = '-I dts -O dtb -i %s' % tmpdir | |
521 | dtb = '%ssandbox-u-boot.dtb' % tmpdir | |
ed47097a | 522 | sig_node = '/configurations/conf-1/signature' |
8729d582 | 523 | |
da76ed27 SG |
524 | create_rsa_pair('dev') |
525 | create_rsa_pair('prod') | |
ce5172cf | 526 | |
8729d582 | 527 | # Create a number kernel image with zeroes |
d5f3aada SG |
528 | with open('%stest-kernel.bin' % tmpdir, 'wb') as fd: |
529 | fd.write(500 * b'\0') | |
530 | ||
531 | # Create a second kernel image with ones | |
532 | evil_kernel = '%stest-kernel1.bin' % tmpdir | |
533 | with open(evil_kernel, 'wb') as fd: | |
534 | fd.write(500 * b'\x01') | |
8729d582 | 535 | |
229c4da6 HS |
536 | # We need to use our own device tree file. Remember to restore it |
537 | # afterwards. | |
538 | old_dtb = cons.config.dtb | |
8729d582 | 539 | try: |
8729d582 | 540 | cons.config.dtb = dtb |
776db4fa PR |
541 | if global_sign: |
542 | test_global_sign(sha_algo, padding, sign_options) | |
543 | elif required: | |
eb7690e8 | 544 | test_required_key(sha_algo, padding, sign_options) |
1b090032 | 545 | else: |
eb7690e8 | 546 | test_with_algo(sha_algo, padding, sign_options) |
8729d582 | 547 | finally: |
27c087d5 | 548 | # Go back to the original U-Boot with the correct dtb. |
8729d582 | 549 | cons.config.dtb = old_dtb |
27c087d5 | 550 | cons.restart_uboot() |
90999b45 RK |
551 | |
552 | ||
553 | TESTDATA_IN = [ | |
554 | ['sha1-basic', 'sha1', '', None, False], | |
555 | ['sha1-pad', 'sha1', '', '-E -p 0x10000', False], | |
556 | ['sha1-pss', 'sha1', '-pss', None, False], | |
557 | ['sha1-pss-pad', 'sha1', '-pss', '-E -p 0x10000', False], | |
558 | ['sha256-basic', 'sha256', '', None, False], | |
559 | ['sha256-pad', 'sha256', '', '-E -p 0x10000', False], | |
560 | ['sha256-pss', 'sha256', '-pss', None, False], | |
561 | ['sha256-pss-pad', 'sha256', '-pss', '-E -p 0x10000', False], | |
562 | ['sha256-pss-required', 'sha256', '-pss', None, False], | |
563 | ['sha256-pss-pad-required', 'sha256', '-pss', '-E -p 0x10000', False], | |
564 | ['sha384-basic', 'sha384', '', None, False], | |
565 | ['sha384-pad', 'sha384', '', '-E -p 0x10000', False], | |
566 | ['algo-arg', 'algo-arg', '', '-o sha256,rsa2048', True], | |
567 | ['sha256-global-sign', 'sha256', '', '', False], | |
568 | ['sha256-global-sign-pss', 'sha256', '-pss', '', False], | |
569 | ] | |
570 | ||
571 | # Mark all but the first test as slow, so they are not run with '-k not slow' | |
572 | TESTDATA = [TESTDATA_IN[0]] | |
573 | TESTDATA += [pytest.param(*v, marks=pytest.mark.slow) for v in TESTDATA_IN[1:]] | |
574 | ||
575 | @pytest.mark.boardspec('sandbox') | |
576 | @pytest.mark.buildconfigspec('fit_signature') | |
577 | @pytest.mark.requiredtool('dtc') | |
578 | @pytest.mark.requiredtool('openssl') | |
579 | @pytest.mark.parametrize("name,sha_algo,padding,sign_options,algo_arg", TESTDATA) | |
580 | def test_fdt_add_pubkey(u_boot_console, name, sha_algo, padding, sign_options, algo_arg): | |
581 | """Test fdt_add_pubkey utility with bunch of different algo options.""" | |
582 | ||
583 | def sign_fit(sha_algo, options): | |
584 | """Sign the FIT | |
585 | ||
586 | Signs the FIT and writes the signature into it. | |
587 | ||
588 | Args: | |
589 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to | |
590 | use. | |
591 | options: Options to provide to mkimage. | |
592 | """ | |
593 | args = [mkimage, '-F', '-k', tmpdir, fit] | |
594 | if options: | |
595 | args += options.split(' ') | |
596 | cons.log.action('%s: Sign images' % sha_algo) | |
597 | util.run_and_log(cons, args) | |
598 | ||
599 | def test_add_pubkey(sha_algo, padding, sign_options): | |
600 | """Test fdt_add_pubkey utility with given hash algorithm and padding. | |
601 | ||
602 | This function tests if fdt_add_pubkey utility may add public keys into dtb. | |
603 | ||
604 | Args: | |
605 | sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use | |
606 | padding: Either '' or '-pss', to select the padding to use for the | |
607 | rsa signature algorithm. | |
608 | sign_options: Options to mkimage when signing a fit image. | |
609 | """ | |
610 | ||
611 | # Create a fresh .dtb without the public keys | |
612 | dtc('sandbox-u-boot.dts', cons, dtc_args, datadir, tmpdir, dtb) | |
613 | ||
614 | cons.log.action('%s: Test fdt_add_pubkey with signed configuration' % sha_algo) | |
615 | # Then add the dev key via the fdt_add_pubkey tool | |
616 | util.run_and_log(cons, [fdt_add_pubkey, '-a', '%s,%s' % ('sha256' if algo_arg else sha_algo, \ | |
617 | 'rsa3072' if sha_algo == 'sha384' else 'rsa2048'), | |
618 | '-k', tmpdir, '-n', 'dev', '-r', 'conf', dtb]) | |
619 | ||
620 | make_fit('sign-configs-%s%s.its' % (sha_algo, padding), cons, mkimage, dtc_args, datadir, fit) | |
621 | ||
622 | # Sign images with our dev keys | |
623 | sign_fit(sha_algo, sign_options) | |
624 | ||
625 | # Check with fit_check_sign that FIT is signed with key | |
626 | util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb]) | |
627 | ||
628 | cons = u_boot_console | |
629 | tmpdir = os.path.join(cons.config.result_dir, name) + '/' | |
630 | if not os.path.exists(tmpdir): | |
631 | os.mkdir(tmpdir) | |
632 | datadir = cons.config.source_dir + '/test/py/tests/vboot/' | |
633 | fit = '%stest.fit' % tmpdir | |
634 | mkimage = cons.config.build_dir + '/tools/mkimage' | |
635 | binman = cons.config.source_dir + '/tools/binman/binman' | |
636 | fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' | |
637 | fdt_add_pubkey = cons.config.build_dir + '/tools/fdt_add_pubkey' | |
638 | dtc_args = '-I dts -O dtb -i %s' % tmpdir | |
639 | dtb = '%ssandbox-u-boot.dtb' % tmpdir | |
640 | ||
641 | # keys created in test_vboot test | |
642 | ||
643 | test_add_pubkey(sha_algo, padding, sign_options) |