Skip to content

Add DICE UDS ML-DSA golden tests - #31056

Draft
xorptr wants to merge 37 commits into
lowRISC:earlgrey_1.0.0from
xorptr:perso_ft_provisioning_add_uds_mldsa_golden_tests
Draft

Add DICE UDS ML-DSA golden tests#31056
xorptr wants to merge 37 commits into
lowRISC:earlgrey_1.0.0from
xorptr:perso_ft_provisioning_add_uds_mldsa_golden_tests

Conversation

@xorptr

@xorptr xorptr commented Aug 19, 2026

Copy link
Copy Markdown

This PR adds unit tests for UDS certificate template using golden data that was captured by running //sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_dice_mldsa_cw340_test on FPGA so that any inadvertent changes to the output format can be detected if the template is updated in the future.

Changes on top of PR #31055

sasdf and others added 30 commits August 13, 2026 18:27
… FT tool

Add per-object TLV format version detection and construction to
perso_tlv_lib. Update ObjHeader with header_size and version, add
MAX_CERT_NAME_LEN and MAX_CERT_LEN constants to PersoTlvTypes, and
update ft_lib to use PersoBlobParser::new() and PersoBlobBuilder::new().

TAG=agy
CONV=f24c5c61-71d1-4fc3-aef0-af78d1125515

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I9d1871e5ad93152b80a7d13725f27e146a6a6964
…ary and firmware

Add 4-byte V1 version prefix (0x010004F0) handling to device perso_tlv_data.
Update perso_tlv_get_cert_obj, perso_tlv_object_type, and perso_tlv_object_size
to detect object version from buffer without external blob_version arguments.
Update ft_personalize.c and test callers accordingly.

TAG=agy
CONV=f24c5c61-71d1-4fc3-aef0-af78d1125515

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I0db63e5d5adf3fa35d9a95fd0bceb4296a6a6964
Remove the blob_version field from manuf_certgen_inputs_t ujson struct
and test harnesses, and remove the BlobVersion enum and --blob-version
CLI argument from sw/host/provisioning/ft.

TAG=agy
CONV=f24c5c61-71d1-4fc3-aef0-af78d1125515

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: I7cad734f7db2b98202d1405052ad71236a6a6964
…blob_v1 target

Remove the blob_version attribute from the sku_cfg Bazel rule, the
SkuConfig Python dataclass, and the ot_dut.py CLI builder, and remove
the unused emulation_blob_v1 target from BUILD and provisioning_inputs.bzl.

TAG=agy
CONV=f24c5c61-71d1-4fc3-aef0-af78d1125515

Signed-off-by: Yi-Hsuan Deng <yhdeng@google.com>
Change-Id: If644fb28344e83937ccd261bb241a2de6a6a6964
Currently, the firmware stores number of objects in perso blob from host
after extracting the "UDS" certificate. There may or may not be object
present before the "UDS" certificate TLV object. Then the firmware
changes the `num_objs` in `perso_blob_from_host`, and later restores it
from the temporary variable initialized after extracting the "UDS"
certificate.

This commit changes the behavior to store the number of objects
received from the host in perso blob just after receiving the blob

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…ped static variables

This commits moves most of the global static variables into function
local scope.

The motive behind this refactor is to allow better reason about the
liveness of these variables when doing future changes to this code.
The declarations are pushed as deep in the call chain as possible based
on how the variables are used currently

- I removed `static` qualifier from `uds_key_ids` and `cdi_0_key_ids`
  since the compiler could not statically initialize fields with
  `uds_pubkey_id` (because now it is an input to the function)

- I added checks for size of `all_certs` against max size of DICE
  certificates. This should not cause any behavior change since
  `all_certs` is statically allocated to be much larger than max size of
  DICE certificates

