prpl
FEAT-29 · prplWare 5.1.0

Secure Boot & Secure Upgrade

How the 2024 prpl reference platforms sign and verify what they boot, from U-Boot → kernel → rootfs, and update themselves safely.

Qualcomm ipq95xx · “Freedom” (WNC) MaxLinear x86-LGM · “OSPv2” (Gemtek)

Scope today: signed SMD plus kernel and rootfs FITs, with A/B secure upgrade. The SoC fuse and U-Boot signing are deferred.

SMD · standard flash layout · FIT signing · HKDF key derivation · A/B banks · SWUpdate. Sources: prplos!1964 · Confluence architecture page · real serial-console & gensmd artifacts.

9f93c81 · 2026-07-01
The one idea

A chain of trust, anchored in silicon

If an attacker can rewrite flash, they can run their code below your OS: persistent, root-level, invisible to the OS itself. Secure boot is the answer. Each stage cryptographically verifies the next before handing over control.

Immutable silicon ROM + fuses Early stageXBL / RBE U-Boot Linux verifiesverifiesverifies Trust flows one way, from what cannot be changed root of trust

Signing ≠ encryption

Signature = authenticity + integrity (“this came from us, unmodified”); a public key lets anyone verify. Encryption = confidentiality (“only we can read it”), using a symmetric key. In FEAT-29 the two cover different things: signatures protect the SMD and the kernel/rootfs FITs; encryption protects only the secret fields inside the SMD. No boot image is encrypted, and U-Boot itself isn't signed yet.

Why anchor in silicon

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.

That one-way chain is the goal. How much of it FEAT-29 enforces today is the subject of the next few slides.

Toolbox

Five primitives the rest of the deck builds on

① Hash

SHA-256. A fixed fingerprint of any data; changing one bit changes it completely. Used to detect tampering.

② Public-key signature

RSA-4096 / PSS for SMD & FIT pre-load; ECDSA P-256 for the device cert. Sign with private, verify with public.

③ Symmetric cipher

AES-256-CBC. One secret key both encrypts and decrypts. Protects the secret fields inside the SMD.

④ Key derivation

HKDF-SHA256. Stretches one root secret into many purpose-specific keys + IVs, deterministically.

⑤ Immutable / protected storage

Fuses / OTP hold the silicon root key (write-once), but aren't blown on these boards yet roadmap. The Linux KRS (kernel keyring) holds derived keys at runtime. feat-29

Read the tags

Each tag marks where a fact comes from; not every line is tagged.
vendor = inherited from the SoC / Irdeto.
feat-29 = new prpl work in MR 1964.
roadmap = deferred to a later epic / future hardening.

From here on, the only questions are which primitive protects what, and who holds the key.

The whole chain · one picture

Silicon → early stage → U-Boot → Linux

Every stage loads the next from flash, verifies it, then hands over. Two SoCs, the same shape; only the vendor early stage differs.

Silicon ROM Early stage U-Boot Kernel FIT initramfs rootfs on-die mask ROM "BL2" · bank select+ HW watchdog BL33 · SMD + GPT+ load kernel FIT kernel · dtb· initramfs auth rootfs · keys→ switch_root squashfs verifiesverifiesverifiesverifiesauthenticates Freedom: PBL → XBL/SBL1 OSPv2: ROM → RBE (SPL) U-Boot 2025.04-prpl U-Boot 2022.01-MXL What is actually enforced today: the U-Boot pre-load RSA-4096/PSS signature on SMD + kernel/rootfs FIT, with per-node SHA-256 root of trust ↓
Qualcomm "Freedom" MaxLinear "OSPv2" green arrow = "verifies before running"
where the trust is really anchored
Deep dive · the honest root of trust

The silicon anchor exists, but isn't blown on these boards yet

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

U-Boot itself isn't verified. It checks what comes after it (the SMD and the kernel/rootfs FITs), but nothing checks U-Boot, so today U-Boot is the trust root in practice, only because the OEM fuse that would authenticate it is un-blown.

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 · Qualcomm PBL/XBL hash-verify (boot ROM) B - 34168 - elf_segs_hash_verify_entry, Start B - 75204 - auth_xbl_sec_hash_seg_entry, Start B - 83649 - xbl_sec_segs_hash_verify_entry, Start D - 6192 - Auth Metadata D - 12383 - Segments hash check D - 51332 - QSEE Image Loaded ... S - Secure Boot: Off ← OEM fuse not blown (dev board)

