1*4882a593Smuzhiyun# Copyright (c) 2016, Google Inc. 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0+ 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# U-Boot Verified Boot Test 6*4882a593Smuzhiyun 7*4882a593Smuzhiyun""" 8*4882a593SmuzhiyunThis tests verified boot in the following ways: 9*4882a593Smuzhiyun 10*4882a593SmuzhiyunFor image verification: 11*4882a593Smuzhiyun- Create FIT (unsigned) with mkimage 12*4882a593Smuzhiyun- Check that verification shows that no keys are verified 13*4882a593Smuzhiyun- Sign image 14*4882a593Smuzhiyun- Check that verification shows that a key is now verified 15*4882a593Smuzhiyun 16*4882a593SmuzhiyunFor configuration verification: 17*4882a593Smuzhiyun- Corrupt signature and check for failure 18*4882a593Smuzhiyun- Create FIT (with unsigned configuration) with mkimage 19*4882a593Smuzhiyun- Check that image verification works 20*4882a593Smuzhiyun- Sign the FIT and mark the key as 'required' for verification 21*4882a593Smuzhiyun- Check that image verification works 22*4882a593Smuzhiyun- Corrupt the signature 23*4882a593Smuzhiyun- Check that image verification no-longer works 24*4882a593Smuzhiyun 25*4882a593SmuzhiyunTests run with both SHA1 and SHA256 hashing. 26*4882a593Smuzhiyun""" 27*4882a593Smuzhiyun 28*4882a593Smuzhiyunimport pytest 29*4882a593Smuzhiyunimport sys 30*4882a593Smuzhiyunimport u_boot_utils as util 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun@pytest.mark.boardspec('sandbox') 33*4882a593Smuzhiyun@pytest.mark.buildconfigspec('fit_signature') 34*4882a593Smuzhiyundef test_vboot(u_boot_console): 35*4882a593Smuzhiyun """Test verified boot signing with mkimage and verification with 'bootm'. 36*4882a593Smuzhiyun 37*4882a593Smuzhiyun This works using sandbox only as it needs to update the device tree used 38*4882a593Smuzhiyun by U-Boot to hold public keys from the signing process. 39*4882a593Smuzhiyun 40*4882a593Smuzhiyun The SHA1 and SHA256 tests are combined into a single test since the 41*4882a593Smuzhiyun key-generation process is quite slow and we want to avoid doing it twice. 42*4882a593Smuzhiyun """ 43*4882a593Smuzhiyun def dtc(dts): 44*4882a593Smuzhiyun """Run the device tree compiler to compile a .dts file 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun The output file will be the same as the input file but with a .dtb 47*4882a593Smuzhiyun extension. 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun Args: 50*4882a593Smuzhiyun dts: Device tree file to compile. 51*4882a593Smuzhiyun """ 52*4882a593Smuzhiyun dtb = dts.replace('.dts', '.dtb') 53*4882a593Smuzhiyun util.run_and_log(cons, 'dtc %s %s%s -O dtb ' 54*4882a593Smuzhiyun '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun def run_bootm(sha_algo, test_type, expect_string, boots): 57*4882a593Smuzhiyun """Run a 'bootm' command U-Boot. 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun This always starts a fresh U-Boot instance since the device tree may 60*4882a593Smuzhiyun contain a new public key. 61*4882a593Smuzhiyun 62*4882a593Smuzhiyun Args: 63*4882a593Smuzhiyun test_type: A string identifying the test type. 64*4882a593Smuzhiyun expect_string: A string which is expected in the output. 65*4882a593Smuzhiyun sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 66*4882a593Smuzhiyun use. 67*4882a593Smuzhiyun boots: A boolean that is True if Linux should boot and False if 68*4882a593Smuzhiyun we are expected to not boot 69*4882a593Smuzhiyun """ 70*4882a593Smuzhiyun cons.restart_uboot() 71*4882a593Smuzhiyun with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)): 72*4882a593Smuzhiyun output = cons.run_command_list( 73*4882a593Smuzhiyun ['sb load hostfs - 100 %stest.fit' % tmpdir, 74*4882a593Smuzhiyun 'fdt addr 100', 75*4882a593Smuzhiyun 'bootm 100']) 76*4882a593Smuzhiyun assert(expect_string in ''.join(output)) 77*4882a593Smuzhiyun if boots: 78*4882a593Smuzhiyun assert('sandbox: continuing, as we cannot run' in ''.join(output)) 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun def make_fit(its): 81*4882a593Smuzhiyun """Make a new FIT from the .its source file. 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun This runs 'mkimage -f' to create a new FIT. 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun Args: 86*4882a593Smuzhiyun its: Filename containing .its source. 87*4882a593Smuzhiyun """ 88*4882a593Smuzhiyun util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', 89*4882a593Smuzhiyun '%s%s' % (datadir, its), fit]) 90*4882a593Smuzhiyun 91*4882a593Smuzhiyun def sign_fit(sha_algo): 92*4882a593Smuzhiyun """Sign the FIT 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun Signs the FIT and writes the signature into it. It also writes the 95*4882a593Smuzhiyun public key into the dtb. 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun Args: 98*4882a593Smuzhiyun sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 99*4882a593Smuzhiyun use. 100*4882a593Smuzhiyun """ 101*4882a593Smuzhiyun cons.log.action('%s: Sign images' % sha_algo) 102*4882a593Smuzhiyun util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, 103*4882a593Smuzhiyun '-r', fit]) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun def test_with_algo(sha_algo): 106*4882a593Smuzhiyun """Test verified boot with the given hash algorithm. 107*4882a593Smuzhiyun 108*4882a593Smuzhiyun This is the main part of the test code. The same procedure is followed 109*4882a593Smuzhiyun for both hashing algorithms. 110*4882a593Smuzhiyun 111*4882a593Smuzhiyun Args: 112*4882a593Smuzhiyun sha_algo: Either 'sha1' or 'sha256', to select the algorithm to 113*4882a593Smuzhiyun use. 114*4882a593Smuzhiyun """ 115*4882a593Smuzhiyun # Compile our device tree files for kernel and U-Boot. These are 116*4882a593Smuzhiyun # regenerated here since mkimage will modify them (by adding a 117*4882a593Smuzhiyun # public key) below. 118*4882a593Smuzhiyun dtc('sandbox-kernel.dts') 119*4882a593Smuzhiyun dtc('sandbox-u-boot.dts') 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun # Build the FIT, but don't sign anything yet 122*4882a593Smuzhiyun cons.log.action('%s: Test FIT with signed images' % sha_algo) 123*4882a593Smuzhiyun make_fit('sign-images-%s.its' % sha_algo) 124*4882a593Smuzhiyun run_bootm(sha_algo, 'unsigned images', 'dev-', True) 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun # Sign images with our dev keys 127*4882a593Smuzhiyun sign_fit(sha_algo) 128*4882a593Smuzhiyun run_bootm(sha_algo, 'signed images', 'dev+', True) 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun # Create a fresh .dtb without the public keys 131*4882a593Smuzhiyun dtc('sandbox-u-boot.dts') 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun cons.log.action('%s: Test FIT with signed configuration' % sha_algo) 134*4882a593Smuzhiyun make_fit('sign-configs-%s.its' % sha_algo) 135*4882a593Smuzhiyun run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun # Sign images with our dev keys 138*4882a593Smuzhiyun sign_fit(sha_algo) 139*4882a593Smuzhiyun run_bootm(sha_algo, 'signed config', 'dev+', True) 140*4882a593Smuzhiyun 141*4882a593Smuzhiyun cons.log.action('%s: Check signed config on the host' % sha_algo) 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, 144*4882a593Smuzhiyun '-k', dtb]) 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun # Increment the first byte of the signature, which should cause failure 147*4882a593Smuzhiyun sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % 148*4882a593Smuzhiyun (fit, sig_node)) 149*4882a593Smuzhiyun byte_list = sig.split() 150*4882a593Smuzhiyun byte = int(byte_list[0], 16) 151*4882a593Smuzhiyun byte_list[0] = '%x' % (byte + 1) 152*4882a593Smuzhiyun sig = ' '.join(byte_list) 153*4882a593Smuzhiyun util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % 154*4882a593Smuzhiyun (fit, sig_node, sig)) 155*4882a593Smuzhiyun 156*4882a593Smuzhiyun run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) 157*4882a593Smuzhiyun 158*4882a593Smuzhiyun cons.log.action('%s: Check bad config on the host' % sha_algo) 159*4882a593Smuzhiyun util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit, 160*4882a593Smuzhiyun '-k', dtb], 1, 'Failed to verify required signature') 161*4882a593Smuzhiyun 162*4882a593Smuzhiyun cons = u_boot_console 163*4882a593Smuzhiyun tmpdir = cons.config.result_dir + '/' 164*4882a593Smuzhiyun tmp = tmpdir + 'vboot.tmp' 165*4882a593Smuzhiyun datadir = cons.config.source_dir + '/test/py/tests/vboot/' 166*4882a593Smuzhiyun fit = '%stest.fit' % tmpdir 167*4882a593Smuzhiyun mkimage = cons.config.build_dir + '/tools/mkimage' 168*4882a593Smuzhiyun fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' 169*4882a593Smuzhiyun dtc_args = '-I dts -O dtb -i %s' % tmpdir 170*4882a593Smuzhiyun dtb = '%ssandbox-u-boot.dtb' % tmpdir 171*4882a593Smuzhiyun sig_node = '/configurations/conf@1/signature@1' 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun # Create an RSA key pair 174*4882a593Smuzhiyun public_exponent = 65537 175*4882a593Smuzhiyun util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key ' 176*4882a593Smuzhiyun '-pkeyopt rsa_keygen_bits:2048 ' 177*4882a593Smuzhiyun '-pkeyopt rsa_keygen_pubexp:%d ' 178*4882a593Smuzhiyun '2>/dev/null' % (tmpdir, public_exponent)) 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun # Create a certificate containing the public key 181*4882a593Smuzhiyun util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out ' 182*4882a593Smuzhiyun '%sdev.crt' % (tmpdir, tmpdir)) 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun # Create a number kernel image with zeroes 185*4882a593Smuzhiyun with open('%stest-kernel.bin' % tmpdir, 'w') as fd: 186*4882a593Smuzhiyun fd.write(5000 * chr(0)) 187*4882a593Smuzhiyun 188*4882a593Smuzhiyun try: 189*4882a593Smuzhiyun # We need to use our own device tree file. Remember to restore it 190*4882a593Smuzhiyun # afterwards. 191*4882a593Smuzhiyun old_dtb = cons.config.dtb 192*4882a593Smuzhiyun cons.config.dtb = dtb 193*4882a593Smuzhiyun test_with_algo('sha1') 194*4882a593Smuzhiyun test_with_algo('sha256') 195*4882a593Smuzhiyun finally: 196*4882a593Smuzhiyun # Go back to the original U-Boot with the correct dtb. 197*4882a593Smuzhiyun cons.config.dtb = old_dtb 198*4882a593Smuzhiyun cons.restart_uboot() 199