1# SPDX-License-Identifier: GPL-2.0+ 2# 3# Copyright (c) 2020,2021 Alexandru Gagniuc <mr.nuke.me@gmail.com> 4 5""" 6Test ECDSA signing of FIT images 7 8This test uses mkimage to sign an existing FIT image with an ECDSA key. The 9signature is then extracted, and verified against pyCryptodome. 10This test doesn't run the sandbox. It only checks the host tool 'mkimage' 11""" 12 13import pytest 14import u_boot_utils as util 15from Cryptodome.Hash import SHA256 16from Cryptodome.PublicKey import ECC 17from Cryptodome.Signature import DSS 18 19class SignableFitImage(object): 20 """ Helper to manipulate a FIT image on disk """ 21 def __init__(self, cons, file_name): 22 self.fit = file_name 23 self.cons = cons 24 self.signable_nodes = set() 25 26 def __fdt_list(self, path): 27 return util.run_and_log(self.cons, f'fdtget -l {self.fit} {path}') 28 29 def __fdt_set(self, node, **prop_value): 30 for prop, value in prop_value.items(): 31 util.run_and_log(self.cons, f'fdtput -ts {self.fit} {node} {prop} {value}') 32 33 def __fdt_get_binary(self, node, prop): 34 numbers = util.run_and_log(self.cons, f'fdtget -tbi {self.fit} {node} {prop}') 35 36 bignum = bytearray() 37 for little_num in numbers.split(): 38 bignum.append(int(little_num)) 39 40 return bignum 41 42 def find_signable_image_nodes(self): 43 for node in self.__fdt_list('/images').split(): 44 image = f'/images/{node}' 45 if 'signature' in self.__fdt_list(image): 46 self.signable_nodes.add(image) 47 48 return self.signable_nodes 49 50 def change_signature_algo_to_ecdsa(self): 51 for image in self.signable_nodes: 52 self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256') 53 54 def sign(self, mkimage, key_file): 55 util.run_and_log(self.cons, [mkimage, '-F', self.fit, f'-G{key_file}']) 56 57 def check_signatures(self, key): 58 for image in self.signable_nodes: 59 raw_sig = self.__fdt_get_binary(f'{image}/signature', 'value') 60 raw_bin = self.__fdt_get_binary(image, 'data') 61 62 sha = SHA256.new(raw_bin) 63 verifier = DSS.new(key, 'fips-186-3') 64 verifier.verify(sha, bytes(raw_sig)) 65 66 67@pytest.mark.buildconfigspec('fit_signature') 68@pytest.mark.requiredtool('dtc') 69@pytest.mark.requiredtool('fdtget') 70@pytest.mark.requiredtool('fdtput') 71def test_fit_ecdsa(u_boot_console): 72 """ Test that signatures generated by mkimage are legible. """ 73 def generate_ecdsa_key(): 74 return ECC.generate(curve='prime256v1') 75 76 def assemble_fit_image(dest_fit, its, destdir): 77 dtc_args = f'-I dts -O dtb -i {destdir}' 78 util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', its, dest_fit]) 79 80 def dtc(dts): 81 dtb = dts.replace('.dts', '.dtb') 82 util.run_and_log(cons, f'dtc {datadir}/{dts} -O dtb -o {tempdir}/{dtb}') 83 84 cons = u_boot_console 85 mkimage = cons.config.build_dir + '/tools/mkimage' 86 datadir = cons.config.source_dir + '/test/py/tests/vboot/' 87 tempdir = cons.config.result_dir 88 key_file = f'{tempdir}/ecdsa-test-key.pem' 89 fit_file = f'{tempdir}/test.fit' 90 dtc('sandbox-kernel.dts') 91 92 key = generate_ecdsa_key() 93 94 # Create a fake kernel image -- zeroes will do just fine 95 with open(f'{tempdir}/test-kernel.bin', 'w') as fd: 96 fd.write(500 * chr(0)) 97 98 # invocations of mkimage expect to read the key from disk 99 with open(key_file, 'w') as f: 100 f.write(key.export_key(format='PEM')) 101 102 assemble_fit_image(fit_file, f'{datadir}/sign-images-sha256.its', tempdir) 103 104 fit = SignableFitImage(cons, fit_file) 105 nodes = fit.find_signable_image_nodes() 106 if len(nodes) == 0: 107 raise ValueError('FIT image has no "/image" nodes with "signature"') 108 109 fit.change_signature_algo_to_ecdsa() 110 fit.sign(mkimage, key_file) 111 fit.check_signatures(key) 112