binman: implement signing FIT images during image build

The patch implement new property 'fit,sign' that can be declared
at the top-level 'fit' node. If that option is declared, fit tryies
to detect private keys directory among binman include directories.
That directory than passed to mkimage using '-k' flag and that enable
signing of FIT.

Signed-off-by: Alexander Kochetkov <al.kochet@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Renumbered files, moved new tests to end:
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Alexander Kochetkov
2024-09-16 11:24:46 +03:00
committed by Simon Glass
parent 9e81f13dbb
commit 133c000ca3
8 changed files with 442 additions and 1 deletions

View File

@@ -22,7 +22,7 @@ class Bintoolmkimage(bintool.Bintool):
# pylint: disable=R0913
def run(self, reset_timestamp=False, output_fname=None, external=False,
pad=None, align=None):
pad=None, align=None, priv_keys_dir=None):
"""Run mkimage
Args:
@@ -34,6 +34,7 @@ class Bintoolmkimage(bintool.Bintool):
other things to be easily added later, if required, such as
signatures
align: Bytes to use for alignment of the FIT and its external data
priv_keys_dir: Path to directory containing private keys
version: True to get the mkimage version
"""
args = []
@@ -45,6 +46,8 @@ class Bintoolmkimage(bintool.Bintool):
args += ['-B', f'{align:x}']
if reset_timestamp:
args.append('-t')
if priv_keys_dir:
args += ['-k', f'{priv_keys_dir}']
if output_fname:
args += ['-F', output_fname]
return self.run_cmd(*args)

View File

@@ -864,6 +864,13 @@ The top-level 'fit' node supports the following special properties:
fit,fdt-list-dir = "arch/arm/dts
fit,sign
Enable signing FIT images via mkimage as described in
verified-boot.rst. If the property is found, the private keys path is
detected among binman include directories and passed to mkimage via
-k flag. All the keys required for signing FIT must be available at
time of signing and must be located in single include directory.
Substitutions
~~~~~~~~~~~~~

View File

