From a lab unit to a production Nerves system on the IOT-DIN-IMX8PLUS
The first series proved HAB secure boot on a disposable lab unit running stock Linux. This one carries it into a real Nerves system — in a fork, so the build that runs in the field is never at risk. Same board, different starting point, and several things that do not transfer.
The first series was about one disposable CompuLab IOT-DIN-IMX8PLUS, kept deliberately wide open, with nothing depending on it, so I could make every possible mistake without consequence. That was the right way to actually learn how HAB works rather than to follow a guide and hope.
This series is the second half: taking what that proved and applying it to a real Nerves system — the one from the iotdin series, which is the system that actually runs the application.
The rule I set before starting
Nothing in this series touches the working Nerves system.
All of it happens in a fork — nerves_system_iotdin_imx8p_secure, copied from
nerves_system_iotdin_imx8p and tracked separately:
rsync -a --delete \
--exclude='_build/' --exclude='deps/' --exclude='.nerves/' --exclude='nerves/' \
../nerves_system_iotdin_imx8p/ src/nerves_system_iotdin_imx8p_secure/
The original stays read-only ground truth. If everything here turns out to be a dead end, the system that works is untouched, and I have lost nothing but time.
That is not caution for its own sake. Secure boot changes the bootloader, and a bootloader is the one component where a mistake is not "the app crashed" but "the board does not start". There is no reason for the working system and the experiment to be the same files.
Same board, so what actually carries over?
It is the same hardware as the first series — CompuLab IOT-DIN-IMX8PLUS, NXP i.MX8M Plus, the same 4 GB SoM, the same eMMC layout, the same two micro-USB ports.
So these transfer unchanged:
- The keys. The same PKI tree from part 3, the same SRK table, the same fuse hash. A second key tree would be a second thing to lose.
- The method. Two CSFs for
imx-boot, the[Unlock] Engine = CAAM / Features = MIDstanza, offsets recomputed from each build's own artefacts. - The recovery path.
umsandfastbootover the board's own U-Boot, because SDP still does not work on an Apple Silicon Mac. - The device state. Still open. Still no fuses burned. Everything here verifiable and reversible.
And these do not, which is most of why this needed its own series:
- The kernel load address is different. Track A signed at
0x40400000; this system loads at0x40480000. Same board, different build — and a signing script that hard-codes it produces a valid signature over the wrong thing. - The build is Buildroot inside Nerves, not a bare
makein a U-Boot tree. EnablingCONFIG_IMX_HABmeans editing a defconfig that a build system generates, and signing has to happen inside the build rather than after it. - The artefacts are assembled by fwup into an A/B firmware image, not written by hand.
Where this starts
- Board: CompuLab IOT-DIN-IMX8PLUS, still open, no fuses written
- Keys: the existing CST 4.0.1 tree, unchanged
- System: a fork of the working
nerves_system_iotdin_imx8p - Toolchain: Elixir 1.19.5 on OTP 28, pinned in
.tool-versions
Next: forking the system, turning on HAB inside a Nerves build, and the three real bugs that blocked the first three attempts — one of which is a dependency-resolver default that produces an error message pointing nowhere near the cause.