Nitrogen93 SMARC - Yocto U-Boot Secure Boot Guide (AHAB, i.MX93)

  1. Variables - Define Once and Reuse
  2. Concept Overview
  3. Prerequisites
    1. Host Packages:
    2. Install NXP SPSDK for parsing and fuse helper:
    3. Install NXP CST with AHAB support:
    4. Directory layout for keys:
  4. Key Generation and SRK Table
    1. Generate an ECC P-384 PKI tree and SRK table with CST:
  5. Yocto Integration for Nitrogen93
    1. Sync Ezurio BSP and set build env:
    2. Local.conf deltas to build wic with raw AHAB container gap and to copy signed FIT into /boot:
    3. Provide the .wks to place the AHAB container at 0x400 and reserve 8 MiB:
  6. Build U-Boot and Produce Unsigned flash.bin
    1. Build U-Boot from Ezurio tree used by Yocto:
  7. Sign AHAB Containers with CST
    1. Compute absolute signature offsets:
    2. Create CSF for container 1 and sign:
    3. Repeat for container 2 and produce final:
    4. Verify the signed image structure:
  8. Prepare Boot Media with Yocto WIC
    1. Produce bootcontainer-1k-offset.bin for the rawcopy section:
    2. Build image.wic:
    3. Flash the .wic to eMMC:
    4. Double checks on target media:
  9. Verification Before Fusing
    1. Boot and check AHAB events in U-Boot:
    2. Validate signed FIT image in U-Boot:
    3. Perform negative tests while the device is still in OEM Open state:
  10. Troubleshooting Common Issues and Fixes:
  11. Fuse Programming
    1. Pre-fuse checklist:
    2. Compute SRKH values to program:
    3. Create an NXPELE fuse script from SRK_HASH.txt:
    4. Program SRKH using NXPELE:
    5. Set device to closed after final validation:
  12. Post-Fuse Validation
    1. Power cycle tests:
    2. FIT verification:
  13. Recovery Procedures
    1. Before fusing:
    2. After fusing:
  14. Operational Guidance
    1. Key rotation and revocation:
    2. Anti-rollback:
    3. Build provenance:
  15. Appendix - Alternative Methods & Checklists
    1. Factory flow using UUU for containers and wic only for partitions:
    2. Adding OP-TEE:
    3. Pre-flight Checklist:
    4. Post-fuse Checklist:
    5. Quick Start:
  16. References

Notes:

  • This is a guide intended for use with a Linux build machine
  • Alternative approaches, checklists, and a quick-start guide are located in the Appendix.
  • Fuses are permanent - Fusing should only take place after thorough testing.

Variables - Define Once and Reuse

#Host and build
WORK_DIR=$HOME/ezurio-secureboot
BSP_DIR=$WORK_DIR/boundary-bsp-platform
BUILD_DIR=$WORK_DIR/build-n93
DEPLOY_DIR=$BUILD_DIR/tmp/deploy/images/nitrogen93

#Keys and tools
KEY_DIR=/opt/ezurio/keys
CST_DIR=/opt/nxp/cst # contains linux64/bin/{cst,srktool} and keys/
SPSDK_VENV=$WORK_DIR/spsdk-venv

#Target
MACHINE=nitrogen93
DISTRO=fsl-imx-xwayland-boundary
IMAGE=boundary-image-full
WKS_FILE=nitrogen93-emmc-ahab.wks
EMMC_DEV=/dev/mmcblkX # replace X with your device node

#U-Boot outputs used by signing
FLASH_UNSIGNED=$WORK_DIR/u-boot/flash.bin
FLASH_SIGNED=$WORK_DIR/u-boot/flash_final_signed.bin

#Convenience
LOADADDR=0x80280000

Concept Overview

  1. AHAB authenticates containers using SRK stored in fuses. ROM on A55 loads containers one by one and asks ELE to authenticate them. For single boot mode on i.MX93 the typical layout is three containers: C0 optional ELE FW, C1 SPL and CM33 image, C2 TF-A and U-Boot.
  2. The SRK table is embedded in the container signature block. The SRK table hash is programmed to fuses to establish the root of trust. Only SRK is supported on current i.MX93 firmware.
  3. The first OEM container header must start at 0x400 from the media start. Do not overwrite the MBR at 0x000. Use wic rawcopy to place the boot container at offset 1 KiB.

