[PATCH v2 11/11] binman: etype: Add xilinx_fsbl_auth etype

Simon Glass sjg at chromium.org
Fri Jul 7 19:35:38 CEST 2023


Hi Lukas,

On Thu, 6 Jul 2023 at 09:38, <lukas.funke-oss at weidmueller.com> wrote:
>
> From: Lukas Funke <lukas.funke at weidmueller.com>
>
> This adds a new etype 'xilinx_fsbl_auth'. Using this etype it is possible
> to created an authenticated SPL (FSBL in Xilinx terms) for ZynqMP boards.
>
> The etype uses Xilinx Bootgen tools in order to transform the SPL into
> a bootable image and sign the image with a given primary and seconrady
> public key. For more information to signing the FSBL please refer to the
> Xilinx Bootgen documentation.
>
> Here is an example of the etype in use:
>
>     spl {
>         filename = "boot.signed.bin";
>
>         xilinx_fsbl_auth {
>             psk-filename = "psk0.pem";
>             ssk-filename = "ssk0.pem";
>             auth-params = "ppk_select=0", "spk_id=0x00000000";
>
>             u_boot_spl_nodtb {
>             };
>             u_boot_spl_dtb {
>             };
>         };
>     };
>
> For this to work the hash of the primary public key has to be fused
> into the ZynqMP device and authentication (RSA_EN) has to be set.
>
> For testing purposes: if ppk hash check should be skipped one can add
> the property 'fsbl_config = "bh_auth_enable";' to the etype. However,
> this should only be used for testing(!).
>
> Signed-off-by: Lukas Funke <lukas.funke at weidmueller.com>
>
> ---
>
> Changes in v2:
> - Add 'keysrc-enc' property to pass down to Bootgen
> - Improved documentation
> - Use predictable output names for intermediated results
>
>  tools/binman/entries.rst               |  53 ++++++
>  tools/binman/etype/xilinx_fsbl_auth.py | 213 +++++++++++++++++++++++++
>  2 files changed, 266 insertions(+)
>  create mode 100644 tools/binman/etype/xilinx_fsbl_auth.py
>
> diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
> index c3c5bda881..98ec3c82a5 100644
> --- a/tools/binman/entries.rst
> +++ b/tools/binman/entries.rst
> @@ -2462,3 +2462,56 @@ may be used instead.
>
>
>
> +.. _etype_xilinx_fsbl_auth:
> +
> +Entry: xilinx-fsbl-auth: Authenticated SPL for booting Xilinx ZynqMP devices
> +----------------------------------------------------------------------------
> +
> +Properties / Entry arguments:
> +    - auth-params: (Optional) Authentication parameters passed to bootgen
> +    - fsbl-config: (Optional) FSBL parameters passed to bootgen
> +    - keysrc-enc: (Optional) Key source when using decryption engine
> +    - pmufw-filename: Filename of PMU firmware. Default: pmu-firmware.elf
> +    - psk-filename: Filename of primary public key
> +    - ssk-filename: Filename of secondary public key
> +
> +The following example builds an authenticated boot image. The fuses of
> +the primary public key (ppk) should be fused together with the RSA_EN flag.
> +
> +Example node::
> +
> +    spl {
> +        filename = "boot.signed.bin";
> +
> +        xilinx-fsbl-auth {
> +            psk-filename = "psk0.pem";
> +            ssk-filename = "ssk0.pem";
> +            auth-params = "ppk_select=0", "spk_id=0x00000000";
> +
> +            u-boot-spl-nodtb {
> +            };
> +            u-boot-spl-pubkey-dtb {
> +                algo = "sha384,rsa4096";
> +                required = "conf";
> +                key-name = "dev";
> +            };
> +        };
> +    };
> +
> +For testing purposes, e.g. if no RSA_EN should be fused, one could add
> +the "bh_auth_enable" flag in the fsbl-config field. This will skip the
> +verification of the ppk fuses and boot the image, even if ppk hash is
> +invalid.
> +
> +Example node::
> +
> +    xilinx-fsbl-auth {
> +        psk-filename = "psk0.pem";
> +        ssk-filename = "ssk0.pem";
> +        ...
> +        fsbl-config = "bh_auth_enable";
> +        ...
> +    };
> +
> +
> +
> diff --git a/tools/binman/etype/xilinx_fsbl_auth.py b/tools/binman/etype/xilinx_fsbl_auth.py
> new file mode 100644
> index 0000000000..72794ad2bc
> --- /dev/null
> +++ b/tools/binman/etype/xilinx_fsbl_auth.py
> @@ -0,0 +1,213 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +# Copyright (c) 2023 Weidmueller GmbH
> +# Written by Lukas Funke <lukas.funke at weidmueller.com>
> +#
> +# Entry-type module for signed ZynqMP boot images (boot.bin)
> +#
> +
> +import tempfile
> +
> +from collections import OrderedDict
> +
> +from binman import elf
> +from binman.entry import Entry
> +
> +from dtoc import fdt_util
> +
> +from u_boot_pylib import tools
> +from u_boot_pylib import command
> +
> +# pylint: disable=C0103
> +class Entry_xilinx_fsbl_auth(Entry):
> +    """Authenticated SPL for booting Xilinx ZynqMP devices
> +
> +    Properties / Entry arguments:
> +        - auth-params: (Optional) Authentication parameters passed to bootgen
> +        - fsbl-config: (Optional) FSBL parameters passed to bootgen
> +        - keysrc-enc: (Optional) Key source when using decryption engine
> +        - pmufw-filename: Filename of PMU firmware. Default: pmu-firmware.elf
> +        - psk-filename: Filename of primary public key
> +        - ssk-filename: Filename of secondary public key

Can you link to some docs that explains what all of these really are,
e.g. format of files.

> +
> +    The following example builds an authenticated boot image. The fuses of
> +    the primary public key (ppk) should be fused together with the RSA_EN flag.
> +
> +    Example node::
> +
> +        spl {
> +            filename = "boot.signed.bin";
> +
> +            xilinx-fsbl-auth {
> +                psk-filename = "psk0.pem";
> +                ssk-filename = "ssk0.pem";
> +                auth-params = "ppk_select=0", "spk_id=0x00000000";
> +
> +                u-boot-spl-nodtb {
> +                };
> +                u-boot-spl-pubkey-dtb {
> +                    algo = "sha384,rsa4096";
> +                    required = "conf";
> +                    key-name = "dev";
> +                };
> +            };
> +        };
> +
> +    For testing purposes, e.g. if no RSA_EN should be fused, one could add
> +    the "bh_auth_enable" flag in the fsbl-config field. This will skip the
> +    verification of the ppk fuses and boot the image, even if ppk hash is
> +    invalid.
> +
> +    Example node::
> +
> +        xilinx-fsbl-auth {
> +            psk-filename = "psk0.pem";
> +            ssk-filename = "ssk0.pem";
> +            ...
> +            fsbl-config = "bh_auth_enable";
> +            ...
> +        };
> +    """
> +    def __init__(self, section, etype, node):
> +        super().__init__(section, etype, node)
> +        self._auth_params = None
> +        self._entries = OrderedDict()
> +        self._filename = None
> +        self._fsbl_config = None
> +        self._keysrc_enc = None
> +        self._pmufw_filename = None
> +        self._psk_filename = None
> +        self._ssk_filename = None
> +        self.align_default = None
> +        self.bootgen = None
> +        self.required_props = ['psk-filename', 'ssk-filename']
> +
> +    def ReadNode(self):
> +        """Read properties from the xilinx_fsbl_auth node"""
> +        super().ReadNode()
> +        self._auth_params = fdt_util.GetStringList(self._node,
> +                                                   'auth-params')
> +        self._filename = fdt_util.GetString(self._node, 'filename')
> +        self._fsbl_config = fdt_util.GetStringList(self._node,
> +                                                   'fsbl-config')
> +        self._keysrc_enc = fdt_util.GetString(self._node,
> +                                                   'keysrc-enc')
> +        self._pmufw_filename = fdt_util.GetString(self._node,
> +                                                  'pmufw-filename',
> +                                                  'pmu-firmware.elf')
> +        self._psk_filename = fdt_util.GetString(self._node, 'psk-filename',
> +                                            'psk.pem')
> +        self._ssk_filename = fdt_util.GetString(self._node, 'ssk-filename',
> +                                            'ssk.pem')
> +        self.ReadEntries()
> +
> +    def ReadEntries(self):
> +        """Read the subnodes to find out what should go in this image"""
> +        for node in self._node.subnodes:
> +            entry = Entry.Create(self, node)
> +            entry.ReadNode()
> +            self._entries[entry.name] = entry

