Merge branch '2024-12-19-assorted-tooling-updates' into next

This brings in assortment of updates to our python tooling, from Paul
HENRYS <paul.henrys_ext@softathome.com>
This commit is contained in:
Tom Rini
2024-12-19 09:10:48 -06:00
14 changed files with 374 additions and 36 deletions

View File

@@ -1788,6 +1788,21 @@ struct cipher_algo {
const unsigned char *data, int data_len,
unsigned char **cipher, int *cipher_len);
/**
* add_cipher_data() - Add cipher data to the FIT and device tree
*
* This is used to add the ciphered data to the FIT and other cipher
* related information (key and initialization vector) to a device tree.
*
* @info: Pointer to image cipher information.
* @keydest: Pointer to a device tree where the key and IV can be
* stored. keydest can be NULL when the key is retrieved at
* runtime by another mean.
* @fit: Pointer to the FIT image.
* @node_noffset: Offset where the cipher information are stored in the
* FIT.
* return: 0 on success, a negative error code otherwise.
*/
int (*add_cipher_data)(struct image_cipher_info *info,
void *keydest, void *fit, int node_noffset);

View File

@@ -84,6 +84,13 @@ int image_aes_add_cipher_data(struct image_cipher_info *info, void *keydest,
char name[128];
int ret = 0;
if (!keydest && !info->ivname) {
/* At least, store the IV in the FIT image */
ret = fdt_setprop(fit, node_noffset, "iv",
info->iv, info->cipher->iv_len);
goto done;
}
/* Either create or overwrite the named cipher node */
parent = fdt_subnode_offset(keydest, 0, FIT_CIPHER_NODENAME);
if (parent == -FDT_ERR_NOTFOUND) {

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, priv_keys_dir=None):
pad=None, align=None, keys_dir=None):
"""Run mkimage
Args:
@@ -34,7 +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
keys_dir: Path to directory containing private and encryption keys
version: True to get the mkimage version
"""
args = []
@@ -46,8 +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 keys_dir:
args += ['-k', f'{keys_dir}']
if output_fname:
args += ['-F', output_fname]
return self.run_cmd(*args)

View File

