Making the Nerves build sign imx-boot and the kernel automatically
Wiring signing into the build, and a correction worth more than the wiring: I planned to point fwup.conf at signed artifacts, and that was the wrong layer entirely — the signed bootloader is not in the .fw file at all.
Signing by hand proves the method. It does not survive contact with a build you run twenty times a week — and a signing step you sometimes forget is worse than none, because you cannot tell by looking at the board.
So: make mix firmware do it.
First, a correction, because it is the useful part
My plan said: "wire fwup.conf to point at the signed artifacts."
That was wrong, and I found it by actually reading fwup.conf rather than continuing to
assume.
The signed bootloader is not in the .fw file at all. fwup writes the eMMC user area.
imx-boot lives in the boot0 hardware partition, and is flashed separately — once, at
bring-up, with fastboot — exactly as the
iotdin series established when I discovered that
OTA never touches the bootloader.
So there was nothing in fwup.conf to point anywhere. The whole task dissolved on contact
with the file.
This is the second time in this project that carefully reading the thing I was about to change deleted the work instead of guiding it. Worth doing before, not after.
The right layer: a post-build hook
The kernel is in the firmware image, so kernel signing has to happen inside the build, after the kernel is produced and before it is packaged — which is precisely what Buildroot's post-build hook is for:
# post-build.sh, in the system fork
./sign_kernel.sh "$IMAGE" "$(read_loadaddr_from uboot/uboot.env)" "$IMAGE"
The load address is read from the fork's own uboot.env at build time rather than passed as a
constant, for the reason part 3
gives: it differs per fork on the same board.
The bootloader keeps its own path — sign_flash.sh after the U-Boot build — because it is
flashed separately anyway.
The failure I was glad to get
The first wiring attempt failed, and it failed loudly, which is the only reason I trust the rest of it.
A signing step that silently no-ops is the worst possible outcome: mix firmware succeeds, a
firmware image appears, it boots, and it is unsigned. Nothing about the build output tells you.
You would find out when you closed the device and it stopped booting — the most expensive
possible moment.
So the hook is built to fail the build rather than warn: missing keys, missing tool, a CSF over budget, or an output whose size changed all abort with a non-zero exit.
Verify, do not trust the exit code
An exit code says a script ran, not that it did the right thing. After a normal mix firmware:
fwup -m -i compulab_cs_gw.fw | grep meta-platform # iotdin_imx8p
and then the real check — load the kernel from the firmware the build just produced and ask the ROM:
u-boot=> hab_auth_img <loadaddr> <size> <ivt_offset>
Calling authenticate_image in ROM
No HAB Events Found!
An ordinary mix firmware, with no manual steps, now produces a firmware whose kernel the
chip authenticates. That is the point of the whole series — not that signing is possible, but
that it is unavoidable.
What this buys
Signing is now a property of the build rather than a thing I remember to do. Anyone who clones
this fork and runs mix firmware gets a signed kernel, or gets a failed build. There is no
third outcome where they get an unsigned image and do not know.
That matters more than it sounds for a fleet: the build is the only place where "was this signed?" can be answered reliably. A device cannot tell you — on an open board, signed and unsigned both boot identically.
Next: flashing it, watching it boot, and finding that a fail-closed gate I had planned to write already existed.