Prerequisites

Host Packages:

sudo apt-get update
sudo apt-get install -y git make gcc g++ python3 python3-venv python3-pip \
device-tree-compiler bmap-tools gdisk util-linux u-boot-tools

Install NXP SPSDK for parsing and fuse helper:

python3 -m venv $SPSDK_VENV
. $SPSDK_VENV/bin/activate
pip install --no-cache-dir spsdk
deactivate

Install NXP CST with AHAB support:

  • Unpack CST to $CST_DIR. Ensure linux64/bin/cst and linux64/bin/srktool exist.
  • Confirm version prints usage
$CST_DIR/linux64/bin/cst -h | head -n1

Directory layout for keys:

sudo mkdir -p $KEY_DIR && sudo chown -R $USER:$USER $KEY_DIR
tree -L 2 $KEY_DIR || true

Key Generation and SRK Table

Generate an ECC P-384 PKI tree and SRK table with CST:

The i.MX93 platform requires exactly 4 SRK certificates for secure boot using AHAB. These are used to build the SRK table and hash stored in fuses.

Run the key generation script and follow the prompts:

cd $CST_DIR/keys
./ahab_pki_tree.sh
# Answer prompts
#- Use existing CA key: n
#- Use Elliptic Curve Cryptography: y
#- ECC length: p384
#- Digest algorithm: sha384
#- PKI tree duration: 10
#- SRK certificates have CA flag: n

This creates the PKI directory tree and 4 SRK certificates at:

./crts/SRK1_crt.pem
./crts/SRK2_crt.pem
./crts/SRK3_crt.pem
./crts/SRK4_crt.pem

Build SRK_Table.bin and SRK hash for fusing:

The tree above creates crts/SRK*_crt.pem files. Use srktool to emit the SRK table and SRK hash used for fuses.

cd $CST_DIR/keys
$CST_DIR/linux64/bin/srktool \
--hab ahab \
--hash sha256 \
--table SRK_Table.bin \
--efuses SRK_efuse_fuse.bin \
--srkcrts
./crts/SRK1_crt.pem,./crts/SRK2_crt.pem,./crts/SRK3_crt.pem,./crts/SRK4_crt.pem \
--out_hash SRK_HASH.txt

For i.MX93, the SRK hash burned in fuses is a SHA-256 hash of the SRK table. This hash occupies 8 words (256 bits total) and is used as the root of trust by the ROM.

Note: Even though the SRK certificates are signed using SHA-384 (to match ECC P‑384), the SRK table hash must always be SHA-256, as expected by the i.MX93 ROM. This is configured via --hash sha256 in srktool.

Yocto Integration for Nitrogen93

Sync Ezurio BSP and set build env:

mkdir -p $WORK_DIR && cd $WORK_DIR
repo init -u https://github.com/boundarydevices/boundary-bsp-platform.git -b ezurio-imx-6.6.52-2.2.1
repo sync -j$(nproc)
source fsl-setup-release.sh -b $(basename $BUILD_DIR) -e $MACHINE -d $DISTRO

Local.conf deltas to build wic with raw AHAB container gap and to copy signed FIT into /boot:

Append this to $BUILD_DIR/conf/local.conf:

IMAGE_FSTYPES += "wic wic.bmap"
WKS_FILE = "${WKS_FILE}"
WKS_FILE_DEPENDS += " u-boot:do_deploy virtual/kernel:do_deploy"
# copy signed FIT and DTBs into /boot
IMAGE_BOOT_FILES += "fitImage.signed;fitImage imx93-nitrogen-smarc.dtb;imx93-nitrogen-smarc.dtb"

Provide the .wks to place the AHAB container at 0x400 and reserve 8 MiB:

Create $BUILD_DIR/conf/wic/${WKS_FILE}

#nitrogen93-emmc-ahab.wks

#raw boot container region at 1 KiB from media start
part bootloader --source rawcopy \
--sourceparams="file=bootcontainer-1k-offset.bin" \
--ondisk mmcblk --no-table --offset 1 --fixed-size 8192

