aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2023-03-16 12:16:14 -0400
committerTom Rini <trini@konsulko.com>2023-03-16 12:16:14 -0400
commitcb90ddb2a64f30e6b0411f9385ddd84c5612314e (patch)
treee0f834058c62cc2d4a852fc83b9f92cd85121857 /tools
parenta5faa4a9eb45f2cc0e858622db8fabafd644085b (diff)
parentc3cea95fd21937ce82be3dbd1062dde8fb0e6114 (diff)
downloadu-boot-cb90ddb2a64f30e6b0411f9385ddd84c5612314e.zip
u-boot-cb90ddb2a64f30e6b0411f9385ddd84c5612314e.tar.gz
u-boot-cb90ddb2a64f30e6b0411f9385ddd84c5612314e.tar.bz2
Merge tag 'dm-next-12mar23a' of git://git.denx.de/u-boot-dm into next
More tests and fixes for fdt command binman signing feature fix buildman -A bug introduced recently Signed-off-by: Tom Rini <trini@konsulko.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore1
-rw-r--r--tools/Makefile3
-rw-r--r--tools/binman/binman.rst47
-rw-r--r--tools/binman/cmdline.py13
-rw-r--r--tools/binman/control.py30
-rw-r--r--tools/binman/entry.py3
-rw-r--r--tools/binman/etype/fit.py16
-rw-r--r--tools/binman/ftest.py93
-rw-r--r--tools/binman/test/280_fit_sign.dts63
-rw-r--r--tools/binman/test/281_sign_non_fit.dts65
-rw-r--r--tools/buildman/toolchain.py4
-rw-r--r--tools/fdt_add_pubkey.c138
12 files changed, 474 insertions, 2 deletions
diff --git a/tools/.gitignore b/tools/.gitignore
index 788ea26..cda3ea6 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -6,6 +6,7 @@
/dumpimage
/easylogo/easylogo
/envcrc
+/fdt_add_pubkey
/fdtgrep
/file2include
/fit_check_sign
diff --git a/tools/Makefile b/tools/Makefile
index e13effb..38699b0 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -65,6 +65,7 @@ mkenvimage-objs := mkenvimage.o os_support.o lib/crc32.o
hostprogs-y += dumpimage mkimage
hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fit_info fit_check_sign
+hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fdt_add_pubkey
ifneq ($(CONFIG_CMD_BOOTEFI_SELFTEST)$(CONFIG_FWU_MDATA_GPT_BLK),)
hostprogs-y += file2include
@@ -150,6 +151,7 @@ dumpimage-objs := $(dumpimage-mkimage-objs) dumpimage.o
mkimage-objs := $(dumpimage-mkimage-objs) mkimage.o
fit_info-objs := $(dumpimage-mkimage-objs) fit_info.o
fit_check_sign-objs := $(dumpimage-mkimage-objs) fit_check_sign.o
+fdt_add_pubkey-objs := $(dumpimage-mkimage-objs) fdt_add_pubkey.o
file2include-objs := file2include.o
ifneq ($(CONFIG_MX23)$(CONFIG_MX28)$(CONFIG_TOOLS_LIBCRYPTO),)
@@ -187,6 +189,7 @@ HOSTCFLAGS_fit_image.o += -DMKIMAGE_DTC=\"$(CONFIG_MKIMAGE_DTC_PATH)\"
HOSTLDLIBS_dumpimage := $(HOSTLDLIBS_mkimage)
HOSTLDLIBS_fit_info := $(HOSTLDLIBS_mkimage)
HOSTLDLIBS_fit_check_sign := $(HOSTLDLIBS_mkimage)
+HOSTLDLIBS_fdt_add_pubkey := $(HOSTLDLIBS_mkimage)
hostprogs-$(CONFIG_EXYNOS5250) += mkexynosspl
hostprogs-$(CONFIG_EXYNOS5420) += mkexynosspl
diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst
index 7fc0c7f..23cbb99 100644
--- a/tools/binman/binman.rst
+++ b/tools/binman/binman.rst
@@ -1366,6 +1366,24 @@ when it was created.
.. _`BinmanLogging`:
+Signing FIT container with private key in an image
+--------------------------------------------------
+
+You can sign FIT container with private key in your image.
+For example::
+
+ $ binman sign -i image.bin -k privatekey -a sha256,rsa4096 fit
+
+binman will extract FIT container, sign and replace it immediately.
+
+If you want to sign and replace FIT container in place::
+
+ $ binman sign -i image.bin -k privatekey -a sha256,rsa4096 -f fit.fit fit
+
+which will sign FIT container with private key and replace it immediately
+inside your image.
+
+
Logging
-------
@@ -1751,6 +1769,35 @@ Options:
output directory if a single test is run (pass test name at the end of the
command line
+binman sign
+-----------
+
+Usage::
+
+ binman sign [-h] -a ALGO [-f FILE] -i IMAGE -k KEY [paths ...]
+
+positional arguments:
+
+paths
+ Paths within file to sign (wildcard)
+
+options:
+
+-h, --help
+ show this help message and exit
+
+-a ALGO, --algo ALGO
+ Hash algorithm e.g. sha256,rsa4096
+
+-f FILE, --file FILE
+ Input filename to sign
+
+-i IMAGE, --image IMAGE
+ Image filename to update
+
+-k KEY, --key KEY
+ Private key file for signing
+
binman tool
-----------
diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py
index 1b7bbe8..4b875a9 100644
--- a/tools/binman/cmdline.py
+++ b/tools/binman/cmdline.py
@@ -176,6 +176,19 @@ controlled by a description in the board device tree.'''
replace_parser.add_argument('paths', type=str, nargs='*',
help='Paths within file to replace (wildcard)')
+ sign_parser = subparsers.add_parser('sign',
+ help='Sign entries in image')
+ sign_parser.add_argument('-a', '--algo', type=str, required=True,
+ help='Hash algorithm e.g. sha256,rsa4096')
+ sign_parser.add_argument('-f', '--file', type=str, required=False,
+ help='Input filename to sign')
+ sign_parser.add_argument('-i', '--image', type=str, required=True,
+ help='Image filename to update')
+ sign_parser.add_argument('-k', '--key', type=str, required=True,
+ help='Private key file for signing')
+ sign_parser.add_argument('paths', type=str, nargs='*',
+ help='Paths within file to sign (wildcard)')
+
if HAS_TESTS:
test_parser = subparsers.add_parser('test', help='Run tests')
test_parser.add_argument('-P', '--processes', type=int,
diff --git a/tools/binman/control.py b/tools/binman/control.py
index 2f2b489..0febcb7 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -448,6 +448,31 @@ def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
return image
+def SignEntries(image_fname, input_fname, privatekey_fname, algo, entry_paths,
+ write_map=False):
+ """Sign and replace the data from one or more entries from input files
+
+ Args:
+ image_fname: Image filename to process
+ input_fname: Single input filename to use if replacing one file, None
+ otherwise
+ algo: Hashing algorithm
+ entry_paths: List of entry paths to sign
+ privatekey_fname: Private key filename
+ write_map (bool): True to write the map file
+ """
+ image_fname = os.path.abspath(image_fname)
+ image = Image.FromFile(image_fname)
+
+ image.mark_build_done()
+
+ BeforeReplace(image, allow_resize=True)
+
+ for entry_path in entry_paths:
+ entry = image.FindEntryPath(entry_path)
+ entry.UpdateSignatures(privatekey_fname, algo, input_fname)
+
+ AfterReplace(image, allow_resize=True, write_map=write_map)
def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
"""Prepare the images to be processed and select the device tree
@@ -660,7 +685,7 @@ def Binman(args):
tools.set_tool_paths(tool_paths or None)
bintool.Bintool.set_tool_dir(args.tooldir)
- if args.cmd in ['ls', 'extract', 'replace', 'tool']:
+ if args.cmd in ['ls', 'extract', 'replace', 'tool', 'sign']:
try:
tout.init(args.verbosity)
if args.cmd == 'replace':
@@ -679,6 +704,9 @@ def Binman(args):
do_compress=not args.compressed,
allow_resize=not args.fix_size, write_map=args.map)
+ if args.cmd == 'sign':
+ SignEntries(args.image, args.file, args.key, args.algo, args.paths)
+
if args.cmd == 'tool':
if args.list:
bintool.Bintool.list_all()
diff --git a/tools/binman/entry.py b/tools/binman/entry.py
index b10a433..3945690 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -1378,3 +1378,6 @@ features to produce new behaviours.
if entries:
for entry in entries.values():
entry.mark_build_done()
+
+ def UpdateSignatures(self, privatekey_fname, algo, input_fname):
+ self.Raise('Updating signatures is not supported with this entry type')
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index 03fe88e..c395706 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -835,3 +835,19 @@ class Entry_fit(Entry_section):
def CheckEntries(self):
pass
+
+ def UpdateSignatures(self, privatekey_fname, algo, input_fname):
+ uniq = self.GetUniqueName()
+ args = [ '-G', privatekey_fname, '-r', '-o', algo, '-F' ]
+ if input_fname:
+ fname = input_fname
+ else:
+ fname = tools.get_output_filename('%s.fit' % uniq)
+ tools.write_file(fname, self.GetData())
+ args.append(fname)
+
+ if self.mkimage.run_cmd(*args) is None:
+ self.Raise("Missing tool: 'mkimage'")
+
+ data = tools.read_file(fname)
+ self.WriteData(data)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index d455ea0..43b4f85 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -709,6 +709,14 @@ class TestFunctional(unittest.TestCase):
AddNode(dtb.GetRoot(), '')
return tree
+ def _CheckSign(self, fit, key):
+ try:
+ tools.run('fit_check_sign', '-k', key, '-f', fit)
+ except:
+ self.fail('Expected signed FIT container')
+ return False
+ return True
+
def testRun(self):
"""Test a basic run with valid args"""
result = self._RunBinman('-h')
@@ -6583,6 +6591,91 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
self._DoTestFile('278_mkimage_missing_multiple.dts', allow_missing=False)
self.assertIn("not found in input path", str(e.exception))
+ def _PrepareSignEnv(self, dts='280_fit_sign.dts'):
+ """Prepare sign environment
+
+ Create private and public keys, add pubkey into dtb.
+
+ Returns:
+ Tuple:
+ FIT container
+ Image name
+ Private key
+ DTB
+ """
+
+ data = self._DoReadFileRealDtb(dts)
+ updated_fname = tools.get_output_filename('image-updated.bin')
+ tools.write_file(updated_fname, data)
+ dtb = tools.get_output_filename('source.dtb')
+ private_key = tools.get_output_filename('test_key.key')
+ public_key = tools.get_output_filename('test_key.crt')
+ fit = tools.get_output_filename('fit.fit')
+ key_dir = tools.get_output_dir()
+
+ tools.run('openssl', 'req', '-batch' , '-newkey', 'rsa:4096',
+ '-sha256', '-new', '-nodes', '-x509', '-keyout',
+ private_key, '-out', public_key)
+ tools.run('fdt_add_pubkey', '-a', 'sha256,rsa4096', '-k', key_dir,
+ '-n', 'test_key', '-r', 'conf', dtb)
+
+ return fit, updated_fname, private_key, dtb
+
+ def testSignSimple(self):
+ """Test that a FIT container can be signed in image"""
+ is_signed = False
+ fit, fname, private_key, dtb = self._PrepareSignEnv()
+
+ # do sign with private key
+ control.SignEntries(fname, None, private_key, 'sha256,rsa4096',
+ ['fit'])
+ is_signed = self._CheckSign(fit, dtb)
+
+ self.assertEqual(is_signed, True)
+
+ def testSignExactFIT(self):
+ """Test that a FIT container can be signed and replaced in image"""
+ is_signed = False
+ fit, fname, private_key, dtb = self._PrepareSignEnv()
+
+ # Make sure we propagate the toolpath, since mkimage may not be on PATH
+ args = []
+ if self.toolpath:
+ for path in self.toolpath:
+ args += ['--toolpath', path]
+
+ # do sign with private key
+ self._DoBinman(*args, 'sign', '-i', fname, '-k', private_key, '-a',
+ 'sha256,rsa4096', '-f', fit, 'fit')
+ is_signed = self._CheckSign(fit, dtb)
+
+ self.assertEqual(is_signed, True)
+
+ def testSignNonFit(self):
+ """Test a non-FIT entry cannot be signed"""
+ is_signed = False
+ fit, fname, private_key, _ = self._PrepareSignEnv(
+ '281_sign_non_fit.dts')
+
+ # do sign with private key
+ with self.assertRaises(ValueError) as e:
+ self._DoBinman('sign', '-i', fname, '-k', private_key, '-a',
+ 'sha256,rsa4096', '-f', fit, 'u-boot')
+ self.assertIn(
+ "Node '/u-boot': Updating signatures is not supported with this entry type",
+ str(e.exception))
+
+ def testSignMissingMkimage(self):
+ """Test that FIT signing handles a missing mkimage tool"""
+ fit, fname, private_key, _ = self._PrepareSignEnv()
+
+ # try to sign with a missing mkimage tool
+ bintool.Bintool.set_missing_list(['mkimage'])
+ with self.assertRaises(ValueError) as e:
+ control.SignEntries(fname, None, private_key, 'sha256,rsa4096',
+ ['fit'])
+ self.assertIn("Node '/fit': Missing tool: 'mkimage'", str(e.exception))
+
if __name__ == "__main__":
unittest.main()
diff --git a/tools/binman/test/280_fit_sign.dts b/tools/binman/test/280_fit_sign.dts
new file mode 100644
index 0000000..b9f17dc
--- /dev/null
+++ b/tools/binman/test/280_fit_sign.dts
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0x100000>;
+ allow-repack;
+
+ fit {
+ description = "U-Boot";
+ offset = <0x10000>;
+ images {
+ u-boot-1 {
+ description = "U-Boot";
+ type = "standalone";
+ arch = "arm64";
+ os = "u-boot";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ u-boot {
+ };
+ };
+
+ fdt-1 {
+ description = "test.dtb";
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ u-boot-spl-dtb {
+ };
+ };
+
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "u-boot with fdt";
+ firmware = "u-boot-1";
+ fdt = "fdt-1";
+ signature-1 {
+ algo = "sha256,rsa4096";
+ key-name-hint = "test_key";
+ sign-images = "firmware", "fdt";
+ };
+
+ };
+ };
+ };
+
+ fdtmap {
+ };
+ };
+};
diff --git a/tools/binman/test/281_sign_non_fit.dts b/tools/binman/test/281_sign_non_fit.dts
new file mode 100644
index 0000000..e16c954
--- /dev/null
+++ b/tools/binman/test/281_sign_non_fit.dts
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ size = <0x100000>;
+ allow-repack;
+
+ u-boot {
+ };
+ fit {
+ description = "U-Boot";
+ offset = <0x10000>;
+ images {
+ u-boot-1 {
+ description = "U-Boot";
+ type = "standalone";
+ arch = "arm64";
+ os = "u-boot";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ u-boot {
+ };
+ };
+
+ fdt-1 {
+ description = "test.dtb";
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ hash-1 {
+ algo = "sha256";
+ };
+ u-boot-spl-dtb {
+ };
+ };
+
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "u-boot with fdt";
+ firmware = "u-boot-1";
+ fdt = "fdt-1";
+ signature-1 {
+ algo = "sha256,rsa4096";
+ key-name-hint = "test_key";
+ sign-images = "firmware", "fdt";
+ };
+
+ };
+ };
+ };
+
+ fdtmap {
+ };
+ };
+};
diff --git a/tools/buildman/toolchain.py b/tools/buildman/toolchain.py
index 8f9130b..241e8e6 100644
--- a/tools/buildman/toolchain.py
+++ b/tools/buildman/toolchain.py
@@ -157,7 +157,9 @@ class Toolchain:
Value of that environment variable or arguments
"""
if which == VAR_CROSS_COMPILE:
- return self.GetWrapper() + self.cross
+ wrapper = self.GetWrapper()
+ base = '' if self.arch == 'sandbox' else self.path
+ return wrapper + os.path.join(base, self.cross)
elif which == VAR_PATH:
return self.path
elif which == VAR_ARCH:
diff --git a/tools/fdt_add_pubkey.c b/tools/fdt_add_pubkey.c
new file mode 100644
index 0000000..999f5a7
--- /dev/null
+++ b/tools/fdt_add_pubkey.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <image.h>
+#include "fit_common.h"
+
+static const char *cmdname;
+
+static const char *algo_name = "sha1,rsa2048"; /* -a <algo> */
+static const char *keydir = "."; /* -k <keydir> */
+static const char *keyname = "key"; /* -n <keyname> */
+static const char *require_keys; /* -r <conf|image> */
+static const char *keydest; /* argv[n] */
+
+static void print_usage(const char *msg)
+{
+ fprintf(stderr, "Error: %s\n", msg);
+ fprintf(stderr, "Usage: %s [-a <algo>] [-k <keydir>] [-n <keyname>] [-r <conf|image>]"
+ " <fdt blob>\n", cmdname);
+ fprintf(stderr, "Help information: %s [-h]\n", cmdname);
+ exit(EXIT_FAILURE);
+}
+
+static void print_help(void)
+{
+ fprintf(stderr, "Options:\n"
+ "\t-a <algo> Cryptographic algorithm. Optional parameter, default value: sha1,rsa2048\n"
+ "\t-k <keydir> Directory with public key. Optional parameter, default value: .\n"
+ "\t-n <keyname> Public key name. Optional parameter, default value: key\n"
+ "\t-r <conf|image> Required: If present this indicates that the key must be verified for the image / configuration to be considered valid.\n"
+ "\t<fdt blob> FDT blob file for adding of the public key. Required parameter.\n");
+ exit(EXIT_FAILURE);
+}
+
+static void process_args(int argc, char *argv[])
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "a:k:n:r:h")) != -1) {
+ switch (opt) {
+ case 'k':
+ keydir = optarg;
+ break;
+ case 'a':
+ algo_name = optarg;
+ break;
+ case 'n':
+ keyname = optarg;
+ break;
+ case 'r':
+ require_keys = optarg;
+ break;
+ case 'h':
+ print_help();
+ default:
+ print_usage("Invalid option");
+ }
+ }
+ /* The last parameter is expected to be the .dtb to add the public key to */
+ if (optind < argc)
+ keydest = argv[optind];
+
+ if (!keydest)
+ print_usage("Missing dtb file to update");
+}
+
+static void reset_info(struct image_sign_info *info)
+{
+ if (!info)
+ fprintf(stderr, "Error: info is NULL in %s\n", __func__);
+
+ memset(info, 0, sizeof(struct image_sign_info));
+
+ info->keydir = keydir;
+ info->keyname = keyname;
+ info->name = algo_name;
+ info->require_keys = require_keys;
+ info->crypto = image_get_crypto_algo(algo_name);
+
+ if (!info->crypto) {
+ fprintf(stderr, "Unsupported signature algorithm '%s'\n",
+ algo_name);
+ exit(EXIT_FAILURE);
+ }
+}
+
+static int add_pubkey(struct image_sign_info *info)
+{
+ int destfd = -1, ret;
+ void *dest_blob = NULL;
+ struct stat dest_sbuf;
+ size_t size_inc = 0;
+
+ if (!info)
+ fprintf(stderr, "Error: info is NULL in %s\n", __func__);
+
+ do {
+ if (destfd >= 0) {
+ munmap(dest_blob, dest_sbuf.st_size);
+ close(destfd);
+
+ fprintf(stderr, ".dtb too small, increasing size by 1024 bytes\n");
+ size_inc = 1024;
+ }
+
+ destfd = mmap_fdt(cmdname, keydest, size_inc, &dest_blob,
+ &dest_sbuf, false, false);
+ if (destfd < 0)
+ exit(EXIT_FAILURE);
+
+ ret = info->crypto->add_verify_data(info, dest_blob);
+ if (ret == -ENOSPC)
+ continue;
+ else if (ret < 0)
+ break;
+ } while (ret == -ENOSPC);
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ struct image_sign_info info;
+ int ret;
+
+ cmdname = argv[0];
+
+ process_args(argc, argv);
+ reset_info(&info);
+ ret = add_pubkey(&info);
+
+ if (ret < 0) {
+ fprintf(stderr, "%s: Cannot add public key to FIT blob: %s\n",
+ cmdname, strerror(ret));
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
+