Hi Philippe, On Fri, 25 Feb 2022 at 07:58, Philippe Reynes <philippe.rey...@softathome.com> wrote: > > Adds the support of the pre-load header with the image signature > to binman. > > Signed-off-by: Philippe Reynes <philippe.rey...@softathome.com> > --- > tools/binman/etype/pre_load.py | 165 ++++++++++++++++++ > tools/binman/ftest.py | 45 +++++ > tools/binman/test/225_dev.key | 28 +++ > tools/binman/test/225_pre_load.dts | 22 +++ > tools/binman/test/226_pre_load_pkcs.dts | 23 +++ > tools/binman/test/227_pre_load_pss.dts | 23 +++ > .../test/228_pre_load_invalid_padding.dts | 23 +++ > .../binman/test/229_pre_load_invalid_sha.dts | 23 +++ > .../binman/test/230_pre_load_invalid_algo.dts | 23 +++ > .../binman/test/231_pre_load_invalid_key.dts | 23 +++ > 10 files changed, 398 insertions(+) > create mode 100644 tools/binman/etype/pre_load.py > create mode 100644 tools/binman/test/225_dev.key > create mode 100644 tools/binman/test/225_pre_load.dts > create mode 100644 tools/binman/test/226_pre_load_pkcs.dts > create mode 100644 tools/binman/test/227_pre_load_pss.dts > create mode 100644 tools/binman/test/228_pre_load_invalid_padding.dts > create mode 100644 tools/binman/test/229_pre_load_invalid_sha.dts > create mode 100644 tools/binman/test/230_pre_load_invalid_algo.dts > create mode 100644 tools/binman/test/231_pre_load_invalid_key.dts
Reviewed-by: Simon Glass <s...@chromium.org> But lots of nits below sorry > > diff --git a/tools/binman/etype/pre_load.py b/tools/binman/etype/pre_load.py > new file mode 100644 > index 0000000000..2af2857404 > --- /dev/null > +++ b/tools/binman/etype/pre_load.py > @@ -0,0 +1,165 @@ > +# SPDX-License-Identifier: GPL-2.0+ > +# Copyright (c) 2022 Softathome > +# Written by Philippe Reynes <philippe.rey...@softathome.com> > +# > +# Entry-type for the global header > +# > + > +import struct > +from dtoc import fdt_util > +from patman import tools > + > +from binman.entry import Entry > +from binman.etype.collection import Entry_collection > +from binman.entry import EntryArg > + > +from Cryptodome.Hash import SHA256, SHA384, SHA512 > +from Cryptodome.PublicKey import RSA > +from Cryptodome.Signature import pkcs1_15 > +from Cryptodome.Signature import pss > + > +PRE_LOAD_MAGIC = b'UBSH' > + > +RSAS = { > + 'rsa1024': 1024 / 8, > + 'rsa2048': 2048 / 8, > + 'rsa4096': 4096 / 8 > +} > + > +SHAS = { > + 'sha256': SHA256, > + 'sha384': SHA384, > + 'sha512': SHA512 > +} > + > +class Entry_pre_load(Entry_collection): > + """Pre load image header Run binman entry-docs >tools/binman/entries.rst to regen that file > + > + Properties / Entry arguments: > + - key-path: Path of the directory that store key (provided by the > environment variable KEY_PATH) > + - content: List of phandles to entries to sign > + - algo-name: Hash and signature algo to use for the signature > + - padding-name: Name of the padding (pkcs-1.5 or pss) > + - key-name: Filename of the private key to sign > + - header-size: Total size of the header > + - version: Version of the header > + > + This entry create a pre-load header that contain a global creates contains > + image signature. > + > + For example, this creates an image with a pre-load header and a binary:: > + > + binman { > + image2 { > + filename = "sandbox.bin"; > + > + pre-load { > + content = <&image>; > + algo-name = "sha256,rsa2048"; > + padding-name = "pss"; > + key-name = "private.pem"; > + header-size = <4096>; > + version = <1>; > + }; > + > + image: blob-ext { > + filename = "sandbox.itb"; > + }; > + }; > + }; > + """ > + > + def __init__(self, section, etype, node): > + super().__init__(section, etype, node) > + self.algo_name = fdt_util.GetString(self._node, 'algo-name') > + self.padding_name = fdt_util.GetString(self._node, 'padding-name') > + self.key_name = fdt_util.GetString(self._node, 'key-name') > + self.header_size = fdt_util.GetInt(self._node, 'header-size') > + self.version = fdt_util.GetInt(self._node, 'version') > + > + def _CreateHeader(self): > + """Create a pre load header""" > + hash_name, sign_name = self.algo_name.split(',') > + padding_name=self.padding_name > + key_path, = self.GetEntryArgsOrProps([EntryArg('key-path', str)]) Please read this into self.key_path in a ReadNode() method. We try to do all the reading in one place. See other entry types for examples. > + if key_path is None or key_path == "": Please use single quotes by default > + key_name = self.key_name > + else: > + key_name = key_path + "/" + self.key_name key_name = os.path.join(key_path, self.key_name) (then you don't need the 'if') > + > + # Check hash and signature name/type > + if hash_name not in SHAS: > + raise ValueError(hash_name + " is not supported") self.Raise(... > + if sign_name not in RSAS: > + raise ValueError(sign_name + "is not supported") self.Raise(... > + > + # Read the key > + with open(key_name, 'rb') as pem: > + key = RSA.import_key(pem.read()) > + > + # Check if the key has the expected size > + if key.size_in_bytes() != RSAS[sign_name]: > + raise ValueError("The key " + self.key_name + " don't have the > expected size") self.Raise(... (the reason for that is we want to report the node that had the problem) > + > + # Compute the hash > + hash_image = SHAS[hash_name].new() > + hash_image.update(self.image) > + > + # Compute the signature > + if padding_name is None: > + padding_name = "pkcs-1.5" > + if padding_name == "pss": > + salt_len = key.size_in_bytes() - hash_image.digest_size - 2 > + padding = pss > + padding_args = {'salt_bytes': salt_len} > + elif padding_name == "pkcs-1.5": > + padding = pkcs1_15 > + padding_args = {} > + else: > + raise ValueError(padding_name + " is not supported") > + > + sig = padding.new(key, **padding_args).sign(hash_image) > + > + hash_sig = SHA256.new() > + hash_sig.update(sig) > + > + version = self.version > + header_size = self.header_size > + image_size = len(self.image) > + ofs_img_sig = 64 + len(sig) > + flags = 0 > + reserved0 = 0 > + reserved1 = 0 > + > + first_header = bytearray(64) > + struct.pack_into('4s', first_header, 0, PRE_LOAD_MAGIC) > + struct.pack_into('>I', first_header, 4, version) > + struct.pack_into('>I', first_header, 8, header_size) > + struct.pack_into('>I', first_header, 12, image_size) > + struct.pack_into('>I', first_header, 16, ofs_img_sig) > + struct.pack_into('>I', first_header, 20, flags) > + struct.pack_into('>I', first_header, 24, reserved0) > + struct.pack_into('>I', first_header, 28, reserved1) > + struct.pack_into('32s', first_header, 32, hash_sig.digest()) Can you do first_header = struct.pack('>4sIIIII...', PRE_LOAD_MAGIC, version, header_size, ... > + > + hash_first_header = SHAS[hash_name].new() > + hash_first_header.update(first_header) > + sig_first_header = padding.new(key, > **padding_args).sign(hash_first_header) > + > + data = first_header + sig_first_header + sig > + pad = bytearray(self.header_size - len(data)) > + > + return data + pad > + > + def ObtainContents(self): > + """Obtain a placeholder for the header contents""" > + # wait that the image is available > + self.image = self.GetContents(False) > + if self.image is None: > + return False > + self.SetContents(self._CreateHeader()) > + return True > + > + def ProcessContents(self): > + data = self._CreateHeader() > + return self.ProcessContentsUpdate(data) > diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py > index 8f00db6945..06b8546354 100644 > --- a/tools/binman/ftest.py > +++ b/tools/binman/ftest.py > @@ -91,6 +91,9 @@ SCP_DATA = b'scp' > TEST_FDT1_DATA = b'fdt1' > TEST_FDT2_DATA = b'test-fdt2' > ENV_DATA = b'var1=1\nvar2="2"' > +PRE_LOAD_MAGIC = b'UBSH' > +PRE_LOAD_VERSION = 0x11223344.to_bytes(4, 'big') > +PRE_LOAD_HDR_SIZE = 0x00001000.to_bytes(4, 'big') > > # Subdirectory of the input dir to use to put test FDTs > TEST_FDT_SUBDIR = 'fdts' > @@ -5321,6 +5324,48 @@ fdt fdtmap Extract the > devicetree blob from the fdtmap > self.assertIn("Node '/binman/fit': Unknown operation 'unknown'", > str(exc.exception)) > > + def testPreLoad(self): > + """Test an image with a pre-load header""" > + entry_args = { > + 'key-path': '.', > + } > + data, _, _, _ = self._DoReadFileDtb('225_pre_load.dts', > + entry_args=entry_args) > + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) > + self.assertEqual(PRE_LOAD_VERSION, data[4:4+len(PRE_LOAD_VERSION)]) > + self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8+len(PRE_LOAD_HDR_SIZE)]) > + data = self._DoReadFile('225_pre_load.dts') Please put each .dts file in its own test function, just because that is how it currently works. > + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) > + self.assertEqual(PRE_LOAD_VERSION, data[4:4+len(PRE_LOAD_VERSION)]) > + self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8+len(PRE_LOAD_HDR_SIZE)]) > + data = self._DoReadFile('226_pre_load_pkcs.dts') > + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) > + self.assertEqual(PRE_LOAD_VERSION, data[4:4+len(PRE_LOAD_VERSION)]) > + self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8+len(PRE_LOAD_HDR_SIZE)]) > + data = self._DoReadFile('227_pre_load_pss.dts') > + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) > + self.assertEqual(PRE_LOAD_VERSION, data[4:4+len(PRE_LOAD_VERSION)]) > + self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8+len(PRE_LOAD_HDR_SIZE)]) Spaces around + please Regards, Simon