Embedded Security In Practice: Secure Boot on FRDM-MCXN947
This post implements ECDSA P-384 secure boot on the FRDM-MCXN947: the boot ROM now refuses any image without a valid signature. Live captures prove TS2 closed.
The M1 Threat Analysis and Risk Assessment (TARA) flagged TS2 at Risk 3. In the default DEVELOP state the boot Read-Only Memory (ROM) runs whatever is in flash: an attacker with debug or In-System Programming (ISP) access writes firmware, the part runs it. The damage scenario (D2) is firmware overwritten to plant persistence or defeat body functions, rated Moderate for safety and Major for operational impact. The ROM checks nothing before running it.
M2 closes that path. Secure boot makes the ROM a gatekeeper: it runs an image if and only if that image carries a valid Elliptic Curve Digital Signature Algorithm (ECDSA) P-384 signature against a root of trust provisioned into the device. An attacker who can rewrite flash still cannot get code to execute without the private key behind that root. This post implements that control on the FRDM-MCXN947 (Secure Provisioning SDK (SPSDK) 3.10.0, board in DEVELOP, One-Time Programmable (OTP) blank throughout), proves it with two live board captures, and scores the residual risk.
| ID | Threat scenario | Impact (S/F/O/P) | Feasibility | Risk (1-5) |
|---|---|---|---|---|
| TS2 | Firmware overwritten, part runs it (D2) | Moderate / Negligible / Major / Negligible | Medium | 3 |
| TS5 | Fault injection to bypass secure boot | Moderate / Negligible / Major / Negligible | Low | 2 |
TS2 is the direct target: secure boot removes the “write flash, it runs” path. TS5 (fault injection timed against the boot ROM’s signature check) is the residual attack against the control we are about to add. It is already at Risk 2 in M1’s TARA because of Low feasibility, and M2 does not change that. Glitch resistance is a Leading-tier anti-tamper problem, the second conversation, scoped for later; no foundational control in this series closes it.
The series roadmap
M2 is the second of eight milestones, each taking one control from the Embedded Security In Practice blog post and applying it to this board. The table situates this post; every milestone traces back to a threat from the M1 TARA.
| Milestone | What it covers |
|---|---|
| M0 | Baseline: a working hello_world on the board, before any security |
| M1 | Target, threat model, and lifecycle plan. No fuses burned |
| M2 (this post) | Secure boot: only signed firmware runs |
| M3 | Authenticated debug: keep debug, behind a credential |
| M4 | Hardware root of trust and key storage |
| M5 | TrustZone-M isolation between secure and non-secure code |
| M6 | Flash encryption: a raw read returns ciphertext |
| M7 | Secure firmware update: signed, fail-safe, anti-rollback |
| M8 | The basics: TRNG, disabled ISP and DFU, no defaults |
The security control: Firmware Signing
Signed firmware is the mitigation. Every image the device runs carries a cryptographic signature, and the boot ROM runs it only after that signature checks out. Flashing still works the same way, so an attacker with debug or bus access can overwrite the firmware. That overwritten firmware will not run: without the signing key, an attacker cannot produce a signature the ROM accepts. Closing TS2 comes down to two questions: who is allowed to sign, and how the device knows to trust them.
Everything depends on who the ROM trusts. The answer is the Root of Trust Key Hash (ROTKH): a 48-byte digest stored in Customer Manufacturing Programmable Area (CMPA) at 0x0100_4060. Each of the four P-384 root public keys is hashed with Secure Hash Algorithm, 384-bit (SHA-384), and those four hashes are concatenated and hashed again into the ROTKH. The ROM trusts the hash, not the keys directly. That indirection enables revocation: a compromised root can be retired while the device keeps booting on the others, without changing the trust anchor baked into CMPA.
The four Root of Trust Keys (RoTK0 through RoTK3) are the OEM’s offline root Certificate Authorities (CA). In a real product these live in a Hardware Security Module (HSM), powered on only to certify signing keys or handle exceptional revocation events. In this walkthrough the keys are PEM files on disk; the chain design is the point here, not production key custody. Offline means not connected to the build pipeline: a root key sits at the top of the trust hierarchy, and keeping it cold is the whole point of the design. The four slots serve distinct roles. RoTK0 and RoTK1 are boot-image CAs: two slots so one root can be retired while the device still boots on the other. RoTK2 is the debug authentication CA, idle until M3. RoTK3 is the firmware-update CA, idle until M7. One shared root-of-trust key table backs all three concerns; the RoTK_USAGE field at 0x0100_4054 assigns per-key roles.
Day-to-day firmware signing uses the Image Signing Key (ISK), not the roots. The ISK is the warm key: it lives in the release pipeline, signs each firmware build, and can be revoked and re-issued under a root if compromised. That is the standard Public Key Infrastructure (PKI) split. Compromising the ISK is painful but recoverable. Compromising a root is the catastrophe the offline posture exists to prevent.
Building a signed image goes like this. Generate a cert block containing the ISK public key, an ISK certificate (signed by RoTK0 with ECDSA P-384), and the ROTKH. Wrap the application binary as a signed_xip Master Boot Image (MBI): app binary plus cert block plus a 96-byte ECDSA P-384 signature computed by the ISK private key. On every boot the ROM walks that chain backward: read ROTKH from CMPA, hash the four root pubkeys from the cert block and confirm the result matches, verify the ISK cert against RoTK0, verify the image signature against the ISK. All three checks must pass. Any failure drops the ROM to its recovery interface instead of running the image.
I provisioned the ROTKH and set SEC_BOOT_EN to ECDSA_SIGNED, writing two things into CMPA. The ROTKH (48 bytes, high-word-first at 0x0100_4060) is the trust anchor. The SEC_BOOT_EN field at 0x0100_4050 is set to ECDSA_SIGNED (value 0x3), which tells the ROM to accept only ECDSA-signed images. The ENF_CNSA = LIMITED_0B01 field pins the algorithm suite to the Commercial National Security Algorithm (CNSA) level: P-384, SHA-384, and Advanced Encryption Standard 256-bit (AES-256), enforced across secure boot, debug authentication, and firmware update. Setting it in M2 means M3 and M7 start with the algorithm choice already made.
All tools: SPSDK 3.10.0, family mcxn947.
Key generation (four P-384 roots plus the ISK):
for i in 0 1 2 3; do
nxpcrypto key generate -k secp384r1 -e pem -o provisioning/keys/rotk${i}_prv.pem
done
nxpcrypto key generate -k secp384r1 -e pem -o provisioning/keys/isk_prv.pem
Cert block and ROTKH readout. Note: nxpimage prints the value labeled RKTH (Root Key Table Hash, SPSDK’s output label). The silicon field on this device is ROTKH, consistent with M1’s naming note. Same value, different label.
nxpimage -v cert-block export -c provisioning/configs/m2/cert_block.yaml
# prints: RKTH: 163e8d7a...799b61
MBI build (edit the template to set inputImageFile, certBlock, signer = isk_prv.pem):
nxpimage mbi export -c provisioning/configs/m2/mcxn947_xip_signed.yaml
CMPA: cross-check byte order, write, and verify. The cmp step catches ROTKH byte-order mismatches before writing:
# cross-check ROTKH byte order: derive CMPA two ways, compare
pfr export -c provisioning/configs/m2/cmpa.yaml -o build/m2/cmpa.bin
pfr export -c provisioning/configs/m2/cmpa.yaml -e build/m2/cert_block.bin \
-o build/m2/cmpa_check.bin
cmp build/m2/cmpa.bin build/m2/cmpa_check.bin # identical => ROTKH order correct
# enter ISP via debug mailbox, flash the signed image, write CMPA, read back
nxpdebugmbox -i mcu-link cmd -f mcxn947 ispmode -m 0
PORT=/dev/cu.usbmodemJABXJL3CFXOL03 # substitute your MCU-Link VCOM port
blhost -p $PORT flash-erase-region 0x0 0x8000
blhost -p $PORT write-memory 0x0 build/m2/mbi_signed.bin
pfr write -f mcxn947 -p $PORT -t cmpa -c provisioning/configs/m2/cmpa.yaml
pfr read -f mcxn947 -p $PORT -t cmpa -o build/m2/cmpa_after.bin # confirm SEC_BOOT_EN=ECDSA_SIGNED
Load and test the control
With CMPA holding SEC_BOOT_EN=ECDSA_SIGNED, the ROTKH, and ENF_CNSA=LIMITED_0B01, two cases show the control working.
Signed firmware runs
On reset (triggered via the debug probe for deterministic capture timing), the ROM authenticated the signed_xip image against the CMPA ROTKH and ran it:
--- boot banner (signed image authenticated against CMPA ROTKH) ---
MCUX SDK version: 2026.06.00
hello world.
--- echo test (proves the app is executing, not just printing once) ---
sent: secure-boot-OK
recv: secure-boot-OK
A single banner print could conceivably come from the ROM before it refuses an image. An echo loop proves the application is actually running. Legitimate, signed firmware gets through.
Tampered firmware is refused
I flipped one byte inside the signed image at offset 0x2340 from 0x00 to 0xFF. cmp -l confirmed exactly one byte differs from the valid signed image. That image went into flash. On reset:
--- listen for the boot banner (expect none) ---
banner bytes captured: 0 # no banner, the app never ran
--- echo test ---
sent: ALIVE?
recv: 5a a7 00 03 01 50 00 00 fb 40 # mboot framing (0x5A) from the ROM, not the app
No banner. No echo. The Universal Asynchronous Receiver/Transmitter (UART) returned mboot protocol bytes from the ROM bootloader, not the application. USB Human Interface Device (USB-HID) 1fc9:014f enumerated. The ROM dropped to its native recovery interface rather than executing the image.
This is the TS2 attack path stopped at the ROM: an attacker who rewrites flash cannot get code to execute without the ISK’s private key. One flipped byte breaks the ECDSA signature check, and the ROM refuses the image. The drop-to-recovery rather than halt-and-die behavior is the right default for dev silicon. Halt-and-stay-dead would punish provisioning mistakes (wrong ROTKH byte order, malformed cert block) just as harshly as it would stop an attacker who has no signing key. Whether a shipped Body Control Module (BCM) should offer any recovery channel at all is an Original Equipment Manufacturer (OEM) policy call, not a silicon default.
The risk after the control
Secure boot shuts the direct TS2 path: an attacker who writes to flash cannot get code to run without the signing key. But M2 as implemented here is not the final state. Three residuals remain.
The CMPA configuration is reversible while OTP stays blank. An attacker with open debug access, which M3 has not yet closed, can call pfr erase-cmpa and wipe the secure-boot configuration. The OTP burn is what makes the policy permanent, and that burn is deliberately deferred until the full control stack is proven.
The update path is a separate residual. A signed but malicious firmware update that passes the signature check because the ISK was compromised, or because the update channel has no rollback protection, is the problem M7 closes.
Fault injection (TS5) against the boot ROM’s signature check is the hardware-level residual. M2 does nothing for it.
The control for both rows: the boot ROM verifies an ECDSA P-384 signature against the provisioned ROTKH before running any image.
| ID | Residual attack path | Feasibility | Risk (1-5) | Closes fully at |
|---|---|---|---|---|
| TS2 | Disable reversible config via open debug; no permanence until OTP burn | Low | 2 | M3 (debug lock) + final OTP burn |
| TS5 | Voltage/EM glitch past the ROM signature check | Low | 2 | Leading-tier anti-tamper (second conversation) |
TS2 drops from Risk 3 (Medium feasibility, no control) to Risk 2 (Low feasibility, control in place but reversible). The feasibility falls because the direct flash-overwrite path is closed; disabling the config now requires open debug access plus knowledge of the provisioning tool chain. M3 and the OTP burn close the residual completely. I score the residual at Risk 2. That is a judgment in M1’s framework, not a board measurement, but it reflects the real state: the direct overwrite path is closed, the reversible-config and open-debug paths are not.
A risk score reflects the current state, not the intended end state. Secure boot is enforced but the config is still reversible and the part is still in DEVELOP, so TS2 stays at Risk 2, not Risk 1, until M3 and the OTP burn close the residual.
Why nothing is fused yet
All of this ran reversibly in CMPA with OTP blank. That is not a gap; it is the designed condition in DEVELOP state. Write the secure-boot config into CMPA, get real ROM enforcement from it, recover with pfr erase-cmpa if something is wrong. On this board, I proved the chain end to end with a recoverable setting before committing anything one-way. Nothing is fused yet because nothing needs to be fused yet. The burn happens once, deliberately, as a final step after all controls are proven.
What’s next
M3 closes the open debug door that is the remaining TS2 residual. After the CMPA write, pyocd still reached the AHB Access Port (AHB-AP) in application mode; the CMPA CC_SOCU recompute that pfr write triggers did not lock debug. M3 starts from known-open debug, with RoTK2 already assigned the debug CA role in the shared root-of-trust key table.
The OTP burn (ROTKH words 0x0b through 0x16, low-word-first per Security Reference Manual (SRM) Rev 5 Table 186, the reverse of CMPA’s high-to-low order) is the final hardening step. It waits until all controls M2 through M8 are implemented and verified. When that point arrives, the ROTKH and secure-boot-config fuses go in once, with the context of a complete working stack behind them.
Further reading
- M1: FRDM-MCXN947 TARA and lifecycle plan: the TARA table M2 closes, the lifecycle state machine, and the full M2-M8 roadmap.
- Embedded Security In Practice (Part 1): why secure boot, debug lockdown, and key storage each exist, vendor-neutral.
- NXP MCX Nx4x Security Reference Manual, Rev 5 (2025-05-19): primary source for all CMPA register definitions and OTP fuse map used here.
- SPSDK 3.10.0 documentation (
nxpimage,pfr,nxpdebugmbox): tool reference for all commands above.
Acronyms
| Acronym | Term | What it is |
|---|---|---|
| AES-256 | Advanced Encryption Standard, 256-bit | Symmetric cipher; the CNSA suite level enforced by ENF_CNSA |
| AHB-AP | AHB Access Port | Debugger memory-access port on the MCX |
| BCM | Body Control Module | The automotive ECU role this board plays in the series (from M1) |
| CA | Certificate Authority | Key that signs and delegates trust to subordinate keys |
| CC_SOCU | (not expanded) | CMPA debug-access configuration field (detailed in M3) |
| CMPA | Customer Manufacturing Programmable Area | Flash region holding secure-boot config; rewritable in DEVELOP |
| CNSA | Commercial National Security Algorithm suite | NSA-defined algorithm suite: P-384, SHA-384, AES-256 |
| DFU | Device Firmware Update | USB download mode disabled in M8 |
| ECDSA | Elliptic Curve Digital Signature Algorithm | Signing algorithm used for image authentication |
| ENF_CNSA | CNSA-enforcement field | CMPA field that pins the algorithm suite to CNSA level |
| HSM | Hardware Security Module | Tamper-resistant device that guards private keys; where the root CAs live |
| ISK | Image Signing Key | Intermediate key that signs images day-to-day; certified by a RoTK |
| ISP | In-System Programming | ROM bootloader mode for flashing and device configuration |
| LC_STATE | lifecycle-state field | CMPA/OTP field encoding the device lifecycle phase |
| MBI | Master Boot Image | NXP firmware container format verified by the boot ROM |
| OEM | Original Equipment Manufacturer | Device maker responsible for provisioning and recovery policy |
| OTP | One-Time Programmable | Fuse bank; writes are permanent |
| PKI | Public Key Infrastructure | The root-CA / signing-key hierarchy that authenticates firmware |
| RKTH | Root Key Table Hash | SPSDK output label for the ROTKH value |
| ROM | Read-Only Memory | Boot ROM that verifies images on every reset |
| RoTK | Root of Trust Key | One of four P-384 keys forming the root of trust |
| RoTK_USAGE | key-role field | Assigns boot, debug, and update CA roles to each RoTK slot |
| ROTKH | Root of Trust Key Hash | 48-byte value in CMPA: each RoTK public key is hashed with SHA-384, and those four hashes are concatenated and hashed again to produce it |
| SB3.1 | Secure Binary 3.1 | NXP firmware-update container format |
| SEC_BOOT_EN | secure-boot-enable field | CMPA field controlling which image types the ROM will boot |
| SHA-384 | Secure Hash Algorithm, 384-bit | Hash algorithm used for the ROTKH and image digests |
| SPSDK | Secure Provisioning SDK | NXP tool suite: nxpimage, pfr, nxpdebugmbox, nxpcrypto |
| SRM | Security Reference Manual | NXP MCX Nx4x Security Reference Manual |
| TARA | Threat Analysis and Risk Assessment | ISO/SAE 21434 security analysis method |
| TRNG | True Random Number Generator | Hardware entropy source, seeded in M8 |
| UART | Universal Asynchronous Receiver/Transmitter | Serial interface used for boot banner and echo test |
| USB-HID | USB Human Interface Device | USB device class the ROM enumerates in recovery mode |
| XIP | Execute In Place | Image type that runs directly from flash; NXP type name: signed_xip |