Skip to content

Add support for PQC secure boot with MCXA 5xx family of MCUs - #36

Open
alamfarjadf wants to merge 29 commits into
OpenDevicePartnership:mainfrom
alamfarjadf:mcxa-577-bootloader
Open

Add support for PQC secure boot with MCXA 5xx family of MCUs#36
alamfarjadf wants to merge 29 commits into
OpenDevicePartnership:mainfrom
alamfarjadf:mcxa-577-bootloader

Conversation

@alamfarjadf

@alamfarjadf alamfarjadf commented Apr 2, 2026

Copy link
Copy Markdown

Adding ROM API support for MCXA 5xx family with Post-Quantum Cryptography hybrid secure boot (ML-DSA-87 and EC-DSA P384).

Added ec-slimloader-mcxa under libs and mcxa-577 examples (bootloader and blinky).

Validated on MCXA577 eval kit with hybrid signed AHAB MBI images which successfully authenticate and boot up.

Not yet added to PR: Imaging tools for MCXA (being worked on as an independent library).

To-dos:

  1. Move journal to external flash with flexSPI IP (already done to minimize RT6 impacts).
  2. Use memory.toml for memory mapping.
  3. Split MCXA ROM APIs as independent library under /libs
  4. Update readme.
  5. General ROM API safety and type improvements.
  6. Add journal update to App.

@jerrysxie jerrysxie assigned jerrysxie and unassigned jerrysxie Apr 7, 2026
Comment thread libs/ec-slimloader-mcxa/src/lib.rs
@alamfarjadf
alamfarjadf force-pushed the mcxa-577-bootloader branch from 08f268c to f185706 Compare April 27, 2026 20:14

@diondokter diondokter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any split image support yet? If there is I can't find it (so please point me to it 😊)
Or are we assuming the ROM memory mapped it for us already?

Comment thread examples/mcxa-577app/bootloader/.cargo/config.toml Outdated
Comment thread libs/ec-slimloader-imxrt/src/lib.rs Outdated
Comment thread libs/ec-slimloader-mcxa/src/certificate.rs
Comment thread libs/ec-slimloader-mcxa/src/jump.rs Outdated
Comment thread libs/ec-slimloader-mcxa/src/lib.rs Outdated
Comment thread libs/ec-slimloader-mcxa/src/memory.rs Outdated
@JamesHuard

Copy link
Copy Markdown
Contributor

Is there any split image support yet? If there is I can't find it (so please point me to it 😊) Or are we assuming the ROM memory mapped it for us already?

The image header that the NXP ROM APIs/ROM Loader use has a section for identifying multiple images. The thought here is that it'd be a 'no-op' from an implementation perspective of the SBL firmware to support split-image, because it's already baked into the infra. The change then is in the build tooling to actually generate these additional fields and hashes.

That's also where the open question is to NXP around if the secondary image(s) are simply hash verified or included in the full image signature block.

@alamfarjadf

Copy link
Copy Markdown
Author

Is there any split image support yet? If there is I can't find it (so please point me to it 😊) Or are we assuming the ROM memory mapped it for us already?

@diondokter There is no split image support yet.

From my understanding, the split image on the verification side should be opaque to the caller, IF the image is built correctly (The idea here is that the second executable occupies the area for the CRC binary and is hashed and verified automatically by the APIs). But that idea hasn't been validated yet partly because I haven't been able to generate a split image binary yet.

We have reached out to NXP for further clarification and are awaiting their response.

When we have a split binary, the API will use the single AHAB container to hash both binaries and validate them, so infrastructure needed is mostly on the artifact generation side (which is not published as part of this public PR yet but available in private repos).

@diondokter diondokter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the responses so far!

What's the reason for trying to massage ec-slimloader into supporting both chips and a radically different way in how the code is executed?

The non-imxrt6xx parts are pretty small. All ec-slimloader does is a little bit of in-flash state management, verifying the image and then jump to it.

But for mcxa, the verification is different. And because we're gonna be running in flash, we need to be able to swap partitions, which means that the state management needs to change too. All that's left is doing the boot jump which is like 20 lines of code.

So why take on the complexity of making everything share the same codebase and structure?

Comment thread libs/ec-slimloader-mcxa/src/rom_api.rs Outdated
Comment thread libs/ec-slimloader-mcxa/src/rom_api.rs Outdated
@diondokter

