mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-10-02 23:11:48 -09:00
U-Boot host tools use the tools configuration namespace when code calls CONFIG_IS_ENABLED(). With USE_HOSTCC, CONFIG_IS_ENABLED(FIT_SIGNATURE) resolves to CONFIG_TOOLS_FIT_SIGNATURE, while CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) resolves to CONFIG_TOOLS_FIT_SIGNATURE_MAX_SIZE. The host-uboot-tools package only generates the old CONFIG_FIT_SIGNATURE define. This makes tools/Makefile include fit_check_sign, but the host code sees FIT signature support as disabled and the OpenSSL-backed signing and verification objects are omitted. As a result, mkimage accepts a signature node without writing its value or injecting a required public key. fit_check_sign then has no required key and reports success without checking the configuration signature. A FIT-support-only configuration never exercises this path, which is why the existing hash-only runtime test still passes. Generate the tools FIT signature options needed by the host code and pass CONFIG_TOOLS_LIBCRYPTO=y so the OpenSSL-backed signing, verification and cipher objects are selected. Keep CONFIG_FIT_SIGNATURE=y in the make options because U-Boot tools/Makefile still uses it to build fit_check_sign. Extend TestHostUbootTools to create an RSA-signed FIT, require a 256-byte configuration signature and a required public key, verify the FIT, corrupt the signature, and require verification to fail. Signed-off-by: James Hilliard <james.hilliard1@gmail.com> [Julien: add comments in runtime test] Signed-off-by: Julien Olivain <ju.o@free.fr>
160 lines
5.8 KiB
Python
160 lines
5.8 KiB
Python
import hashlib
|
|
import re
|
|
import subprocess
|
|
import zlib
|
|
from pathlib import Path
|
|
|
|
import infra.basetest
|
|
|
|
EXAMPLE_ITS = Path(__file__).parent / "test_uboot_tools/example.its"
|
|
KEY_DTS = Path(__file__).parent / "test_uboot_tools/key.dts"
|
|
SIGNED_ITS = Path(__file__).parent / "test_uboot_tools/signed.its"
|
|
|
|
|
|
def get_hashes(output: str) -> dict[str, str]:
|
|
"""Get hashes from dumpimage output (assumes exactly one image in FIT)"""
|
|
pat = re.compile(
|
|
r"Hash algo:\s+(\S+)$\s+Hash value:\s+(\w+)$", flags=re.MULTILINE)
|
|
return dict(pat.findall(output))
|
|
|
|
|
|
def calc_hashes(data: bytes) -> dict[str, str]:
|
|
"""Calculate the hashes dumpimage should report for a given blob"""
|
|
return {
|
|
"crc32": f"{zlib.crc32(data):08x}",
|
|
"md5": hashlib.md5(data).hexdigest(),
|
|
"sha1": hashlib.sha1(data).hexdigest(),
|
|
"sha256": hashlib.sha256(data).hexdigest(),
|
|
"sha384": hashlib.sha384(data).hexdigest(),
|
|
"sha512": hashlib.sha512(data).hexdigest(),
|
|
}
|
|
|
|
|
|
class TestUbootTools(infra.basetest.BRTest):
|
|
rootfs_overlay = \
|
|
infra.filepath("tests/package/test_uboot_tools")
|
|
config = infra.basetest.BASIC_TOOLCHAIN_CONFIG + \
|
|
f"""
|
|
BR2_PACKAGE_UBOOT_TOOLS=y
|
|
BR2_PACKAGE_UBOOT_TOOLS_DUMPIMAGE=y
|
|
BR2_PACKAGE_UBOOT_TOOLS_FIT_SUPPORT=y
|
|
BR2_PACKAGE_UBOOT_TOOLS_MKIMAGE=y
|
|
BR2_ROOTFS_OVERLAY="{rootfs_overlay}"
|
|
BR2_TARGET_ROOTFS_CPIO=y
|
|
# BR2_TARGET_ROOTFS_TAR is not set
|
|
"""
|
|
|
|
def test_run(self):
|
|
ramdisk = Path(self.builddir) / "images/rootfs.cpio"
|
|
self.emulator.boot(arch="armv5",
|
|
kernel="builtin",
|
|
options=["-initrd", str(ramdisk)])
|
|
self.emulator.login()
|
|
|
|
self.assertRunOk(f"mkimage -f /{EXAMPLE_ITS.name} /tmp/test.fit")
|
|
|
|
cmd = "dumpimage -l /tmp/test.fit"
|
|
out, exit_code = self.emulator.run(cmd, -1)
|
|
self.assertEqual(
|
|
exit_code,
|
|
0,
|
|
"\nFailed to run: {}\noutput was:\n{}".format(cmd, ' '+'\n '.join(out))
|
|
)
|
|
reported = get_hashes("\n".join(out))
|
|
expected = calc_hashes(EXAMPLE_ITS.read_bytes())
|
|
for h in expected:
|
|
with self.subTest(hash=h):
|
|
self.assertEqual(expected[h], reported[h])
|
|
# Python does not have built-in CRC16 support, just check it is present
|
|
self.assertIn("crc16-ccitt", reported)
|
|
|
|
|
|
class TestHostUbootTools(infra.basetest.BRHostPkgTest):
|
|
hostpkgs = ["host-uboot-tools"]
|
|
config = infra.basetest.BASIC_TOOLCHAIN_CONFIG + \
|
|
"""
|
|
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
|
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
|
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
|
"""
|
|
|
|
def test_run(self):
|
|
cmd = ["host/bin/mkimage", "-f", str(EXAMPLE_ITS), "test.fit"]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
cmd = ["host/bin/dumpimage", "-l", "test.fit"]
|
|
reported = get_hashes(infra.run_cmd_on_host(self.builddir, cmd))
|
|
expected = calc_hashes(EXAMPLE_ITS.read_bytes())
|
|
for h in expected:
|
|
with self.subTest(hash=h):
|
|
self.assertEqual(expected[h], reported[h])
|
|
# Python does not have built-in CRC16 support, just check it is present
|
|
self.assertIn("crc16-ccitt", reported)
|
|
|
|
# See U-Boot FIT Signature Verification documentation:
|
|
# https://source.denx.de/u-boot/u-boot/-/blob/v2026.07/doc/usage/fit/signature.rst
|
|
keydir = Path(self.builddir) / "keys"
|
|
keydir.mkdir(exist_ok=True)
|
|
|
|
# We generate a prublic/private key pair to sign the image.
|
|
cmd = [
|
|
"host/bin/openssl", "req", "-batch", "-new", "-x509", "-nodes",
|
|
"-newkey", "rsa:2048", "-keyout", str(keydir / "dev.key"),
|
|
"-out", str(keydir / "dev.crt"), "-subj", "/CN=Buildroot FIT test",
|
|
]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
# We compile the empty dtb that will be used to store the
|
|
# public key.
|
|
cmd = [
|
|
"host/bin/dtc", "-I", "dts", "-O", "dtb", "-p", "0x1000",
|
|
"-o", "test-key.dtb", str(KEY_DTS),
|
|
]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
# We sign the image and write the public key in our key
|
|
# storage dtb.
|
|
cmd = [
|
|
"host/bin/mkimage", "-f", str(SIGNED_ITS), "-k", str(keydir),
|
|
"-K", "test-key.dtb", "-r", "signed.fit",
|
|
]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
# We check there is a signature present in the FIT image.
|
|
cmd = [
|
|
"host/bin/fdtget", "-t", "bx", "signed.fit",
|
|
"/configurations/config-1/signature-1", "value",
|
|
]
|
|
signature = infra.run_cmd_on_host(self.builddir, cmd).split()
|
|
self.assertEqual(len(signature), 256)
|
|
|
|
# We check the key is marked as required for the configuration.
|
|
cmd = [
|
|
"host/bin/fdtget", "-t", "s", "test-key.dtb",
|
|
"/signature/key-dev", "required",
|
|
]
|
|
required = infra.run_cmd_on_host(self.builddir, cmd).strip()
|
|
self.assertEqual(required, "conf")
|
|
|
|
# We actually check the signature is valid.
|
|
cmd = [
|
|
"host/bin/fit_check_sign", "-f", "signed.fit",
|
|
"-k", "test-key.dtb",
|
|
]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
# We "corrupt" the signature, by setting it to zero.
|
|
cmd = [
|
|
"host/bin/fdtput", "-t", "bx", "signed.fit",
|
|
"/configurations/config-1/signature-1", "value", "00",
|
|
]
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|
|
|
|
# We check again the siganute, and expect a failure.
|
|
cmd = [
|
|
"host/bin/fit_check_sign", "-f", "signed.fit",
|
|
"-k", "test-key.dtb",
|
|
]
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
infra.run_cmd_on_host(self.builddir, cmd)
|