[PATCH] tools: binman: add build key directory option

James Hilliard james.hilliard1 at gmail.com
Fri Jul 3 23:33:03 CEST 2026


U-Boot's Makefile passes KEYDIR to direct mkimage invocations, but
internal binman builds only received the generic BINMAN_INDIRS search
path. FIT entries with fit,sign or fit,encrypt therefore had to infer
the key directory from include directories before invoking mkimage -k.

Add a binman build -k/--keydir option and pass KEYDIR through with
that option from the top-level Makefile. FIT entries prefer this key
directory over include-dir discovery and then pass it to mkimage -k.

Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 Makefile                  |  1 +
 tools/binman/binman.rst   | 10 +++++++-
 tools/binman/cmdline.py   |  2 ++
 tools/binman/control.py   |  1 +
 tools/binman/etype/fit.py | 24 +++++++++++-------
 tools/binman/ftest.py     | 51 ++++++++++++++++++++++++++++++++++++---
 tools/binman/state.py     | 21 ++++++++++++++++
 7 files changed, 97 insertions(+), 13 deletions(-)

diff --git a/Makefile b/Makefile
index d8287323c43..27be2a81e83 100644
--- a/Makefile
+++ b/Makefile
@@ -1689,6 +1689,7 @@ cmd_binman = $(srctree)/tools/binman/binman $(if $(BINMAN_DEBUG),-D) \
 		-I . -I $(srctree)/board/$(BOARDDIR) -I $(srctree) \
 		$(foreach f,$(of_list_dirs),-I $(f)) -a of-list=$(of_list) \
 		$(foreach f,$(BINMAN_INDIRS),-I $(f)) \
+		$(if $(KEYDIR),-k $(KEYDIR)) \
 		-a atf-bl1-path=${BL1} \
 		-a atf-bl31-path=${BL31} \
 		-a tee-os-path=${TEE} \
diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst
index 366491089ad..8770b279ff8 100644
--- a/tools/binman/binman.rst
+++ b/tools/binman/binman.rst
@@ -1711,7 +1711,7 @@ Usage::
 
     binman build [-h] [-a ENTRY_ARG] [-b BOARD] [-d DT] [--fake-dtb]
         [--fake-ext-blobs] [--force-missing-bintools FORCE_MISSING_BINTOOLS]
-        [-i IMAGE] [-I INDIR] [-m] [-M] [-n] [-O OUTDIR] [-p] [-u]
+        [-i IMAGE] [-I INDIR] [-k KEYDIR] [-m] [-M] [-n] [-O OUTDIR] [-p] [-u]
         [--update-fdt-in-elf UPDATE_FDT_IN_ELF] [-W]
 
 Options:
@@ -1738,6 +1738,9 @@ Options:
     Add a path to the list of directories to use for input files. This can be
     specified multiple times to add more than one path.
 
+-k KEYDIR, --keydir KEYDIR
+    Directory containing keys for signing/encryption.
+
 -m, --map
     Output a map file for each image. See `Map files`_.
 
@@ -2161,6 +2164,11 @@ BINMAN_INDIRS
     Sets the search path for input files used by binman by adding one or more
     `-I` arguments. See :ref:`External blobs`.
 
+KEYDIR
+    Sets the key directory passed to FIT entries by adding a `-k` argument.
+    FIT entries use this directory for mkimage's `-k` argument when `fit,sign`
+    or `fit,encrypt` is enabled.
+
 BINMAN_TOOLPATHS
     Sets the search path for external tool used by binman by adding one or more
     `--toolpath` arguments. See :ref:`External tools`.
diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py
index 9632ec115e5..cd4d05dfb69 100644
--- a/tools/binman/cmdline.py
+++ b/tools/binman/cmdline.py
@@ -128,6 +128,8 @@ controlled by a description in the board device tree.'''
             help='Image filename to build (if not specified, build all)')
     build_parser.add_argument('-I', '--indir', action='append',
             help='Add a path to the list of directories to use for input files')
+    build_parser.add_argument('-k', '--keydir', type=str,
+            help='Directory containing keys for signing/encryption')
     build_parser.add_argument('-m', '--map', action='store_true',
         default=False, help='Output a map file for each image')
     build_parser.add_argument('-M', '--allow-missing', action='store_true',
diff --git a/tools/binman/control.py b/tools/binman/control.py
index 816f7c1eba2..84586d1baf1 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -923,6 +923,7 @@ def Binman(args):
             tools.set_input_dirs(args.indir)
             tools.prepare_output_dir(args.outdir, args.preserve)
             state.SetEntryArgs(args.entry_arg)
+            state.SetKeydir(args.keydir)
             state.SetThreads(args.threads)
 
             images = PrepareImagesAndDtbs(dtb_fname, args.image,
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index f28b1e6b4cb..404e33d3022 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -14,6 +14,7 @@ import os
 from binman.entry import Entry, EntryArg
 from binman.etype.section import Entry_section
 from binman import elf
+from binman import state
 from dtoc import fdt_util
 from dtoc.fdt import Fdt
 from u_boot_pylib import tools
@@ -105,18 +106,20 @@ class Entry_fit(Entry_section):
         fit,sign
             Enable signing FIT images via mkimage as described in
             verified-boot.rst.
-            If the property is found and fit,engine is not set, 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.
+            If the property is found and fit,engine is not set, the key
+            directory passed with binman build -k is passed to mkimage via the
+            -k flag. If no key directory is provided, the private keys path is
+            detected among binman include directories. All the keys required for
+            signing FIT must be available at time of signing and must be
+            located in a single 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
+            is found, the key directory passed with binman build -k is passed
+            to mkimage via the -k flag. If no key directory is provided, the
+            keys path is detected among binman include directories. 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.
+            encrypting and must be located in a single directory.
 
             Incompatible with fit,engine.
 
@@ -485,6 +488,8 @@ class Entry_fit(Entry_section):
                 includes 'generator' entries which are used to create the FIT,
                 but should not be processed as real entries. This is set up once
                 we have the entries
+            _keydir (str): Key directory from the binman build -k option, if
+                provided
             _loadables (list of str): List of generated split-elf nodes, each
                 a node name
             _remove_props (list of str): Value of of-spl-remove-props EntryArg,
@@ -500,6 +505,7 @@ class Entry_fit(Entry_section):
         self._fit_list_prop = None
         self._fit_default_dt = None
         self._priv_entries = {}
+        self._keydir = state.GetKeydir()
         self._loadables = []
         self._remove_props = []
         props = self.GetEntryArgsOrProps(
@@ -701,7 +707,7 @@ class Entry_fit(Entry_section):
             args.update({'engine': engine})
             # If no engine, keys must exist locally, find them
             if engine is None:
-                keydir = self._get_keys_dir(data)
+                keydir = self._keydir or self._get_keys_dir(data)
             elif self._fit_props.get('fit,encrypt') is not None:
                 self.Raise('fit,engine currently does not support encryption')
 
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index bf98b268ac1..0fc00fade3a 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -385,7 +385,8 @@ class TestFunctional(unittest.TestCase):
                     use_expanded=False, verbosity=None, allow_missing=False,
                     allow_fake_blobs=False, extra_indirs=None, threads=None,
                     test_section_timeout=False, update_fdt_in_elf=None,
-                    force_missing_bintools='', ignore_missing=False, output_dir=None):
+                    force_missing_bintools='', ignore_missing=False,
+                    output_dir=None, keydir=None):
         """Run binman with a given test file
 
         Args:
@@ -419,6 +420,7 @@ class TestFunctional(unittest.TestCase):
             ignore_missing (bool): True to return success even if there are
                 missing blobs or bintools
             output_dir: Specific output directory to use for image using -O
+            keydir: Directory containing keys for signing/encryption
 
         Returns:
             int return code, 0 on success
@@ -438,6 +440,8 @@ class TestFunctional(unittest.TestCase):
         if test_section_timeout:
             args.append('--test-section-timeout')
         args += ['build', '-p', '-I', self._indir, '-d', self.TestFile(fname)]
+        if keydir:
+            args += ['-k', keydir]
         if map:
             args.append('-m')
         if update_dtb:
@@ -518,7 +522,7 @@ class TestFunctional(unittest.TestCase):
     def _DoReadFileDtb(self, fname, use_real_dtb=False, use_expanded=False,
                        verbosity=None, allow_fake_blobs=True, map=False,
                        update_dtb=False, entry_args=None, reset_dtbs=True,
-                       extra_indirs=None, threads=None):
+                       extra_indirs=None, threads=None, keydir=None):
         """Run binman and return the resulting image
 
         This runs binman with a given test file and then reads the resulting
@@ -549,6 +553,7 @@ class TestFunctional(unittest.TestCase):
             extra_indirs: Extra input directories to add using -I
             threads: Number of threads to use (None for default, 0 for
                 single-threaded)
+            keydir: Directory containing keys for signing/encryption
 
         Returns:
             Tuple:
@@ -575,7 +580,7 @@ class TestFunctional(unittest.TestCase):
                     entry_args=entry_args, use_real_dtb=use_real_dtb,
                     use_expanded=use_expanded, verbosity=verbosity,
                     allow_fake_blobs=allow_fake_blobs, extra_indirs=extra_indirs,
-                    threads=threads)
+                    threads=threads, keydir=keydir)
             self.assertEqual(0, retcode)
             out_dtb_fname = tools.get_output_filename('u-boot.dtb.out')
 
@@ -8167,6 +8172,31 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
             self.assertIsNotNone(signature)
             self.assertIsNotNone(signature.props.get('value'))
 
+    def testFitSignKeydir(self):
+        """Test that binman build -k is passed to mkimage"""
+        if not elf.ELF_TOOLS:
+            self.skipTest('Python elftools not available')
+        data = tools.read_file(self.TestFile("fit/rsa2048.key"))
+        self._MakeInputFile("keys/rsa2048.key", data)
+
+        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
+        keys_subdir = os.path.join(self._indir, "keys")
+        entry_args = {
+            'of-list': 'test-fdt1',
+            'default-dt': 'test-fdt1',
+            'atf-bl31-path': 'bl31.elf',
+        }
+        data = self._DoReadFileDtb(
+            'fit/signature.dts',
+            entry_args=entry_args,
+            extra_indirs=[test_subdir],
+            keydir=keys_subdir)[0]
+
+        dtb = fdt.Fdt.FromData(data)
+        dtb.Scan()
+        signature = dtb.GetNode('/configurations/conf-uboot-1/signature')
+        self.assertIsNotNone(signature.props.get('value'))
+
     def testFitSignEngineSimple(self):
         """Test that image with FIT and signature nodes can be signed with an
            OpenSSL Engine"""
@@ -8507,6 +8537,21 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
             dec_data = file.read()
         self.assertEqual(U_BOOT_NODTB_DATA, dec_data.encode('ascii'))
 
+    def testSimpleFitEncryptedDataKeydir(self):
+        """Test that encrypted FIT data uses binman build -k"""
+        data = tools.read_file(self.TestFile("fit/aes256.bin"))
+        self._MakeInputFile("keys/aes256.bin", data)
+
+        keys_subdir = os.path.join(self._indir, "keys")
+        data = self._DoReadFileDtb(
+            'fit/encrypt_data.dts',
+            keydir=keys_subdir)[0]
+
+        fit = fdt.Fdt.FromData(data)
+        fit.Scan()
+        node = fit.GetNode('/images/u-boot')
+        self.assertIn('data-size-unciphered', fit.GetProps(node))
+
     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:
diff --git a/tools/binman/state.py b/tools/binman/state.py
index f4d885c772a..15e97f68893 100644
--- a/tools/binman/state.py
+++ b/tools/binman/state.py
@@ -41,6 +41,9 @@ fdt_path_prefix = ''
 # Arguments passed to binman to provide arguments to entries
 entry_args = {}
 
+# Directory containing keys for FIT signing/encryption
+keydir = None
+
 # True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
 # ftest.py)
 use_fake_dtb = False
@@ -206,6 +209,24 @@ def GetEntryArgBool(name):
     val = GetEntryArg(name)
     return val and val not in ['n', '0']
 
+def SetKeydir(pathname):
+    """Set the key directory to use for signing/encryption
+
+    Args:
+        pathname: Key directory, or None if not provided
+    """
+    global keydir
+
+    keydir = pathname
+
+def GetKeydir():
+    """Get the key directory to use for signing/encryption
+
+    Returns:
+        Key directory, or None if not provided
+    """
+    return keydir
+
 def Prepare(images, dtb):
     """Get device tree files ready for use
 
-- 
2.53.0



More information about the U-Boot mailing list