Copy link
Copy Markdown
Member

Ok, I've been trying to build this PR and that just doesn't work.

@JamesHuard

Copy link
Copy Markdown
Contributor

Thanks for the responses so far!

What's the reason for trying to massage ec-slimloader into supporting both chips and a radically different way in how the code is executed?

The non-imxrt6xx parts are pretty small. All ec-slimloader does is a little bit of in-flash state management, verifying the image and then jump to it.

But for mcxa, the verification is different. And because we're gonna be running in flash, we need to be able to swap partitions, which means that the state management needs to change too. All that's left is doing the boot jump which is like 20 lines of code.

So why take on the complexity of making everything share the same codebase and structure?

This is a fair question.

The main thing I think we want is a common interface to the boot sequencing, particularly the journal management and rollback behaviors we want to be 'the same'. We'd like to have a common 'entry point' for various products/OEM lines that give a standard set of behavioral guarantees, even if the underlying hardware mechanisms (secure signing checks, memory layouts, extra steps) are radically different. Almost all, or perhaps all, of this is achieved through the Journal and banking management.

If the current structures/interfaces are too restrictive and getting in the way, then let's evolve. If that means a new interpretation of 'ec-slimloader' as just a trait and then some chip-specific impl's of that trait, I think that's fine.

What do you see as the lowest friction approach here? From my "500-meter high" view, I'd think the basic bootloading process is 'the same', that is:

  1. Setup hardware
  2. Read journal state
  3. Either load valid image, or engage rollback recovery

2&3 being 'the same' is the useful thing here. That could easily be a small helper function on top of a trait that consumes the journal and hardware instances, which I think isn't too far off from what we have today, but there might be too much coupling here if it adds friction. I'm totally open to restructuring to make our job here easier

@alamfarjadf

alamfarjadf commented Jun 15, 2026

Copy link
Copy Markdown
Author

Ok, I've been trying to build this PR and that just doesn't work.

For 1, The embassy dependencies have not been updated in a while.

Added gitignore.

For 3, The CI build for that branch fails because chip JSON files in NXP-PAC are missing the SGI clock and power gating attributes. I was going to update them but never got the time. Once the SGI PAC is re-generated with the attributes, it will build successfully (does so in local).

@diondokter

Copy link
Copy Markdown
Member

Validated on MCXA577 eval kit with hybrid signed AHAB MBI images which successfully authenticate and boot up.

@alamfarjadf do you have a script lying around with which you did this? Or do you have it written down.
If not, I have to duplicate the time looking into this

@alamfarjadf

alamfarjadf commented Jun 22, 2026

Copy link
Copy Markdown
Author

Validated on MCXA577 eval kit with hybrid signed AHAB MBI images which successfully authenticate and boot up.

@alamfarjadf do you have a script lying around with which you did this? Or do you have it written down. If not, I have to duplicate the time looking into this

@diondokter Are you interested in the AHAB signed image generation process?

Yes, I have a CLI tool that does this. I just gave you access to the private repo with the tool (ahab-mbi-tool). Build it, run the .exe with the --build path and the blinky BIN file and you will be able to generate a signed MBI image.

Caveats:

You need ECDSA keys (public and the corresponding signer) and MLDSA-87 keys. The current MLDSA keys are from TSS' auto-signer CLI tool (which you would need to separately get access to). For signing the image, you can use the tool to sign ECDSA and MLDSA.

OR are you simply interested in the signed binaries to flash? On an "un-provisioned" eval. kit, you can flash both binaries (BL and blinky app) in the corresponding flash locations and it should validate and boot (dev mode).

To provision the MCU, refer to mcxa-provisioning part of this PR.

@alamfarjadf
alamfarjadf marked this pull request as ready for review August 11, 2026 22:15
@alamfarjadf
alamfarjadf requested a review from a team as a code owner August 11, 2026 22:15
@jerrysxie jerrysxie self-assigned this Aug 13, 2026

@JamesHuard JamesHuard left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand there's a desire to merge this in quickly to unblock follow up workstreams, and while some of the changes we'll need I could see us easily following up on, I can't in good conscience sign off on this until we're at least sure the changes introduced to the generic components that touch IMXRT aren't themselves breaking changes for those platforms. Those at minimum need to be addressed prior to check in, IMHO

