18729d582SSimon Glass# SPDX-License-Identifier: GPL-2.0+ 283d290c5STom Rini# Copyright (c) 2016, Google Inc. 38729d582SSimon Glass# 48729d582SSimon Glass# U-Boot Verified Boot Test 58729d582SSimon Glass 68729d582SSimon Glass""" 78729d582SSimon GlassThis tests verified boot in the following ways: 88729d582SSimon Glass 98729d582SSimon GlassFor image verification: 108729d582SSimon Glass- Create FIT (unsigned) with mkimage 118729d582SSimon Glass- Check that verification shows that no keys are verified 128729d582SSimon Glass- Sign image 138729d582SSimon Glass- Check that verification shows that a key is now verified 148729d582SSimon Glass 158729d582SSimon GlassFor configuration verification: 168729d582SSimon Glass- Corrupt signature and check for failure 178729d582SSimon Glass- Create FIT (with unsigned configuration) with mkimage 1872f52268SSimon Glass- Check that image verification works 198729d582SSimon Glass- Sign the FIT and mark the key as 'required' for verification 208729d582SSimon Glass- Check that image verification works 218729d582SSimon Glass- Corrupt the signature 228729d582SSimon Glass- Check that image verification no-longer works 238729d582SSimon Glass 248729d582SSimon GlassTests run with both SHA1 and SHA256 hashing. 258729d582SSimon Glass""" 268729d582SSimon Glass 278729d582SSimon Glassimport pytest 288729d582SSimon Glassimport sys 2972239fc8STeddy Reedimport struct 308729d582SSimon Glassimport u_boot_utils as util 318729d582SSimon Glass 3204a4786cSMichal Simek@pytest.mark.boardspec('sandbox') 338729d582SSimon Glass@pytest.mark.buildconfigspec('fit_signature') 342d26bf6cSStephen Warren@pytest.mark.requiredtool('dtc') 352d26bf6cSStephen Warren@pytest.mark.requiredtool('fdtget') 362d26bf6cSStephen Warren@pytest.mark.requiredtool('fdtput') 372d26bf6cSStephen Warren@pytest.mark.requiredtool('openssl') 388729d582SSimon Glassdef test_vboot(u_boot_console): 398729d582SSimon Glass """Test verified boot signing with mkimage and verification with 'bootm'. 408729d582SSimon Glass 418729d582SSimon Glass This works using sandbox only as it needs to update the device tree used 428729d582SSimon Glass by U-Boot to hold public keys from the signing process. 438729d582SSimon Glass 448729d582SSimon Glass The SHA1 and SHA256 tests are combined into a single test since the 458729d582SSimon Glass key-generation process is quite slow and we want to avoid doing it twice. 468729d582SSimon Glass """ 478729d582SSimon Glass def dtc(dts): 4872f52268SSimon Glass """Run the device tree compiler to compile a .dts file 498729d582SSimon Glass 508729d582SSimon Glass The output file will be the same as the input file but with a .dtb 518729d582SSimon Glass extension. 528729d582SSimon Glass 538729d582SSimon Glass Args: 548729d582SSimon Glass dts: Device tree file to compile. 558729d582SSimon Glass """ 568729d582SSimon Glass dtb = dts.replace('.dts', '.dtb') 57ec70f8a9SSimon Glass util.run_and_log(cons, 'dtc %s %s%s -O dtb ' 588729d582SSimon Glass '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) 598729d582SSimon Glass 60de4be9ecSTom Rini def run_bootm(sha_algo, test_type, expect_string, boots): 618729d582SSimon Glass """Run a 'bootm' command U-Boot. 628729d582SSimon Glass 638729d582SSimon Glass This always starts a fresh U-Boot instance since the device tree may 648729d582SSimon Glass contain a new public key. 658729d582SSimon Glass 668729d582SSimon Glass Args: 67ac9a23cfSSimon Glass test_type: A string identifying the test type. 68ac9a23cfSSimon Glass expect_string: A string which is expected in the output. 69ac9a23cfSSimon Glass sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 70ac9a23cfSSimon Glass use. 71de4be9ecSTom Rini boots: A boolean that is True if Linux should boot and False if 72de4be9ecSTom Rini we are expected to not boot 738729d582SSimon Glass """ 7427c087d5SSimon Glass cons.restart_uboot() 75851271a7SSimon Glass with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)): 768729d582SSimon Glass output = cons.run_command_list( 776d07d63dSSimon Glass ['host load hostfs - 100 %stest.fit' % tmpdir, 788729d582SSimon Glass 'fdt addr 100', 798729d582SSimon Glass 'bootm 100']) 80f6d34651SSimon Glass assert(expect_string in ''.join(output)) 81de4be9ecSTom Rini if boots: 82de4be9ecSTom Rini assert('sandbox: continuing, as we cannot run' in ''.join(output)) 838729d582SSimon Glass 848729d582SSimon Glass def make_fit(its): 8572f52268SSimon Glass """Make a new FIT from the .its source file. 868729d582SSimon Glass 878729d582SSimon Glass This runs 'mkimage -f' to create a new FIT. 888729d582SSimon Glass 898729d582SSimon Glass Args: 9072f52268SSimon Glass its: Filename containing .its source. 918729d582SSimon Glass """ 928729d582SSimon Glass util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', 938729d582SSimon Glass '%s%s' % (datadir, its), fit]) 948729d582SSimon Glass 95ac9a23cfSSimon Glass def sign_fit(sha_algo): 968729d582SSimon Glass """Sign the FIT 978729d582SSimon Glass 988729d582SSimon Glass Signs the FIT and writes the signature into it. It also writes the 998729d582SSimon Glass public key into the dtb. 100ac9a23cfSSimon Glass 101ac9a23cfSSimon Glass Args: 102ac9a23cfSSimon Glass sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 103ac9a23cfSSimon Glass use. 1048729d582SSimon Glass """ 105ac9a23cfSSimon Glass cons.log.action('%s: Sign images' % sha_algo) 1068729d582SSimon Glass util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, 1078729d582SSimon Glass '-r', fit]) 1088729d582SSimon Glass 10972239fc8STeddy Reed def replace_fit_totalsize(size): 11072239fc8STeddy Reed """Replace FIT header's totalsize with something greater. 11172239fc8STeddy Reed 11272239fc8STeddy Reed The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE. 11372239fc8STeddy Reed If the size is greater, the signature verification should return false. 11472239fc8STeddy Reed 11572239fc8STeddy Reed Args: 11672239fc8STeddy Reed size: The new totalsize of the header 11772239fc8STeddy Reed 11872239fc8STeddy Reed Returns: 11972239fc8STeddy Reed prev_size: The previous totalsize read from the header 12072239fc8STeddy Reed """ 12172239fc8STeddy Reed total_size = 0 12272239fc8STeddy Reed with open(fit, 'r+b') as handle: 12372239fc8STeddy Reed handle.seek(4) 12472239fc8STeddy Reed total_size = handle.read(4) 12572239fc8STeddy Reed handle.seek(4) 12672239fc8STeddy Reed handle.write(struct.pack(">I", size)) 12772239fc8STeddy Reed return struct.unpack(">I", total_size)[0] 12872239fc8STeddy Reed 129e246b728SPhilippe Reynes def test_with_algo(sha_algo, padding): 13072f52268SSimon Glass """Test verified boot with the given hash algorithm. 1318729d582SSimon Glass 1328729d582SSimon Glass This is the main part of the test code. The same procedure is followed 1338729d582SSimon Glass for both hashing algorithms. 1348729d582SSimon Glass 1358729d582SSimon Glass Args: 136ac9a23cfSSimon Glass sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 137ac9a23cfSSimon Glass use. 1388729d582SSimon Glass """ 139bcbd0c8fSSimon Glass # Compile our device tree files for kernel and U-Boot. These are 140bcbd0c8fSSimon Glass # regenerated here since mkimage will modify them (by adding a 141bcbd0c8fSSimon Glass # public key) below. 1428729d582SSimon Glass dtc('sandbox-kernel.dts') 1438729d582SSimon Glass dtc('sandbox-u-boot.dts') 1448729d582SSimon Glass 1458729d582SSimon Glass # Build the FIT, but don't sign anything yet 146ac9a23cfSSimon Glass cons.log.action('%s: Test FIT with signed images' % sha_algo) 147e246b728SPhilippe Reynes make_fit('sign-images-%s%s.its' % (sha_algo , padding)) 148de4be9ecSTom Rini run_bootm(sha_algo, 'unsigned images', 'dev-', True) 1498729d582SSimon Glass 1508729d582SSimon Glass # Sign images with our dev keys 151ac9a23cfSSimon Glass sign_fit(sha_algo) 152de4be9ecSTom Rini run_bootm(sha_algo, 'signed images', 'dev+', True) 1538729d582SSimon Glass 1548729d582SSimon Glass # Create a fresh .dtb without the public keys 1558729d582SSimon Glass dtc('sandbox-u-boot.dts') 1568729d582SSimon Glass 157ac9a23cfSSimon Glass cons.log.action('%s: Test FIT with signed configuration' % sha_algo) 158e246b728SPhilippe Reynes make_fit('sign-configs-%s%s.its' % (sha_algo , padding)) 159de4be9ecSTom Rini run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) 1608729d582SSimon Glass 1618729d582SSimon Glass # Sign images with our dev keys 162ac9a23cfSSimon Glass sign_fit(sha_algo) 163de4be9ecSTom Rini run_bootm(sha_algo, 'signed config', 'dev+', True) 1648729d582SSimon Glass 165ac9a23cfSSimon Glass cons.log.action('%s: Check signed config on the host' % sha_algo) 1668729d582SSimon Glass 1678729d582SSimon Glass util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, 1688729d582SSimon Glass '-k', dtb]) 1698729d582SSimon Glass 17072239fc8STeddy Reed # Replace header bytes 17172239fc8STeddy Reed bcfg = u_boot_console.config.buildconfig 17272239fc8STeddy Reed max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0) 17372239fc8STeddy Reed existing_size = replace_fit_totalsize(max_size + 1) 17472239fc8STeddy Reed run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) 17572239fc8STeddy Reed cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo) 17672239fc8STeddy Reed 17772239fc8STeddy Reed # Replace with existing header bytes 17872239fc8STeddy Reed replace_fit_totalsize(existing_size) 17972239fc8STeddy Reed run_bootm(sha_algo, 'signed config', 'dev+', True) 18072239fc8STeddy Reed cons.log.action('%s: Check default FIT header totalsize' % sha_algo) 18172239fc8STeddy Reed 1828729d582SSimon Glass # Increment the first byte of the signature, which should cause failure 183ec70f8a9SSimon Glass sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % 184ec70f8a9SSimon Glass (fit, sig_node)) 1858729d582SSimon Glass byte_list = sig.split() 1868729d582SSimon Glass byte = int(byte_list[0], 16) 187bcbd0c8fSSimon Glass byte_list[0] = '%x' % (byte + 1) 1888729d582SSimon Glass sig = ' '.join(byte_list) 189ec70f8a9SSimon Glass util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % 190ec70f8a9SSimon Glass (fit, sig_node, sig)) 1918729d582SSimon Glass 192de4be9ecSTom Rini run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) 1938729d582SSimon Glass 194ac9a23cfSSimon Glass cons.log.action('%s: Check bad config on the host' % sha_algo) 1958729d582SSimon Glass util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit, 1968729d582SSimon Glass '-k', dtb], 1, 'Failed to verify required signature') 1978729d582SSimon Glass 1988729d582SSimon Glass cons = u_boot_console 1998729d582SSimon Glass tmpdir = cons.config.result_dir + '/' 2008729d582SSimon Glass tmp = tmpdir + 'vboot.tmp' 201c9ba60c4SStephen Warren datadir = cons.config.source_dir + '/test/py/tests/vboot/' 2028729d582SSimon Glass fit = '%stest.fit' % tmpdir 2038729d582SSimon Glass mkimage = cons.config.build_dir + '/tools/mkimage' 2048729d582SSimon Glass fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' 2058729d582SSimon Glass dtc_args = '-I dts -O dtb -i %s' % tmpdir 2068729d582SSimon Glass dtb = '%ssandbox-u-boot.dtb' % tmpdir 207*ed47097aSPhilippe Reynes sig_node = '/configurations/conf-1/signature' 2088729d582SSimon Glass 2098729d582SSimon Glass # Create an RSA key pair 2108729d582SSimon Glass public_exponent = 65537 211ec70f8a9SSimon Glass util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key ' 2128729d582SSimon Glass '-pkeyopt rsa_keygen_bits:2048 ' 2138793631eSPaul Burton '-pkeyopt rsa_keygen_pubexp:%d' % 2148793631eSPaul Burton (tmpdir, public_exponent)) 2158729d582SSimon Glass 2168729d582SSimon Glass # Create a certificate containing the public key 217ec70f8a9SSimon Glass util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out ' 2188729d582SSimon Glass '%sdev.crt' % (tmpdir, tmpdir)) 2198729d582SSimon Glass 2208729d582SSimon Glass # Create a number kernel image with zeroes 2218729d582SSimon Glass with open('%stest-kernel.bin' % tmpdir, 'w') as fd: 2228729d582SSimon Glass fd.write(5000 * chr(0)) 2238729d582SSimon Glass 2248729d582SSimon Glass try: 2258729d582SSimon Glass # We need to use our own device tree file. Remember to restore it 2268729d582SSimon Glass # afterwards. 2278729d582SSimon Glass old_dtb = cons.config.dtb 2288729d582SSimon Glass cons.config.dtb = dtb 229e246b728SPhilippe Reynes test_with_algo('sha1','') 230e246b728SPhilippe Reynes test_with_algo('sha1','-pss') 231e246b728SPhilippe Reynes test_with_algo('sha256','') 232e246b728SPhilippe Reynes test_with_algo('sha256','-pss') 2338729d582SSimon Glass finally: 23427c087d5SSimon Glass # Go back to the original U-Boot with the correct dtb. 2358729d582SSimon Glass cons.config.dtb = old_dtb 23627c087d5SSimon Glass cons.restart_uboot() 237