- I removed `orig_num_objects_from_host`. Instead the function now has a
  local variable to temporarily store the original count, and the count
  is restored before the function returns

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.6%  30.4Ki    .bss
  NAN%       0  26.4%  8.00Ki    provision.all_certs
  NAN%       0  16.5%  5.01Ki    provision.blob_from_host
  NAN%       0  16.5%  5.01Ki    provision.blob_to_host
  NAN%       0  13.2%  4.00Ki    owner_page
  NAN%       0   6.6%  2.00Ki    personalize_endorse_certificates.dice_page
  NAN%       0   6.5%  1.97Ki    main_spi_buf
  NAN%       0   3.3%    1024    hash_certificate.cert_buffer
  NAN%       0   2.3%     708    measure_otp_partition.otp_state
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.cdi_0_pubkey
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.curr_pubkey
  NAN%       0   0.2%      64    provision.uds_pubkey
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    provision.certgen_inputs
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.attestation_binding_value
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.cdi_0_pubkey_id
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.cdi_1_pubkey_id
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.sealing_binding_value
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.uds_endorsement_key_id
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.zero_digest
  NAN%       0   0.1%      32    provision.otp_creator_sw_cfg_measurement
  NAN%       0   0.1%      32    provision.otp_owner_sw_cfg_measurement
  NAN%       0   0.1%      32    provision.otp_rot_creator_auth_codesign_measurement
  NAN%       0   0.1%      32    provision.otp_rot_creator_auth_state_measurement
  NAN%       0   0.1%      32    provision.uds_pubkey_id
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit removes `static` qualifier from few static variables which
were moved to function scope in the previous commit. I arbitraily chose
32 bytes as the limit so as to not increase the total stack usage while
also reducing a little bit of `.bss` section use. The stack variables
are zero-initialized as they were earlier when they were static

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.5%  30.1Ki    .bss
  NAN%       0  26.6%  8.00Ki    provision.all_certs
  NAN%       0  16.7%  5.01Ki    provision.blob_from_host
  NAN%       0  16.7%  5.01Ki    provision.blob_to_host
  NAN%       0  13.3%  4.00Ki    owner_page
  NAN%       0   6.7%  2.00Ki    personalize_endorse_certificates.dice_page
  NAN%       0   6.6%  1.97Ki    main_spi_buf
  NAN%       0   3.3%    1024    hash_certificate.cert_buffer
  NAN%       0   2.3%     704    measure_otp_partition.otp_state
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.cdi_0_pubkey
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.curr_pubkey
  NAN%       0   0.2%      64    provision.uds_pubkey
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    provision.certgen_inputs
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.attestation_binding_value
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.sealing_binding_value
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commits makes appropriate function parameters `const` qualified.
The motive behind this change is to make it easier to reason about
mutability of the static variables left in the previous commit.

A notable refactor in this commit is change from
`perso_tlv_get_cert_obj` to `perso_tlv_get_cert_obj_view`. The new
function now returns `perso_tlv_cert_obj_view_t` instead of
`perso_tlv_cert_obj_t`. `perso_tlv_cert_obj_view_t` is similar to
`perso_tlv_cert_obj_t`, but internally it uses const pointers to the
underlying buffer (hence giving only immutable access/view to the
underlying buffer). This allows making the buffer passed to
`perso_tlv_get_cert_obj_view` `const` qualified, which in turn helps
confirm that `personalize_endorse_certificates` uses `blob_to_host` only
for reads.

I also changes `zero_digest` to `const static` so that it can be moved
to `.rodata`

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.5%  30.1Ki    .bss
  NAN%       0  26.6%  8.00Ki    provision.all_certs
  NAN%       0  16.7%  5.01Ki    provision.blob_from_host
  NAN%       0  16.7%  5.01Ki    provision.blob_to_host
  NAN%       0  13.3%  4.00Ki    owner_page
  NAN%       0   6.7%  2.00Ki    personalize_endorse_certificates.dice_page
  NAN%       0   6.6%  1.97Ki    main_spi_buf
  NAN%       0   3.3%    1024    hash_certificate.cert_buffer
  NAN%       0   2.3%     704    measure_otp_partition.otp_state
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.cdi_0_pubkey
  NAN%       0   0.2%      64    personalize_gen_dice_certificates.curr_pubkey
  NAN%       0   0.2%      64    provision.uds_pubkey
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    provision.certgen_inputs
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.attestation_binding_value
  NAN%       0   0.1%      32    personalize_gen_dice_certificates.sealing_binding_value
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…chain

This commit moves the static variables that were not marked with `const`
qualifier up in the call chain. I have moved them to the function where
the 2 major stages of provisioning occur: pre-endorsement and
post-endorsement. The motive behind this changes is to (in the future)
split the variables into different sets based on which stages use them