freedom-uboot-only-prpl-flash…log · freedom-usb-edl…dump.txt

BL33 · where prpl takes over

U-Boot: authenticate the SMD, then the kernel FIT

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.

  1. SMD pre-load sig verified → set ethaddr from the SMD's MAC field feat-29
  2. GPT verified (and a corrupt copy auto-recovered from the backup GPT)
  3. Bank chosen by the early stage is read from the DT (booted-bank)
  4. Kernel FIT loaded → pre-load RSA-4096/PSS signature checked
  5. Each sub-image (kernel, fdt, initrd) SHA-256 verified
  6. root=PARTLABEL=rootfs-active handed to the kernel cmdline
## U-Boot (BL33) · real boot, Freedom INFO: signature check has succeed ← SMD authenticated Set ethaddr to ac:91:9b:ff:f1:ac Verify GPT: success! Loading raw FIT from partition kernel-active Booting FIT image at 0x50000000 INFO: signature check has succeed ← FIT pre-load sig ## Loading kernel (any) from FIT Image ... Hash algo: sha256 Verifying Hash Integrity ... sha256+ OK Trying 'fdt-1' fdt subimage … sha256+ OK Trying 'initrd-1' ramdisk … sha256+ OK

freedom-post-flash-linux-boot…log

The handoff into Linux

A pivoting initramfs authenticates the rootfs, then switches into it

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.

StepScriptDoes
S03rootfs-selectionbank ← root=PARTLABEL=
S04rootfs-sig-preloadauthenticate rootfs FIT (mandatory)
S05rootfs-fitdm linear map to squashfs
S06rootfs-mountmount the rootfs
S08key_derivationHKDF K_UK+IV → kernel keyring
**** initramfs · real boot **** Selected rootfs partition: rootfs-active **** Starting S04rootfs-sig-preload.sh **** (auth OK) **** Starting S05rootfs-fit.sh **** **** Starting S06rootfs-mount.sh **** **** Starting S08key_derivation.sh **** Switching to rootfs

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

why "authenticate once" ≠ dm-verity
Deep dive · integrity model

Whole-image authentication vs per-block verification

FEAT-29 today: whole-image

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, so 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: per-block (future)

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; it's shown here as the likely next step, not a promise.

The board's signed identity

SMD: Secure Manufacturing Data

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.

one RSA-4096/PSS signature covers everything ↓ CLEAR (readable by U-Boot) SERIAL_NUMBER BASE_MAC_ADDRESS MANUFACTURER · MODEL_NAME HARDWARE_VERSION WLAN_SSID · WLAN_REGDOMAIN KDF_CTX (per-board KDF context) ENCRYPTED (AES-256-CBC) USERFS_KEY · WLAN_PASSPHRASE · DEVICE_CERT → K_UK DEVICE_CERT_PRIVATE → K_UT

Signed → authentic

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.

Encrypted → confidential

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.

the real fields & the UBSH header
Deep dive · a real decoded SMD

OSPv2 A8C2463D425C: fields & signature header

# dd bs=4096 skip=1 <smd> | dtc -I dtb -O dts - (encrypted values elided) MFG_DATA { MFG_DATA_version = "1.2"; SERIAL_NUMBER = "ospv2_A8C2463D425C"; BASE_MAC_ADDRESS = [a8 c2 46 3d 42 5c]; MANUFACTURER = "Gemtek"; MODEL_NAME = "Gemtek-OSPv2"; HARDWARE_VERSION = "MB_V02"; WLAN_SSID = "prpl-3D425C"; WLAN_REGDOMAIN = "US"; KDF_CTX = "KTYYFuMhxRY6cUwvlRm0pyWTzEzg3WKD"; USERFS_KEY { cipher { key_name="mfg:Kuk"; format="aes-256-cbc"; }; }; WLAN_PASSPHRASE { cipher { key_name="mfg:Kuk"; format="aes-256-cbc"; }; }; DEVICE_CERT { cipher { key_name="mfg:Kuk"; format="aes-256-cbc"; }; }; DEVICE_CERT_PRIVATE { cipher { key_name="mfg:Kut"; format="aes-256-cbc"; }; }; };

smd/ospv2-A8C2463D425C/ospv2_A8C2463D425C.dtb

# the 4096-byte UBSH pre-load header 55 42 53 48 "UBSH" magic 00 00 00 01 version 1 00 00 10 00 header_size = 4096 00 00 0d 44 image_size = DTB length 00 00 02 40 ofs_img_sig = 576 ... [ 32 B ] sha256(image signature) [512 B ] RSA-4096/PSS sig over the 64-B header [512 B ] RSA-4096/PSS sig over the DTB [ pad ] → 4096, then the DTB itself

U-Boot verifies via image_pre_load() against the /mfg/sig public key in its control DTB: algo sha256,rsa4096, padding pss, mandatory="yes".

Provisioning · gensmd + Irdeto

How an SMD is made: a CSR to Irdeto, then sign and seal

INPUTS MAC, modelkey_type = ECC_P256metadata.yaml:root_key (secret)mfg:Kuk / mfg:Kutsmd_priv.pem (RSA-4096)input_data.csv (1 row/board) Irdeto PKI Certificate-Authority-as-a-service ① OAuth2 token② POST CSR → signs leafreturns leaf + chain vendor · holds the CA keys prpl_certificate.py local keygen → CSR → write key+chain gen_smd.py HKDF → K_UK, K_UT, IVbuild MFG_DATA treeAES-256-CBC encrypt secretsRSA-4096/PSS pre-load sign feat-29 tooling <serial>.dtb UBSH header + DTB → flashed to mfgdata

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.

the prpl certificate chain
Deep dive · the prpl device certificate

A three-tier ECDSA trust chain, minted by Irdeto

prpl Foundation Root CA TEST G1 P-384 · ECDSA-SHA384 · (not in SMD) prpl Foundation Device CA TEST G1 P-384 · intermediate · in chain Device leaf · CN = <MAC> EC P-256 · ECDSA-SHA256 OU = WNC-Freedom · EKU = TLS client/server
# the two Irdeto calls (OAuth2 client_credentials) POST api.pki.qa.key-central.irdeto.com /pki/v1/prpl/auth/token → access_token POST /pki/v1/prpl/certificate { profile: "prpl_platform_ECC_P256", subject_vars: { base-mac, model }, csr: "<CSR PEM>" } → { certificate (leaf), chain, certificate_id }

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 are worth calling out as "test/dev today". roadmap

Runtime · three actors

Authenticate before you decrypt

No field is read until the global signature verifies. Responsibility is split across the boot stages by what each one is allowed to see.

① U-Boot

Authenticates the SMD (pre-load sig). Reads only clear fields, chiefly BASE_MAC_ADDRESSethaddr. Never decrypts.

② initramfs

Reads clear KDF_CTX, derives K_UK + IV (HKDF), and provisions K_UK into the Linux KRS (kernel keyring) as logon: mfg:Kuk.

③ Linux userspace

libmfg/readmfg authenticate + parse, then decrypt the AES-256-CBC fields via libkcapi using the keyring-held K_UK.

# negative test: a tampered SMD is rejected outright ERROR: header signature check has failed (err=-22) ERROR: Failed to verify global header signature of mfg data parse_dtb: Failed to read the MFG DTB

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

One secret, many keys

Key derivation: a global secret → per-board keys

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.

root_key global secret · 32 B KDF_CTX per-board · from SMD (clear) HKDF HMAC-SHA256 salt=∅ · info=KDF_CTX‖id K_UK info = KDF_CTX + "mfg:Kuk" · 32 B K_UT info = KDF_CTX + "mfg:Kut" · 32 B IV info = KDF_CTX + "IV" · 16 B Linux KRS (kernel keyring) logon: mfg:Kuk ← K_UKKuk-aes-iv.hex ← IV → libkcapi AES-256-CBC decrypt DEVICE_CERT_PRIVATEWLAN_PASSPHRASEUSERFS_KEY → RW partitions

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

exactly what's secret, and what isn't yet
Deep dive · the honest crypto detail

Where the security really rests, and the dev placeholder

