Series Secure boot on Nerves Part 4 of 5 · all parts

The build signs itself now

Up to this point, every signed thing in this project was signed by me, by hand, one command at a time.

Up to this point, every signed thing in this project was signed by me, by hand, one command at a time. That's fine for proving a concept and completely wrong for anything a team actually uses day to day: the moment signing is a separate manual step someone has to remember, it eventually gets forgotten. This post is about making it stop being a step at all.

Finding the right place to put it — after being wrong about it first

My initial assumption was that the firmware-packaging configuration was where signing belonged, since that's the file describing what goes where on the device's storage. I was wrong, and I only found out by actually reading that configuration properly instead of continuing to assume.

Two things turned out to be true. First, the bootloader isn't part of the packaged firmware file at all — it's written separately to a special area of the chip's storage, which is exactly the manual step I'd already been doing. Nothing to automate there in that file, because it was never in scope. Second, the kernel isn't a separate item in the package either: it lives inside the compressed root filesystem image, as an ordinary file, and the bootloader reads it out of there at startup. The packaging config just writes that whole filesystem image as one opaque blob — it has no visibility into individual files inside it.

So the right place is earlier: a hook the build system already provides that runs after the root filesystem tree is assembled but before it gets compressed into that blob. Sign the kernel file in place, right there, and everything downstream just carries the signed version along without knowing anything changed.

A failure I was glad to get

I wired the signing script into that hook, ran the build, and it failed: the script couldn't be found, because I'd pointed at the wrong directory level. The build stopped immediately with a loud error.

I want to dwell on why that was the good outcome. The dangerous failure mode here isn't a build that stops — it's a build that quietly succeeds while skipping the signing step, handing you an unsigned kernel inside firmware that looks completely finished. Because the hook is written to abort on any error rather than continue, a broken path became an obvious red failure instead of a silent security hole. That's worth designing for deliberately.

Fixed the path, ran it again, and the build completed normally.

Verifying it properly, not just trusting the exit code

"The build succeeded" is not the same as "the kernel got signed," so I checked four separate ways: the file's size matched the signed size exactly; the arithmetic confirmed it was signed once and not accidentally twice; the small structure appended to the end had all the right values inside it; and — the one that actually settles it — I extracted the kernel back out of the finished compressed filesystem and confirmed it was byte-for-byte identical to the signed file. The signed bytes really are what ships.

One genuinely useful gotcha found along the way: the signing script's own console output doesn't appear anywhere in the build log, because the build system handles that hook's output separately. So "I don't see signing in the log" absolutely does not mean signing didn't happen. It briefly fooled me. The artifact is the evidence; the log isn't.

I also closed a trap I'd left for my future self. The script that creates this whole forked copy of the production system wipes and re-syncs it from the original, which would have silently deleted both of my secure-boot changes — leaving a build that runs fine and produces unsigned firmware. Both changes now get re-applied by that script itself, with checks that stop everything if either fails to take.

And then the part that made it real

Finally: I took the kernel straight off the build — one nobody had touched by hand at any point — put it on the board, and asked the chip whether it considered it validly signed.

Completely clean. Same answer as every correctly-signed thing in this project so far.

That's the checkpoint I've actually been working toward across both series: not "I can sign a file," but "the normal, everyday build command produces firmware whose bootloader and kernel are both signed, and the hardware itself confirms both." No manual steps, nothing to remember, nothing to forget.

What's left

Two things, and I'm being deliberate about both. One is booting the whole finished firmware for real, rather than just verifying its signatures — that means reformatting the device's storage into the production layout, which is a bigger commitment than anything I've done on this board so far, so it gets its own careful session. The other is making the boot process actually refuse to continue when a check fails, which I can write correctly but — as covered in series one's closing post — cannot fully prove on a device I'm deliberately leaving unlocked.