I also added 2 types `cert_scratch_buffer_t` and
`aligned_dice_storage_page_t` so that the alignment requirement on
`cert_buffer` and `dice_page` can be expressed in function parameters
until it is not needed anymore

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.5%  30.1Ki    .bss
  NAN%       0  26.6%  8.00Ki    provision.all_certs
  NAN%       0  16.7%  5.01Ki    provision.blob_from_host
  NAN%       0  16.7%  5.01Ki    provision.blob_to_host
  NAN%       0  13.3%  4.00Ki    owner_page
  NAN%       0   6.7%  2.00Ki    provision.dice_page
  NAN%       0   6.6%  1.97Ki    main_spi_buf
  NAN%       0   3.3%    1024    provision.cert_buffer
  NAN%       0   2.3%     704    provision.otp_state
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      64    provision.cdi_0_pubkey
  NAN%       0   0.2%      64    provision.curr_pubkey
  NAN%       0   0.2%      64    provision.uds_pubkey
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    provision.certgen_inputs
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      32    provision.attestation_binding_value
  NAN%       0   0.1%      32    provision.sealing_binding_value
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit splits the static variables moved up the call chain in the
previous commit. The variables are split into 2 categories at first
level: variables that are used by both pre-endorsement and
post-endorsement stages, and variables that are used by only one of
those stages. The variables used only by one stage are put into a union
to save space in SRAM. Since the stages occur one after another, only
the stage specific and shared variables need to be live for a given
stage.

**NOTE: This optimization assumes that
`personalize_extension_pre_cert_endorse` does not store any references
to the pre-endorsement stage specific data after the call ends.** Since
the underlying buffer will be re-used for post endorsement specific
data, the data pointed to by such references will get invalidated.

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.3%  29.8Ki    .bss
  NAN%       0  72.9%  21.7Ki    provision.perso_data
  NAN%       0  13.4%  4.00Ki    owner_page
  NAN%       0   6.6%  1.97Ki    main_spi_buf
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      24    rand_testutils_rng_ctx
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit changes the function signatures to reduce the number of
parameters. In the previous commit, the functions had too many
parameters and many parameters had the same type. So a simple mistake
such as swapping 2 parameters in the long list would be hard to detect.
With a smaller list and different types for parameters where possible,
such mistakes could be avoided. New types are also added to help with
reducing the parameters count.

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  18.3%  29.8Ki    .bss
  NAN%       0  72.9%  21.7Ki    provision.perso_data
  NAN%       0  13.4%  4.00Ki    owner_page
  NAN%       0   6.6%  1.97Ki    main_spi_buf
  NAN%       0   1.9%     588    cdi_1_tbs_buffer
  NAN%       0   1.6%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
… in union

Based on the observation that `dice_page` and `certs_buffer` are not
live together, this commit further splits post-endorsement stage
specific static variables into 2 groups and puts them into a union. This
saves a little bit more space in `.bss` section.

Size for few entries in `.bss` section from
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  17.8%  28.8Ki    .bss
  NAN%       0  72.0%  20.7Ki    provision.perso_data
  NAN%       0  13.9%  4.00Ki    owner_page
  NAN%       0   6.9%  1.97Ki    main_spi_buf
  NAN%       0   2.0%     588    cdi_1_tbs_buffer
  NAN%       0   1.7%     488    cdi_0_tbs_buffer
  NAN%       0   0.9%     272    ottf_console_spi_getc.info
  NAN%       0   0.8%     224    otbn_boot_attestation_key_clear.dmem_backup
  NAN%       0   0.3%     100    pxReadyTasksLists
  NAN%       0   0.2%      64    curr_tbs_signature
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.1%      40    status_report_list
  NAN%       0   0.1%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…itionals

Putting statement for single line conditionals in curly braces so that
someone is less likely to make a mistake when new statements are added

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…provisioning

This commit changes perso firmware code to NOT generate CDI_0 and CDI_1
certificates during provisioning flow. The current behavior is as
follows:

1. Device generates
   [CDI_0](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L655-L656)
   and [CDI_1](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L675-L677)
   endorsed certificates with zeroed out digest value and sends them to
   the host along with the generated UDS TBS certificate