#/boot holds signed FIT and DTBs
part /boot --source bootimg-partition --ondisk mmcblk \
--fstype=vfat --label boot --align 8192 --size 128

#rootfs
part / --source rootfs --ondisk mmcblk \
--fstype=ext4 --label rootfs --align 8192 --size 204
--offset and --fixed-size are specified in KiB, while --align and --size are in MiB. In this layout, the first 8 MiB are reserved for raw boot containers, starting at offset 1 KiB (0x400). Ensure all partitions start after this reserved region.

Build U-Boot and Produce Unsigned flash.bin

Build U-Boot from Ezurio tree used by Yocto:

cd $WORK_DIR
git clone https://github.com/boundarydevices/u-boot.git
cd u-boot
git checkout ezurio-lf_v2024.04
make imx93_nitrogen_smarc_defconfig
export CROSS_COMPILE=aarch64-linux-gnu-
make -j$(nproc)

Expected artifacts:

  • flash.bin
  • spl/u-boot-spl.cfgout
  • u-boot-container.cfgout

Note: CONFIG_SPL_LOAD_IMX_CONTAINER remains enabled for AHAB. The cfgout files expose container offsets used below.

Parse flash.bin to confirm layout and record offsets:

. $SPSDK_VENV/bin/activate
nxpimage ahab parse -b $FLASH_UNSIGNED -f mimx9352 -o $WORK_DIR/parse_unsigned
deactivate

Expected parse excerpt:

CST: CONTAINER 0 offset: 0x400
CST: CONTAINER 0: Signature Block: offset is at 0x490

These offsets drive CSF fields for signing:

The first offset (e.g., 0x400) points to the container header in the image (container start).
The second offset (e.g., 0x490) is the location of the container’s signature block relative to the image.These two offsets must match the actual layout of the signed image exactly, or the signature will fail to verify. Always re-derive these offsets from the output of nxpimage ahab parse or the .cfgout files generated by U‑Boot.

Sign AHAB Containers with CST

Compute absolute signature offsets:

Example from nxpimage output or *.cfgout:

C1_abs_start=0x400
C1_sig_rel=0x90
C1_sig_abs=$((C1_abs_start + C1_sig_rel)) # 0x490

Create CSF for container 1 and sign:

Create $WORK_DIR/flash-c1.csf that contains:

[Header]
Target = AHAB
Version = 1.0

[Install SRK]
File = "./SRK_Table.bin"
Source = "./crts/SRK1_crt.pem"
Source index = 0
Source set = OEM
Revocations = 0x0

[Authenticate Data]
File = "./flash.bin"
Offsets = 0x400 0x490

Then run CST:

cd $CST_DIR/keys
cp $FLASH_UNSIGNED ./flash.bin
$CST_DIR/linux64/bin/cst -i flash-c1.csf -o flash_after_c1_signed.bin

Expected output tail:

CSF Processed successfully and signed image available in flash_after_c1_signed.bin

Repeat for container 2 and produce final:

  • Determine C2_abs_start and C2_sig_abs exactly as above
  • Create flash-c2.csf pointing to flash_after_c1_signed.bin
  • Run CST again to produce flash_final_signed.bin
$CST_DIR/linux64/bin/cst -i flash-c2.csf -o flash_final_signed.bin

Verify the signed image structure:

. $SPSDK_VENV/bin/activate
nxpimage ahab parse -b flash_final_signed.bin -f mimx9352 -o $WORK_DIR/parse_final
deactivate

Expected results:

  • ELE container remains signed by NXP
  • OEM containers show SRK table present and signature parsed without errors.

Prepare Boot Media with Yocto WIC

Produce bootcontainer-1k-offset.bin for the rawcopy section:

Most flash_final_signed.bin files include a 1 KiB leading pad so C0 header lands at 0x400 internally. Trim the first 1 KiB so the first byte of the file is the AHAB header and wic will place it at 1 KiB media offset.

cd $WORK_DIR/u-boot
dd if=flash_final_signed.bin of=bootcontainer-1k-offset.bin bs=1K skip=1

Build image.wic:

cd $BUILD_DIR
bitbake wic-tools
bitbake ${IMAGE}

Flash the .wic to eMMC:

Option 1 - bmaptool:

