| # SPDX-License-Identifier: GPL-2.0+ |
| # |
| # Copyright (c) 2020,2021 Alexandru Gagniuc <mr.nuke.me@gmail.com> |
| |
| """ |
| Test ECDSA signing of FIT images |
| |
| This test uses mkimage to sign an existing FIT image with an ECDSA key. The |
| signature is then extracted, and verified against pyCryptodome. |
| This test doesn't run the sandbox. It only checks the host tool 'mkimage' |
| """ |
| |
| import os |
| import pytest |
| import u_boot_utils as util |
| from Cryptodome.Hash import SHA256 |
| from Cryptodome.PublicKey import ECC |
| from Cryptodome.Signature import DSS |
| |
| class SignableFitImage(object): |
| """ Helper to manipulate a FIT image on disk """ |
| def __init__(self, cons, file_name): |
| self.fit = file_name |
| self.cons = cons |
| self.signable_nodes = set() |
| |
| def __fdt_list(self, path): |
| return util.run_and_log(self.cons, f'fdtget -l {self.fit} {path}') |
| |
| def __fdt_set(self, node, **prop_value): |
| for prop, value in prop_value.items(): |
| util.run_and_log(self.cons, f'fdtput -ts {self.fit} {node} {prop} {value}') |
| |
| 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 find_signable_image_nodes(self): |
| for node in self.__fdt_list('/images').split(): |
| image = f'/images/{node}' |
| if 'signature' in self.__fdt_list(image): |
| self.signable_nodes.add(image) |
| |
| return self.signable_nodes |
| |
| def change_signature_algo_to_ecdsa(self): |
| for image in self.signable_nodes: |
| self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256') |
| |
| def sign(self, mkimage, key_file): |
| util.run_and_log(self.cons, [mkimage, '-F', self.fit, f'-G{key_file}']) |
| |
| def check_signatures(self, key): |
| for image in self.signable_nodes: |
| raw_sig = self.__fdt_get_binary(f'{image}/signature', 'value') |
| raw_bin = self.__fdt_get_binary(image, 'data') |
| |
| sha = SHA256.new(raw_bin) |
| verifier = DSS.new(key, 'fips-186-3') |
| verifier.verify(sha, bytes(raw_sig)) |
| |
| |
| @pytest.mark.buildconfigspec('fit_signature') |
| @pytest.mark.requiredtool('dtc') |
| @pytest.mark.requiredtool('fdtget') |
| @pytest.mark.requiredtool('fdtput') |
| def test_fit_ecdsa(u_boot_console): |
| """ Test that signatures generated by mkimage are legible. """ |
| def generate_ecdsa_key(): |
| return ECC.generate(curve='prime256v1') |
| |
| def assemble_fit_image(dest_fit, its, destdir): |
| dtc_args = f'-I dts -O dtb -i {destdir}' |
| util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', its, dest_fit]) |
| |
| def dtc(dts): |
| dtb = dts.replace('.dts', '.dtb') |
| util.run_and_log(cons, f'dtc {datadir}/{dts} -O dtb -o {tempdir}/{dtb}') |
| |
| cons = u_boot_console |
| mkimage = cons.config.build_dir + '/tools/mkimage' |
| datadir = cons.config.source_dir + '/test/py/tests/vboot/' |
| tempdir = os.path.join(cons.config.result_dir, 'ecdsa') |
| os.makedirs(tempdir, exist_ok=True) |
| key_file = f'{tempdir}/ecdsa-test-key.pem' |
| fit_file = f'{tempdir}/test.fit' |
| dtc('sandbox-kernel.dts') |
| |
| key = generate_ecdsa_key() |
| |
| # Create a fake kernel image -- zeroes will do just fine |
| with open(f'{tempdir}/test-kernel.bin', 'w') as fd: |
| fd.write(500 * chr(0)) |
| |
| # invocations of mkimage expect to read the key from disk |
| with open(key_file, 'w') as f: |
| f.write(key.export_key(format='PEM')) |
| |
| assemble_fit_image(fit_file, f'{datadir}/sign-images-sha256.its', tempdir) |
| |
| fit = SignableFitImage(cons, fit_file) |
| nodes = fit.find_signable_image_nodes() |
| if len(nodes) == 0: |
| raise ValueError('FIT image has no "/image" nodes with "signature"') |
| |
| fit.change_signature_algo_to_ecdsa() |
| fit.sign(mkimage, key_file) |
| fit.check_signatures(key) |