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

View File

@@ -9,6 +9,7 @@ import glob
import os import os
import libfdt import libfdt
import os
from binman.entry import Entry, EntryArg from binman.entry import Entry, EntryArg
from binman.etype.section import Entry_section 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 In this case the input directories are ignored and all devicetree
files must be in that directory. 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 Substitutions
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@@ -426,6 +435,7 @@ class Entry_fit(Entry_section):
self._remove_props = props.split() self._remove_props = props.split()
self.mkimage = None self.mkimage = None
self.fdtgrep = None self.fdtgrep = None
self._fit_sign = None
def ReadNode(self): def ReadNode(self):
super().ReadNode() super().ReadNode()
@@ -508,6 +518,45 @@ class Entry_fit(Entry_section):
# are removed from self._entries later. # are removed from self._entries later.
self._priv_entries = dict(self._entries) 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): def BuildSectionData(self, required):
"""Build FIT entry contents """Build FIT entry contents
@@ -538,6 +587,8 @@ class Entry_fit(Entry_section):
align = self._fit_props.get('fit,align') align = self._fit_props.get('fit,align')
if align is not None: if align is not None:
args.update({'align': fdt_util.fdt32_to_cpu(align.value)}) 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, if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
**args) is None: **args) is None:
if not self.GetAllowMissing(): 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""" """Test that binman can produce an iMX8 image"""
self._DoTestFile('339_nxp_imx8.dts') 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__": if __name__ == "__main__":
unittest.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;
};
};
};
};
};