2. Host uses these endorsed certificates to [compute the hash](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/host/provisioning/ft_lib/src/lib.rs#L419-L422)
   of the data that device is expected to write to the flash

3. Host DOES NOT send endorsed CDI_0 and CDI_1 certificates back to the
   device. Host does send endorsed UDS certificate (see
   [code](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/host/provisioning/ft_lib/src/lib.rs#L381))

4. Device reads endorsed UDS certificate sent by the host for X.509, or
   from blob sent to the host for CWT. Device reads endorsed CDI_0 and
   CDI_1 certificates from blob sent to the host (see
   [code](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L990-L1016))

5. Device writes endorsed UDS certificate to FACTORY page, and endorsed
   CDI_0 and CDI_1 certificates to DICE page (see
   [code](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L1059-L1084))

6. Device [reads and hashes endorsed UDS, CDI_0, and CDI_1
   certificates](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L502-L537)
   along with other data, and [sends them to the
   host](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/manuf/base/ft_personalize.c#L1230-L1231)

7. Host checks the device hash against the hash it expects (see
   [code](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/host/provisioning/ft_lib/src/lib.rs#L453-L475))

The new behavior is as follows:

1. Device only generates UDS TBS certificate for DICE chain and sends it
   to the host for endorsement

2. Host only takes into account endorsed UDS certificate (for the DICE
   chain) when computing the hash it expects later

3. Host sends endorsed UDS certificate back to the device

4. Device reads endorsed UDS certificate sent by the host for X.509, or
   from the blob sent to the host for CWT.

5. Device only writes endorsed UDS certificate (for the DICE chain) to
   FACTORY page

6. Device computed hash only includes endorsed UDS certificate (for the
   DICE chain). Device sends the computed hash to the host

7. Host checks the hash sent by the device against the hash it expects

NOTES:
1. I have still kept the "DICE" page marked as "used" in the flash
layout list since I am not sure if the extensions look for the first
unused page to determine pages that are available to them

2. I changed the logic to validate the DICE CWT chain since [it needs at
   least 2
   certificates](https://android.googlesource.com/platform/tools/security/+/d80118316fb3b87092d4128196884b11fa19e08c/remote_provisioning/hwtrust/src/cbor/dice/chain.rs#109).
   This has a side effect that the generated UDS certificate will not be
   checked for valid form since it is not parsed
   [earlier](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/host/provisioning/ft_lib/src/lib.rs#L416-L418)
   as well

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally
…iate locations

The [documentation](https://github.com/lowRISC/opentitan/blob/9d35c96fc0728d82fcc0a0e1ffa06ab532dbe448/sw/device/silicon_creator/lib/drivers/keymgr.h#L111-L134)
for keymgr functions mention that call to `sc_keymgr_sw_binding_unlock_wait` is
recommended after calls to `sc_keymgr_sw_binding_set` or
`sc_keymgr_advance_state`. This commit adds calls to
`sc_keymgr_sw_binding_unlock_wait` at appropriate locations in
`sw/device/silicon_creator/manuf/base/ft_personalize.c` as per that
documentation

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
and  `//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
… it is

This commit changes the behavior of the provisioning flow so that the
firmware sends generated UDS CWT DICE certificate to the host, and then
expects the host to send it back. This allows for saving space in SRAM by
using only one perso blob variable (done in a later commit as a
mechanical refactor of code).

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…ve at time

The device no longer needs to use the perso blob it sent to the host
after changes in previous commit where the host sends back UDS CWT
certificate. Therefore, only one of those variables need to be live at a
given time: `blob_to_host` during pre-endorsement stage and
`blob_from_host` during post-endorsement stage.

This commit does a mechanical refactor to split the perso blob variables
into appropriate union fields so that device SRAM usage can be reduced.
`blob_from_host` is moved to shared post-endorsement since it is used in
stage 1 and after stage 2.

I also deleted variable to keep track of UDS CWT certificate offset in
blob_to_host since it is not needed anymore.

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
before this change:

```
   0.0%       0  17.7%  27.4Ki    .bss
     NAN%       0  75.5%  20.7Ki    provision.perso_data
     NAN%       0  14.6%  4.00Ki    owner_page
     NAN%       0   7.2%  1.97Ki    main_spi_buf
     NAN%       0   1.0%     272    ottf_console_spi_getc.info
     NAN%       0   0.4%     100    pxReadyTasksLists
     NAN%       0   0.2%      52    main_console
     NAN%       0   0.1%      40    status_report_list
     NAN%       0   0.1%      24    rand_testutils_rng_ctx
     NAN%       0   0.1%      20    xDelayedTaskList1
```

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  14.9%  22.4Ki    .bss
  NAN%       0  70.0%  15.7Ki    provision.perso_data
  NAN%       0  17.8%  4.00Ki    owner_page
  NAN%       0   8.8%  1.97Ki    main_spi_buf
  NAN%       0   1.2%     272    ottf_console_spi_getc.info
  NAN%       0   0.4%     100    pxReadyTasksLists
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.2%      40    status_report_list
```
Currently, the pre-endorsement stage uses `all_certs` to store TBS
certificates before pushing them into perso blob sent to the host.

This commit makes a mechanical change in code to use a separate smaller
scratch certificate buffer during the pre-endorsement stage.`all_certs`
is now needed only in sub-stage 1 of the post-endorsement stage. This
does not change the amount of SRAM needed. The motive behind this change
is to be able to reason about appropriate size of `all_certs` keeping in
mind that it is only needed during sub-stage 1 of the post-endorsement
stage.

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
Currently, the post endorsement stage uses a large `all_certs` buffer
to store all the certificates received from the host. Then the firmware
writes the certificates from that buffer into internal flash. Gemini
suggested that this behavior can be changed so that the firmware writes
certificate data to the internal flash directly from the perso blob from
host.

This commit replaces `all_certs` buffer with a smaller scratch
`cert_buffer` as precursor to the suggested change

1. Instead of storing all certificates read from `blob_from_host`, the
   code now reads each one of them into `cert_buffer` (overwriting the
   previously read certificate each time). This logic is still preserved
   so as to find any issues with reading certificates from
   `blob_from_host` before writing anything to the internal flash.
2. The code then re-reads each certificate from `blob_from_host` into
   `cert_buffer` to write them to the internal flash as per the layout

Step 1 can be optimized further but I haven't done so in this commit to
keep the set of changes small. I will optimize that in the next commit
(part 2)

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`
targets locally

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
before this change:

```
0.0%       0  14.9%  22.4Ki    .bss
  NAN%       0  70.0%  15.7Ki    provision.perso_data
  NAN%       0  17.8%  4.00Ki    owner_page
  NAN%       0   8.8%  1.97Ki    main_spi_buf
  NAN%       0   1.2%     272    ottf_console_spi_getc.info
  NAN%       0   0.4%     100    pxReadyTasksLists
  NAN%       0   0.2%      52    main_console
  NAN%       0   0.2%      40    status_report_list
  NAN%       0   0.1%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
  NAN%       0   0.1%      20    xDelayedTaskList2
```

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  10.8%  15.4Ki    .bss
  NAN%       0  56.4%  8.71Ki    provision.perso_data
  NAN%       0  25.9%  4.00Ki    owner_page
  NAN%       0  12.8%  1.97Ki    main_spi_buf
  NAN%       0   1.7%     272    ottf_console_spi_getc.info
  NAN%       0   0.6%     100    pxReadyTasksLists
  NAN%       0   0.3%      52    main_console
  NAN%       0   0.3%      40    status_report_list
  NAN%       0   0.2%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…rtificates to internal flash

In the previous commit, a small scratch cert buffer is used to read
endorsed certificates sent by the host in perso blob before writing them
to the internal flash.

This commit is part 2 of the changes suggested by Gemini to directly use
perso blob from the host to write endorsed certificates to the internal
flash.

This commit changes the code to populate `perso_tlv_cert_obj_view_t`
directly from the perso blob object sent from the host to do preliminary
checks. The same idea is then used to write the endorsed certificates to
the internal flash. This eliminates the need for the scratch cert buffer
for the purpose of writing endorsed certificates to the internal flash,
hence that buffer is now removed leaving more space in `.bss` section

Tested by running
` //sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
before this change:

```
0.0%       0  10.8%  15.4Ki    .bss
  NAN%       0  56.4%  8.71Ki    provision.perso_data
  NAN%       0  25.9%  4.00Ki    owner_page
  NAN%       0  12.8%  1.97Ki    main_spi_buf
  NAN%       0   1.7%     272    ottf_console_spi_getc.info
  NAN%       0   0.6%     100    pxReadyTasksLists
  NAN%       0   0.3%      52    main_console
  NAN%       0   0.3%      40    status_report_list
  NAN%       0   0.2%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
  NAN%       0   0.1%      20    xDelayedTaskList2
  NAN%       0   0.1%      20    xPendingReadyList
  NAN%       0   0.1%      20    xSuspendedTaskList
```

Output from `bloaty` for `.bss` section in
`ft_personalize_emulation_dice_mldsa_sim_qemu_rom_with_fake_keys.elf`
after this change:

```
0.0%       0  10.2%  14.4Ki    .bss
  NAN%       0  53.4%  7.71Ki    provision.perso_data
  NAN%       0  27.7%  4.00Ki    owner_page
  NAN%       0  13.7%  1.97Ki    main_spi_buf
  NAN%       0   1.8%     272    ottf_console_spi_getc.info
  NAN%       0   0.7%     100    pxReadyTasksLists
  NAN%       0   0.4%      52    main_console
  NAN%       0   0.3%      40    status_report_list
  NAN%       0   0.2%      24    rand_testutils_rng_ctx
  NAN%       0   0.1%      20    xDelayedTaskList1
  NAN%       0   0.1%      20    xDelayedTaskList2
```

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
Change buffer size for scratch buffer used to generate TBS certificates
to max of 1024 (current size) or max UDS TBS certificate size from
template

For hashing endorsed certificates, the size is kept same as now since
the buffer will only be used to hash non-PQ certs and SKU extension
certs, both of which are currently generated by the firmware

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
I am planning on using [`ml-dsa = "0.1.1"`](https://crates.io/crates/ml-dsa)
library in host tooling for benchtop provisioning tests (ft provisioning). The
current rust toolchain errors out with the following message:

```
error[E0405]: cannot find trait `AsyncFn` in this scope
   --> external/rules_rust++crate+crate_index__signature-3.0.0/src/verifier.rs:114:37
    |
114 |     async fn verify_digest_async<F: AsyncFn(&mut D) -> Result<(), Error>>(
    |                                     ^^^^^^^ not found in this scope
    |
help: consider importing this trait
    |
3   + use core::ops::AsyncFn;
    |
```

To address this issue, I am updating the toolchain to nightly version
2025-02-01. `AsyncFn` was
[merged on 2025-01-30](rust-lang/rust#135852). By trial
and error, I found that the compiler stops running into that error starting
with nightly 2025-02-01

I also fixed clippy warnings (raised as errors by `-Dwarnings` that occurred
after updating the compiler toolchain

I verified the new hashes manually from [rust
CDN](https://static.rust-lang.org/dist/2025-02-01/channel-rust-nightly.toml)

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…brary

I am planning to use functions from
`//sw/device/silicon_creator/lib/cert:dice_mldsa` library to derive seed for
UDS MLDSA keygen in `sw/device/silicon_creator/manuf/base/ft_personalize.c`.
Linking `dice_mldsa` library with `ft_personalize.c` runs into failure since it
defines few functions from the already linked
`//sw/device/silicon_creator/lib/cert:dice` (like `dice_attest_cdi_0` and
`dice_attest_cdi_1`). Since `ft_personalize.c` only needs functions to build
and populate DICE certificate for UDS, CDI0, and CDI1, I am refactoring the
code to move these function definitions inside a new library target
`//sw/device/silicon_creator/lib/cert:dice_cert_build` so that they can be
linked along with `//sw/device/silicon_creator/lib/cert:dice_mldsa` when
building new `ft_personalize.c` with ML-DSA provisioning support

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test` and
`//sw/device/silicon_creator/rom_ext/e2e/attestation:print_certs_test_fpga_cw340_rom_ext`
locally
This commit imports ML-DSA 87 tiny implementation from embedpqc into
third party. Current plan is to use this library to generate ML-DSA 87
public keys from UDS during ft_personalize.

Tested locally by running
`//sw/device/tests/embedpqc:mldsa87_tiny_test_sim_qemu_rom_with_fake_keys`
target

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…s during testing

- Create a new constant that represents stack space allocated to run
  DICE ML-DSA operations during attestation

- When running tests for embedpqc ML-DSA implementations, compare the
  runtime stack usage against stack space allocated for ML-DSA
  operations. While this may not be better than a full static analysis,
  this adds a (perhaps) slightly useful layer of protection against
  stack overflow during DICE ML-DSA operations

Tested by running
`//sw/device/tests/embedpqc:mldsa44_tiny_test_sim_qemu_rom_with_fake_keys`
and
`//sw/device/tests/embedpqc:mldsa87_tiny_test_sim_qemu_rom_with_fake_keys`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…te generation

I am planning to change `sw/device/silicon_creator/lib/cert/uds.hjson`
to support MLDSA public keys and signatures. To make sure that those
changes still allow existing EC-DSA P256 certificates to be generated
and parsed without any behavior change, I am adding a unit test to
generate TBS certificate and endorsed certificate using DICE UDS
template, and compare it against data I captured from
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test` at
commit `f11678e9553c8e4e612a30a40c1de488965209de`

I did not use firmware code in
`//sw/device/silicon_creator/lib/cert:dice_cert_build` since one of its
dependency `//sw/device/silicon_creator/lib:otbn_boot_services` does not
build on the host platform

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
TBS certificate generation on the host currently does not handle
variables with `tweak-msb` set to `true` correctly. This commit makes
changes to address that.

This issue was found by Gemini when I was working on a golden test for
UDS ECDSA certificate, and Gemini made all the changes for this fix.

Also update `svn` values in `sw/host/ot_certs/tests/example.hjson` since
the variable for that field (`security-version`) has `tweak-msb` set to
`true` in `sw/host/ot_certs/tests/generic.hjson`

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit changes DICE UDS certificate template and code to support
generating ML-DSA TBS certificates

- Add selectors for public key in template: "ecdsa-p256", "mldsa-44",
  and "mldsa-87"
- Add selectors for signature in template: "ecdsa-p256", and "mldsa-87"
- Add function to fill in TBS template for UDS to generate TBS
  certificate with ML-DSA algorithms
- Update certificate generation for UDS ECDSA certificate to select
  `ecdsa-p256` for public key and signature to support existing UDS
  certificates using ECDSA algorithm
- Add a new header file specifically for ML-DSA DICE API

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Also tested that golden test for UDS certificate generation work by
testing
`//sw/host/ot_certs:ot_certs_e2e_test_tests/uds_golden_tests_test`
target locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit creates 2 scratch stacks for DICE ML-DSA operations: 32 KiB
for ROM_EXT, and 24 KiB for perso firmware, since perso firmware
currently doesn't have enough SRAM available for 32 KiB scratch stack
and ROM_EXT might merge other data in the 32 KiB stack

ML-DSA 44 and ML-DSA 87 tiny tests are also updated to test that the
runtime stack use is less than size of both the stacks

Tested by running
`//sw/device/silicon_creator/rom_ext/e2e/attestation:print_mldsa_certs_test_fpga_cw340_rom_ext`,
`//sw/device/silicon_creator/rom_ext/e2e/attestation:print_certs_test_fpga_cw340_rom_ext`,
`//sw/device/tests/embedpqc:mldsa44_tiny_test_sim_qemu_rom_with_fake_keys`, and
`//sw/device/tests/embedpqc:mldsa87_tiny_test_sim_qemu_rom_with_fake_keys`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
Since `ft_personalize` binary will be generated for SKUs that don't need
ML-DSA support, this commit creates an empty library that those SKUs can
link to the binary

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
xorptr added 7 commits August 18, 2026 12:30
…lic key

This commit changes the perso firmware to take 2 additional inputs
during the provisioning process: boolean requesting MLDSA TBS
certificate generation, and key ID for CA which will endorse that
certificate

When ML-DSA TBS certificate generation is requested, the provisioning
flow will generate and send the TBS certificate along with other data to
the host in the currently used perso blob. The host is expected to
endorse this certificate and send the endorsed certificate back to the
device. To support this, the size of data in perso blob is increased by
amount of space required to store the endorsed certificate (8 KiB,
`static_assert` statements check for appropriate size).

For SKUs that don't need ML-DSA support during provisioning, empty DICE
ML-DSA library is linked to the `ft_personalize` binary

The host tool for bench top provisioning currently does not request
ML-DSA provisioning support from device firmware

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit changes the host benchtop provisioning tool to request
ML-DSA TBS certificate generation from device when DICE CA configuration
for ML-DSA is provided. The tool now takes 3 additional inputs:
- list of names of ML-DSA TBS certificates to expected from device
- list of names of ML-DSA certificates to endorse using the DICE CA
  configuration for ML-DSA
- list of names of endorsed ML-DSA certificates to send back to the
  device. The tool expects that the device will write these certificates
  to the internal flash, and include them in the final SHA256 hash sent
  by the device for all certificates

I am using [`ml-dsa`](https://crates.io/crates/ml-dsa) for endorsing the
certificates. Since this crate currently has a security warning
regarding lack of independent testing, I am putting the endorsement
using ML-DSA DICE CA configuration behind a feature flag:
`mldsa-provisioning-test`

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…ioning during personalization

- Add fake CA certificate (ML-DSA 87) to endorse TBS ML-DSA certificate
  during provisioning test, and add a `sku_cert` entry for it
- Add new `sku_config` for DICE ML-DSA emulation testing. This SKU
  config uses appropriate personalization binary target and enabled
  ML-DSA provisioning during personalization by specifying ML-DSA DICE
  CA configuration
- Make changes to testing scripts to load and pass appropriate
  parameters from the newly added `sku_config` so that the
  personalization tool running on the host is called with expected
  arguments
- Change the test timeout to 120 seconds if ML-DSA provisioning is
  requested since exchanging large ML-DSA certificates between device
  and host takes more time as compared to EC-DSA certificates

Tested that non-PQ provisioning flows work as expected by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_cwt_cw340_test`,
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_cw340_test`,
and `//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_tpm_cw340_test`
targets locally

Tested that PQ provisioning flow works as expected by running
`//sw/host/provisioning/orchestrator/tests:e2e_emulation_dice_mldsa_cw340_test`
target with additional bazel option `--define
mldsa_provisioning_test=true`. Logs show `PQ_UDS` TBS certificate from
device is endorsed by the host and sent back to the device. Host also
validates the endorsement of ML-DSA certificate using ML-DSA DICE CA
configuration

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit adds a default perso firmware extension for DICE ML-DSA
emulation. This extension writes the endorsed certificate with ML-DSA 44
public key to Owner reserved INFO pages 0, 1, 2, and 3. Then it confirms
and includes the hash of the certificate so that perso firmware can send
the final hash of all certificates to the host tool. The host tool will
compare this hash against the one which it expects

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_dice_mldsa_cw340_test`
target locally using `--define mldsa_provisioning_test=true` bazel flag.
If I comment out the code to hash the `PQ_UDS` certificate in the
extension, the test fails due to hash mismatch. With the hash logic
present, it passes.

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
This commit changes the name of UDS certificate generated during
provisioning with ML-DSA 44 public key to `PQ_UDS_44` so that it matches
with certificate name that would be used for the certificate generated
with ML-DSA 87 public key (`PQ_UDS_87`)

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_dice_mldsa_cw340_test`
target locally with bazel argument
`--define mldsa_provisioning_test=true`. Logs show certificate with name
`PQ_UDS_44` generated and endorsed

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…public key

This commit adds support to generate a TBS certificate with ML-DSA 87
public key in perso firmware and endorse it in the host tool. NOTE: This
certificate is NOT send back to the device. The host tool just endorses
the TBS certificate and validates the endorsed certificate with DICE
ML-DSA 87 CA certificate.

I also changed the `static_assert` conditions to reflect new constraints
after adding support for this certificate

Tested by running
`//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_dice_mldsa_cw340_test`
target locally with bazel argument
`--define mldsa_provisioning_test=true`. Test logs show that device
generates and sends both certificate (`PQ_UDS_44` and `PQ_UDS_87`) to
the host. The host tool endorses them both, but sends only `PQ_UDS_44`
back to the device

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
…DSA 87 UDS certificates

This commits finishes up a TODO item from a previous commit to add unit
tests with golden data for generating DICE UDS certificates with ML-DSA
44 and ML-DSA 87 public keys, and ML-DSA 87 signatures. The data for
these golden tests was generated by running
`//sw/host/provisioning/orchestrator/tests:e2e_multistage_emulation_dice_mldsa_cw340_test`
target with bazel argument `--define mldsa_provisioning_test=true` after
minor modifications to the perso firmware and tool to print measurements
and TBS certificates.

Tested by running
`//sw/host/ot_certs:ot_certs_e2e_test_tests/uds_golden_tests_test`
target locally.

Signed-off-by: Lovepreet Singh <lpsingh@google.com>
@xorptr
xorptr marked this pull request as ready for review August 20, 2026 07:11
@xorptr
xorptr requested review from a team and cfrantz as code owners August 20, 2026 07:11
@xorptr
xorptr requested review from pamaury and removed request for a team August 20, 2026 07:11
@xorptr
xorptr marked this pull request as draft August 20, 2026 09:59
@xorptr

xorptr commented Aug 20, 2026

Copy link
Copy Markdown
Author

Need to update for different Subject Key IDs for ML-DSA 44 and ML-DSA 87 public key certificates

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.

2 participants