It boots — and a gate I didn't know I had
This is the post where it all actually comes together — and where I found out that something I'd been planning to build had been there the whole time.
This is the post where it all actually comes together — and where I found out that something I'd been planning to build had been there the whole time.
Flashing it, for real
Everything so far had been signed-and-verified but never actually run. The last step was writing the whole finished firmware onto the device, which meant reformatting its internal storage into the production layout — the first genuinely destructive thing I'd done to this board in weeks. The old Linux install on it was replaced entirely.
Then I powered it up and watched.
The bootloader loaded the kernel. The chip checked the kernel's signature.
It came back clean. Linux started, thirty-eight applications came up, my own
hello world line printed exactly where I'd put it, and I got a live
interactive prompt on the console.
A signed bootloader, loading a signed kernel, verified by the hardware, booting a real operating system running my own code. That's the thing this whole two-series project has been building toward.
The part I got wrong, and what it revealed
Here's the interesting bit. Scrolling back through the boot log, I saw the chip performing a full signature check on the kernel — the same detailed check I'd been running by hand for weeks with an explicit command.
But I hadn't asked for it. I'd checked the boot configuration directly, earlier, specifically looking for that command, and it wasn't there. I'd concluded — and written down, and told people — that nothing in the automatic boot path verified anything yet, and that making it do so was still work left to do.
I was wrong, and the way I was wrong is worth explaining, because the check was correct and the conclusion drawn from it was too broad.
Looking in the bootloader's own source code, the boot command that loads a Linux kernel has this built into it, active whenever secure-boot support is compiled in:
check the image's signature; if the check fails, print "Authenticate Image Fail, Please check" and refuse to boot.
So simply enabling secure-boot support doesn't only give you a manual verification command to play with — it silently installs an automatic, refuse-to-boot gate on every single kernel boot, with no configuration whatsoever. The task I'd had on my list for weeks ("wire the check into the boot process") didn't need doing. It was already done, by people who wrote that bootloader long before I got here.
I confirmed this was really the mechanism rather than a lucky guess: the automatic path computes the byte range it checks using a specific formula, and that formula's result matched the exact number printed in my boot log, to the byte.
So is the device protected now?
This is where the honest answer from series one's closing post comes back, and it hasn't changed.
The wiring is complete. The authority isn't.
The gate exists, it runs on every boot, and it is genuinely written to refuse. But on a device that hasn't had its one-way lock fuse burned, the underlying check is designed to always report success — so the gate always opens. Feed it a tampered kernel and it will print real, specific failure evidence to the console, and then boot it anyway.
Which means what I actually have today is tamper-evident, not tamper-proof — and, importantly, I have that for free, right now, with no extra work. Every boot either prints a clean bill of health or prints exactly what's wrong. That's genuinely useful for a fleet: you would know.
Turning "knowing" into "refusing" is one fuse away, and that fuse stays unburned by deliberate choice on this board.
A smaller lesson that will bite anyone doing this on a team
One last thing, less dramatic but more likely to catch someone out.
I couldn't log into the device from my laptop. Password prompt, every time, and no password would ever work — this system only accepts SSH keys.
The cause: the project template bakes in the SSH keys found on whichever machine built the firmware. I build on a separate build server, so the device happily accepted that server's key and had never heard of my laptop.
That's fine for one person on one machine. It's wrong for a team, and it's completely broken for an automated build system, which usually has no personal keys at all.
So I changed it: the build now also reads an explicit list of authorized keys from a file kept in version control, added on top of whatever the build machine happens to have. Public keys aren't secrets, so that list is reviewable in the repository like anything else — which is the point. Who can access the fleet becomes a deliberate, visible decision instead of an accident of whose laptop ran the build.
Rebuilt, reflashed, and logged in from my laptop first try.
Where this leaves things
The device on my desk right now runs firmware where the bootloader is signed, the kernel is signed, both are verified by the hardware at every boot, the signing happens automatically as part of the ordinary build command, and I can reach it over the network from anywhere I've authorized.
There's one experiment left that I actually want to watch rather than predict: deliberately roll the device back to an older, unsigned firmware and confirm with my own eyes that it complains loudly and boots anyway, exactly as the code says it should. Predicting it from source is one thing; seeing it is another, and this project has taught me repeatedly that those two are not the same.