Boot a signed Zephyr image with U-Boot on Arm Cortex-A
Introduction
Understand where U-Boot verifies Zephyr in the Cortex-A boot chain
Set up the host tools for your target
Build a Zephyr image that U-Boot can start
Create signing keys and sign the Zephyr image into a FIT
Build U-Boot with the public key and a boot command that fails closed
Boot the signed Zephyr FIT with U-Boot on QEMU or the TI AM62L EVM
(Optional) Test that U-Boot refuses a wrong key and a tampered image
Review the Zephyr FIT verification boundary and production needs
Next Steps
Boot a signed Zephyr image with U-Boot on Arm Cortex-A
Introduction
Understand where U-Boot verifies Zephyr in the Cortex-A boot chain
Set up the host tools for your target
Build a Zephyr image that U-Boot can start
Create signing keys and sign the Zephyr image into a FIT
Build U-Boot with the public key and a boot command that fails closed
Boot the signed Zephyr FIT with U-Boot on QEMU or the TI AM62L EVM
(Optional) Test that U-Boot refuses a wrong key and a tampered image
Review the Zephyr FIT verification boundary and production needs
Next Steps
Build the U-Boot host tools
Open a terminal and load the environment file for your target:
- QEMU:
source $HOME/zephyr-secure-boot/env-qemu.sh - AM62L evaluation module (EVM):
source $HOME/zephyr-secure-boot/env-am62l.sh
Build mkimage to create and sign Flattened Image Tree (FIT) images, and fit_check_sign to verify them on the host. Use the U-Boot source tree for your target so that the host tools match the U-Boot version that you’ll run.
Before building the tools, generate .config from the default configuration for your target. Run the target named by UBOOT_DEFCONFIG in your environment file:
make -C $UBOOT_SRC O=$UBOOT_OUT CROSS_COMPILE=$CROSS CC="$UBOOT_CC" $UBOOT_DEFCONFIG
UBOOT_CC stores the compiler command. For QEMU, it names the cross compiler installed from Ubuntu. For the AM62L EVM, it also includes --sysroot to locate the headers and libraries in the SDK. Keep CC="$UBOOT_CC" on every make command, including when you build U-Boot later.
Then, build the host tools:
make -C $UBOOT_SRC O=$UBOOT_OUT CROSS_COMPILE=$CROSS CC="$UBOOT_CC" tools
Both tools are written to $UBOOT_OUT/tools/. Check that mkimage runs:
$UBOOT_OUT/tools/mkimage -V
The output is similar to:
mkimage version 2026.01-g5fb294342321
The version string identifies your U-Boot source tree. The example output indicates the TI tree for the AM62L EVM, version 2026.01. For the QEMU setup, expect mkimage version 2025.07.
If the tools build reports a missing pylibfdt dependency, swig, or gnutls/gnutls.h, check the packages installed during
host-tool setup
. Run the shared apt install command again, then retry the tools build.
Create two signing key pairs
Use the private key to sign the image and keep it on the host. You’ll embed only the public key in U-Boot.
Create two key pairs. You’ll configure U-Boot to trust key-a and leave out the public key for key-b. The optional
wrong-key and tampered-image tests
use key-b to demonstrate rejection of an image signed with an untrusted key.
Generate both pairs with OpenSSL:
cd $KEYS
for k in key-a key-b; do
openssl genpkey -algorithm RSA -quiet -out $k.key \
-pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537
openssl req -batch -new -x509 -key $k.key -out $k.crt -subj "/CN=$k" -days 3650
done
ls $KEYS
The expected output is:
key-a.crt key-a.key key-b.crt key-b.key
mkimage -k <dir> expects the following files in the key directory:
<name>.keyholds the private key.<name>.crtis a self-signed certificate containing the public key.- The shared file-name stem,
<name>, matcheskey-name-hintin the FIT source.
These 2048-bit keys are generated on the build host for this demonstration. The later section on the Zephyr FIT verification boundary and production needs covers production key handling.
Write the FIT source
Describe the FIT in an image tree source (.its) file. mkimage compiles and signs it to produce an image tree blob (.itb). Create the source with the following command. The unquoted EOF delimiter lets the shell expand $WORK and $ZEPHYR_ADDR:
cat > $FIT/zephyr-a.its <<EOF
/dts-v1/;
/ {
description = "Zephyr RTOS, signed with key-a";
#address-cells = <1>;
images {
kernel-1 {
description = "Zephyr RTOS image";
data = /incbin/("$WORK/zephyrproject/applications/hello/build/primary/zephyr/zephyr.bin");
type = "kernel";
arch = "arm64";
os = "u-boot";
compression = "none";
load = <$ZEPHYR_ADDR>;
entry = <$ZEPHYR_ADDR>;
hash-1 { algo = "sha256"; };
};
};
configurations {
default = "conf-1";
conf-1 {
description = "Zephyr on Cortex-A";
kernel = "kernel-1";
signature-1 {
algo = "sha256,rsa2048";
key-name-hint = "key-a";
sign-images = "kernel";
};
};
};
};
EOF
The FIT source defines how U-Boot handles the payload:
os = "u-boot"marks Zephyr as a standalone program, so U-Boot verifies and copies it without looking for a Linux kernel header or device tree.signature-1sits under the configuration, because therequired = "conf"rule checks the configuration U-Boot boots. Withsign-images = "kernel", the signature also covers thehash-1node of the image, andkey-name-hintnames the key in$KEYS.loadandentryareZEPHYR_ADDR, where U-Boot copies the verified payload and wheregojumps.
Sign the image
Compile and sign the FIT. -k $KEYS tells mkimage where the private key is:
$UBOOT_OUT/tools/mkimage -f $FIT/zephyr-a.its -k $KEYS $FIT/zephyr-a.itb
mkimage prints the FIT contents. Your timestamps, Hash value, and Sign value will differ. The output is similar to:
FIT description: Zephyr RTOS, signed with key-a
Created: Fri Sep 11 19:14:00 2026
Image 0 (kernel-1)
Description: Zephyr RTOS image
Created: Fri Sep 11 19:14:00 2026
Type: Kernel Image
Compression: uncompressed
Data Size: 58340 Bytes = 56.97 KiB = 0.06 MiB
Architecture: AArch64
OS: U-Boot
Load Address: 0x82000000
Entry Point: 0x82000000
Hash algo: sha256
Hash value: 1d1d375f14c3354579e9e5310986a69b831bcd1944cd6b35aa8e83632b658166
Default Configuration: 'conf-1'
Configuration 0 (conf-1)
Description: Zephyr on Cortex-A
Kernel: kernel-1
Sign algo: sha256,rsa2048:key-a
Sign value: 6d5f9933722189a0dcee58791243429d0bf28fe0dce96ac093da23c1f24ead70...
Timestamp: Fri Sep 11 19:14:00 2026
Signature written to '/home/user/zephyr-secure-boot/fit/zephyr-a.itb', node '/configurations/conf-1/signature-1'
The Sign value is shortened for readability.
Check Sign algo: sha256,rsa2048:key-a and the final Signature written to ... line. They confirm that mkimage used key-a and wrote its signature into conf-1. Hash value is the SHA-256 hash of zephyr.bin, which U-Boot recomputes during verification.
The example output is for the AM62L EVM build. For the QEMU build, expect Data Size: 37040 Bytes and Load Address: 0x40000000.
Omit mkimage -K when signing this FIT. You’ll add the public key to the U-Boot control device tree during the build.
What you’ve accomplished and what’s next
You’ve built the U-Boot host tools, created two key pairs, and packaged Zephyr in the signed FIT $FIT/zephyr-a.itb.
Next, you’ll build U-Boot with the trusted public key and a boot command that starts Zephyr only after verification succeeds.