Securing U-Boot with FIT Signature and Key Injection on ZynqMP

Securing U-Boot with FIT Signature and Key Injection on ZynqMP

This blog post provides a comprehensive guide to implementing secure boot with U-Boot using FIT image signatures and RSA public key injection, targeting Xilinx ZynqMP platforms such as the ZCU102. It includes theory, differences between DTB embedding methods, and a practical Yocto-based implementation.

Background: FIT Signing and Public Key Injection

Modern U-Boot supports signed FIT images to enforce boot-time validation of kernel and other binaries. The verification is done using an RSA public key embedded in U-Boot’s DTB.

To enable this:

  • You create a signed FIT image (fitImage.itb) using mkimage and a private key.
  • You inject the corresponding public key (X.509) into u-boot.dtb using mkimage -K.
  • U-Boot, when configured correctly, will refuse to boot unsigned or invalid images.

Embedded vs Separated DTB in U-Boot

U-Boot can be built in two modes regarding the Device Tree:

Mode CONFIG Option Description
Embedded CONFIG_OF_EMBED=y DTB is compiled directly into the U-Boot binary
Separate CONFIG_OF_SEPARATE=y DTB is loaded separately; must be deployed explicitly

For signature injection to take effect, you must inject the key into the DTB actually used at runtime. If using OF_EMBED, you must re-link U-Boot after modifying the DTB.

Key Injection Workflow Summary

  1. Generate keys (one-time or for dev):

    openssl genpkey -algorithm RSA -out dev.key -pkeyopt rsa_keygen_bits:2048
    openssl req -new -x509 -key dev.key -out dev.crt -subj "/CN=dev/"
    
  2. Prepare a minimal fit.its for key injection (not full FIT):

    /dts-v1/;
    / {
        description = "Key Injection Only";
        #address-cells = <1>;
        images {
            dummy {
                data = /incbin/("/dev/null");
                type = "kernel";
                arch = "arm";
                os = "linux";
                compression = "none";
                hash@1 { algo = "sha256"; };
            };
        };
        signatures {
            sig1 {
                algo = "sha256,rsa2048";
                key-name-hint = "dev";
                sign-images = "dummy";
            };
        };
    };
    
  3. Inject the public key into the DTB:

    mkimage -f fit.its -k keys/ -K u-boot.dtb
    

    ⚠️ Do not pass an output image name like fit.itb if you’re only injecting the key!

  4. Verify key injection:

    fdtdump u-boot.dtb | grep -A20 signature
    

    You should see the RSA modulus, exponent, and key-name-hint.

  5. (Optional) Create a signed FIT image:

    mkimage -f full-fit.its -k keys/ -K u-boot.dtb signed.itb
    

Yocto Integration Notes

  • Use do_compile:append() to build the DTB and inject the key after DTB exists.
  • Do not run oe_runmake after the injection, or it will overwrite it.
  • In do_deploy:append(), install the final u-boot.dtb into ${DEPLOYDIR} under a unique name (u-boot-injected.dtb).
  • If using CONFIG_OF_EMBED=y, ensure U-Boot is rebuilt after injection so the DTB is re-linked into it.

Yocto Integration Notes

To integrate FIT signature and key injection into a Yocto build, you can append logic to your u-boot-xlnx_%.bbappend using do_compile:prepend() and do_deploy:append().

Here is a practical example of the injection logic:

do_compile:prepend() {
    # Generate key if missing (in real systems: use secure storage!)
    if [ ! -f "${FIT_KEY_DIR}/${FIT_KEY_NAME}.key" ]; then
        mkdir -p ${FIT_KEY_DIR}
        openssl genpkey -algorithm RSA -out ${FIT_KEY_DIR}/${FIT_KEY_NAME}.key -pkeyopt rsa_keygen_bits:2048
        openssl req -new -x509 -key ${FIT_KEY_DIR}/${FIT_KEY_NAME}.key -out ${FIT_KEY_DIR}/${FIT_KEY_NAME}.crt -subj "/CN=dev/"
    fi

    bbnote "Step 1: Forcing early build of u-boot.dtb"
    oe_runmake ${UBOOT_DTB_BINARY}

    bbnote "Step 2: Injecting FIT public key into DTB"
    touch ${TMP_NULL}

    # This command embeds the public key into u-boot.dtb. It will fail if fit.its is malformed or the key is invalid.
    ${B}/tools/mkimage -f ${WORKDIR}/fit.its -k ${WORKDIR}/keys -K ${B}/${UBOOT_DTB_BINARY} ${WORKDIR}/fit.itb || die "mkimage key injection failed"

    bbnote "Step 3: Generate boot.scr from boot.cmd"
    ${B}/tools/mkimage -A arm -T script -C none -n "Boot Script"         -d ${WORKDIR}/boot.cmd ${WORKDIR}/boot.scr
}

In this script:

  • The keys are generated if they don’t exist
  • The DTB is explicitly built to ensure it exists before the injection step
  • The public key is injected via mkimage -K
  • A boot.scr script is generated from boot.cmd

Make sure to copy the final u-boot.dtb to ${DEPLOYDIR} in do_deploy:append() if you’re using CONFIG_OF_SEPARATE=y. For CONFIG_OF_EMBED=y, ensure U-Boot is rebuilt after injection.

U-Boot Configuration

To enable FIT signature support in U-Boot, add the following to your configuration fragment or defconfig:

CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_RSA=y
CONFIG_OF_CONTROL=y
CONFIG_OF_EMBED=y  # or CONFIG_OF_SEPARATE=y depending on your setup

These enable U-Boot to validate FIT signatures using embedded public keys. Ensure that your u-boot.dtb is actually used at boot by U-Boot.

Common Pitfalls

  • mkimage with -K must not include a FIT output filename if you’re only injecting a key.
  • Using /dev/null as a dummy image works only without configurations {} block.
  • Messages like “Public key written…” can be misleading if private key is missing — verify with fdtdump.

Conclusion

This guide walks you through enabling FIT image signing in U-Boot with RSA public key verification, focusing on practical Yocto integration. It’s essential for secure embedded systems boot chains, especially in safety- and security-critical environments.

Comments
comments powered by Disqus