@@ -9,6 +9,7 @@ import glob
import os
import libfdt
import os
from binman.entry import Entry, EntryArg
from binman.etype.section import Entry_section
@@ -101,6 +102,14 @@ class Entry_fit(Entry_section):
In this case the input directories are ignored and all devicetree
files must be in that directory.
fit,sign
Enable signing FIT images via mkimage as described in
verified-boot.rst. If the property is found, the private keys path
is detected among binman include directories and passed to mkimage
via -k flag. All the keys required for signing FIT must be
available at time of signing and must be located in single include
directory.
Substitutions
~~~~~~~~~~~~~
@@ -426,6 +435,7 @@ class Entry_fit(Entry_section):
self._remove_props = props.split()
self.mkimage = None
self.fdtgrep = None
self._fit_sign = None
def ReadNode(self):
super().ReadNode()
@@ -508,6 +518,45 @@ class Entry_fit(Entry_section):
# are removed from self._entries later.
self._priv_entries = dict(self._entries)
def _get_priv_keys_dir(self, data):
"""Detect private keys path among binman include directories
Args:
data: FIT image in binary format
Returns:
str: Single path containing all private keys found or None
Raises:
ValueError: Filename 'rsa2048.key' not found in input path
ValueError: Multiple key paths found
"""
def _find_keys_dir(node):
for subnode in node.subnodes:
if subnode.name.startswith('signature'):
if subnode.props.get('key-name-hint') is None:
continue
hint = subnode.props['key-name-hint'].value
name = tools.get_input_filename(f"{hint}.key")
path = os.path.dirname(name)
if path not in paths:
paths.append(path)
else:
_find_keys_dir(subnode)
return None
fdt = Fdt.FromData(data)
fdt.Scan()
paths = []
_find_keys_dir(fdt.GetRoot())
if len(paths) > 1:
self.Raise("multiple key paths found (%s)" % ",".join(paths))
return paths[0] if len(paths) else None
def BuildSectionData(self, required):
"""Build FIT entry contents
@@ -538,6 +587,8 @@ class Entry_fit(Entry_section):
align = self._fit_props.get('fit,align')
if align is not None:
args.update({'align': fdt_util.fdt32_to_cpu(align.value)})
if self._fit_props.get('fit,sign') is not None:
args.update({'priv_keys_dir': self._get_priv_keys_dir(data)})
if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
**args) is None:
if not self.GetAllowMissing():

View File

@@ -7804,6 +7804,101 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
"""Test that binman can produce an iMX8 image"""
self._DoTestFile('339_nxp_imx8.dts')
def testFitSignSimple(self):
"""Test that image with FIT and signature nodes can be signed"""
if not elf.ELF_TOOLS:
self.skipTest('Python elftools not available')
entry_args = {
'of-list': 'test-fdt1',
'default-dt': 'test-fdt1',
'atf-bl31-path': 'bl31.elf',
}
data = tools.read_file(self.TestFile("340_rsa2048.key"))
self._MakeInputFile("keys/rsa2048.key", data)
test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
keys_subdir = os.path.join(self._indir, "keys")
data = self._DoReadFileDtb(
'340_fit_signature.dts',
entry_args=entry_args,
extra_indirs=[test_subdir, keys_subdir])[0]
dtb = fdt.Fdt.FromData(data)
dtb.Scan()
conf = dtb.GetNode('/configurations/conf-uboot-1')
self.assertIsNotNone(conf)
signature = conf.FindNode('signature')
self.assertIsNotNone(signature)
self.assertIsNotNone(signature.props.get('value'))
images = dtb.GetNode('/images')
self.assertIsNotNone(images)
for subnode in images.subnodes:
signature = subnode.FindNode('signature')
self.assertIsNotNone(signature)
self.assertIsNotNone(signature.props.get('value'))
def testFitSignKeyNotFound(self):
"""Test that missing keys raise an error"""
if not elf.ELF_TOOLS:
self.skipTest('Python elftools not available')
entry_args = {
'of-list': 'test-fdt1',
'default-dt': 'test-fdt1',
'atf-bl31-path': 'bl31.elf',
}
test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
with self.assertRaises(ValueError) as e:
self._DoReadFileDtb(
'340_fit_signature.dts',
entry_args=entry_args,
extra_indirs=[test_subdir])[0]
self.assertIn(
'Filename \'rsa2048.key\' not found in input path',
str(e.exception))
def testFitSignMultipleKeyPaths(self):
"""Test that keys found in multiple paths raise an error"""
if not elf.ELF_TOOLS:
self.skipTest('Python elftools not available')
entry_args = {
'of-list': 'test-fdt1',
'default-dt': 'test-fdt1',
'atf-bl31-path': 'bl31.elf',
}
data = tools.read_file(self.TestFile("340_rsa2048.key"))
self._MakeInputFile("keys1/rsa2048.key", data)
data = tools.read_file(self.TestFile("340_rsa2048.key"))
self._MakeInputFile("keys2/conf-rsa2048.key", data)
test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
keys_subdir1 = os.path.join(self._indir, "keys1")
keys_subdir2 = os.path.join(self._indir, "keys2")
with self.assertRaises(ValueError) as e:
self._DoReadFileDtb(
'341_fit_signature.dts',
entry_args=entry_args,
extra_indirs=[test_subdir, keys_subdir1, keys_subdir2])[0]
self.assertIn(
'Node \'/binman/fit\': multiple key paths found',
str(e.exception))
def testFitSignNoSingatureNodes(self):
"""Test that fit,sign doens't raise error if no signature nodes found"""
if not elf.ELF_TOOLS:
self.skipTest('Python elftools not available')
entry_args = {
'of-list': 'test-fdt1',
'default-dt': 'test-fdt1',
'atf-bl31-path': 'bl31.elf',
}
test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
self._DoReadFileDtb(
'342_fit_signature.dts',
entry_args=entry_args,
extra_indirs=[test_subdir])[0]
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
fit {
description = "test desc";
#address-cells = <1>;
fit,fdt-list = "of-list";
fit,sign;
images {
u-boot {
description = "test u-boot";
type = "standalone";
arch = "arm64";
os = "u-boot";
compression = "none";
load = <0x00000000>;
entry = <0x00000000>;
u-boot-nodtb {
};
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
@atf-SEQ {
fit,operation = "split-elf";
description = "test tf-a";
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
fit,load;
fit,entry;
fit,data;
atf-bl31 {
};
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
@fdt-SEQ {
description = "test fdt";
type = "flat_dt";
compression = "none";
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
};
configurations {
default = "@conf-uboot-DEFAULT-SEQ";
@conf-uboot-SEQ {
description = "uboot config";
fdt = "fdt-SEQ";
fit,firmware = "u-boot";
fit,loadables;
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
sign-images = "firmware", "loadables", "fdt";
};
};
};
};
};
};

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDVUiT2JAF8Ajcx
3XTB5qdGxuPMVFcXKJH+4L66oSt4YUBGi1bClo80U2azu08BTzk2Jzv6hez/mvzL
hBvL3WnPwMl5vdOxb1kvUQyKLSw2bkM8VB0X1jGsKsKjzArg/aI8RknfiaSc5jua
2lqwUFwv2RMF8jvIMN/1GnTLdECeMFVgVFSFkzIocISAHGPoGUOxTf8xK7o0x4RX
NzB+95RtIqTQ5Az/KPVCOcQR5ETrUBXHF1I0rYjJjHHO4dUxxfDqFabt60EzQ/R2
oZu58C4y0TrRI98g4hVPBYapildWjaNQm1Exa4ZaSDVl01OXsFW9Dm80PqfW4tTH
Cm4nuCq5AgMBAAECggEBAIoG5b2SHJfFwzrzpQmVmeTU6i6a3+MvMBAwEZkmkb8J
hhJfNFsiGjTsRgbDiuI5BbbBejCmmWvmN+3jZCzr7fwsLPEl36TufFF+atO5WOM7
Qyv07QIwaOGSpXBgpSVhV6kSfdgy8p1G54hSAt4UkSGwnnt5ei8VWMP6Q1oltW3k
f9DQ/ar4UEVa4jlJU3xqchcUTiKBKSH6pMC/Fqlq8x5JTLmk1Yb6C2UNcgJYez1u
sHkdCA0FG3rFPrpFoQ1LUjMj1uEYNAxM3jOxE7Uvmk4yo9WpQDY7cRb2+Th9YY8a
IKQ2s81Yg2TmkGzr8f5nrZz3WbAmQhQgsKbwlo6snjUCgYEA7kBOt0JlU7bJTfOr
9s51g2VUfIH9lDS2Eh8MY+Bt6Y0Kdw/UK4HR8ZlN/nn0bHuHkc12K8lXEsQpgIEW
DaqHytZJHqFs2egzKu/IvQYZ2WXEMj47LZQxEDHO9gtjE+5qCW9yJGqxW9BJKPVD
F4spus4NqC+yD5OHM+6ESUtL/wMCgYEA5TZj6OHmECeh3efrwHqjDcjrqQbOTozU
KPCNCY3Pv4Cg4xas/L93TE2CY6HJTr6mwEMUM+M4Ujjj15VCmSDQ/YYcGau1jo+f
XdphOEENrPwoe9ATWIyBpT/wDrEz3L6JbE9dWMYY8vKYESt3qhVqDlbpmnYl8Jm+
O3r5Cy2NlJMCgYEAyqzsCZuy5QcesnByvm8dqpxdxdkzJYu9wyakfKZj+gUgfO57
OFOkjFk07yFB27MuPctCFredmfpDr+ygHRoPkG7AHw2Fss2EEaeP5bU18ilPQMqN
vxVMs5EblVVUgJUVoVcsC2yz2f4S7oPOAk5BPoehOIzydauznWrvIAas7I8CgYBr
CFHxLoNq6cbZQ3JACERZrIf2/vmZjoOHtoR1gKYRK7R1NmKDB7lihRMtCSBix/4/
61Lkw+bJ5kzmn4lgzgUpTdWTWy5FquVlQxOA3EfRjlItNsXB5KKpksi7Y53vJ34u
eIUDbkW6NPQzmFOhtaw3k/gzq5Yd2v0M82iWAqiJRwKBgQCl2+e2cjISK31QhKTC
puhwQ0/YuC3zlwMXQgB3nPw8b9RlaDTMrRBCIUFIrrX11tHswGWpyVsxW2AvZ3Zm
jsWpwGkUdpRdXJBhSaisV/PA+x3kYhpibzEI8FrzhU69zNROCb8CTkN4WcdBdq6J
PUh/jRtKoE79qrlnIlNvFoz2gQ==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
fit {
description = "test desc";
#address-cells = <1>;
fit,fdt-list = "of-list";
fit,sign;
images {
u-boot {
description = "test u-boot";
type = "standalone";
arch = "arm64";
os = "u-boot";
compression = "none";
load = <0x00000000>;
entry = <0x00000000>;
u-boot-nodtb {
};
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
@atf-SEQ {
fit,operation = "split-elf";
description = "test tf-a";
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
fit,load;
fit,entry;
fit,data;
atf-bl31 {
};
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
@fdt-SEQ {
description = "test fdt";
type = "flat_dt";
compression = "none";
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "rsa2048";
};
};
};
configurations {
default = "@conf-uboot-DEFAULT-SEQ";
@conf-uboot-SEQ {
description = "uboot config";
fdt = "fdt-SEQ";
fit,firmware = "u-boot";
fit,loadables;
hash {
algo = "sha256";
};
signature {
algo = "sha256,rsa2048";
key-name-hint = "conf-rsa2048";
sign-images = "firmware", "loadables", "fdt";
};
};
};
};
};
};

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
fit {
description = "test desc";
#address-cells = <1>;
fit,fdt-list = "of-list";
fit,sign;
images {
u-boot {
description = "test u-boot";
type = "standalone";
arch = "arm64";
os = "u-boot";
compression = "none";
load = <0x00000000>;
entry = <0x00000000>;
u-boot-nodtb {
};
};
@atf-SEQ {
fit,operation = "split-elf";
description = "test tf-a";
type = "firmware";
arch = "arm64";
os = "arm-trusted-firmware";
compression = "none";
fit,load;
fit,entry;
fit,data;
atf-bl31 {
};
};
@fdt-SEQ {
description = "test fdt";
type = "flat_dt";
compression = "none";
};
};
configurations {
default = "@conf-uboot-DEFAULT-SEQ";
@conf-uboot-SEQ {
description = "uboot config";
fdt = "fdt-SEQ";
fit,firmware = "u-boot";
fit,loadables;
};
};
};
};
};