This looks like a section, so I suspect your class should be a
subclass of entry_Section. Then you can drop this function since it is
already there.

> +
> +    @classmethod
> +    def __ToElf(self, data, output_fname):

Single _

> +        """ Convert SPL object file to bootable ELF file.

Drop space before C

> +
> +        Args:
> +            data (bytearray): u-boot-spl-nodtb + u-boot-spl-pubkey-dtb obj file
> +                                data
> +            output_fname (str): Filename of converted FSBL ELF file
> +        """
> +        platform_elfflags = []
> +
> +        gcc, args = tools.get_target_compile_tool('cc')
> +        args += ['-dumpmachine']
> +        stdout = command.output(gcc, *args)
> +        # split target machine triplet (arch, vendor, os)
> +        arch, _, _ = stdout.split('-')
> +
> +        if arch == 'aarch64':
> +            platform_elfflags = ["-B", "aarch64", "-O", "elf64-littleaarch64"]
> +        elif arch == 'x86_64':
> +            # amd64 support makes no sense for the target platform, but we
> +            # include it here to enable testing on hosts
> +            platform_elfflags = ["-B", "i386", "-O", "elf64-x86-64"]
> +
> +        spl_text_base = hex(elf.GetSymbolAddress(
> +            tools.get_input_filename('spl/u-boot-spl'), ".text"))
> +
> +        # Obj file to swap data and text section (rename-section)
> +        with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-",
> +                                    suffix=".o.tmp",
> +                                    dir=tools.get_output_dir())\
> +                                    as tmp_obj:
> +            input_objcopy_fname = tmp_obj.name
> +            # Align packed content to 4 byte boundary
> +            pad = bytearray(tools.align(len(data), 4) - len(data))
> +            tools.write_file(input_objcopy_fname, data + pad)
> +            # Final output elf file which contains a valid start address
> +            with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-elf-",
> +                                            suffix=".o.tmp",
> +                                            dir=tools.get_output_dir())\
> +                                                as tmp_elf_obj:
> +                input_ld_fname = tmp_elf_obj.name
> +                objcopy, args = tools.get_target_compile_tool('objcopy')
> +                args += ["--rename-section", ".data=.text",
> +                        "-I", "binary"]
> +                args += platform_elfflags
> +                args += [input_objcopy_fname, input_ld_fname]
> +                command.run(objcopy, *args)
> +
> +                ld, args = tools.get_target_compile_tool('ld')
> +                args += [input_ld_fname, '-o', output_fname,
> +                         "--defsym", f"_start={spl_text_base}",
> +                         "-Ttext", spl_text_base]
> +                command.run(ld, *args)
> +
> +    def ObtainContents(self, skip_entry=None, fake_size=0):
> +        """ Pack node content, and create bootable, signed ZynqMP boot image

Drop space before first P

> +
> +        The method collects the content of this node (usually SPL + dtb) and
> +        converts them to an ELF file. The ELF file is passed to the
> +        Xilinx bootgen tool which packs the SPL ELF file together with
> +        Platform Management Unit (PMU) firmware into a bootable image
> +        for ZynqMP devices. The image is signed within this step.
> +
> +        The result is a bootable, authenticated SPL image for Xilinx ZynqMP
> +        devices.
> +
> +        """
> +        bootbin_fname = self._filename if self._filename else \
> +                            tools.get_output_filename(
> +                            f'boot.{self.GetUniqueName()}.bin')
> +
> +        pmufw_elf_fname = tools.get_input_filename(self._pmufw_filename)
> +        psk_fname = tools.get_input_filename(self._psk_filename)
> +        ssk_fname = tools.get_input_filename(self._ssk_filename)
> +        fsbl_config = ";".join(self._fsbl_config) if self._fsbl_config else None
> +        auth_params = ";".join(self._auth_params) if self._auth_params else None
> +
> +        spl_elf_fname = tools.get_output_filename('u-boot-spl-pubkey.dtb.elf')
> +
> +        # Collect node contents. This is usually the SPL concatenated
> +        # with the SPL dtb (device tree).
> +        data, _, _ = self.collect_contents_to_file(
> +                                self._entries.values(), 'spl')

data = self.collect_contents_to_file(...)[0]

> +
> +        # We need to convert to node content (see above) into an ELF
> +        # file in order to be processed by bootgen.
> +        self.__ToElf(bytearray(data), spl_elf_fname)
> +
> +        # Call Bootgen in order to sign the SPL
> +        self.bootgen.sign('zynqmp', spl_elf_fname, pmufw_elf_fname,
> +                        psk_fname, ssk_fname, fsbl_config,
> +                        auth_params, self._keysrc_enc, bootbin_fname)
> +
> +        self.SetContents(tools.read_file(bootbin_fname))
> +
> +        return True

If you convert this class to a sectoin then should you implement
BuildSectionContents() instead of ObtainContents()

> +
> +    # pylint: disable=C0116
> +    def AddBintools(self, btools):
> +        super().AddBintools(btools)
> +        for entry in self._entries.values():
> +            entry.AddBintools(btools)
> +        self.bootgen = self.AddBintool(btools, 'bootgen')
> --
> 2.30.2
>

I notice that test coverage is not 100% with these patches. Please use
'binman test -T' to see what is missing and add more test cases to the
bottom of ftest.py

Regards,
Simon


More information about the U-Boot mailing list