tools: binman: add support for pre-load header

Adds the support of the pre-load header with the image signature
to binman.

Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Philippe Reynes <philippe.reynes@softathome.com>
diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index be8de55..ae4305c 100644
--- a/tools/binman/entries.rst
+++ b/tools/binman/entries.rst
@@ -1155,6 +1155,44 @@
 
 
 
+Entry: pre-load: Pre load image header
+--------------------------------------
+
+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 creates a pre-load header that contains a global
+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";
+            };
+        };
+    };
+
+
+
 Entry: scp: System Control Processor (SCP) firmware blob
 --------------------------------------------------------
 
diff --git a/tools/binman/etype/pre_load.py b/tools/binman/etype/pre_load.py
new file mode 100644
index 0000000..245ee75
--- /dev/null
+++ b/tools/binman/etype/pre_load.py
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2022 Softathome
+# Written by Philippe Reynes <philippe.reynes@softathome.com>
+#
+# Entry-type for the global header
+#
+
+import os
+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
+
+    Properties / Entry arguments:
+        - pre-load-key-path: Path of the directory that store key (provided by the environment variable PRE_LOAD_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 creates a pre-load header that contains a global
+    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 ReadNode(self):
+        super().ReadNode()
+        self.key_path, = self.GetEntryArgsOrProps([EntryArg('pre-load-key-path', str)])
+        if self.key_path is None:
+            self.key_path = ''
+
+    def _CreateHeader(self):
+        """Create a pre load header"""
+        hash_name, sign_name = self.algo_name.split(',')
+        padding_name = self.padding_name
+        key_name = os.path.join(self.key_path, self.key_name)
+
+        # Check hash and signature name/type
+        if hash_name not in SHAS:
+            self.Raise(hash_name + " is not supported")
+        if sign_name not in RSAS:
+            self.Raise(sign_name + " is not supported")
+
+        # 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]:
+            self.Raise("The key " + self.key_name + " don't have the expected size")
+
+        # 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:
+            self.Raise(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 = struct.pack('>4sIIIIIII32s', PRE_LOAD_MAGIC,
+                                   version, header_size, image_size,
+                                   ofs_img_sig, flags, reserved0,
+                                   reserved1, hash_sig.digest())
+
+        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 876953f..4ce181a 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -91,6 +91,9 @@
 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'
@@ -5471,6 +5474,54 @@
             err,
             "Image '.*' is missing external blobs and is non-functional: .*")
 
+    def testPreLoad(self):
+        """Test an image with a pre-load header"""
+        entry_args = {
+            'pre-load-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')
+        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)])
+
+    def testPreLoadPkcs(self):
+        """Test an image with a pre-load header with padding pkcs"""
+        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)])
+
+    def testPreLoadPss(self):
+        """Test an image with a pre-load header with padding pss"""
+        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)])
+
+    def testPreLoadInvalidPadding(self):
+        """Test an image with a pre-load header with an invalid padding"""
+        with self.assertRaises(ValueError) as e:
+            data = self._DoReadFile('228_pre_load_invalid_padding.dts')
+
+    def testPreLoadInvalidSha(self):
+        """Test an image with a pre-load header with an invalid hash"""
+        with self.assertRaises(ValueError) as e:
+            data = self._DoReadFile('229_pre_load_invalid_sha.dts')
+
+    def testPreLoadInvalidAlgo(self):
+        """Test an image with a pre-load header with an invalid algo"""
+        with self.assertRaises(ValueError) as e:
+            data = self._DoReadFile('230_pre_load_invalid_algo.dts')
+
+    def testPreLoadInvalidKey(self):
+        """Test an image with a pre-load header with an invalid key"""
+        with self.assertRaises(ValueError) as e:
+            data = self._DoReadFile('231_pre_load_invalid_key.dts')
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/test/225_dev.key b/tools/binman/test/225_dev.key
new file mode 100644
index 0000000..b36bad2
--- /dev/null
+++ b/tools/binman/test/225_dev.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYngNWUvXYRXX/
+WEUI7k164fcpv1srXz+u+5Y3Yhouw3kPs+ffvYyHAPfjF7aUIAgezKk/4o7AvsxE
+Rdih3T+0deAd/q/yuqN4Adzt6ImnsO/EqdtYl3Yh+Vck9xWhLd3SAw1++GfSmNMT
+gxlcc/z6z+bIh2tJNtPtRSNNHMmvYYOkBmkfwcjbMXD+fe4vBwYjVrIize+l7Yuv
+1qN2nFlq56pFi8Lj5vOvFyNhZHRvwcpWdUdkx39beNUfwrGhgewOeWngTcY75n7S
+FY45TBR1G2PR90CQvyDinCi9Mm0u5s+1WASQWPblovfD6CPbHQu4GZm+FAs7yUvr
+hA7VCyNxAgMBAAECggEAUbq0uaJNfc8faTtNuMPo2d9eGRNI+8FRTt0/3R+Xj2NT
+TvhrGUD0P4++96Df012OkshXZ3I8uD6E5ZGQ3emTeqwq5kZM7oE64jGZwO3G2k1o
++cO4reFfwgvItHrBX3HlyrI6KljhG1Vr9mW1cOuWXK+KfMiTUylrpo86dYLSGeg3
+7ZlsOPArr4eof/A0iPryQZX6X5POf7k/e9qRFYsOkoRQO8pBL3J4rIKwBl3uBN3K
++FY40vCkd8JyTo2DNfHeIe1XYA9fG2ahjD2qMsw10TUsRRMd5yhonEcJ7VzGzy8m
+MnuMDAr7CwbbLkKi4UfZUl6YDkojqerwLOrxikBqkQKBgQD6sS6asDgwiq5MtstE
+4/PxMrVEsCdkrU+jjQN749qIt/41a6lbp0Pr6aUKKKGs0QbcnCtlpp7qmhvymBcW
+hlqxk2wokKMChv4WLXjZS3DGcOdMglc81y2F+252bToN8vwUfm6DPp9/GKtejA0a
+GP57GeHxoVO7vfDX1F/vZRogRQKBgQDdNCLWOlGWvnKjfgNZHgX+Ou6ZgTSAzy+/
+hRsZPlY5nwO5iD7YkIKvqBdOmfyjlUpHWk2uAcT9pfgzYygvyBRaoQhAYBGkHItt
+slaMxnLd+09wWufoCbgJvFn+wVQxBLcA5PXB98ws0Dq8ZYuo6AOuoRivsSO4lblK
+MW0guBJXPQKBgQDGjf0ukbH/aGfC5Oi8SJvWhuYhYC/jQo2YKUEAKCjXLnuOThZW
+PHXEbUrFcAcVfH0l0B9jJIQrpiHKlAF9Wq6MhQoeWuhxQQAQCrXzzRemZJgd9gIo
+cvlgbBNCgyJ/F9vmU3kuRDRJkv1wJhbee7tbPtXA7pkGUttl5pSRZI87zQKBgQC/
+0ZkwCox72xTQP9MpcYai6nnDta5Q0NnIC+Xu4wakmwcA2WweIlqhdnMXnyLcu/YY
+n+9iqHgpuMXd0eukW62C1cexA13o4TPrYU36b5BmfKprdPlLVzo3fxTPfNjEVSFY
+7jNLC9YLOlrkym3sf53Jzjr5B/RA+d0ewHOwfs6wxQKBgFSyfjx5wtdHK4fO+Z1+
+q3bxouZryM/4CiPCFuw4+aZmRHPmufuNCvfXdF+IH8dM0E9ObwKZAe/aMP/Y+Abx
+Wz9Vm4CP6g7k3DU3INEygyjmIQQDKQ9lFdDnsP9ESzrPbaGxZhc4x2lo7qmeW1BR
+/RuiAofleFkT4s+EhLrfE/v5
+-----END PRIVATE KEY-----
diff --git a/tools/binman/test/225_pre_load.dts b/tools/binman/test/225_pre_load.dts
new file mode 100644
index 0000000..c1ffe1a
--- /dev/null
+++ b/tools/binman/test/225_pre_load.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa2048";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <0x11223344>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/226_pre_load_pkcs.dts b/tools/binman/test/226_pre_load_pkcs.dts
new file mode 100644
index 0000000..3db0a37
--- /dev/null
+++ b/tools/binman/test/226_pre_load_pkcs.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa2048";
+			 padding-name = "pkcs-1.5";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <0x11223344>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/227_pre_load_pss.dts b/tools/binman/test/227_pre_load_pss.dts
new file mode 100644
index 0000000..b1b01d5
--- /dev/null
+++ b/tools/binman/test/227_pre_load_pss.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa2048";
+			 padding-name = "pss";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <0x11223344>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/228_pre_load_invalid_padding.dts b/tools/binman/test/228_pre_load_invalid_padding.dts
new file mode 100644
index 0000000..84fe289
--- /dev/null
+++ b/tools/binman/test/228_pre_load_invalid_padding.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa2048";
+			 padding-name = "padding";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <1>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/229_pre_load_invalid_sha.dts b/tools/binman/test/229_pre_load_invalid_sha.dts
new file mode 100644
index 0000000..a2b6725
--- /dev/null
+++ b/tools/binman/test/229_pre_load_invalid_sha.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha2560,rsa2048";
+			 padding-name = "pkcs-1.5";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <1>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/230_pre_load_invalid_algo.dts b/tools/binman/test/230_pre_load_invalid_algo.dts
new file mode 100644
index 0000000..34c8d34
--- /dev/null
+++ b/tools/binman/test/230_pre_load_invalid_algo.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa20480";
+			 padding-name = "pkcs-1.5";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <1>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};
diff --git a/tools/binman/test/231_pre_load_invalid_key.dts b/tools/binman/test/231_pre_load_invalid_key.dts
new file mode 100644
index 0000000..08d5a75
--- /dev/null
+++ b/tools/binman/test/231_pre_load_invalid_key.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	binman {
+		pre-load {
+			content = <&image>;
+			 algo-name = "sha256,rsa4096";
+			 padding-name = "pkcs-1.5";
+			 key-name = "tools/binman/test/225_dev.key";
+			 header-size = <4096>;
+			 version = <1>;
+		};
+
+		image: blob-ext {
+			filename = "refcode.bin";
+		};
+	};
+};