Comment thread examples/mcxa-577app/app/src/main.rs Outdated
Comment thread examples/mcxa-577app/bootloader/src/main.rs
Comment thread examples/mcxa-577app/bootloader/MCXA_custom.yaml
Comment thread libs/ec-slimloader-imxrt/src/lib.rs Outdated
Comment thread libs/ec-slimloader-mcxa/src/rom_api/flash.rs
Comment thread README.md
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
@alamfarjadf

alamfarjadf commented Aug 13, 2026

Copy link
Copy Markdown
Author

I understand there's a desire to merge this in quickly to unblock follow up workstreams, and while some of the changes we'll need I could see us easily following up on, I can't in good conscience sign off on this until we're at least sure the changes introduced to the generic components that touch IMXRT aren't themselves breaking changes for those platforms. Those at minimum need to be addressed prior to check in, IMHO

Yes, IMXRT is untested. But the scope of impact to IMXRT is limited via the ec-slimloader crate itself, not libs/IMXRT.

Update: Working on integrating mcxa.rs External journal so all changes impacting RT6 can be undone.

Co-authored-by: Jimi Huard <james.huard@microsoft.com>

@jerrysxie jerrysxie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not done with the review yet, posting comments I have so far.

Comment thread libs/ec-slimloader-state/src/flash.rs Outdated
Comment thread libs/ec-slimloader-state/src/flash.rs
Comment thread libs/Cargo.toml
num_enum = { version = "0.7", default-features = false }
device-driver = "1.0"

[patch.crates-io]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we point to crates-io instead? Does embassy need to do another release?

Comment thread libs/ec-slimloader/src/lib.rs Outdated
Comment thread libs/ec-slimloader/src/lib.rs Outdated
Comment thread mcxa-security-provisioning/src/lifecycle_provisioning.rs
Comment thread mcxa-security-provisioning/Cargo.toml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that this app should be "complete". That is, it should open the journal and mark it Confirmed. Examples set the patterns folks will end up using.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I will add this.

Comment thread examples/mcxa-577app/app/src/main.rs Outdated
Comment thread libs/ec-slimloader-state/src/flash.rs
Comment thread mcxa-security-provisioning/src/lifecycle_provisioning.rs Outdated
@jerrysxie

Copy link
Copy Markdown
Contributor

@alamfarjadf Is there more changes coming for this PR? Or is this is the first PR in a series of PRs? It sounds like we intend to do a design change, not sure if the code we are reviewing in this PR will be relevant or not after the design change?

@alamfarjadf

alamfarjadf commented Aug 17, 2026

Copy link
Copy Markdown
Author

@alamfarjadf Is there more changes coming for this PR? Or is this is the first PR in a series of PRs? It sounds like we intend to do a design change, not sure if the code we are reviewing in this PR will be relevant or not after the design change?

@jerrysxie Yes, I am changing the journal to EXTERNAL flash with flexSPI IP commands, also getting rid of the erase-copy-verify flow.

A lot of the code will not be relevant (completely removed) after this next commit. However, additional design changes should be incorporated as separate PRs after.

Both of these should reduce the impacts to IMXRT.

@alamfarjadf

Copy link
Copy Markdown
Author

Is there any split image support yet? If there is I can't find it (so please point me to it 😊) Or are we assuming the ROM memory mapped it for us already?

Update: It is now confirmed that no change is needed on the SBL side for a split image, as long as the generator correctly hashes and places the correct metadata in the image array index.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the examples should follow the same names. So this example should be called examples/mcxa-577/application and the bootloader should be examples/mcxa-577/bootloader

Comment on lines +19 to +33
embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-mcxa = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0", default-features = false }
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0", default-features = false }
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }

[patch.crates-io]
embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-executor-macros = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-hal-internal = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }
embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy", rev = "f21d63db2d104a3b4c070408e529c109a64e65b0" }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should used released versions of embassy (when available, so only mcxa should be a git dep)

