Skip to content

[rom_ext] Add ML-DSA DICE cert chain implementation#30701

Open
sasdf wants to merge 13 commits into
lowRISC:earlgrey_1.0.0from
sasdf:mjTylorroyo
Open

[rom_ext] Add ML-DSA DICE cert chain implementation#30701
sasdf wants to merge 13 commits into
lowRISC:earlgrey_1.0.0from
sasdf:mjTylorroyo

Conversation

@sasdf

@sasdf sasdf commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Depends on:


This PR introduces end-to-end support for generating post-quantum ML-DSA (and also existing ECDSA) DICE attestation certificate chains in rom_ext, alongside host-side parsing and verification support in ot_certs.

  • The signed ROM_EXT binary: //sw/device/silicon_creator/rom_ext/sival:rom_ext_prod_dice_mldsa_usbdfu is around 77,316 bytes (including spx signature).
  • The generated CDI cert chain is validated on local machine with newer OpenSSL that supports MLDSA using //sw/device/silicon_creator/rom_ext/e2e/attestation:print_mldsa_certs_test test.

Key Changes

  • Hybrid DICE Certificate Templates (cdi_hybrid):
    • Adds cdi_hybrid.hjson X.509 certificate template with dynamic selector branching between classical ECDSA-P256 and ML-DSA-44 public keys and signatures.
    • Implements dice_mldsa.c for multi-stage certificate generation (dice_attest_cdi_0, dice_attest_cdi_1) and RAM handover messaging (dice_cert_gen_msg_t) across boot stages.
  • KMAC Shared State Conflict Resolution:
    • Explicitly reconfigures KMAC hardware to KMAC-256 mode (kmac_kmac256_hw_configure()) in ownership_seal_init(), preventing block conflicts after ML-DSA SHAKE operations.
  • Host Certificate Tooling (ot_certs):
    • Extends X.509 certificate parsing (x509.rs) to support ML-DSA public key OID identification and signature algorithms (ML-DSA-44, ML-DSA-65, ML-DSA-87).
  • ROM_EXT Integration & E2E Testing:
    • Enhances attestation test suites (print_certs.c and attestation_test.rs) to verify ML-DSA certificate extraction, structural equivalence against ECDSA certificates, and key ID constancy across warm reboots in Retention SRAM.

@sasdf sasdf force-pushed the mjTylorroyo branch 4 times, most recently from 507cd23 to f1ade2f Compare July 10, 2026 05:00
@sasdf sasdf marked this pull request as ready for review July 10, 2026 06:17
@sasdf sasdf requested review from a team as code owners July 10, 2026 06:17
@siemen11

Copy link
Copy Markdown
Contributor

Thank you @sasdf ! Just to understand where we are: this PR enables ML-DSA cert generation, however, it is not yet integrated to the ROM_EXT_IMM, that would be the next step?

@sasdf

sasdf commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

It's integrated to both immutable (UDS -> CDI0) and mutable (CDI0 -> CDI1) romext. The MLDSA romext is fully functional after this PR, and the print_certs test verifies the MLDSA CDI0 & CDI1 certs the romext generated.

Comment thread sw/device/silicon_creator/lib/cert/dice_mldsa.c Outdated
Comment thread sw/device/silicon_creator/lib/cert/dice_mldsa.c Outdated
Comment thread sw/device/silicon_creator/lib/cert/dice_mldsa.c Outdated
Comment thread sw/device/silicon_creator/lib/cert/dice_mldsa.c Outdated
Comment thread sw/device/silicon_creator/lib/cert/dice_mldsa.c Outdated
Comment on lines +480 to +482
retention_sram_t *retram = retention_sram_get();
dice_cert_gen_msg_t *msg = &retram->creator.dice_cert_gen;
bool regenerate = msg->hdr.type == kDiceCertGenRequest;

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.

Are we implementing on-demand only here? Are we also going to add a config word to the manifest to operate in the more conventional "update if measurements change" mode?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, only on-demand mode is planned for the very first alpha release. We'll implement other features after.

}

