How the 2024 prpl reference platforms verify every stage — silicon → XBL → U-Boot → Linux — and update themselves safely.
SMD · standard flash layout · FIT signing · HKDF key derivation · A/B banks · SWUpdate. Sources: prplos!1964 · Confluence architecture page · real serial-console & gensmd artifacts.
If an attacker can rewrite flash, they can run their code below your OS — persistently, as root, invisible to it. Secure boot answers this: each stage cryptographically verifies the next before handing over control.
Signature = authenticity + integrity (“this came from us, unmodified”) — public-key, anyone can verify. Encryption = confidentiality (“only we can read it”) — symmetric key. FEAT-29 uses both: images are signed; secrets in the SMD are also encrypted.
A signature is only as trustworthy as the key that checks it. Put that key (or its hash) in one-time-programmable fuses the attacker cannot rewrite — and the whole chain inherits that immutability.
SHA-256. A fixed fingerprint of any data; changing one bit changes it completely. Used to detect tampering.
RSA-4096 / PSS for SMD & FIT pre-load; ECDSA P-256 for the device cert. Sign with private, verify with public.
AES-256-CBC. One secret key both encrypts and decrypts. Protects the secret fields inside the SMD.
HKDF-SHA256. Stretches one root secret into many purpose-specific keys + IVs, deterministically.
Fuses / OTP hold the silicon root key (write-once). The Linux KRS (kernel keyring) holds derived keys at runtime.
vendor = inherited from the SoC / Irdeto.
feat-29 = new prpl work in MR 1964.
scope = deferred to a later epic.
Keep these five in mind — the rest of the deck is just which primitive protects what, and who holds the key.
Every stage loads the next from flash, verifies it, then hands over. Two SoCs, the same shape — only the vendor early stage differs.
Both SoCs can anchor trust in one-time-programmable fuses (a hash of the OEM signing key in QFPROM / SoC OTP). On the 2024 FastTrack / migration boards that fuse is deliberately not blown — so the ROM/XBL hash-checks run but aren't yet anchored to an immutable OEM key. vendor
What FEAT-29 does enforce, unconditionally, is the prpl pre-load signature: U-Boot will not use the SMD or boot a kernel/rootfs FIT whose RSA-4096/PSS signature doesn't verify against the public key baked into U-Boot's control DTB. feat-29
Blowing the OEM fuse (and signing U-Boot itself) is the next hardening step — out of scope of FEAT-29's migration phase. roadmap
freedom-uboot-only-prpl-flash…log · freedom-usb-edl…dump.txt
U-Boot is the first stage fully owned by prpl. In order, it: ① authenticates the SMD and pulls the MAC from it; ② verifies/recovers the GPT; ③ loads the kernel FIT from the selected bank and checks its pre-load signature + every node's SHA-256.
ethaddr from the SMD's MAC field feat-29booted-bank)kernel, fdt, initrd) SHA-256 verifiedroot=PARTLABEL=rootfs-active handed to the kernel cmdlinefreedom-post-flash-linux-boot…log
The kernel FIT carries a tiny purpose-built initramfs. It runs eight ordered scripts (S00–S08): pick the
bank, authenticate the rootfs FIT, map it, mount it, derive keys, and switch_root.
| Step | Script | Does |
|---|---|---|
| S03 | rootfs-selection | bank ← root=PARTLABEL= |
| S04 | rootfs-sig-preload | authenticate rootfs FIT (mandatory) |
| S05 | rootfs-fit | dm linear map to squashfs |
| S06 | rootfs-mount | mount the rootfs |
| S08 | key_derivation | HKDF K_UK+IV → kernel keyring |
freedom-post-flash-linux-boot…log
dm-verity is intentionally descoped. Integrity is the
whole-image RSA-4096 signature checked once at S04 — the dm device is linear, not
verity (no per-block runtime checking yet). roadmap
The entire rootfs FIT is covered by one RSA-4096/PSS/SHA-256 pre-load signature,
verified once in initramfs S04 before the squashfs is mounted (mandatory:
…ROOTFS_SIG_PRELOAD_MANDATORY=y — boot aborts if it fails or is absent). Tamper anywhere in the
image ⇒ signature fails ⇒ no boot.
Block device = device-mapper linear onto the squashfs payload inside the FIT.
dm-verity hashes every block in a Merkle tree and re-checks on each read at runtime — catching corruption or tampering that happens after mount. Stronger, but heavier; explicitly out of scope of FEAT-29 and listed as future hardening. roadmap
No in-scope source commits to a date — shown here as the natural next step, not a promise.
A per-board blob written once at the factory into the mfgdata partition. It is a
device tree (DTB): the whole thing is signed; the secret fields are also encrypted.
The DTB is wrapped in a U-Boot pre-load header (magic UBSH) carrying an RSA-4096/PSS/SHA-256 signature over the whole tree. Nothing is trusted until that verifies.
Keys, the WiFi passphrase, and the device certificate's private key are individually AES-256-CBC encrypted on top of the signature, so they're never in the clear on flash.
Format: prpl Secure Manufacturing Data Standard v1.2. Read it back with readmfg.
smd/ospv2-A8C2463D425C/ospv2_A8C2463D425C.dtb
U-Boot verifies via image_pre_load() against the
/mfg/sig public key in its control DTB: algo
sha256,rsa4096, padding pss, mandatory="yes".
The device private key is generated locally and never leaves the host — only a CSR goes to Irdeto, which signs it under the prpl Device CA.
note The standard lists the device key as RSA-4096, but the implemented FastTrack reality is EC P-256, and the endpoint is Irdeto's QA PKI — both worth calling out as "test/dev today".
No field is read until the global signature verifies. Responsibility is split across the boot stages by what each one is allowed to see.
Authenticates the SMD (pre-load sig). Reads only clear fields — chiefly BASE_MAC_ADDRESS → ethaddr. Never decrypts.
Reads clear KDF_CTX, derives K_UK + IV (HKDF), and provisions K_UK into the Linux KRS (kernel keyring) as logon: mfg:Kuk.
libmfg/readmfg authenticate + parse, then decrypt the AES-256-CBC fields via libkcapi using the keyring-held K_UK.
Corrupt one byte of the SMD and U-Boot refuses to read any of it — MAC included. The signature is the gate; encryption is a second wall behind it. ospv2-manual-uboot…log
FEAT-29 never stores the AES keys. It derives them — at generation time and again at boot —
from one root secret plus the board's clear-text KDF_CTX, using HKDF-SHA256.
Same function at the factory and at boot ⇒ reproducible keys, no key ever written to flash. openssl kdf -kdfopt digest:SHA2-256 -kdfopt hexkey:<root> -kdfopt info:"<KDF_CTX>mfg:Kuk" HKDF
RFC-5869 HKDF, HMAC-SHA256. salt = none; the per-board value goes into info
(KDF_CTX ∥ key-id), not the salt — the prose word "salt" is misleading. Output length sets the key
(32 B) or IV (16 B). Both ciphers share root_key + KDF_CTX, so they derive the
same IV.
K_UK = "Key-Unique-Kernel" (non-secure world). K_UT = "Key-Unique-Tee" (secure world) — no TEE in FEAT-29, so it lands in the keyring too.
On dev/FastTrack material root_key is the well-known
0011…EEFF and lives inside the initramfs. That's fine for migration but means
the whole derivation is only as secret as that constant.
Hardening (out of scope): move the root secret into SoC OTP / an encrypted image, so it's bound to the silicon — the same gap as the un-blown secure-boot fuse. roadmap
Three things are signed, all with the same primitive (RSA-4096 / PSS / SHA-256 U-Boot pre-load). Images are built and signed by binman; the matching public keys are baked into the verifier so the chain has nowhere to slip.
| Artifact | Signed with (private) | Verified by | Public key lives in |
|---|---|---|---|
| SMD (mfgdata DTB) | smd_priv.pem | U-Boot (BL33) | U-Boot control DTB /mfg/sig |
| Kernel FIT | Ksoft-sign-priv.pem | U-Boot (BL33) | U-Boot control DTB |
| rootfs FIT | Ksoft-sign-priv.pem | initramfs (mandatory) | /security/smd_pub.pem in initramfs |
| .swu upgrade | SWUpdate signing key | SWUpdate (Linux) | /security/public.pem |
pre-load{ algo="sha256,rsa4096"; padding="pss"; header-size=4096 } wraps the whole FIT; inside, each node (kernel, fdt, ramdisk, rootfs) carries its own hash{ sha256 }.
image_pre_load()Upstream U-Boot mechanism (≥ v2022.07). The public key in the control DTB is immutable to anything U-Boot loads afterward — so the verifier can't be swapped by the thing it verifies.
This RSA-4096 layer is what's enforced. The link below it — U-Boot itself, and the OEM-key fuse — isn't anchored on these migration boards yet. The root of trust is cryptographic today, silicon-bound tomorrow.
Every updatable image exists twice: *-active and *-inactive. The early stage
boots by partition name, so there's no stored bank index that can rot. Switching banks =
atomically renaming the GPT labels.
State is split across three stores — deliberately keeping
bootcount out of flash, so a freshly-flashed bad U-Boot can't disable its own failover:
| State | Lives in | Interface |
|---|---|---|
bootcount | SoC register / RAM | /sys/class/registers/bootcount |
force-inactive | SoC register (one-shot) | /sys/class/registers/force-inactive |
failover | U-Boot env (flash) | fw_printenv failover |
| booted-bank | device tree (runtime) | /proc/.../u-boot,booted-bank |
freedom-bootcount-late-probe.log
The early stage arms a hardware watchdog, increments bootcount each attempt, and — while
failover is armed — falls back to the inactive bank once the active bank fails to boot bootlimit times.
Temporary failover (the prpl default) is armed during upgrade and disabled on commit. Permanent failover = a compile flag, or an OTP fuse when the early stage is a vendor binary.
After a real SWUpdate + gptswap, the freshly-written kernel-active ext4
partition hit a controller DMA error and couldn't be read — inside the initramfs, past BL2's bank
selection. So BL2's bootcount failover, which only governs the early stage, never engaged; the
box dropped to a rescue shell instead.
Recovery was a U-Boot re-flash of U-Boot+SPL over TFTP. The lesson on the slide: BL2 failover covers load failures it can see — a later-stage mount failure needs a different safety net (the deferred Active-Bank-Recovery / commit machinery). scope
2026-05-30-…-swupdate-brick-serial-console.log
The whole point of A/B + signed images: update the bank you're not running, prove it's authentic, then flip to it — with a fallback if the new image won't boot.
.swu built by SWUGenerator; its sw-description is signed/security/public.pem — wrong key ⇒ refusedgptpart handler)prpl-enable-failover.lua sets failover=1failover=0)2026-05-30-…-swupdate-brick-serial-console.log
scope FEAT-29 ships the command-line upgrade. The upgrade service (data model, commit API, restore, failure hooks) is a later epic.
The prpl flash-layout spec wants each FIT in its own raw partition (Freedom does this). OSPv2 got a sanctioned exception: kernel and rootfs FITs share one ext4 filesystem.
6 dedicated raw FIT partitions — kernel and rootfs are separate.
cram 212 checks 3 labels: u-boot, kernel, rootfs.
2 ext4 kernel partitions, each holding both FITs as files. No rootfs partition.
cram 212 checks 2 labels: u-boot, kernel.
| Dimension | Qualcomm · Freedom | MaxLinear · OSPv2 |
|---|---|---|
| ROM → early stage | PBL → XBL/SBL1 (proprietary binary) | ROM → RBE / SPL (u-boot-spl-emmc.bin) |
| U-Boot | 2025.04-prpl (from u-boot-active FIT) | 2022.01-MXL · OPEN_BOOT |
| Permanent failover | OTP fuse (early stage is a binary) | compile flag (SPL is U-Boot-based) |
| U-Boot env | single 0:APPSBLENV | redundant env_a + env_b |
| Securestore / cal | 0:ART, 0:ETHPHYFW, securestore | calibration_a/b, securestore_a/b, tep-* |
| Migration | 3 U-Boot .scr scripts (backup→temp U-Boot→new GPT) | update_script.itb + run update_prpl |
| Kernel artifact | separate kernel.itb + rootfs.itb | one *-ext4.img → both kernel banks |
Same prpl contract underneath both: boot-by-name, bootcount + temporary failover,
booted-bank in the DT, root=PARTLABEL=rootfs-active, 2 GPTs for atomic swap, SMD in mfgdata,
RSA-4096-signed FITs. Only the vendor-shaped bits differ.
A cryptographically enforced boot chain (RSA-4096/PSS-signed SMD + FITs, SHA-256 nodes), a signed & partly-encrypted manufacturing identity with an Irdeto-minted device cert, HKDF-derived keys provisioned to the kernel keyring, and a resilient A/B + bootcount-failover + SWUpdate upgrade — on two different SoCs.
OEM secure-boot fuse un-blown & U-Boot unsigned (migration phase); root_key is a dev
placeholder; cert PKI is Irdeto QA; dm-verity, a TEE, and the upgrade
service (commit API, restore, failure hooks) are future epics. The deck flags each where it appears.
prplos!1964 · Confluence SMD / Flash Layout / Secure Upgrade · gensmd + prpl_certificate.py
Every console snippet, decoded SMD, cert chain & partition table is from real feat-29-tools serial-console & device artifacts (Rigol + serial harness).
Qualcomm ipq95xx Freedom (WNC) · MaxLinear x86-LGM OSPv2 (Gemtek). eMMC; NAND out of scope.
One design, two silicons: verify before you run, derive instead of store, update the bank you're not on.