/// distinct from section 1's sequential RGB pattern.
#[link_section = ".text_flash1"]
#[inline(never)]
async fn flash1_pattern(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked where this is actually placed? In the memory.x there's no section defined with this name, so it's not clear to me this actually works.

I tried compiling the example to check myself, but it doesn't compile. The security provisioning crate is broken and even then there's stuff missing like a flash1.x file and the partition manager setup.

Comment on lines +14 to +17
File::create(out.join("flash1.x"))
.unwrap()
.write_all(include_bytes!("flash1.x"))
.unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file doesn't exist. Also if it did exists, it wouldn't do anything. Just put it in the memory.x or if you really want to keep it separate, include the file from the memory.x

Comment on lines +8 to +15
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] }
defmt = "1.0"
defmt-rtt = "1.0"
embassy-mcxa = { workspace = true, features = ["rt", "defmt", "mcxa5xx"] }
embassy-executor = { workspace = true, features = ["platform-cortex-m", "executor-thread"] }
embassy-time = { workspace = true, features = ["defmt", "defmt-timestamp-uptime"] }
panic-probe = { version = "1.0", features = ["print-defmt"] }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be more consistent. Use the workspace or don't.

}
}

pub type BootloaderTree = RomApi;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless

Comment on lines +196 to +201
/// Helper function to get a pointer to the flash driver API from the ROM API tree.
pub fn flash_driver() -> FlashDriver {
// Match NXP usage: g_bootloaderTree->flashDriver->...
// The bootloader tree stores a direct pointer to the flash driver interface.
bootloader_tree().flash_api()
}

@diondokter diondokter Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless helpers (below too). People can call rom_api().flash_api() themselves just fine and that's a lot clearer then what's actually being done

Comment thread libs/ec-slimloader-mcxa/src/rom_api.rs
Comment thread libs/ec-slimloader-mcxa/src/verification.rs
Comment on lines +157 to +158
/// In dev mode, if the RKTH derived from the image does not match the ROTKH in CMPA, it will be copied to the ROTKH to allow authentication to proceed (this allows flexibility in dev mode since keys may not be provisioned yet),
/// but in production mode, a mismatch will cause authentication to fail (to prevent unauthorized images from being authenticated).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be in here IMO. This just makes the barrier between accepting and not accepting an image only one cmp instruction that can be influenced by a simple fault injection. NXP spends a lot of engineering effort into their firmware to prevent that from being possible, but that's all for naught if we are not careful.

If people want to make it easy to work with in dev mode, then they just need to update the CMPA/CFPA pages to accept all images or use a different bootloader that just jumps to whatever is in the app slots.

Not doing this also makes the entire bootloader half as complex

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for factory bring up/provisioning. Without this, the first images won't boot. We can discuss specifics offline.

On the security side, all debug ports are locked once Secure Boot is provisioned. However, you bring up a good point. Currently the is_dev_mode() is acting as a "God Boolean", a good target for hardening.

I will re-work the decision path to be more fault/glitch resistant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then how did the bootloader already boot? It went through the same checks except the ROM did it.
I don't see how you can get to that point, but have the bootloader properly set up, but not the application.

I know roughly what the provisioning will look like. But that could change the settings of the types of images it accepts. Another way would be to sign the provisioning firmware with the same keys across the board and then once the provisioning is done, revoke that key.

These both seem so much more preferable to putting so much complexity in a bootloader that is not updatable.


And then still, say it all makes sense and the existing flow is what we want, why not just jump to the image? Why verify it's signed with a signature we're just making up? That's just doing pointless work.

The only thing I can come up with is that you verify the image is correct (apart from its signature). But that's also not useful to know, because the image you're checking is the provisioning image.

@alamfarjadf alamfarjadf Aug 19, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For first boot, secure boot remains off, so ROM does not validate the SBL signature. Secure Boot is off by default behavior until provisioned/fused. The SBL will always authenticate one of the app slots (active / backup).

There is no more provisioning firmware, the libraries for provisioning will become part of the actual image binary.

There is an effort ongoing regarding test/production signing criteria in factory, details of which we can discuss offline, but we are moving away from the key revocation model as a result.

The SBL and app image(s) will be signed by the same trust chain; it's just that until secure boot is turned on it's using that image embedded hash so the bootloader can authenticate it. Once secure boot is turned on, the IFR becomes the source of truth. Since there is no more re-programming (there is no "load provisioning image, then replace with app image"), this allows the intended setup to boot for the first time at the factory temporarily, until the factory flow provisions and fuses the device, at which point the root of trust is permanently set. Since no reprogram, there is one shot to make sure we get to a bootable provisioning sequence when device is first powered on.

Some of the details can not be discussed publicly, but we can setup time to discuss offline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants