def __init__(self, u_boot_console): """Initialize a new GptTestDiskImage object. Args: u_boot_console: A U-Boot console. Returns: Nothing. """ filename = 'test_gpt_disk_image.bin' persistent = u_boot_console.config.persistent_data_dir + '/' + filename self.path = u_boot_console.config.result_dir + '/' + filename if os.path.exists(persistent): u_boot_console.log.action('Disk image file ' + persistent + ' already exists') else: u_boot_console.log.action('Generating ' + persistent) fd = os.open(persistent, os.O_RDWR | os.O_CREAT) os.ftruncate(fd, 4194304) os.close(fd) cmd = ('sgdisk', '-U', '375a56f7-d6c9-4e81-b5f0-09d41ca89efe', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=1:2048:2560', '-c 1:part1', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=2:4096:4608', '-c 2:part2', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '-l', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('cp', persistent, self.path) u_boot_utils.run_and_log(u_boot_console, cmd)
def make_dtb(fdt_type, comp): """Create a sample DTB file. Creates a DTS file and compiles it to a DTB. Args: fdt_type: The type of the FDT, i.e. internal, user. comp: Flag to enable gzip compression. Return: The path of the created file. """ # Generate resources referenced by FDT. fdt_params = { 'sys-arch': sys_arch, 'fdt_type': fdt_type, } # Generate a test FDT file. dts = make_fpath('test-efi-fit-%s.dts' % fdt_type) with open(dts, 'w') as file: file.write(FDT_DATA % fdt_params) # Build the test FDT. dtb = make_fpath('test-efi-fit-%s.dtb' % fdt_type) util.run_and_log(cons, ['dtc', '-I', 'dts', '-O', 'dtb', '-o', dtb, dts]) if comp: util.run_and_log(cons, ['gzip', '-f', dtb]) dtb += '.gz' return dtb
def test_with_algo(sha_algo): """Test verified boot with the given hash algorithm. This is the main part of the test code. The same procedure is followed for both hashing algorithms. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ # Compile our device tree files for kernel and U-Boot. These are # regenerated here since mkimage will modify them (by adding a # public key) below. dtc('sandbox-kernel.dts') dtc('sandbox-u-boot.dts') # Build the FIT, but don't sign anything yet cons.log.action('%s: Test FIT with signed images' % sha_algo) make_fit('sign-images-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned images', 'dev-', True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed images', 'dev+', True) # Create a fresh .dtb without the public keys dtc('sandbox-u-boot.dts') cons.log.action('%s: Test FIT with signed configuration' % sha_algo) make_fit('sign-configs-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed config', 'dev+', True) cons.log.action('%s: Check signed config on the host' % sha_algo) util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, '-k', dtb]) # Increment the first byte of the signature, which should cause failure sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % (fit, sig_node)) byte_list = sig.split() byte = int(byte_list[0], 16) byte_list[0] = '%x' % (byte + 1) sig = ' '.join(byte_list) util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % (fit, sig_node, sig)) run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) cons.log.action('%s: Check bad config on the host' % sha_algo) util.run_and_log_expect_exception( cons, [fit_check_sign, '-f', fit, '-k', dtb], 1, 'Failed to verify required signature')
def sign_fit(): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. """ cons.log.action('%s: Sign images' % algo) util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit])
def test_with_algo(sha_algo): """Test verified boot with the given hash algorithm. This is the main part of the test code. The same procedure is followed for both hashing algorithms. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ # Compile our device tree files for kernel and U-Boot. These are # regenerated here since mkimage will modify them (by adding a # public key) below. dtc('sandbox-kernel.dts') dtc('sandbox-u-boot.dts') # Build the FIT, but don't sign anything yet cons.log.action('%s: Test FIT with signed images' % sha_algo) make_fit('sign-images-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned images', 'dev-', True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed images', 'dev+', True) # Create a fresh .dtb without the public keys dtc('sandbox-u-boot.dts') cons.log.action('%s: Test FIT with signed configuration' % sha_algo) make_fit('sign-configs-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed config', 'dev+', True) cons.log.action('%s: Check signed config on the host' % sha_algo) util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, '-k', dtb]) # Increment the first byte of the signature, which should cause failure sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % (fit, sig_node)) byte_list = sig.split() byte = int(byte_list[0], 16) byte_list[0] = '%x' % (byte + 1) sig = ' '.join(byte_list) util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % (fit, sig_node, sig)) run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) cons.log.action('%s: Check bad config on the host' % sha_algo) util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit, '-k', dtb], 1, 'Failed to verify required signature')
def make_fit(its): """Make a new FIT from the .its source file This runs 'mkimage -f' to create a new FIT. Args: its: Filename containing .its source """ util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', '%s%s' % (datadir, its), fit])
def make_fit(its): """Make a new FIT from the .its source file. This runs 'mkimage -f' to create a new FIT. Args: its: Filename containing .its source. """ util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', '%s%s' % (datadir, its), fit])
def mk_env_ext4(state_test_env): """Create a empty ext4 file system volume.""" c = state_test_env.u_boot_console filename = 'env.ext4.img' persistent = c.config.persistent_data_dir + '/' + filename fs_img = c.config.result_dir + '/' + filename if os.path.exists(persistent): c.log.action('Disk image file ' + persistent + ' already exists') else: # Some distributions do not add /sbin to the default PATH, where mkfs.ext4 lives os.environ["PATH"] += os.pathsep + '/sbin' try: u_boot_utils.run_and_log( c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent) u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent) sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent) if 'metadata_csum' in sb_content: u_boot_utils.run_and_log( c, 'tune2fs -O ^metadata_csum %s' % persistent) except CalledProcessError: call('rm -f %s' % persistent, shell=True) raise u_boot_utils.run_and_log(c, ['cp', '-f', persistent, fs_img]) return fs_img
def dtc(dts): """Run the device tree compiler to compile a .dts file The output file will be the same as the input file but with a .dtb extension. Args: dts: Device tree file to compile. """ dtb = dts.replace('.dts', '.dtb') util.run_and_log(cons, 'dtc %s %s%s -O dtb ' '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
def make_dtb(): """Make a sample .dts file and compile it to a .dtb Returns: Filename of .dtb file created """ src = make_fname('u-boot.dts') dtb = make_fname('u-boot.dtb') with open(src, 'w') as fd: print >> fd, base_fdt util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb]) return dtb
def sign_fit_norequire(sha_algo): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ cons.log.action('%s: Sign images' % sha_algo) util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit])
def sign_fit(sha_algo): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ cons.log.action('%s: Sign images' % sha_algo) util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit])
def mount(): """Mount the block device that U-Boot exports. Args: None. Returns: Nothing. """ u_boot_console.log.action('Mounting exported UMS device') cmd = ('/bin/mount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd)
def __init__(self, u_boot_console): """Initialize a new GptTestDiskImage object. Args: u_boot_console: A U-Boot console. Returns: Nothing. """ filename = 'test_gpt_disk_image.bin' self.path = u_boot_console.config.persistent_data_dir + '/' + filename if os.path.exists(self.path): u_boot_console.log.action('Disk image file ' + self.path + ' already exists') else: u_boot_console.log.action('Generating ' + self.path) fd = os.open(self.path, os.O_RDWR | os.O_CREAT) os.ftruncate(fd, 4194304) os.close(fd) cmd = ('sgdisk', '-U', '375a56f7-d6c9-4e81-b5f0-09d41ca89efe', self.path) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=1:2048:2560', self.path) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=2:4096:4608', self.path) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '-l', self.path) u_boot_utils.run_and_log(u_boot_console, cmd)
def __init__(self, u_boot_console): """Initialize a new ABTestDiskImage object. Args: u_boot_console: A U-Boot console. Returns: Nothing. """ filename = 'test_ab_disk_image.bin' persistent = u_boot_console.config.persistent_data_dir + '/' + filename self.path = u_boot_console.config.result_dir + '/' + filename with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent): if os.path.exists(persistent): u_boot_console.log.action('Disk image file ' + persistent + ' already exists') else: u_boot_console.log.action('Generating ' + persistent) fd = os.open(persistent, os.O_RDWR | os.O_CREAT) os.ftruncate(fd, 524288) os.close(fd) cmd = ('sgdisk', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=1:64:512', '--change-name=1:misc', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--load-backup=' + persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('cp', persistent, self.path) u_boot_utils.run_and_log(u_boot_console, cmd)
def run_binman(dtb): """Run binman to build an image Args: dtb: Device tree file used as input file. """ pythonpath = os.environ.get('PYTHONPATH', '') os.environ[ 'PYTHONPATH'] = pythonpath + ':' + '%s/../scripts/dtc/pylibfdt' % tmpdir util.run_and_log(cons, [ binman, 'build', '-d', "%s/%s" % (tmpdir, dtb), '-a', "pre-load-key-path=%s" % tmpdir, '-O', tmpdir, '-I', tmpdir ]) os.environ['PYTHONPATH'] = pythonpath
def umount(ignore_errors): """Unmount the block device that U-Boot exports. Args: ignore_errors: Ignore any errors. This is useful if an error has already been detected, and the code is performing best-effort cleanup. In this case, we do not want to mask the original error by "honoring" any new errors. Returns: Nothing. """ u_boot_console.log.action('Unmounting UMS device') cmd = ('/bin/umount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors)
def create_rsa_pair(name): """Generate a new RSA key paid and certificate Args: name: Name of of the key (e.g. 'dev') """ public_exponent = 65537 util.run_and_log( cons, 'openssl genpkey -algorithm RSA -out %s%s.key ' '-pkeyopt rsa_keygen_bits:2048 ' '-pkeyopt rsa_keygen_pubexp:%d' % (tmpdir, name, public_exponent)) # Create a certificate containing the public key util.run_and_log( cons, 'openssl req -batch -new -x509 -key %s%s.key ' '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
def sign_fit_norequire(sha_algo, options): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. It does not mark key as 'required' in dtb. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. options: Options to provide to mkimage. """ args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit] if options: args += options.split(' ') cons.log.action('%s: Sign images' % sha_algo) util.run_and_log(cons, args)
def __fdt_get_sexadecimal(self, node, prop): numbers = util.run_and_log(self.cons, f'fdtget -tbx {self.fit} {node} {prop}') sexadecimal = '' for num in numbers.rstrip('\n').split(' '): sexadecimal += num.zfill(2) return sexadecimal
def test_event_dump(u_boot_console): """Test that the "help" command can be executed.""" cons = u_boot_console sandbox = cons.config.build_dir + '/u-boot' out = util.run_and_log(cons, ['scripts/event_dump.py', sandbox]) expect = '''.*Event type Id Source location -------------------- ------------------------------ ------------------------------ EVT_MISC_INIT_F sandbox_misc_init_f .*arch/sandbox/cpu/start.c:''' assert re.match(expect, out, re.MULTILINE) is not None
def make_fit(mkimage, params): """Make a sample .fit file ready for loading This creates a .its script with the selected parameters and uses mkimage to turn this into a .fit image. Args: mkimage: Filename of 'mkimage' utility params: Dictionary containing parameters to embed in the %() strings Return: Filename of .fit file created """ fit = make_fname('test.fit') its = make_its(params) util.run_and_log(cons, [mkimage, '-f', its, fit]) with open(make_fname('u-boot.dts'), 'w') as fd: print >> fd, base_fdt return fit
def __fdt_get_binary(self, node, prop): numbers = util.run_and_log(self.cons, f'fdtget -tbi {self.fit} {node} {prop}') bignum = bytearray() for little_num in numbers.split(): bignum.append(int(little_num)) return bignum
def run_dfu_util(alt_setting, fn, up_dn_load_arg): """Invoke dfu-util on the host. Args: alt_setting: The DFU "alternate setting" identifier to interact with. fn: The host-side file name to transfer. up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or download operation should be performed. Returns: Nothing. """ cmd = ['dfu-util', '-a', alt_setting, up_dn_load_arg, fn] if 'host_usb_port_path' in env__usb_dev_port: cmd += ['-p', env__usb_dev_port['host_usb_port_path']] u_boot_utils.run_and_log(u_boot_console, cmd) u_boot_console.wait_for('Ctrl+C to exit ...')
def run_dfu_util(alt_setting, fn, up_dn_load_arg): """Invoke dfu-util on the host. Args: alt_setting: The DFU "alternate setting" identifier to interact with. fn: The host-side file name to transfer. up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or download operation should be performed. Returns: Nothing. """ cmd = ['dfu-util', '-a', str(alt_setting), up_dn_load_arg, fn] if 'host_usb_port_path' in env__usb_dev_port: cmd += ['-p', env__usb_dev_port['host_usb_port_path']] u_boot_utils.run_and_log(u_boot_console, cmd) u_boot_console.wait_for('Ctrl+C to exit ...')
def __init__(self, u_boot_console): """Initialize a new GptTestDiskImage object. Args: u_boot_console: A U-Boot console. Returns: Nothing. """ filename = 'test_gpt_disk_image.bin' persistent = u_boot_console.config.persistent_data_dir + '/' + filename self.path = u_boot_console.config.result_dir + '/' + filename with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent): if os.path.exists(persistent): u_boot_console.log.action('Disk image file ' + persistent + ' already exists') else: u_boot_console.log.action('Generating ' + persistent) fd = os.open(persistent, os.O_RDWR | os.O_CREAT) os.ftruncate(fd, 4194304) os.close(fd) cmd = ('sgdisk', '--disk-guid=375a56f7-d6c9-4e81-b5f0-09d41ca89efe', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) # part1 offset 1MB size 1MB cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1', persistent) # part2 offset 2MB size 1.5MB u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=2:4096:7167', '--change-name=2:part2', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--load-backup=' + persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('cp', persistent, self.path) u_boot_utils.run_and_log(u_boot_console, cmd)
def make_efi(fname, comp): """Create an UEFI binary. This simply copies lib/efi_loader/helloworld.efi into U-Boot build dir and, optionally, compresses the file using gzip. Args: fname: The target file name within U-Boot build dir. comp: Flag to enable gzip compression. Return: The path of the created file. """ bin_path = make_fpath(fname) util.run_and_log(cons, ['cp', make_fpath('lib/efi_loader/helloworld.efi'), bin_path]) if comp: util.run_and_log(cons, ['gzip', '-f', bin_path]) bin_path += '.gz' return bin_path
def make_fit(comp): """Create a sample FIT image. Runs 'mkimage' to create a FIT image within U-Boot build dir. Args: comp -- Enable gzip compression for the EFI binary and FDT blob. Return: The path of the created file. """ # Generate resources referenced by ITS. its_params = { 'sys-arch': sys_arch, 'efi-bin': os.path.basename(make_efi('test-efi-fit-helloworld.efi', comp)), 'kernel-type': 'kernel' if comp else 'kernel_noload', 'efi-comp': 'gzip' if comp else 'none', 'fdt-bin': os.path.basename(make_dtb('user', comp)), 'fdt-comp': 'gzip' if comp else 'none', } # Generate a test ITS file. its_path = make_fpath('test-efi-fit-helloworld.its') with open(its_path, 'w', encoding='ascii') as file: file.write(ITS_DATA % its_params) # Build the test ITS. fit_path = make_fpath('test-efi-fit-helloworld.fit') util.run_and_log( cons, [make_fpath('tools/mkimage'), '-f', its_path, fit_path]) return fit_path
def test_spl_devicetree(u_boot_console): """Test content of spl device-tree""" cons = u_boot_console dtb = cons.config.build_dir + '/spl/u-boot-spl.dtb' fdtgrep = cons.config.build_dir + '/tools/fdtgrep' output = util.run_and_log(cons, [fdtgrep, '-l', dtb]) assert "u-boot,dm-pre-reloc" not in output assert "u-boot,dm-pre-proper" not in output assert "u-boot,dm-spl" not in output assert "u-boot,dm-tpl" not in output assert "spl-test5" not in output assert "spl-test6" not in output assert "spl-test7" in output
def check_script(intext, expect_val): """Check a test case Args: intext: Text to pass to the script expect_val: Expected value of the CONFIG_EXTRA_ENV_TEXT string, or None if we expect it not to be defined """ with tempfile.TemporaryDirectory() as path: fname = os.path.join(path, 'infile') with open(fname, 'w') as inf: print(intext, file=inf) result = u_boot_utils.run_and_log(cons, ['awk', '-f', script, fname]) if expect_val is not None: expect = '#define CONFIG_EXTRA_ENV_TEXT "%s"\n' % expect_val assert result == expect else: assert result == ''
def mk_env_ext4(state_test_env): """Create a empty ext4 file system volume.""" c = state_test_env.u_boot_console filename = 'env.ext4.img' persistent = c.config.persistent_data_dir + '/' + filename fs_img = c.config.result_dir + '/' + filename if os.path.exists(persistent): c.log.action('Disk image file ' + persistent + ' already exists') else: try: u_boot_utils.run_and_log( c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent) u_boot_utils.run_and_log( c, 'mkfs.ext4 -O ^metadata_csum %s' % persistent) except CalledProcessError: call('rm -f %s' % persistent, shell=True) raise u_boot_utils.run_and_log(c, ['cp', '-f', persistent, fs_img]) return fs_img
def __init__(self, u_boot_console): """Initialize a new AbootimgDiskImage object. Args: u_boot_console: A U-Boot console. Returns: Nothing. """ gz_hex = u_boot_console.config.persistent_data_dir + '/boot.img.gz.hex' gz = u_boot_console.config.persistent_data_dir + '/boot.img.gz' filename = 'boot.img' persistent = u_boot_console.config.persistent_data_dir + '/' + filename self.path = u_boot_console.config.result_dir + '/' + filename with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent): if os.path.exists(persistent): u_boot_console.log.action('Disk image file ' + persistent + ' already exists') else: u_boot_console.log.action('Generating ' + persistent) f = open(gz_hex, "w") f.write(img_hex) f.close() cmd = ('xxd', '-r', '-p', gz_hex, gz) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('gunzip', '-9', gz) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('cp', persistent, self.path) u_boot_utils.run_and_log(u_boot_console, cmd)
def test_vboot(u_boot_console): """Test verified boot signing with mkimage and verification with 'bootm'. This works using sandbox only as it needs to update the device tree used by U-Boot to hold public keys from the signing process. The SHA1 and SHA256 tests are combined into a single test since the key-generation process is quite slow and we want to avoid doing it twice. """ def dtc(dts): """Run the device tree compiler to compile a .dts file The output file will be the same as the input file but with a .dtb extension. Args: dts: Device tree file to compile. """ dtb = dts.replace('.dts', '.dtb') util.run_and_log( cons, 'dtc %s %s%s -O dtb ' '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) def run_bootm(sha_algo, test_type, expect_string, boots): """Run a 'bootm' command U-Boot. This always starts a fresh U-Boot instance since the device tree may contain a new public key. Args: test_type: A string identifying the test type. expect_string: A string which is expected in the output. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. boots: A boolean that is True if Linux should boot and False if we are expected to not boot """ cons.restart_uboot() with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)): output = cons.run_command_list([ 'sb load hostfs - 100 %stest.fit' % tmpdir, 'fdt addr 100', 'bootm 100' ]) assert (expect_string in ''.join(output)) if boots: assert ('sandbox: continuing, as we cannot run' in ''.join(output)) def make_fit(its): """Make a new FIT from the .its source file. This runs 'mkimage -f' to create a new FIT. Args: its: Filename containing .its source. """ util.run_and_log( cons, [mkimage, '-D', dtc_args, '-f', '%s%s' % (datadir, its), fit]) def sign_fit(sha_algo): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ cons.log.action('%s: Sign images' % sha_algo) util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]) def test_with_algo(sha_algo): """Test verified boot with the given hash algorithm. This is the main part of the test code. The same procedure is followed for both hashing algorithms. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ # Compile our device tree files for kernel and U-Boot. These are # regenerated here since mkimage will modify them (by adding a # public key) below. dtc('sandbox-kernel.dts') dtc('sandbox-u-boot.dts') # Build the FIT, but don't sign anything yet cons.log.action('%s: Test FIT with signed images' % sha_algo) make_fit('sign-images-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned images', 'dev-', True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed images', 'dev+', True) # Create a fresh .dtb without the public keys dtc('sandbox-u-boot.dts') cons.log.action('%s: Test FIT with signed configuration' % sha_algo) make_fit('sign-configs-%s.its' % sha_algo) run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed config', 'dev+', True) cons.log.action('%s: Check signed config on the host' % sha_algo) util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, '-k', dtb]) # Increment the first byte of the signature, which should cause failure sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % (fit, sig_node)) byte_list = sig.split() byte = int(byte_list[0], 16) byte_list[0] = '%x' % (byte + 1) sig = ' '.join(byte_list) util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % (fit, sig_node, sig)) run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) cons.log.action('%s: Check bad config on the host' % sha_algo) util.run_and_log_expect_exception( cons, [fit_check_sign, '-f', fit, '-k', dtb], 1, 'Failed to verify required signature') cons = u_boot_console tmpdir = cons.config.result_dir + '/' tmp = tmpdir + 'vboot.tmp' datadir = cons.config.source_dir + '/test/py/tests/vboot/' fit = '%stest.fit' % tmpdir mkimage = cons.config.build_dir + '/tools/mkimage' fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' dtc_args = '-I dts -O dtb -i %s' % tmpdir dtb = '%ssandbox-u-boot.dtb' % tmpdir sig_node = '/configurations/conf@1/signature@1' # Create an RSA key pair public_exponent = 65537 util.run_and_log( cons, 'openssl genpkey -algorithm RSA -out %sdev.key ' '-pkeyopt rsa_keygen_bits:2048 ' '-pkeyopt rsa_keygen_pubexp:%d ' '2>/dev/null' % (tmpdir, public_exponent)) # Create a certificate containing the public key util.run_and_log( cons, 'openssl req -batch -new -x509 -key %sdev.key -out ' '%sdev.crt' % (tmpdir, tmpdir)) # Create a number kernel image with zeroes with open('%stest-kernel.bin' % tmpdir, 'w') as fd: fd.write(5000 * chr(0)) try: # We need to use our own device tree file. Remember to restore it # afterwards. old_dtb = cons.config.dtb cons.config.dtb = dtb test_with_algo('sha1') test_with_algo('sha256') finally: # Go back to the original U-Boot with the correct dtb. cons.config.dtb = old_dtb cons.restart_uboot()
def test_required_key(sha_algo, padding, sign_options): """Test verified boot with the given hash algorithm. This function tests if U-Boot rejects an image when a required key isn't used to sign a FIT. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use padding: Either '' or '-pss', to select the padding to use for the rsa signature algorithm. sign_options: Options to mkimage when signing a fit image. """ # Compile our device tree files for kernel and U-Boot. These are # regenerated here since mkimage will modify them (by adding a # public key) below. dtc('sandbox-kernel.dts') dtc('sandbox-u-boot.dts') cons.log.action('%s: Test FIT with configs images' % sha_algo) # Build the FIT with prod key (keys required) and sign it. This puts the # signature into sandbox-u-boot.dtb, marked 'required' make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding)) sign_fit(sha_algo, sign_options) # Build the FIT with dev key (keys NOT required). This adds the # signature into sandbox-u-boot.dtb, NOT marked 'required'. make_fit('sign-configs-%s%s.its' % (sha_algo, padding)) sign_fit_norequire(sha_algo, sign_options) # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. # Only the prod key is set as 'required'. But FIT we just built has # a dev signature only (sign_fit_norequire() overwrites the FIT). # Try to boot the FIT with dev key. This FIT should not be accepted by # U-Boot because the prod key is required. run_bootm(sha_algo, 'required key', '', False) # Build the FIT with dev key (keys required) and sign it. This puts the # signature into sandbox-u-boot.dtb, marked 'required'. make_fit('sign-configs-%s%s.its' % (sha_algo, padding)) sign_fit(sha_algo, sign_options) # Set the required-mode policy to "any". # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. # Both the dev and prod key are set as 'required'. But FIT we just built has # a dev signature only (sign_fit() overwrites the FIT). # Try to boot the FIT with dev key. This FIT should be accepted by # U-Boot because the dev key is required and policy is "any" required key. util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' % (dtb)) run_bootm(sha_algo, 'multi required key', 'dev+', True) # Set the required-mode policy to "all". # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys. # Both the dev and prod key are set as 'required'. But FIT we just built has # a dev signature only (sign_fit() overwrites the FIT). # Try to boot the FIT with dev key. This FIT should not be accepted by # U-Boot because the prod key is required and policy is "all" required key util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' % (dtb)) run_bootm(sha_algo, 'multi required key', '', False)
def test_ums(u_boot_console, env__usb_dev_port, env__block_devs): """Test the "ums" command; the host system must be able to enumerate a UMS device when "ums" is running, block and optionally file I/O are tested, and this device must disappear when "ums" is aborted. Args: u_boot_console: A U-Boot console connection. env__usb_dev_port: The single USB device-mode port specification on which to run the test. See the file-level comment above for details of the format. env__block_devs: The list of block devices that the target U-Boot device has attached. See the file-level comment above for details of the format. Returns: Nothing. """ have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0] if not have_writable_fs_partition: # If 'writable_fs_subdir' is missing, we'll skip all parts of the # testing which mount filesystems. u_boot_console.log.warning( 'boardenv missing "writable_fs_partition"; ' + 'UMS testing will be limited.') tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr'] host_ums_dev_node = env__usb_dev_port['host_ums_dev_node'] # We're interested in testing USB device mode on each port, not the cross- # product of that with each device. So, just pick the first entry in the # device list here. We'll test each block device somewhere else. tgt_dev_type = env__block_devs[0]['type'] tgt_dev_id = env__block_devs[0]['id'] if have_writable_fs_partition: mount_point = u_boot_console.config.env['env__mount_points'][0] mount_subdir = env__block_devs[0]['writable_fs_subdir'] part_num = env__block_devs[0]['writable_fs_partition'] host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num) else: host_ums_part_node = host_ums_dev_node test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin', 1024 * 1024); if have_writable_fs_partition: mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn def start_ums(): """Start U-Boot's ums shell command. This also waits for the host-side USB enumeration process to complete. Args: None. Returns: Nothing. """ u_boot_console.log.action( 'Starting long-running U-Boot ums shell command') cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id) u_boot_console.run_command(cmd, wait_for_prompt=False) u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]')) fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node) u_boot_console.log.action('Reading raw data from UMS device') fh.read(4096) fh.close() def mount(): """Mount the block device that U-Boot exports. Args: None. Returns: Nothing. """ u_boot_console.log.action('Mounting exported UMS device') cmd = ('/bin/mount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd) def umount(ignore_errors): """Unmount the block device that U-Boot exports. Args: ignore_errors: Ignore any errors. This is useful if an error has already been detected, and the code is performing best-effort cleanup. In this case, we do not want to mask the original error by "honoring" any new errors. Returns: Nothing. """ u_boot_console.log.action('Unmounting UMS device') cmd = ('/bin/umount', host_ums_part_node) u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors) def stop_ums(ignore_errors): """Stop U-Boot's ums shell command from executing. This also waits for the host-side USB de-enumeration process to complete. Args: ignore_errors: Ignore any errors. This is useful if an error has already been detected, and the code is performing best-effort cleanup. In this case, we do not want to mask the original error by "honoring" any new errors. Returns: Nothing. """ u_boot_console.log.action( 'Stopping long-running U-Boot ums shell command') u_boot_console.ctrlc() u_boot_utils.wait_until_file_open_fails(host_ums_part_node, ignore_errors) ignore_cleanup_errors = True try: start_ums() if not have_writable_fs_partition: # Skip filesystem-based testing if not configured return try: mount() u_boot_console.log.action('Writing test file via UMS') cmd = ('rm', '-f', mounted_test_fn) u_boot_utils.run_and_log(u_boot_console, cmd) if os.path.exists(mounted_test_fn): raise Exception('Could not rm target UMS test file') cmd = ('cp', test_f.abs_fn, mounted_test_fn) u_boot_utils.run_and_log(u_boot_console, cmd) ignore_cleanup_errors = False finally: umount(ignore_errors=ignore_cleanup_errors) finally: stop_ums(ignore_errors=ignore_cleanup_errors) ignore_cleanup_errors = True try: start_ums() try: mount() u_boot_console.log.action('Reading test file back via UMS') read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn) cmd = ('rm', '-f', mounted_test_fn) u_boot_utils.run_and_log(u_boot_console, cmd) ignore_cleanup_errors = False finally: umount(ignore_errors=ignore_cleanup_errors) finally: stop_ums(ignore_errors=ignore_cleanup_errors) written_hash = test_f.content_hash assert(written_hash == read_back_hash)
def test_vboot(u_boot_console): """Test verified boot signing with mkimage and verification with 'bootm'. This works using sandbox only as it needs to update the device tree used by U-Boot to hold public keys from the signing process. The SHA1 and SHA256 tests are combined into a single test since the key-generation process is quite slow and we want to avoid doing it twice. """ def dtc(dts): """Run the device tree compiler to compile a .dts file The output file will be the same as the input file but with a .dtb extension. Args: dts: Device tree file to compile. """ dtb = dts.replace('.dts', '.dtb') util.run_and_log(cons, 'dtc %s %s%s -O dtb ' '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb)) def run_bootm(sha_algo, test_type, expect_string, boots): """Run a 'bootm' command U-Boot. This always starts a fresh U-Boot instance since the device tree may contain a new public key. Args: test_type: A string identifying the test type. expect_string: A string which is expected in the output. sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. boots: A boolean that is True if Linux should boot and False if we are expected to not boot """ cons.restart_uboot() with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)): output = cons.run_command_list( ['host load hostfs - 100 %stest.fit' % tmpdir, 'fdt addr 100', 'bootm 100']) assert(expect_string in ''.join(output)) if boots: assert('sandbox: continuing, as we cannot run' in ''.join(output)) def make_fit(its): """Make a new FIT from the .its source file. This runs 'mkimage -f' to create a new FIT. Args: its: Filename containing .its source. """ util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', '%s%s' % (datadir, its), fit]) def sign_fit(sha_algo): """Sign the FIT Signs the FIT and writes the signature into it. It also writes the public key into the dtb. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ cons.log.action('%s: Sign images' % sha_algo) util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]) def replace_fit_totalsize(size): """Replace FIT header's totalsize with something greater. The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE. If the size is greater, the signature verification should return false. Args: size: The new totalsize of the header Returns: prev_size: The previous totalsize read from the header """ total_size = 0 with open(fit, 'r+b') as handle: handle.seek(4) total_size = handle.read(4) handle.seek(4) handle.write(struct.pack(">I", size)) return struct.unpack(">I", total_size)[0] def test_with_algo(sha_algo, padding): """Test verified boot with the given hash algorithm. This is the main part of the test code. The same procedure is followed for both hashing algorithms. Args: sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use. """ # Compile our device tree files for kernel and U-Boot. These are # regenerated here since mkimage will modify them (by adding a # public key) below. dtc('sandbox-kernel.dts') dtc('sandbox-u-boot.dts') # Build the FIT, but don't sign anything yet cons.log.action('%s: Test FIT with signed images' % sha_algo) make_fit('sign-images-%s%s.its' % (sha_algo , padding)) run_bootm(sha_algo, 'unsigned images', 'dev-', True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed images', 'dev+', True) # Create a fresh .dtb without the public keys dtc('sandbox-u-boot.dts') cons.log.action('%s: Test FIT with signed configuration' % sha_algo) make_fit('sign-configs-%s%s.its' % (sha_algo , padding)) run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True) # Sign images with our dev keys sign_fit(sha_algo) run_bootm(sha_algo, 'signed config', 'dev+', True) cons.log.action('%s: Check signed config on the host' % sha_algo) util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir, '-k', dtb]) # Replace header bytes bcfg = u_boot_console.config.buildconfig max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0) existing_size = replace_fit_totalsize(max_size + 1) run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo) # Replace with existing header bytes replace_fit_totalsize(existing_size) run_bootm(sha_algo, 'signed config', 'dev+', True) cons.log.action('%s: Check default FIT header totalsize' % sha_algo) # Increment the first byte of the signature, which should cause failure sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' % (fit, sig_node)) byte_list = sig.split() byte = int(byte_list[0], 16) byte_list[0] = '%x' % (byte + 1) sig = ' '.join(byte_list) util.run_and_log(cons, 'fdtput -t bx %s %s value %s' % (fit, sig_node, sig)) run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False) cons.log.action('%s: Check bad config on the host' % sha_algo) util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit, '-k', dtb], 1, 'Failed to verify required signature') cons = u_boot_console tmpdir = cons.config.result_dir + '/' tmp = tmpdir + 'vboot.tmp' datadir = cons.config.source_dir + '/test/py/tests/vboot/' fit = '%stest.fit' % tmpdir mkimage = cons.config.build_dir + '/tools/mkimage' fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign' dtc_args = '-I dts -O dtb -i %s' % tmpdir dtb = '%ssandbox-u-boot.dtb' % tmpdir sig_node = '/configurations/conf-1/signature' # Create an RSA key pair public_exponent = 65537 util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key ' '-pkeyopt rsa_keygen_bits:2048 ' '-pkeyopt rsa_keygen_pubexp:%d' % (tmpdir, public_exponent)) # Create a certificate containing the public key util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out ' '%sdev.crt' % (tmpdir, tmpdir)) # Create a number kernel image with zeroes with open('%stest-kernel.bin' % tmpdir, 'w') as fd: fd.write(5000 * chr(0)) try: # We need to use our own device tree file. Remember to restore it # afterwards. old_dtb = cons.config.dtb cons.config.dtb = dtb test_with_algo('sha1','') test_with_algo('sha1','-pss') test_with_algo('sha256','') test_with_algo('sha256','-pss') finally: # Go back to the original U-Boot with the correct dtb. cons.config.dtb = old_dtb cons.restart_uboot()
def launch_efi(enable_fdt, enable_comp): """Launch U-Boot's helloworld.efi binary from a FIT image. An external image file can be downloaded from TFTP, when related details are provided by the boardenv_* file; see the comment at the beginning of this file. If the size of the TFTP file is not provided within env__efi_fit_tftp_file, the test image is generated automatically and placed in the TFTP root directory specified via the 'dn' field. When running the tests on Sandbox, the image file is loaded directly from the host filesystem. Once the load address is available on U-Boot console, the 'bootm' command is executed for either 'config-efi-fdt' or 'config-efi-nofdt' FIT configuration, depending on the value of the 'enable_fdt' function argument. Eventually the 'Hello, world' message is expected in the U-Boot console. Args: enable_fdt: Flag to enable using the FDT blob inside FIT image. enable_comp: Flag to enable GZIP compression on EFI and FDT generated content. """ with cons.log.section('FDT=%s;COMP=%s' % (enable_fdt, enable_comp)): if is_sandbox: fit = { 'dn': cons.config.build_dir, } else: # Init networking. net_pre_commands() net_set_up = net_dhcp() net_set_up = net_setup_static() or net_set_up if not net_set_up: pytest.skip('Network not initialized') fit = cons.config.env.get('env__efi_fit_tftp_file', None) if not fit: pytest.skip('No env__efi_fit_tftp_file binary specified in environment') sz = fit.get('size', None) if not sz: if not fit.get('dn', None): pytest.skip('Neither "size", nor "dn" info provided in env__efi_fit_tftp_file') # Create test FIT image. fit_path = make_fit(enable_comp) fit['fn'] = os.path.basename(fit_path) fit['size'] = os.path.getsize(fit_path) # Copy image to TFTP root directory. if fit['dn'] != cons.config.build_dir: util.run_and_log(cons, ['mv', '-f', fit_path, '%s/' % fit['dn']]) # Load FIT image. addr = load_fit_from_host(fit) if is_sandbox else load_fit_from_tftp(fit) # Select boot configuration. fit_config = 'config-efi-fdt' if enable_fdt else 'config-efi-nofdt' # Try booting. cons.run_command( 'bootm %x#%s' % (addr, fit_config), wait_for_prompt=False) if enable_fdt: cons.wait_for('Booting using the fdt blob') cons.wait_for('Hello, world') cons.wait_for('## Application terminated, r = 0') cons.restart_uboot();