sudo bmaptool copy ${DEPLOY_DIR}/${IMAGE}-${MACHINE}.wic.gz ${EMMC_DEV}
sudo partprobe ${EMMC_DEV}

Option 2 - dd:

zcat ${DEPLOY_DIR}/${IMAGE}-${MACHINE}.wic.gz | sudo dd of=${EMMC_DEV} bs=4M
iflag=fullblock conv=fsync status=progress
sudo partprobe ${EMMC_DEV}

Double checks on target media:

Confirm the AHAB container header is at media offset 0x400:

sudo dd if=${EMMC_DEV} of=first8M.bin bs=1M count=8 status=none
. $SPSDK_VENV/bin/activate
nxpimage ahab parse -b first8M.bin -f mimx9352 -o $WORK_DIR/check_out
deactivate

Verification Before Fusing

Boot and check AHAB events in U-Boot:

#interrupt autoboot
ahab_status

Expected output when clean:

Lifecycle: 0x00000008, OEM Open
No Events Found!
ahab_status displays the device lifecycle state and any secure boot event logs detected by the ROM or ELE firmware.
  • "OEM Open" indicates the device is not yet closed and still allows reprogramming.
  • "No Events Found!" confirms that all containers were authenticated successfully and no tamper or verification failures occurred.

This output is critical for confirming readiness before fusing. If events are reported here, do not proceed to fuse SRKH.

Validate signed FIT image in U-Boot:

Interrupt U-Boot autoboot, then manually load and verify the FIT image:

fatload mmc 2:1 ${loadaddr} fitImage
iminfo -v ${loadaddr}
  • mmc 2:1 refers to eMMC device 2, partition 1. Adjust if your board uses a different MMC index.
  • ${loadaddr} should be set appropriately (e.g., 0x80280000).

Expected output:

Verifying Hash Integrity ... OK
Verify OK

This confirms that the FIT image was signed correctly and its contents (kernel, DTBs) passed signature verification in U-Boot.

If the image is unsigned or tampered, verification will fail with a hash or signature error.

Perform negative tests while the device is still in OEM Open state:

  • Wrong Key Test:
    Re-sign one container (e.g., container 2) using a different SRK set and flash it over the original.
    Boot the device and run ahab_status.
    Expected AHAB event:
    0x0087FA00 — Container authentication failed due to SRK hash mismatch (wrong key or SRK fuses not yet programmed).
  • Tamper Test:
    Modify one byte inside the signed container binary (e.g., flash_final_signed.bin) and reflash it.
    Boot the device and run ahab_status.
    Expected AHAB event:
    0x0087EE00 — Container authentication failed because signature verification failed (tampered content).

These tests confirm the boot ROM is enforcing integrity and authenticity checks before you commit to irreversible fusing.

Troubleshooting Common Issues and Fixes:

  • Signature offset mismatch -> re-derive Offsets from nxpimage parse logs and *.cfgout precisely.
  • No AHAB events but boot fails -> confirm ELE is optional on i.MX93. If ELE container is present it must be authentic.
  • In OEM open some integrity failures may be tolerated, so rely on ahab_status output to confirm issues before fusing.

Fuse Programming

!!!WARNING: FUSES ARE PERMANENT!!!
Incorrect programming can brick the device.
Proceed only after section 8 passes on multiple power cycles.

Pre-fuse checklist:

  • Boot succeeds from eMMC with signed containers.
  • ahab_status shows “No Events Found”.
  • Wrong-key and tamper tests produced expected AHAB events.
  • Signed FIT verification succeeds.

Compute SRKH values to program:

Use SRK_HASH.txt emitted in section 3B. The hash is 256 bits split into 8 words for i.MX9x SRKH.

Create an NXPELE fuse script from SRK_HASH.txt:

Example SRK_HASH.txt content:

srk_hash=0x772e1009c66d2dd57ab25dc6a20409b686676463983e7e54cf24e55b38644f37

Create $WORK_DIR/srkh_nxpele.bcf:

#nxpele AHAB SRK fuses programming script
#Family: mimx9352

