Forking the system, and a first signed boot
Series one's flow is fully proven now — real signing, real hardware checks, real tampering caught and rejected, all without ever locking the device down.
Series one's flow is fully proven now — real signing, real hardware checks, real tampering caught and rejected, all without ever locking the device down. This post is where that gets carried over into the actual production build.
Setting up the fork
The production system I'm porting this into isn't a generic Linux distribution — it's built with Nerves, a toolchain that compiles Elixir applications straight into complete, minimal embedded Linux firmware images. The board's own hardware support (kernel, bootloader, device tree, all of it) lives in a separate piece called a "system" — one specific package this project already has, already proven, already running well in the field. Rather than editing that working copy, I made a plain, literal copy of it into a brand new location, deliberately never touching the original.
Two identity strings needed changing in that copy, both easy to miss and both real enough to actually break a build if left alone: the internal package name Elixir's build tool uses to track it, and a separate name the underlying build system uses for the same purpose. Left unchanged, both would quietly collide with the original system's own names the moment anything tried to use the fork — which is exactly what happened the first time I tried to build it, producing a genuinely confusing error message that had nothing obviously to do with a naming collision. Found by reading the failing build's full log closely rather than just the last line, fixed by renaming both, and now baked into the copying script itself so it can never happen again on a future fork.
With the fork in place, the actual change this whole exercise exists for was one line: turning on the same bootloader-level signature-checking feature series one already proved works.
The first build: an old bug, right on schedule
The very first attempt at building this — a real, from-source compile of the bootloader, the kernel, and the entire minimal Linux userland around it — hit the exact size-limit bug from series one, the one where turning on signature checking makes the chip's tiny first-stage bootloader grow past the space reserved for it. I already knew the shape of the fix from last time, but I made myself measure this build's own real numbers rather than just reusing the old ones blindly — different build, potentially a different amount of overflow. It turned out to need almost exactly the same fix as before, confirmed by the actual math this time, not assumed.
A second, unrelated snag showed up right at the very last step, after literally everything else — kernel, bootloader, the whole userland — had already compiled cleanly. The tool that assembles everything into the final installable firmware file was missing one small script it needed. I compared the incomplete build directly against an older, already-known-good build of the original (unforked) system sitting on the same machine, and found the missing file was identical, byte for byte, to a copy already sitting inside one of the build tool's own dependencies — confirming it was just a straightforward "this needs to be copied into place" gap in how a from-scratch local build is put together, not something specific to my fork at all. Copied it in, and the build finished properly:
Firmware built successfully! 🎉
A real, complete, installable firmware image — for this exact board, with signature-checking support compiled straight into its bootloader.
Signing it, and the numbers that gave me real confidence
Series one already has a working script that signs a bootloader image using the four keys generated back in that series' early posts. Rather than write something new from scratch, I reused its exact logic here, adjusted only for where this build system happens to put its output files — everything about how the signing itself works stayed identical.
Before trusting that reuse, I checked one specific number the signing process depends on — an internal memory offset baked into the bootloader's own structure — and it came back exactly the same value series one's own already-hardware-proven build uses. Same board, same underlying source, same layout, even after being built through a completely different toolchain. That match mattered to me more than I expected going in — it's the kind of independent, unplanned confirmation that's worth more than just trusting the theory.
Signing itself went cleanly on the first attempt: same file size before and after (exactly as expected — a signature only fills space already set aside for it, never grows the file), a real new fingerprint confirming the content actually changed.
Putting it on the board
Copied the signed file over, flashed it the exact same way series one's own bootloader gets flashed, and asked the chip the same direct question I've now asked several times across this whole project: does it consider this image validly signed?
Completely clean. No complaints, nothing to explain away — the exact same clean answer series one's own bootloader produces, now for a bootloader built through an entirely different, purpose-built production toolchain.
That's a genuinely satisfying checkpoint: the security work from series one isn't just a one-off lab result: it carries over, cleanly, into the real system this hardware is actually meant to run.
What's still ahead
This only covers the bootloader. The kernel itself — the next layer up, and the one series one spent real time getting right, mistakes included — still needs the same treatment here. That's the next post.