blob: 63f2f6a44e6cc922a2a2bb7ab700b7392adff1c6 [file] [log] [blame]
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -06001# 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
Simon Glass998a2f22022-08-06 17:51:49 -060013import os
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060014import pytest
Simon Glassdb0e4532025-02-09 09:07:16 -070015import utils
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060016from Cryptodome.Hash import SHA256
17from Cryptodome.PublicKey import ECC
18from Cryptodome.Signature import DSS
19
20class SignableFitImage(object):
21 """ Helper to manipulate a FIT image on disk """
22 def __init__(self, cons, file_name):
23 self.fit = file_name
24 self.cons = cons
25 self.signable_nodes = set()
26
27 def __fdt_list(self, path):
Simon Glassdb0e4532025-02-09 09:07:16 -070028 return utils.run_and_log(self.cons, f'fdtget -l {self.fit} {path}')
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060029
30 def __fdt_set(self, node, **prop_value):
31 for prop, value in prop_value.items():
Simon Glassdb0e4532025-02-09 09:07:16 -070032 utils.run_and_log(self.cons,
33 f'fdtput -ts {self.fit} {node} {prop} {value}')
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060034
35 def __fdt_get_binary(self, node, prop):
Simon Glassdb0e4532025-02-09 09:07:16 -070036 numbers = utils.run_and_log(self.cons,
37 f'fdtget -tbi {self.fit} {node} {prop}')
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060038
39 bignum = bytearray()
40 for little_num in numbers.split():
41 bignum.append(int(little_num))
42
43 return bignum
44
45 def find_signable_image_nodes(self):
46 for node in self.__fdt_list('/images').split():
47 image = f'/images/{node}'
48 if 'signature' in self.__fdt_list(image):
49 self.signable_nodes.add(image)
50
51 return self.signable_nodes
52
53 def change_signature_algo_to_ecdsa(self):
54 for image in self.signable_nodes:
55 self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256')
56
57 def sign(self, mkimage, key_file):
Simon Glassdb0e4532025-02-09 09:07:16 -070058 utils.run_and_log(self.cons, [mkimage, '-F', self.fit, f'-G{key_file}'])
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060059
60 def check_signatures(self, key):
61 for image in self.signable_nodes:
62 raw_sig = self.__fdt_get_binary(f'{image}/signature', 'value')
63 raw_bin = self.__fdt_get_binary(image, 'data')
64
65 sha = SHA256.new(raw_bin)
66 verifier = DSS.new(key, 'fips-186-3')
67 verifier.verify(sha, bytes(raw_sig))
68
69
70@pytest.mark.buildconfigspec('fit_signature')
71@pytest.mark.requiredtool('dtc')
72@pytest.mark.requiredtool('fdtget')
73@pytest.mark.requiredtool('fdtput')
Simon Glassddba5202025-02-09 09:07:14 -070074def test_fit_ecdsa(ubman):
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060075 """ Test that signatures generated by mkimage are legible. """
76 def generate_ecdsa_key():
77 return ECC.generate(curve='prime256v1')
78
79 def assemble_fit_image(dest_fit, its, destdir):
80 dtc_args = f'-I dts -O dtb -i {destdir}'
Simon Glassdb0e4532025-02-09 09:07:16 -070081 utils.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', its, dest_fit])
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060082
83 def dtc(dts):
84 dtb = dts.replace('.dts', '.dtb')
Simon Glassdb0e4532025-02-09 09:07:16 -070085 utils.run_and_log(cons, f'dtc {datadir}/{dts} -O dtb -o {tempdir}/{dtb}')
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060086
Simon Glassddba5202025-02-09 09:07:14 -070087 cons = ubman
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060088 mkimage = cons.config.build_dir + '/tools/mkimage'
89 datadir = cons.config.source_dir + '/test/py/tests/vboot/'
Simon Glass998a2f22022-08-06 17:51:49 -060090 tempdir = os.path.join(cons.config.result_dir, 'ecdsa')
91 os.makedirs(tempdir, exist_ok=True)
Alexandru Gagniuc9b6fc0c2021-02-19 12:45:15 -060092 key_file = f'{tempdir}/ecdsa-test-key.pem'
93 fit_file = f'{tempdir}/test.fit'
94 dtc('sandbox-kernel.dts')
95
96 key = generate_ecdsa_key()
97
98 # Create a fake kernel image -- zeroes will do just fine
99 with open(f'{tempdir}/test-kernel.bin', 'w') as fd:
100 fd.write(500 * chr(0))
101
102 # invocations of mkimage expect to read the key from disk
103 with open(key_file, 'w') as f:
104 f.write(key.export_key(format='PEM'))
105
106 assemble_fit_image(fit_file, f'{datadir}/sign-images-sha256.its', tempdir)
107
108 fit = SignableFitImage(cons, fit_file)
109 nodes = fit.find_signable_image_nodes()
110 if len(nodes) == 0:
111 raise ValueError('FIT image has no "/image" nodes with "signature"')
112
113 fit.change_signature_algo_to_ecdsa()
114 fit.sign(mkimage, key_file)
115 fit.check_signatures(key)