/* Public CDI 0 API declared in dice.h */
rom_error_t dice_attest_cdi_0(keymgr_binding_value_t *rom_ext_measurement,

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.

How much of the body of dice_attest_cdi_{0, 1} is duplicated with their P256-only implementations? Is there an opportunity to refactor the bodies of these functions into a common set of more primitive functions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree that we should deduplicate the code later.

In the dice_mldsa variant, we did a significant amount of refactoring compared to the existing dice/dice_cwt variants in order to prepare for exporting the generic attestation stub (dice_attest_next_cdi) from the imm_section.

My suggestion is to first complete the new flow within dice_mldsa. Once the new interfaces are settled, we can refactor the existing dice and dice_cwt to leverage this new construction.

sasdf added 3 commits July 10, 2026 22:25
Add `--host_jvm_args=-Xmx8g` to the default `BAZELISK` command in
`prep-bazel-airgapped-build.sh` to prevent JVM out-of-memory errors
during the airgapped prep.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: Ie8d4ff2990ba18307c3165b0aaf1ff006a6a6964
Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: If6b2be221d2330cbe42062f8cd061da26a6a6964
This adds `uds_pub_size` to `static_dice_mldsa_cdi_t` to keep track of the
actual size of the public key, and updates the member offset/size assertions
and the linker script accordingly.

Currently, there are no references to `static_dice_mldsa_cdi_t`, making it
safe to modify the struct layout.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
@sasdf sasdf force-pushed the mjTylorroyo branch 2 times, most recently from 2ff54fd to e4b2b8a Compare July 10, 2026 23:58

@siemen11 siemen11 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.

Thank you @sasdf! From a security perspective the real pillar is that this is a hybrid DICE, meaning you only accept the certificate if ECDSA sign is correct AND ML-DSA sign is correct

/**
* Returns true if the OwnerSw is booting outside of prod domain.
*/
static bool get_debug_mode_cdi1(owner_app_domain_t key_domain) {

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 I ask what the debug mode is?

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.

In this function I would HARDENED_CHECK_NE on key_domain and kOwnerAppDomainProd, because my guess is that the adversarial road is to try and get this to work in debug mode while it was not in debug mode (and so HARDENED_CHECK_EQ(key_domain, kOwnerAppDomainProd); is less important and can be removed)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In general, the debug flag indicates the device is running in insecure mode. (ref from OpenDICE)

So we hardened the prod route (i.e. the boot stage is trustworthy) rather than the debug route (i.e. the boot stage is insecure) .

cdi_hybrid_sig_values_t sig_params;
memset(&sig_params, 0, sizeof(sig_params));
if (tbs_values->key_alg == kCdiHybridKeyAlgMldsa44) {
mldsa44_tiny_sign_deterministic_with_stack(

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's an ask, but if possible I would still keep with the hedged ML-DSA with a random input. Even an EDN0 pull would be better (hardened_memshred).
But I would like to try if I can pull from the CSRNG. Meaning, that you connect the random input from ML-DSA with a hardened_memshred and I try see after this PR if I can switch it out with a lightweight ask to CSRNG?

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.

Just to be clear the randomness in ML-DSA serves a net positive security impact. Even if the randomness is bad, the security is better vs deterministic ML-DSA. This is not like ECDSA where bad randomness causes an attack

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. Switched to the randomized mldsa version and initialized the randomness with hardened_memshred.

sasdf added 10 commits July 13, 2026 22:26
Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I0e26238477ea16647e8f1a235614fb466a6a6964
This ensures the kmac peripheral is configured to the expected mode.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
Adds a certificate template for CDI that supports both ECDSA and
ML-DSA signature and key algorithms.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
This patch adds a new DICE attestation module (`dice_mldsa.c`) that
implements DICE CDI_0/CDI_1 certificate generation using ML-DSA
(specifically `mldsa44_tiny` from embedpqc) alongside existing
ECDSA-P256 attestation.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
This change extends the x509 parser in `ot_certs` to parse ML-DSA
public keys and signatures from certificates.

Since rust-openssl bindings do not fully support ML-DSA yet, we manually
parse the OID and variant byte from the exported public key DER, and
resolve the ML-DSA signature algorithms' OID strings directly.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I5c60b9fb690104c6a349843d8802b3bb6a6a6964
This adds `print_mldsa_certs` variations to the end-to-end
attestation tests.

In the device test, we handle the ML-DSA cert generation handover message,
save the key IDs across a warm reboot to verify constancy, and print the
resulting Base64-encoded certs from Retention SRAM.

On the host side, we verify the printed ML-DSA certificates signatures using
OpenSSL (if the host's OpenSSL supports ML-DSA-87) and perform a structural
equivalence comparison against the corresponding ECDSA certs.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
Add code to debug_mode_test.c to request DICE certificate generation and
trigger a reboot on a cold boot. This ensures that the DICE certificates
are generated and available in flash before the debug mode checks run.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I73205032681dd6cb25f102d772bbce4d6a6a6964
Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I51435131706009e93f37aaccbad2c5b66a6a6964
This allows the binary size to be measured and analyzed more accurately,
as the spx signatures are now properly attached to the final binaries.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I2eefefcbfc1150e452ac63d210150e3e6a6a6964
This variant produces a binary that is more similar to the production
configuration, which is useful for analyzing code size and coverage.

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I1eb88b1b6c28306295daea3aa4ca0e296a6a6964
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.

3 participants