HKDF, precisely

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.

The root secret is a placeholder today

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

Closing the loop

Who signs what, and where the verify key lives

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.

ArtifactSigned with (private)Verified byPublic key lives in
SMD (mfgdata DTB)smd_priv.pemU-Boot (BL33)U-Boot control DTB /mfg/sig
Kernel FITKsoft-sign-priv.pemU-Boot (BL33)U-Boot control DTB
rootfs FITKsoft-sign-priv.peminitramfs (mandatory)/security/smd_pub.pem in initramfs
.swu upgradeSWUpdate signing keySWUpdate (Linux)/security/public.pem

FIT = one signed container

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 }.

Verified by 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.

The honest caveat

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.

Why signed images exist · resilience

Two banks, selected by name; no "active" pointer to corrupt

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 = renaming the GPT partition labels. The rename isn't strictly atomic (unlike UBI's ubiswap), but the two GPT copies (primary + backup) make it fail-safe.

ACTIVE bank INACTIVE bank u-boot-active kernel-active rootfs-active u-boot-inactive kernel-inactive rootfs-inactive gptswap → rename partition labels 2 GPTs (primary + backup) make it fail-safe, not atomic

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:

StateLives inInterface
bootcountSoC register / RAM/sys/class/registers/bootcount
force-inactiveSoC register (one-shot)/sys/class/registers/force-inactive
failoverU-Boot env (flash)fw_printenv failover
booted-bankdevice tree (runtime)/proc/.../u-boot,booted-bank
# bootcount is the register, NOT a U-Boot env var $ fw_printenv bootcount → "bootcount" not defined $ cat /sys/class/registers/bootcount → 0

freedom-bootcount-late-probe.log

Self-healing boot

Bootcount failover: try active, fall back to inactive

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.

Power-on (BL2)read count · failover Boot ACTIVEdefault Boot INACTIVEfailover INACTIVE (forced)one-shot Linux upreset bootcount=0 End of Worldcount > 2×limit · LED Commitfailover = 0 defaultcount≥limitforce-inactive both banks fail 1 good boot

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.

a later-stage failure, and how it recovers
Deep dive · a later-stage failure, and how it recovers

A post-upgrade mount failure: debug mode masked the failover

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. BL2's bootcount failover only governs the early stage, so it never saw this.

At the time the initramfs ran with debug mode on, which drops to a rescue shell on failure instead of rebooting, so the box just sat there and needed a manual U-Boot+SPL re-flash over TFTP.

Debug mode is now off by default, so the initramfs reboots on failure; with failover armed, that reboot lets bootcount fall back to the inactive bank on its own. Debug mode is only for working on the secure-boot logic itself, where the rescue shell helps; in general use it just looks like a hang.

A full Active-Bank-Recovery / commit service (data model, commit API, restore, failure hooks) is still a later epic. roadmap

# post-gptswap boot, debug mode ON: kernel-active = mmcblk0p12 (ext4) [ 8.230495] mmc0: cqhci: unable to map sg lists, -12 [ 8.236770] mmc0: cqhci: failed to setup tx desc: -12 [ 8.253754] EXT4-fs error (mmcblk0p12): unable to read itable block mount: mounting /dev/disk/by-partlabel/kernel-active on /mnt/kernel_part failed: Invalid argument Failed to mount kernel-active. Startup failed Dropping to rescue shell for debug ← debug on; default now reboots → failover

2026-05-30-…-swupdate-brick-serial-console.log

# the manual recovery that was needed then, from U-Boot run update_uboot Updating uboot at partition uboot_a / uboot_b Saving Environment to MMC... OK
Secure upgrade

SWUpdate: verify, write the inactive bank, swap, arm failover

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.

  1. .swu built by SWUGenerator; its sw-description is signed
  2. SWUpdate verifies the signature against /security/public.pem; wrong key ⇒ refused
  3. Images stream-installed into the inactive partitions (gptpart handler)
  4. gptswap renames active↔inactive labels atomically
  5. Post-install prpl-enable-failover.lua sets failover=1
  6. Reboot into the new bank → one good boot → commit (failover=0)