#SRKH words derived from SRK_HASH.txt
write-fuse --index 128 --data 0x772e1009
write-fuse --index 129 --data 0xc66d2dd5
write-fuse --index 130 --data 0x7ab25dc6
write-fuse --index 131 --data 0xa20409b6
write-fuse --index 132 --data 0x86676463
write-fuse --index 133 --data 0x983e7e54
write-fuse --index 134 --data 0xcf24e55b
write-fuse --index 135 --data 0x38644f37

Important: The example uses fuse indices 128–135 to program the SRKH (8 x 32-bit words).
These indices are correct for many i.MX93 platforms, but you must verify the correct eFuse word indices for your exact SoC revision and board variant using the NXP reference manual and/or board schematic.

Never program fuses unless you're certain of the index locations. Some fuse words may be reserved or used for other functions and incorrect programming can permanently brick the device.

Program SRKH using NXPELE:

. $SPSDK_VENV/bin/activate
nxpele -v -f mimx9352 batch $WORK_DIR/srkh_nxpele.bcf
deactivate

Expected output shows each write-fuse with success.

Set device to closed after final validation:

Keep device open until post-fuse validation is complete. The close step is platform specific and controlled by ELE lifecycle APIs.

Post-Fuse Validation

Power cycle tests:

  • Cold boot three times - Confirm ahab_status shows “No Events Found”.
  • Re-run negative test with a differently signed container. Expected: 0x0087FA00 wrong key event and boot blocked.

FIT verification:

fatload mmc 2:1 ${loadaddr} fitImage
iminfo -v ${loadaddr}

Recovery Procedures

Before fusing:

Keep an unsigned flash.bin as fallback. You can always reflash signed or unsigned containers with UUU while device is open.

After fusing:

If SRKH does not match the container SRK table the ROM will reject the boot container. Recovery requires re-signing with the fused SRK set. ELE lifecycle return paths may require OEM signed messages and are outside the scope of this guide.

Operational Guidance

Key rotation and revocation:

Maintain all four SRK certificates in your PKI tree. i.MX93 allows revocation of SRK1 through SRK3 using the SRK revoke bits.

Important:

  • The SRK used to sign the currently booting container cannot be revoked.
  • SRK revocation is only applied after successful authentication of a container carrying a valid SRK table and revoke command.
  • Revoke bits are programmed by including a Revoke directive in the CST script and signing a new container using a non-revoked key.

This mechanism allows controlled key rotation while preserving the chain of trust.

Anti-rollback:

Plan software version fields in containers. AHAB validates SW version fields when enabled.

Build provenance:

Keep hashes of flash_final_signed.bin and fitImage.signed plus the CST input files and SRK table for audit.

Appendix - Alternative Methods & Checklists

Factory flow using UUU for containers and wic only for partitions:

uuu -b emmc flash_final_signed.bin
LOOP=$(sudo losetup -Pf --show ${DEPLOY_DIR}/${IMAGE}-${MACHINE}.wic)
sudo sfdisk -d ${LOOP} | sudo sfdisk ${EMMC_DEV}
sudo partprobe ${EMMC_DEV}
sudo dd if=${LOOP}p1 of=${EMMC_DEV}p1 bs=4M conv=fsync status=progress
sudo dd if=${LOOP}p2 of=${EMMC_DEV}p2 bs=4M conv=fsync status=progress
sudo losetup -d ${LOOP}

Adding OP-TEE:

If required, include OP-TEE in C2 with TF-A and U-Boot and re-sign. Maintain offsets and limits.

Pre-flight Checklist:

  • Host packages and tools installed.
  • PKI tree and SRK table generated.
  • flash_final_signed.bin parsed cleanly.
  • bootcontainer-1k-offset.bin created and referenced by wks.
  • .wic flashed and container header confirmed at 0x400.
  • Board boots and ahab_status shows No Events Found.

Post-fuse Checklist:

  • SRKH programmed using NXPELE script and verified.
  • Board boots and ahab_status shows No Events Found.
  • Wrong-key test blocked as expected.
  • FIT signature verification passes.

Quick Start:

  1. Generate keys and SRK table.
  2. Build U-Boot.
  3. Sign containers with CST.
  4. Build Yocto image and create .wic.
  5. Verify boot and ahab_status.
  6. Program SRKH using NXPELE only after all tests pass.
  7. Close device lifecycle when approved.

References