[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