# real install, OSPv2: swupdate -e active,full [swupdate_verify_file] : Verified OK [_parse_scripts] : Found Script: prpl-enable-failover.lua [_parse_scripts] : Found Script: prpl-backup.lua [gpt_swap_partition] : swap kernel-inactive ↔ kernel-active [gpt_swap_partition] : swap u-boot-inactive ↔ u-boot-active Enabling the temporary failover [INFO ] : SWUPDATE successful !

2026-05-30-…-swupdate-brick-serial-console.log

FEAT-29 ships the command-line upgrade. The upgrade service (data model, commit API, restore, failure hooks) is a later epic. roadmap

Two boards, one design; diverging layout

Separate raw FIT partitions vs one shared ext4

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.

Qualcomm · Freedom ipq95xx · eMMC

6 dedicated raw FIT partitions; kernel and rootfs are separate.

# mmc part (post-migration) 16 "u-boot-active" 4 MiB raw 17 "u-boot-inactive" 23 "kernel-active" 32 MiB raw FIT 24 "kernel-inactive" 25 "rootfs-active" 256 MiB raw FIT 26 "rootfs-inactive" 30 "mfgdata" SMD

cram 212 checks 3 labels: u-boot, kernel, rootfs.

MaxLinear · OSPv2 x86-LGM · eMMC

2 ext4 kernel partitions, each holding both FITs as files. No rootfs partition.

9 "u-boot-inactive" raw 10 "u-boot-active" 11 "kernel-inactive" ext4 ~112 MiB 12 "kernel-active" ext4: kernel.itb + rootfs.itb 13 "mfgdata" SMD (no rootfs-* partition) rootfs → /dev/mapper/rootfs-fit

cram 212 checks 2 labels: u-boot, kernel.

# the one-line proof: OSPv2 loads the FIT as a FILE from a filesystem Loading FIT /kernel.itb from ext4 partition kernel-active ← OSPv2 Loading raw FIT from partition kernel-active ← Freedom
boot stages, env & migration
Deep dive · everything else that differs

Early stage, env redundancy, and the migration path

DimensionQualcomm · FreedomMaxLinear · OSPv2
ROM → early stagePBL → XBL/SBL1 (proprietary binary)ROM → RBE / SPL (u-boot-spl-emmc.bin)
U-Boot2025.04-prpl (from u-boot-active FIT)2022.01-MXL · OPEN_BOOT
Permanent failoverOTP fuse (early stage is a binary)compile flag (SPL is U-Boot-based)
U-Boot envsingle 0:APPSBLENVredundant env_a + env_b
Securestore / cal0:ART, 0:ETHPHYFW, securestorecalibration_a/b, securestore_a/b, tep-*
Migration3 U-Boot .scr scripts (backup→temp U-Boot→new GPT)update_script.itb + run update_prpl
Kernel artifactseparate kernel.itb + rootfs.itbone *-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.

Everything, on one slide

The whole FEAT-29 picture

TRUST Silicon ROMfuse (un-blown) Early stageXBL / RBE U-Boot (BL33)RSA-4096 verify Kernel FITsig + sha256 initramfsauth rootfs rootfssquashfs verify key inU-Boot control DTB IDENTITY & SECRETS SMD (mfgdata)signed DTB · Irdeto certenc: K_UK / K_UT KDF_CTX + root→ HKDF-SHA256 K_UK · IV→ Linux KRS libkcapi decryptcert key · userfs · wifi MAC → ethaddr · KDF_CTX → initramfs RESILIENCE A/B banksboot by name · 2 GPTs bootcount failoverregister · watchdog SWUpdateverify → write inactive gptswap → commitfailover=0 ↻ next update targets the new inactive bank enforced today ✓ RSA-4096 sig: SMD + FITs✓ SHA-256 per FIT node✓ AES-256 SMD secrets✓ A/B + bootcount failover deferred … OEM fuse + signed U-Boot… dm-verity · TEE · commit API
Where this came from

Honest scope & provenance

What FEAT-29 delivers

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.

What's deliberately not done yet

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.

Code & spec

prplos!1964 · Confluence SMD / Flash Layout / Secure Upgrade · gensmd + prpl_certificate.py

Real evidence

Every console snippet, decoded SMD, cert chain and partition table was captured from the actual Freedom and OSPv2 boards over the serial console.

Boards

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.