@@ -526,7 +526,7 @@ def _RemoveTemplates(parent):
if node.name.startswith('template'):
node.Delete()
def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded, indir):
"""Prepare the images to be processed and select the device tree
This function:
@@ -543,6 +543,7 @@ def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
use_expanded: True to use expanded versions of entries, if available.
So if 'u-boot' is called for, we use 'u-boot-expanded' instead. This
is needed if update_fdt is True (although tests may disable it)
indir: List of directories where input files can be found
Returns:
OrderedDict of images:
@@ -558,7 +559,9 @@ def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
# Get the device tree ready by compiling it and copying the compiled
# output into a file in our output directly. Then scan it for use
# in binman.
dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
if indir is None:
indir = []
dtb_fname = fdt_util.EnsureCompiled(dtb_fname, indir=indir)
fname = tools.get_output_filename('u-boot.dtb.out')
tools.write_file(fname, tools.read_file(dtb_fname))
dtb = fdt.FdtScan(fname)
@@ -846,7 +849,7 @@ def Binman(args):
state.SetThreads(args.threads)
images = PrepareImagesAndDtbs(dtb_fname, args.image,
args.update_fdt, use_expanded)
args.update_fdt, use_expanded, args.indir)
if args.test_section_timeout:
# Set the first image to timeout, used in testThreadTimeout()

View File

@@ -871,6 +871,13 @@ The top-level 'fit' node supports the following special properties:
-k flag. All the keys required for signing FIT must be available at
time of signing and must be located in single include directory.
fit,encrypt
Enable data encryption in FIT images via mkimage. If the property
is found, the keys path is detected among binman include
directories and passed to mkimage via -k flag. All the keys
required for encrypting the FIT must be available at the time of
encrypting and must be located in a single include directory.
Substitutions
~~~~~~~~~~~~~
@@ -892,6 +899,9 @@ DEFAULT-SEQ:
Sequence number of the default fdt, as provided by the 'default-dt'
entry argument
DEFAULT-NAME:
Name of the default fdt, as provided by the 'default-dt' entry argument
Available operations
~~~~~~~~~~~~~~~~~~~~
@@ -953,6 +963,21 @@ You can create config nodes in a similar way::
This tells binman to create nodes `config-1` and `config-2`, i.e. a config
for each of your two files.
It is also possible to use NAME in the node names so that the FDT files name
will be used instead of the sequence number. This can be useful to identify
easily at runtime in U-Boot, the config to be used::
configurations {
default = "@config-DEFAULT-NAME";
@config-NAME {
description = "NAME";
firmware = "atf";
loadables = "uboot";
fdt = "fdt-NAME";
fit,compatible; // optional
};
};
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.

View File

@@ -110,6 +110,13 @@ class Entry_fit(Entry_section):
available at time of signing and must be located in single include
directory.
fit,encrypt
Enable data encryption in FIT images via mkimage. If the property
is found, the keys path is detected among binman include
directories and passed to mkimage via -k flag. All the keys
required for encrypting the FIT must be available at the time of
encrypting and must be located in a single include directory.
Substitutions
~~~~~~~~~~~~~
@@ -131,6 +138,9 @@ class Entry_fit(Entry_section):
Sequence number of the default fdt, as provided by the 'default-dt'
entry argument
DEFAULT-NAME:
Name of the default fdt, as provided by the 'default-dt' entry argument
Available operations
~~~~~~~~~~~~~~~~~~~~
@@ -192,6 +202,21 @@ class Entry_fit(Entry_section):
This tells binman to create nodes `config-1` and `config-2`, i.e. a config
for each of your two files.
It is also possible to use NAME in the node names so that the FDT files name
will be used instead of the sequence number. This can be useful to identify
easily at runtime in U-Boot, the config to be used::
configurations {
default = "@config-DEFAULT-NAME";
@config-NAME {
description = "NAME";
firmware = "atf";
loadables = "uboot";
fdt = "fdt-NAME";
fit,compatible; // optional
};
};
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
@@ -452,6 +477,8 @@ class Entry_fit(Entry_section):
self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir')
if self._fdt_dir:
indir = tools.get_input_filename(self._fdt_dir)
if indir:
tools.append_input_dirs(indir)
fdts = glob.glob('*.dtb', root_dir=indir)
self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)]
else:
@@ -518,14 +545,14 @@ 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
def _get_keys_dir(self, data):
"""Detect private and encryption keys path among binman include directories
Args:
data: FIT image in binary format
Returns:
str: Single path containing all private keys found or None
str: Single path containing all keys found or None
Raises:
ValueError: Filename 'rsa2048.key' not found in input path
@@ -533,11 +560,14 @@ class Entry_fit(Entry_section):
"""
def _find_keys_dir(node):
for subnode in node.subnodes:
if subnode.name.startswith('signature'):
if (subnode.name.startswith('signature') or
subnode.name.startswith('cipher')):
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")
name = tools.get_input_filename(
f"{hint}.key" if subnode.name.startswith('signature')
else f"{hint}.bin")
path = os.path.dirname(name)
if path not in paths:
paths.append(path)
@@ -587,8 +617,9 @@ 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._fit_props.get('fit,sign') is not None or
self._fit_props.get('fit,encrypt') is not None):
args.update({'keys_dir': self._get_keys_dir(data)})
if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
**args) is None:
if not self.GetAllowMissing():
@@ -663,6 +694,7 @@ class Entry_fit(Entry_section):
f"not found in fdt list: {', '.join(self._fdts)}")
seq = self._fdts.index(default_dt)
val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
val = val.replace('DEFAULT-NAME', self._fit_default_dt)
fsw.property_string(pname, val)
return
elif pname.startswith('fit,'):
@@ -729,6 +761,7 @@ class Entry_fit(Entry_section):
# Generate nodes for each FDT
for seq, fdt_fname in enumerate(self._fdts):
node_name = node.name[1:].replace('SEQ', str(seq + 1))
node_name = node_name.replace('NAME', fdt_fname)
if self._fdt_dir:
fname = os.path.join(self._fdt_dir, fdt_fname + '.dtb')
else:

View File

@@ -4233,56 +4233,69 @@ class TestFunctional(unittest.TestCase):
self.assertEqual(SCP_DATA, data[:len(SCP_DATA)])
def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True,
default_dt=None):
default_dt=None, use_seq_num=True):
"""Check an image with an FIT with multiple FDT images"""
def _CheckFdt(seq, expected_data):
def _CheckFdt(val, expected_data):
"""Check the FDT nodes
Args:
seq: Sequence number to check (0 or 1)
val: Sequence number to check (0 or 1) or fdt name
expected_data: Expected contents of 'data' property
"""
name = 'fdt-%d' % seq
name = 'fdt-%s' % val
fnode = dtb.GetNode('/images/%s' % name)
self.assertIsNotNone(fnode)
self.assertEqual({'description','type', 'compression', 'data'},
set(fnode.props.keys()))
self.assertEqual(expected_data, fnode.props['data'].bytes)
self.assertEqual('fdt-test-fdt%d.dtb' % seq,
fnode.props['description'].value)
description = (
'fdt-test-fdt%s.dtb' % val if len(val) == 1 else
'fdt-%s.dtb' % val
)
self.assertEqual(description, fnode.props['description'].value)
self.assertEqual(fnode.subnodes[0].name, 'hash')
def _CheckConfig(seq, expected_data):
def _CheckConfig(val, expected_data):
"""Check the configuration nodes
Args:
seq: Sequence number to check (0 or 1)
val: Sequence number to check (0 or 1) or fdt name
expected_data: Expected contents of 'data' property
"""
cnode = dtb.GetNode('/configurations')
self.assertIn('default', cnode.props)
self.assertEqual('config-2', cnode.props['default'].value)
default = (
'config-2' if len(val) == 1 else
'config-test-fdt2'
)
self.assertEqual(default, cnode.props['default'].value)
name = 'config-%d' % seq
name = 'config-%s' % val
fnode = dtb.GetNode('/configurations/%s' % name)
self.assertIsNotNone(fnode)
self.assertEqual({'description','firmware', 'loadables', 'fdt'},
set(fnode.props.keys()))
self.assertEqual('conf-test-fdt%d.dtb' % seq,
fnode.props['description'].value)
self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value)
description = (
'conf-test-fdt%s.dtb' % val if len(val) == 1 else
'conf-%s.dtb' % val
)
self.assertEqual(description, fnode.props['description'].value)
self.assertEqual('fdt-%s' % val, fnode.props['fdt'].value)
entry_args = {
'default-dt': 'test-fdt2',
}
extra_indirs = None
if use_fdt_list:
entry_args['of-list'] = 'test-fdt1 test-fdt2'
if default_dt:
entry_args['default-dt'] = default_dt
if use_fdt_list:
extra_indirs = [os.path.join(self._indir, TEST_FDT_SUBDIR)]
data = self._DoReadFileDtb(
dts,
entry_args=entry_args,
extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
extra_indirs=extra_indirs)[0]
self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
@@ -4291,13 +4304,22 @@ class TestFunctional(unittest.TestCase):
fnode = dtb.GetNode('/images/kernel')
self.assertIn('data', fnode.props)
# Check all the properties in fdt-1 and fdt-2
_CheckFdt(1, TEST_FDT1_DATA)
_CheckFdt(2, TEST_FDT2_DATA)
if use_seq_num == True:
# Check all the properties in fdt-1 and fdt-2
_CheckFdt('1', TEST_FDT1_DATA)
_CheckFdt('2', TEST_FDT2_DATA)
# Check configurations
_CheckConfig(1, TEST_FDT1_DATA)
_CheckConfig(2, TEST_FDT2_DATA)
# Check configurations
_CheckConfig('1', TEST_FDT1_DATA)
_CheckConfig('2', TEST_FDT2_DATA)
else:
# Check all the properties in fdt-1 and fdt-2
_CheckFdt('test-fdt1', TEST_FDT1_DATA)
_CheckFdt('test-fdt2', TEST_FDT2_DATA)
# Check configurations
_CheckConfig('test-fdt1', TEST_FDT1_DATA)
_CheckConfig('test-fdt2', TEST_FDT2_DATA)
def testFitFdt(self):
"""Test an image with an FIT with multiple FDT images"""
@@ -7900,5 +7922,54 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
extra_indirs=[test_subdir])[0]
def testSimpleFitEncryptedData(self):
"""Test an image with a FIT containing data to be encrypted"""
data = tools.read_file(self.TestFile("aes256.bin"))
self._MakeInputFile("keys/aes256.bin", data)
keys_subdir = os.path.join(self._indir, "keys")
data = self._DoReadFileDtb(
'343_fit_encrypt_data.dts',
extra_indirs=[keys_subdir])[0]
fit = fdt.Fdt.FromData(data)
fit.Scan()
# Extract the encrypted data and the Initialization Vector from the FIT
node = fit.GetNode('/images/u-boot')
subnode = fit.GetNode('/images/u-boot/cipher')
data_size_unciphered = int.from_bytes(fit.GetProps(node)['data-size-unciphered'].bytes,
byteorder='big')
self.assertEqual(data_size_unciphered, len(U_BOOT_NODTB_DATA))
# Retrieve the key name from the FIT removing any null byte
key_name = fit.GetProps(subnode)['key-name-hint'].bytes.replace(b'\x00', b'')
with open(self.TestFile(key_name.decode('ascii') + '.bin'), 'rb') as file:
key = file.read()
iv = fit.GetProps(subnode)['iv'].bytes.hex()
enc_data = fit.GetProps(node)['data'].bytes
outdir = tools.get_output_dir()
enc_data_file = os.path.join(outdir, 'encrypted_data.bin')
tools.write_file(enc_data_file, enc_data)
data_file = os.path.join(outdir, 'data.bin')
# Decrypt the encrypted data from the FIT and compare the data
tools.run('openssl', 'enc', '-aes-256-cbc', '-nosalt', '-d', '-in',
enc_data_file, '-out', data_file, '-K', key.hex(), '-iv', iv)
with open(data_file, 'r') as file:
dec_data = file.read()
self.assertEqual(U_BOOT_NODTB_DATA, dec_data.encode('ascii'))
def testSimpleFitEncryptedDataMissingKey(self):
"""Test an image with a FIT containing data to be encrypted but with a missing key"""
with self.assertRaises(ValueError) as e:
self._DoReadFile('344_fit_encrypt_data_no_key.dts')
self.assertIn("Filename 'aes256.bin' not found in input path", str(e.exception))
def testFitFdtName(self):
"""Test an image with an FIT with multiple FDT images using NAME"""
self.CheckFitFdt('345_fit_fdt_name.dts', use_seq_num=False)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
fit {
fit,encrypt;
description = "Test a FIT with encrypted data";
#address-cells = <1>;
images {
u-boot {
description = "U-Boot";
type = "firmware";
arch = "arm64";
os = "U-Boot";
compression = "none";
load = <00000000>;
entry = <00000000>;
cipher {
algo = "aes256";
key-name-hint = "aes256";
};
u-boot-nodtb {
};
};
fdt-1 {
description = "Flattened Device Tree blob";
type = "flat_dt";
arch = "arm64";
compression = "none";
cipher {
algo = "aes256";
key-name-hint = "aes256";
};
};
};
configurations {
default = "conf-1";
conf-1 {
description = "Boot U-Boot with FDT blob";
firmware = "u-boot";
fdt = "fdt-1";
};
};
};
};
};

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
fit {
fit,encrypt;
description = "Test a FIT with encrypted data";
#address-cells = <1>;
images {
u-boot {
description = "U-Boot";
type = "firmware";
arch = "arm64";
os = "U-Boot";
compression = "none";
load = <00000000>;
entry = <00000000>;
cipher {
algo = "aes256";
key-name-hint = "aes256";
};
u-boot-nodtb {
};
};
fdt-1 {
description = "Flattened Device Tree blob";
type = "flat_dt";
arch = "arm64";
compression = "none";
cipher {
algo = "aes256";
key-name-hint = "aes256";
};
};
};
configurations {
default = "conf-1";
conf-1 {
description = "Boot U-Boot with FDT blob";
firmware = "u-boot";
fdt = "fdt-1";
};
};
};
};
};

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
u-boot {
};
fit {
description = "test-desc";
#address-cells = <1>;
fit,fdt-list = "of-list";
images {
kernel {
description = "Vanilla Linux kernel";
type = "kernel";
arch = "ppc";
os = "linux";
compression = "gzip";
load = <00000000>;
entry = <00000000>;
hash-1 {
algo = "crc32";
};
hash-2 {
algo = "sha1";
};
u-boot {
};
};
@fdt-NAME {
description = "fdt-NAME.dtb";
type = "flat_dt";
compression = "none";
hash {
algo = "sha256";
};
};
};
configurations {
default = "@config-DEFAULT-NAME";
@config-NAME {
description = "conf-NAME.dtb";
firmware = "uboot";
loadables = "atf";
fdt = "fdt-NAME";
};
};
};
u-boot-nodtb {
};
};
};

View File

@@ -0,0 +1 @@
1234567890abcdefghijklmnopqrstuv

View File

@@ -55,7 +55,7 @@ def fdt_cells_to_cpu(val, cells):
out = out << 32 | fdt32_to_cpu(val[1])
return out
def EnsureCompiled(fname, tmpdir=None, capture_stderr=False):
def EnsureCompiled(fname, tmpdir=None, capture_stderr=False, indir=None):
"""Compile an fdt .dts source file into a .dtb binary blob if needed.
Args:
@@ -63,6 +63,7 @@ def EnsureCompiled(fname, tmpdir=None, capture_stderr=False):
left alone
tmpdir: Temporary directory for output files, or None to use the
tools-module output directory
indir: List of directories where input files can be found
Returns:
Filename of resulting .dtb file
@@ -79,6 +80,8 @@ def EnsureCompiled(fname, tmpdir=None, capture_stderr=False):
dtb_output = tools.get_output_filename('source.dtb')
search_paths = [os.path.join(os.getcwd(), 'include')]
if indir is not None:
search_paths += indir
root, _ = os.path.splitext(fname)
cc, args = tools.get_target_compile_tool('cc')
args += ['-E', '-P', '-x', 'assembler-with-cpp', '-D__ASSEMBLY__']

View File

@@ -535,7 +535,7 @@ fit_image_process_cipher(const char *keydir, void *keydest, void *fit,
* size values
* And, if needed, write the iv in the FIT file
*/
if (keydest) {
if (keydest || (!keydest && !info.ivname)) {
ret = info.cipher->add_cipher_data(&info, keydest, fit, node_noffset);
if (ret) {
fprintf(stderr,

View File

@@ -123,6 +123,22 @@ def set_input_dirs(dirname):
indir = dirname
tout.debug("Using input directories %s" % indir)
def append_input_dirs(dirname):
"""Append a list of input directories to the current list of input
directories
Args:
dirname: a list of paths to input directories to use for obtaining
files needed by binman to place in the image.
"""
global indir
for dir in dirname:
if dirname not in indir:
indir.append(dirname)
tout.debug("Updated input directories %s" % indir)
def get_input_filename(fname, allow_missing=False):
"""Return a filename for use as input.