diff options
Diffstat (limited to 'tools/binman')
-rw-r--r-- | tools/binman/btool/mkimage.py | 8 | ||||
-rw-r--r-- | tools/binman/control.py | 9 | ||||
-rw-r--r-- | tools/binman/entries.rst | 44 | ||||
-rw-r--r-- | tools/binman/etype/fit.py | 47 | ||||
-rw-r--r-- | tools/binman/ftest.py | 111 | ||||
-rw-r--r-- | tools/binman/test/343_fit_encrypt_data.dts | 53 | ||||
-rw-r--r-- | tools/binman/test/344_fit_encrypt_data_no_key.dts | 53 | ||||
-rw-r--r-- | tools/binman/test/345_fit_fdt_name.dts | 58 | ||||
-rw-r--r-- | tools/binman/test/aes256.bin | 1 |
9 files changed, 343 insertions, 41 deletions
diff --git a/tools/binman/btool/mkimage.py b/tools/binman/btool/mkimage.py index 78d3301bc10..3f84220fb1a 100644 --- a/tools/binman/btool/mkimage.py +++ b/tools/binman/btool/mkimage.py @@ -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) diff --git a/tools/binman/control.py b/tools/binman/control.py index 542c2b45644..e73c598298c 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -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() diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index e918162fb48..780e9817fb6 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -197,7 +197,7 @@ source files that the tool examples: To run the tool:: - $ tools/binman/fip_util.py -s /path/to/trusted-firmware-a + $ tools/binman/fip_util.py -s /path/to/arm-trusted-firmware Warning: UUID 'UUID_NON_TRUSTED_WORLD_KEY_CERT' is not mentioned in tbbr_config.c file Existing code in 'tools/binman/fip_util.py' is up-to-date @@ -862,14 +862,25 @@ The top-level 'fit' node supports the following special properties: can be provided as a directory. Each .dtb file in the directory is processed, , e.g.:: - fit,fdt-list-dir = "arch/arm/dts + fit,fdt-list-dir = "arch/arm/dts"; + + 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. + 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. + + 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 +903,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 +967,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. @@ -985,7 +1014,8 @@ same approach can of course be used for SPL images. Note that the `of-spl-remove-props` entryarg can be used to indicate additional properties to remove. It is often used to remove properties like -`clock-names` and `pinctrl-names` which are not needed in SPL builds. +`clock-names` and `pinctrl-names` which are not needed in SPL builds. This +value is automatically passed to binman by the U-Boot build. See :ref:`fdtgrep_filter` for more information. diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index b5afbda41b5..803fb66ea83 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -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: diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 156567ace77..a553ca9e564 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -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) + 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(1, TEST_FDT1_DATA) - _CheckConfig(2, 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() diff --git a/tools/binman/test/343_fit_encrypt_data.dts b/tools/binman/test/343_fit_encrypt_data.dts new file mode 100644 index 00000000000..d70de3426cf --- /dev/null +++ b/tools/binman/test/343_fit_encrypt_data.dts @@ -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"; + }; + }; + }; + }; +}; diff --git a/tools/binman/test/344_fit_encrypt_data_no_key.dts b/tools/binman/test/344_fit_encrypt_data_no_key.dts new file mode 100644 index 00000000000..d70de3426cf --- /dev/null +++ b/tools/binman/test/344_fit_encrypt_data_no_key.dts @@ -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"; + }; + }; + }; + }; +}; diff --git a/tools/binman/test/345_fit_fdt_name.dts b/tools/binman/test/345_fit_fdt_name.dts new file mode 100644 index 00000000000..631a8e5f59b --- /dev/null +++ b/tools/binman/test/345_fit_fdt_name.dts @@ -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 { + }; + }; +}; diff --git a/tools/binman/test/aes256.bin b/tools/binman/test/aes256.bin new file mode 100644 index 00000000000..09b8bf6254a --- /dev/null +++ b/tools/binman/test/aes256.bin @@ -0,0 +1 @@ +1234567890abcdefghijklmnopqrstuv
\ No newline at end of file |