Changes to ROM Specification

Comparing version 2.1 to 2.0
+996 additions -55 deletions
@@ -1,9 +1,9 @@
11 <div style="font-size: 0.85em; color: #656d76; margin-bottom: 1em; padding: 0.5em; background: #f6f8fa; border-radius: 4px;">
2-πŸ“„ Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/a32ac08170df3018580f45db2304fea35e3362d0/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>a32ac08</code>
2+πŸ“„ Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/61a20816d836d39f2c97c151bce08dc5e7c199cd/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>61a2081</code>
33 </div>
44
55
6-# Caliptra - ROM Specification v2.0.4
6+# Caliptra - ROM Specification v2.1
77
88 *Spec Version: 1.0*
99
@@ -59,23 +59,28 @@
5959 ### Fuse Registers
6060 | Register | Width (bits) | Description |
6161 | :------------------------------ | :------------ | :----------------------------------------------------- |
62-| FUSE_UDS_SEED | 512 | Obfuscated UDS |
63-| FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy |
64-| FUSE_VENDOR_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors |
62+| FUSE_UDS_SEED | 512 | Obfuscated UDS. Stored as `[u32; 16]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
63+| FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy. Stored as `[u32; 8]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
64+| FUSE_VENDOR_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors. Stored as `[u32; 12]` β€” see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal). |
6565 | FUSE_ECC_REVOCATION | 4 | Manufacturer ECC Public Key Revocation Mask |
6666 | FUSE_LMS_REVOCATION | 32 | Manufacturer LMS Public Key Revocation Mask |
6767 | FUSE_MLDSA_REVOCATION | 4 | Manufacturer MLDSA Public Key Revocation Mask |
68-| FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number |
68+| FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number. 128-bit bitmap β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
6969 | FUSE_ANTI_ROLLBACK_DISABLE | 1 | Disable SVN checking for firmware when bit is set |
70-| FUSE_IDEVID_CERT_ATTR | 768 | FUSE containing information for generating IDEVID CSR <br> **Word 0:bits[0-2]**: ECDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 0:bits[3-5]**: MLDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 1,2,3,4,5**: ECDSA Subject Key Id <br> **Word 6,7,8,9,10**: MLDSA Subject Key Id <br> **Words 11**: UEID type as defined in [IETF RATS specification](https://www.ietf.org/archive/id/draft-ietf-rats-eat-21.html#section-4.2.1.1) <br> **Words 12,13,14,15**: Manufacturer Serial Number |
71-| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization |
70+| FUSE_IDEVID_CERT_ATTR | 768 | FUSE containing information for generating IDEVID CSR <br> **Word 0:bits[0-2]**: ECDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 0:bits[3-5]**: MLDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 1,2,3,4,5**: ECDSA Subject Key Id <br> **Word 6,7,8,9,10**: MLDSA Subject Key Id <br> **Words 11**: UEID type as defined in the [IETF EAT specification](https://www.rfc-editor.org/rfc/rfc9711.html#section-4.2.1.1) <br> **Words 12,13,14,15**: Manufacturer Serial Number |
71+| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization. Stored as `[u32; 16]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
7272 | FUSE_PQC_KEY_TYPE | 2 | One-hot encoded selection of PQC key type for firmware validation. <br> **Bit 0**: MLDSA <br> **Bit 1**: LMS |
73+| FUSE_HEK_SEED | 256 | OCP HEK Seed. Stored as `[u32; 8]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
74+| FUSE_SOC_MANIFEST_SVN | 128 | SoC Manifest Security Version Number. 128-bit bitmap β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
75+| FUSE_SOC_MANIFEST_MAX_SVN | 8 | Maximum SoC Manifest Security Version Number |
76+| FUSE_SOC_STEPPING_ID | 16 | SoC Stepping Identifier |
77+| FUSE_IDEVID_MANUF_HSM_ID | 128 | Manufacturer HSM Identifier. Stored as `[u32; 4]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
7378
7479
7580 ### Architectural Registers
7681 | Register | Width (bits) | Description |
7782 | :------------------------------ | :------------ | :----------------------------------------------------- |
78-| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash |
83+| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash. Stored as `[u32; 12]` β€” see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal). |
7984
8085
8186 ### Subsystem Generic Strap Allocation
@@ -96,7 +101,8 @@
96101 | `SS_STRAP_GENERIC[3]` | `[0]` | Stable Owner Key enable. | ROM stable-key derivation and Runtime cryptographic mailbox |
97102 | `SS_STRAP_GENERIC[3]` | `[7:1]` | Reserved. | β€” |
98103 | `SS_STRAP_GENERIC[3]` | `[15:8]` | Owner Authorization Manifest minimum SVN, encoded as an unsigned integer. | Runtime owner authorization manifest verification |
99-| `SS_STRAP_GENERIC[3]` | `[31:16]` | Reserved. | β€” |
104+| `SS_STRAP_GENERIC[3]` | `[30:16]` | Reserved. | β€” |
105+| `SS_STRAP_GENERIC[3]` | `[31]` | Disable vendor-authorized debug images. | ROM firmware image and Runtime authorization manifest verification |
100106
101107
102108 ### Entropy Source Configuration Registers
@@ -105,21 +111,44 @@
105111
106112 | Register | Field/Bits | Description |
107113 | :------------------------------- | :------------ | :------------------------------------------------------ |
108-| SS_STRAP_GENERIC[2] | [15:0] | Health test window size for FIPS mode (default: 512). This is the window size for all health tests when entropy is tested in FIPS mode. In single-bit mode entropy_src internally tests four times this many samples on the selected lane. |
114+| SS_STRAP_GENERIC[2] | [15:0] | Health test window size for FIPS mode (default: 1024). This is the window size for all health tests when entropy is tested in FIPS mode. In single-bit mode entropy_src internally tests four times this many samples on the selected lane. |
109115 | SS_STRAP_GENERIC[2] | [16] | Entropy source single-bit mode. When set to 1, ROM enables `rng_bit_enable` and clears `threshold_scope`. |
110116 | SS_STRAP_GENERIC[2] | [18:17] | Entropy source single-bit mode `rng_bit_sel`. Selects which RNG bit stream to process when single-bit mode is enabled. |
111117 | SS_STRAP_GENERIC[2] | [31] | Entropy bypass mode. When set to 1, enables bypass mode (`es_type`) to allow entropy characterization directly without passing through conditioning. |
112-| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [15:0] | Adaptive Proportion test high threshold (default: 1536). The test fails if any window has more than this threshold of 1's. |
113-| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [31:16] | Adaptive Proportion test low threshold (default: 512). The test fails if any window has less than this threshold of 1's. |
118+| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [15:0] | Adaptive Proportion test high threshold (default: 75% of the FIPS window, i.e. 768 for the default window). The test fails if any window has more than this threshold of 1's. |
119+| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [31:16] | Adaptive Proportion test low threshold (default: 25% of the FIPS window, i.e. 256 for the default window). The test fails if any window has less than this threshold of 1's. |
114120 | CPTRA_I_TRNG_ENTROPY_CONFIG_1 | [15:0] | Repetition Count test threshold (default: 41). The test fails if an RNG wire repeats the same bit this many times in a row. |
115121 | CPTRA_I_TRNG_ENTROPY_CONFIG_1 | [31:16] | Alert threshold (default: 2). Number of health check failures before an alert is triggered. |
116122
117123
118124 **Notes:**
119125 - If any threshold value is set to 0, the ROM uses the default value specified above.
120-- These configuration values are stored in persistent storage after first read to prevent malicious modification (reloaded on cold reset).
126+- The Adaptive Proportion default thresholds are derived from the FIPS window (75% high, 25% low). In single-bit mode entropy_src scales the health-test window by four, so when the default window is used the ROM scales these defaults to match (high: 3072, low: 1024). An explicit threshold or window supplied by the SoC is used as-is.
127+- The thresholds, health-test window, and single-bit-mode values are stored in persistent storage after first read to prevent malicious modification (reloaded on cold reset). Conditioning bypass is read directly from the locked `SS_STRAP_GENERIC[2][31]` field.
121128 - In debug mode (`debug_locked == false`), entropy source configuration registers remain unlocked for characterization.
122129 - In production mode, ROM locks the entropy source configuration after programming to prevent modification.
130+
131+### Stable Owner Key Root Derivation
132+
133+The Stable Owner Key feature is only available in subsystem mode when OCP LOCK is disabled and the following subsystem strap is set:
134+
135+| Register | Field/Bits | Description |
136+| :------------------------------- | :--------- | :------------------------------------------------------ |
137+| SS_STRAP_GENERIC[3] | [0] | Stable Owner Key enable. When set to 1, ROM derives the Stable Owner Root Key from the HEK seed and allows `CM_DERIVE_STABLE_KEY` with `key_type = OwnerKey` when the other availability requirements are met. When clear, Stable Owner Key derivation is disabled. |
138+
139+
140+When the feature is available, ROM derives the Stable Owner Root Key during the IDevID stage before clearing DOE secrets:
141+
142+1. DOE decrypts the obfuscated HEK seed into `KEY_ID_HEK_SEED` (`KeyId14`) with HMAC block usage.
143+2. HKDF-Extract uses HMAC-SHA512 with salt `stable_owner_root_key`, zero-padded to 64 bytes, and reads `KEY_ID_HEK_SEED` as HMAC block data. The resulting PRK overwrites `KEY_ID_HEK_SEED` with HMAC key usage.
144+3. HKDF-Expand uses HMAC-SHA512 with the PRK and label `stable_owner_root_key` to populate `KEY_ID_STABLE_OWNER` (`KeyId15`) with AES key usage.
145+4. ROM write-locks `KEY_ID_STABLE_OWNER` and erases the temporary `KEY_ID_HEK_SEED` slot.
146+
147+If subsystem mode is not active, the strap is clear, or OCP LOCK is enabled, ROM skips this derivation and `CM_DERIVE_STABLE_KEY` with `key_type = OwnerKey` is unavailable.
148+
149+The following diagram summarizes the ROM-populated stable roots, including IDevID, LDevID, and the optional Owner root:
150+
151+![Stable Root Key Derivation](../images/caliptra-sw/rom/dev/doc/svg/stable-root-derivation.svg)
123152
124153 For a comprehensive overview of the SOC interface registers, please refer to the following link::
125154 https://chipsalliance.github.io/caliptra-rtl/main/external-regs/?p=caliptra_top_reg.generic_and_fuse_reg
@@ -142,11 +171,18 @@
142171
143172 Firmware manifest consists of preamble, header and table of contents.
144173
174+ROM copies the manifest into protected internal memory before verification.
175+Public-key hashes, header signatures, and the table-of-contents digest are
176+verified against that same snapshot, which is also used for image validation and
177+loading. Changes to the manifest in external staging memory after capture do not
178+change the authenticated metadata. Payloads are separately hashed against the
179+authenticated TOC digests, including after loading into ICCM.
180+
145181 #### Preamble
146182
147183 It is the unsigned portion of the manifest. Preamble contains the signing public keys and signatures. ROM is responsible for parsing the preamble. ROM performs the following steps:
148184
149-- Loads the preamble from the mailbox.
185+- Uses the preamble from the protected manifest snapshot.
150186 - Calculates the hash of ECC and LMS or MLDSA [Public Key Descriptors](#Public-Key-Descriptor) in the preamble and compares it against the hash in the fuse (FUSE_VENDOR_PK_HASH). If the hashes do not match, the boot fails.
151187 - Verifies the active Manufacturer Public Key(s) based on fuse (FUSE_ECC_REVOCATION for ECC public key, FUSE_LMS_REVOCATION for LMS public key or FUSE_MLDSA_REVOCATION for MLDSA public key)
152188
@@ -182,7 +218,7 @@
182218 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
183219 | Reserved | 1 | Reserved |
184220 | Key Hash Count | 1 | Number of valid public key hashes |
185-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4 |
221+| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4. Each hash is stored in reversed-dword format (see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)). |
186222
187223
188224 #### PQC Manufacturer Public Key Descriptor
@@ -192,7 +228,7 @@
192228 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
193229 | Key Type | 1 | Type of the key in the descriptor <br> 0x1 - MLDSA <br> 0x3 - LMS |
194230 | Key Hash Count | 1 | Number of valid public key hashes |
195-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. LMS: n = 32, MLDSA: n = 4 |
231+| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. n = 32 for both LMS and MLDSA (the struct always allocates 32 slots; for MLDSA only the first 4 are populated and the rest are zero). Each hash is stored in reversed-dword format (see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)). |
196232
197233
198234 #### Header
@@ -208,6 +244,7 @@
208244 | TOC Entry Count | 4 | Number of entries in TOC. |
209245 | PL0 PAUSER | 4 | The PAUSER with PL0 privileges. |
210246 | TOC Digest | 48 | SHA2-384 Digest of table of contents. |
247+| FW SVN | 4 | Security Version Number for the firmware image bundle. Compared to FW SVN fuses during image validation. |
211248 | Vendor Data | 40 | Vendor Data. <br> **Not Before:** Vendor Start Date [ASN1 Time Format] For Alias FMC and Alias RT certificates (15 bytes) <br> **Not After:** Vendor End Date [ASN1 Time Format] For Alias FMC and Alias RT certificates (15 bytes) <br> **Reserved:** (10 bytes) |
212249 | Owner Data | 40 | Owner Data. <br> **Not Before:** Owner Start Date [ASN1 Time Format] For Alias FMC and Alias RT certificates. Takes preference over vendor start date (15 bytes) <br> **Not After:** Owner End Date [ASN1 Time Format] For Alias FMC and Alias RT certificates. Takes preference over vendor end date (15 bytes) <br> **Reserved:** (10 bytes) |
213250
@@ -223,10 +260,9 @@
223260 | Image Type | 4 | Image Type that defines format of the image section <br> **0x0000_0001:** Executable |
224261 | Image Revision | 20 | Git Commit hash of the build |
225262 | Image Version | 4 | Firmware release number |
226-| Image SVN | 4 | Security Version Number for the image. It is compared to FW SVN fuses. FMC TOC entry's SVN field is ignored. |
227-| Reserved | 4 | Reserved field |
263+| Reserved | 8 | Reserved field |
228264 | Image Load Address | 4 | Load address |
229-| Image Entry Point | 4 | Entry point to start the execution from |
265+| Image Entry Point | 4 | Entry point to start the execution from. Must equal Image Load Address. |
230266 | Image Offset | 4 | Offset from beginning of the image |
231267 | Image Size | 4 | Image Size |
232268 | Image Hash | 48 | SHA2-384 hash of image |
@@ -314,7 +350,7 @@
314350 - The UDS Seed programming base address from the `SS_UDS_SEED_BASE_ADDR_L` and `SS_UDS_SEED_BASE_ADDR_H` registers.
315351 - The Fuse Controller's base address from the `SS_OTP_FC_BASE_ADDR_L` and `SS_OTP_FC_BASE_ADDR_H` registers.
316352
317-3. ROM then retrieves the UDS granularity from the `CPTRA_GENERIC_INPUT_WIRES` register0 Bit31 to learn if the fuse row is accessible with 32-bit or 64-bit granularity. If the bit is reset, it indicates 64-bit granularity; otherwise, it indicates 32-bit granularity.
353+3. ROM then retrieves the UDS granularity from `CPTRA_HW_CONFIG.FUSE_GRANULARITY`. If the bit is reset, it indicates 64-bit granularity; otherwise, it indicates 32-bit granularity.
318354
319355 4. ROM computes the following values:
320356 - `STATUS` register address: Fuse Controller's base address + ((`SS_STRAP_GENERIC` register0) & 0xFFFF).
@@ -406,10 +442,10 @@
406442 | Unlock Level | 1 | Debug unlock Level (Number 1-8). |
407443 | Reserved | 3 | Reserved field. |
408444 | Challenge | 48 | Random number sent in `AUTH_DEBUG_UNLOCK_CHALLENGE` mailbox command payload. |
409-| ECC Public Key | 96 | ECC P-384 public key used to verify the Message Signature <br> **X-Coordinate:** Public Key X-Coordinate (48 bytes, big endian) <br> **Y-Coordinate:** Public Key Y-Coordinate (48 bytes, big endian) |
410-| MLDSA Public Key | 2592 | MLDSA-87 public key used to verify the Message Signature. |
411-| ECC Signature | 96 | ECC P-384 signature of the Message hashed using SHA2-384. <br> **R-Coordinate:** Random Point (48 bytes) <br> **S-Coordinate:** Proof (48 bytes). |
412-| MLDSA Signature | 4628 | MLDSA signature of the Message hashed using SHA2-512. (4627 bytes + 1 Reserved byte). |
445+| ECC Public Key | 96 | ECC P-384 public key used to verify the Message Signature <br> **X-Coordinate:** Public Key X-Coordinate (48 bytes) <br> **Y-Coordinate:** Public Key Y-Coordinate (48 bytes). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
446+| MLDSA Public Key | 2592 | MLDSA-87 public key used to verify the Message Signature. See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
447+| ECC Signature | 96 | ECC P-384 signature of the Message hashed using SHA2-384. <br> **R-Coordinate:** Random Point (48 bytes) <br> **S-Coordinate:** Proof (48 bytes). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
448+| MLDSA Signature | 4628 | MLDSA-87 signature of the Message hashed using SHA2-512 (4627 bytes + 1 Reserved byte). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
413449
414450
415451 7. On receiving this payload, ROM performs the following validations:
@@ -418,7 +454,7 @@
418454 - Calculates the address of the public key hash fuse as follows: <br>
419455 **SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET register value + ( (Debug Unlock Level - 1) * SHA2-384 hash size (48 bytes) )**
420456 - Retrieves the SHA2-384 hash (48 bytes) from the calculated address using DMA assist.
421- - Computes the SHA2-384 hash of the message formed by concatenating the ECC and MLDSA public keys in the payload.
457+ - Computes the SHA2-384 hash of the message formed by concatenating the ECC and MLDSA public keys in the payload. See [Production debug unlock public key hashes: byte ordering](#production-debug-unlock-public-key-hashes-byte-ordering) for the exact byte order and fuse programming details.
422458 - Compares the retrieved and computed hashes. It the comparison fails, the ROM blocks the debug unlock request by setting the registers outlined in step 3.
423459 - Upon hash comparison failure, the ROM exits the payload validation flow and completes the mailbox command.
424460
@@ -586,11 +622,11 @@
586622 | Field | Size (bytes) | Description |
587623 | ---------------- | -------------- | ------------------------------------------------------------------------------------------------- |
588624 | Marker | 4 | Magic Number marking the start of the CSR payload. The value must be 0x435352 (β€˜CSR’ in ASCII). |
589-| Size | 4 | Size of the entire CSR payload. Current size is 8272 bytes. |
625+| Size | 4 | Size of the entire CSR payload. Current size is 8784 bytes. |
590626 | ECC CSR Size | 4 | Size of the ECC CSR in bytes. |
591627 | ECC CSR | 512 | ECC CSR buffer. Actual CSR size is indicated by 'ECC CSR Size'. |
592628 | MLDSA CSR Size | 4 | Size of the MLDSA CSR in bytes. |
593-| MLDSA CSR | 7680 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. |
629+| MLDSA CSR | 8192 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. |
594630 | CSR MAC | 64 | HMAC-512 MAC, computed over the envelope bytes up to but not including this field. |
595631
596632
@@ -614,6 +650,105 @@
614650 | πŸ”’IDevID MLDSA Pub Key |
615651
616652
653+#### UEID (Unique Endpoint Identifier)
654+
655+The UEID is a 17-byte identifier that is embedded (as an X.509 extension) in the
656+IDevID CSR, the LDevID certificate, and the FMC Alias certificate. Its value is
657+derived entirely from fuses.
658+
659+##### Source fuses
660+
661+The UEID is assembled from 5 consecutive 32-bit words of the
662+`FUSE_IDEVID_CERT_ATTR` fuse bank (see the [Fuse Registers](#fuse-registers)
663+table):
664+
665+| Fuse word | `IdevidCertAttr` variant | Usage in UEID |
666+| ----------- | ------------------------------- | ----------------------------------------- |
667+| 11 | `UeidType` | UEID type byte (see RFC 9711 Β§4.2.1.1) |
668+| 12 | `ManufacturerSerialNumber1` | First 4 bytes of the endpoint serial |
669+| 13 | `ManufacturerSerialNumber2` | Next 4 bytes of the endpoint serial |
670+| 14 | `ManufacturerSerialNumber3` | Next 4 bytes of the endpoint serial |
671+| 15 | `ManufacturerSerialNumber4` | Last 4 bytes of the endpoint serial |
672+
673+
674+Only the low byte of word 11 is used; the high 3 bytes of that word are
675+discarded. Each of the four serial-number words is written to the UEID buffer
676+in **little-endian** order (the natural byte order of the u32 register).
677+
678+##### Byte layout
679+
680+```
681+ byte 0 byte 1 ─ byte 4 byte 5 ─ byte 8 byte 9 ─ byte 12 byte 13 ─ byte 16
682+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
683+ β”‚ UeidType β”‚ β”‚ MfgSerialNum1 β”‚ β”‚ MfgSerialNum2 β”‚ β”‚ MfgSerialNum3 β”‚ β”‚ MfgSerialNum4 β”‚
684+ β”‚ (byte 0) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚
685+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
686+```
687+
688+This assembly is implemented in `caliptra_drivers::FuseBank::ueid` in
689+`drivers/src/fuse_bank.rs`, returning a `[u8; 17]`.
690+
691+##### Placement in the certificate / CSR
692+
693+The 17-byte UEID is placed in the TCG DICE "Ueid" X.509 extension (OID
694+`2.23.133.5.4.4`, not marked critical). The extension's `extnValue`
695+`OCTET STRING` contains a DER-encoded `SEQUENCE { ueid OCTET STRING }`, as
696+defined by the TCG DICE specification. The DER bytes written into the TBS
697+template are:
698+
699+| DER bytes | Meaning |
700+| --------------------------- | --------------------------------------------------------- |
701+| `30 1F` | `SEQUENCE`, length 31 β€” the `Extension` |
702+| `06 06 67 81 05 05 04 04` | `OID 2.23.133.5.4.4` (`tcg-dice-Ueid`) |
703+| `04 15` | `OCTET STRING`, length 21 β€” the `extnValue` wrapper |
704+| `30 13` | inner `SEQUENCE`, length 19 β€” the `TcgUeid` structure |
705+| `04 11` | inner `OCTET STRING`, length 17 β€” the UEID value |
706+| `XX XX … XX` (17 B) | the 17 UEID bytes assembled above |
707+
708+
709+The template slot for the 17 UEID bytes sits at a fixed offset in the TBS
710+template (e.g. `UEID_OFFSET = 312` for `InitDevIdCsrTbsEcc384`); the ROM copies
711+the UEID returned by `FuseBank::ueid` directly into that slot with no further
712+transformation. See `x509/gen/src/x509.rs::make_tcg_ueid_ext` for the generator
713+and `x509/build/*` for the resulting pre-baked templates.
714+
715+##### End-to-end example
716+
717+Given the following example fuse values (as programmed by the integration test
718+`cert_test_with_ueid` in `rom/dev/tests/rom_integration_tests/test_image_validation.rs`):
719+
720+| Fuse word | Field | Value |
721+| ----------- | -------------------------------- | --------------- |
722+| 11 | `UeidType` | `0x0000_0001` |
723+| 12 | `ManufacturerSerialNumber1` | `0x0403_0201` |
724+| 13 | `ManufacturerSerialNumber2` | `0x0807_0605` |
725+| 14 | `ManufacturerSerialNumber3` | `0x0C0B_0A09` |
726+| 15 | `ManufacturerSerialNumber4` | `0x100F_0E0D` |
727+
728+
729+Step-by-step:
730+
731+1. `FuseBank::ueid` reads the five fuse words and takes the low byte of word 11:
732+ `ueid_type = 0x01`.
733+2. Each serial-number word is converted to little-endian bytes:
734+ - `0x04030201 β†’ 01 02 03 04`
735+ - `0x08070605 β†’ 05 06 07 08`
736+ - `0x0C0B0A09 β†’ 09 0A 0B 0C`
737+ - `0x100F0E0D β†’ 0D 0E 0F 10`
738+3. The 17-byte UEID is:
739+ `01 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10`
740+ (byte 0 is the type; bytes 1–16 are the endpoint serial).
741+4. The UEID is wrapped in the DER framing shown above and emitted verbatim in
742+ the IDevID CSR, LDevID certificate, and FMC Alias certificate. The resulting
743+ bytes on the wire for the Ueid extension are:
744+ `30 1F 06 06 67 81 05 05 04 04 04 15 30 13 04 11 01 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10`.
745+
746+The `cert_test_with_ueid` test programs exactly these fuses, boots the ROM,
747+retrieves the IDevID ECC CSR, LDevID cert, and FMC Alias cert from the UART
748+log, and asserts that the hex-encoded bytes
749+`010102030405060708090A0B0C0D0E0F10` appear in all three β€” confirming both the
750+fuse-to-UEID assembly and the DER placement described here.
751+
617752 ### Local Device ID DICE layer
618753
619754 Local Device ID Layer derives the Owner CDI, ECC and MLDSA Keys. This layer represents the owner DICE Identity as it is mixed with the Field Entropy programmed by the Owner.
@@ -629,7 +764,7 @@
629764
630765 1. Derive the stable identity root secret from IDevID and store the resultant MAC in Key Vault Slot 0.
631766
632- `hmac512_mac(KvSlot0, b"stable_identity_root_idev", KvSlot6)`
767+ `hmac512_kdf(KvSlot6, b"stable_identity_root_idev", KvSlot0)`
633768
634769 2. Derive the LDevID CDI using IDevID CDI in Key Vault Slot 6 as HMAC Key and Field Entropy stored in Key Vault Slot 1 as data. The resultant MAC is stored back in Key Vault Slot 6.
635770
@@ -645,7 +780,7 @@
645780
646781 4. Derive the stable identity root secret from LDevID and store the resultant MAC in Key Vault Slot 1.
647782
648- `hmac512_mac(KvSlot1, b"stable_identity_root_ldev", KvSlot6)`
783+ `hmac512_kdf(KvSlot6, b"stable_identity_root_ldev", KvSlot1)`
649784
650785 5. Derive ECDSA Key Pair using CDI in Key Vault Slot 6 and store the generated private key in Key Vault Slot 5.
651786
@@ -732,24 +867,63 @@
732867
733868 #### Handling commands from mailbox
734869
735-ROM supports the following set of commands before handling the FW_DOWNLOAD command in PASSIVE mode (described in section 9.6) or RI_DOWNLOAD_FIRMWARE command in SUBSYSTEM mode. Once the FW_DOWNLOAD or RI_DOWNLOAD_FIRMWARE is issued, ROM stops processing any additional mailbox commands.
736-
737-1. **STASH_MEASUREMENT**: Up to eight measurements can be sent to the ROM for recording. Sending more than eight measurements will result in an FW_PROC_MAILBOX_STASH_MEASUREMENT_MAX_LIMIT fatal error. Format of a measurement is documented at [Stash Measurement command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#stash_measurement).
738-2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#version).
739-3. **SELF_TEST_START**: This command is used to invoke the FIPS Known-Answer-Tests (aka KAT) on demand. [Self Test Start command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#self_test_start).
740-4. **SELF_TEST_GET_RESULTS**: This command is used to check if a SELF_TEST command is in progress. [Self Test Get Results command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#self_test_get_results).
741-5. **SHUTDOWN**: This command is used clear the hardware crypto blocks including the keyvault. [Shutdown command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#shutdown).
742-6. **CAPABILITIES**: This command is used to query the ROM capabilities. Capabilities is a 128-bit value with individual bits indicating a specific capability. Currently, the only capability supported is ROM_BASE (bit 0). [Capabilities command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#capabilities).
743-7. **GET_IDEVID_CSR**: This command is used to fetch the IDevID CSR from ROM. [Fetch IDevIDCSR command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#get_idevid_csr).
744-8. **CM_DERIVE_STABLE_KEY**: This command is used to derive a stable key for Device Ownership Transfer or other flows. Note that in Caliptra 2.0 in subsystem mode, derived stable keys, their derivatives, and commands using them will be marked with a FIPS status of invalid since the UDS and FE cannot be completely zeroized. See [CM_DERIVE_STABLE_KEY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_derive_stable_key).
745-9. **CM_HMAC**: This command uses derived stable keys for Device Ownership Transfer or other flows. [CM_HMAC](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_hmac)
746-10. **ECDSA384_SIGNATURE_VERIFY**: This command verifies ECDSA384 signatures for Device Ownership Transfer or other flows. [ECDSA384_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#ecdsa384_signature_verify)
747-11. **MLDSA87_SIGNATURE_VERIFY**: This command verifies MLDSA87 signatures for Device Ownership Transfer or other flows. [MLDSA87_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#mldsa87_signature_verify)
748-12. **CM_RANDOM_GENERATE**: This command returns random numbers from Caliptra's RNG for Device Ownership Transfer or other flows. [CM_RANDOM_GENERATE](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_random_generate)
870+ROM supports the following set of commands before handling the FW_DOWNLOAD command in PASSIVE mode (described in section 9.6) or RI_DOWNLOAD_FIRMWARE/RI_DOWNLOAD_ENCRYPTED_FIRMWARE command in SUBSYSTEM mode. Once the FW_DOWNLOAD, RI_DOWNLOAD_FIRMWARE, or RI_DOWNLOAD_ENCRYPTED_FIRMWARE is issued, ROM stops processing any additional mailbox commands.
871+
872+1. **STASH_MEASUREMENT**: Up to eight measurements can be sent to the ROM for recording. Sending more than eight measurements will result in an FW_PROC_MAILBOX_STASH_MEASUREMENT_MAX_LIMIT fatal error. Format of a measurement is documented at [Stash Measurement command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#stash_measurement).
873+2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#version).
874+3. **SELF_TEST_START**: This command is used to invoke the FIPS Known-Answer-Tests (aka KAT) on demand. [Self Test Start command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#self_test_start).
875+4. **SELF_TEST_GET_RESULTS**: This command is used to check if a SELF_TEST command is in progress. [Self Test Get Results command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#self_test_get_results).
876+5. **SHUTDOWN**: This command is used clear the hardware crypto blocks including the keyvault. [Shutdown command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#shutdown).
877+6. **CAPABILITIES**: This command is used to query the ROM capabilities. Capabilities is a 128-bit value with individual bits indicating a specific capability. Capabilities are documented in the [Capabilities command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#capabilities).
878+7. **GET_IDEVID_CSR**: This command is used to fetch the IDevID CSR from ROM. [Fetch IDevIDCSR command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#get_idevid_csr).
879+8. **CM_DERIVE_STABLE_KEY**: This command is used to derive a stable key for Device Ownership Transfer or other flows. [CM_DERIVE_STABLE_KEY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_derive_stable_key)
880+9. **CM_HMAC**: This command uses derived stable keys for Device Ownership Transfer or other flows. [CM_HMAC](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_hmac)
881+10. **ECDSA384_SIGNATURE_VERIFY**: This command verifies ECDSA384 signatures for Device Ownership Transfer or other flows. [ECDSA384_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#ecdsa384_signature_verify)
882+11. **MLDSA87_SIGNATURE_VERIFY**: This command verifies MLDSA87 signatures for Device Ownership Transfer or other flows. [MLDSA87_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#mldsa87_signature_verify)
883+12. **CM_RANDOM_GENERATE**: This command returns random numbers from Caliptra's RNG for Device Ownership Transfer or other flows. [CM_RANDOM_GENERATE](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_random_generate)
749884 13. **CM_SHA**: This ROM-only command (ROM 2.0.1+ only) computes a SHA-384 or SHA-512 hash of input data in a single operation. This is useful for MCU ROM to verify signatures and hashes against Vendor PK hash without needing its own hash implementation. Unlike the runtime CM_SHA_INIT/CM_SHA_UPDATE/CM_SHA_FINAL commands, this is a one-shot operation that does not support streaming or contexts. See [CM_SHA](#cm_sha) below for details.
750-14. **GET_LDEV_ECC384_CERT**: This command fetches an LDevID ECC384 certificate signed by the ECC384 IDevID private key. [GET_LDEV_ECC384_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#get_ldev_ecc384_cert)
751-15. **GET_LDEV_MLDSA87_CERT**: This command fetches an LDevID MLDSA87 certificate signed by the MLDSA87 IDevID private key. [GET_LDEV_MLDSA87_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#get_ldev_mldsa87_cert)
752-16. **INSTALL_OWNER_PK_HASH**: This command saves the owner public key hash to persistent data. [INSTALL_OWNER_PK_HASH](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#install_owner_pk_hash)
885+14. **GET_LDEV_ECC384_CERT**: This command fetches an LDevID ECC384 certificate signed by the ECC384 IDevID private key. [GET_LDEV_ECC384_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#get_ldev_ecc384_cert)
886+15. **GET_LDEV_MLDSA87_CERT**: This command fetches an LDevID MLDSA87 certificate signed by the MLDSA87 IDevID private key. [GET_LDEV_MLDSA87_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#get_ldev_mldsa87_cert)
887+16. **INSTALL_OWNER_PK_HASH**: This command saves the owner public key hash to persistent data. [INSTALL_OWNER_PK_HASH](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#install_owner_pk_hash)
888+17. **OCP_LOCK_REPORT_HEK_METADATA**: This command allows the MCU to report HEK seed state and metadata to the ROM, which determines if the HEK is available. See the [OCP LOCK specification](https://github.com/chipsalliance/Caliptra/blob/main/doc/ocp_lock/releases/OCP_LOCK_Specification_v1.0_RC2.pdf) for details.
889+18. **ZEROIZE_UDS_FE**
890+
891+Zeroizes (sets to 0xFFFFFFFF) the UDS (Unique Device Secret) and/or FE (Field Entropy) partitions in the OTP fuse controller. This command is typically used during device decommissioning or ownership transfer flows.
892+
893+The command accepts a flags field where each bit controls a specific partition. Multiple partitions can be zeroized in a single command by setting multiple flag bits.
894+
895+The zeroization process follows these steps for each partition:
896+1. Clears the zeroization marker first to mask potential ECC errors during power failures
897+2. Zeroizes the seed data
898+3. Clears the partition digest
899+
900+All operations are verified to return 0xFFFFFFFF before proceeding.
901+
902+Command Code: `0x5A45_5546` ("ZEUF")
903+
904+*Table: `ZEROIZE_UDS_FE` input arguments*
905+
906+| **Name** | **Type** | **Description**
907+| -------- | -------- | ---------------
908+| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
909+| flags | u32 | Partition flags. See ZEROIZE_UDS_FE_FLAGS below.
910+
911+*Table: `ZEROIZE_UDS_FE_FLAGS` input flags*
912+
913+| **Name** | **Value** | **Description**
914+| ------------------ | --------- | ---------------
915+| ZEROIZE_UDS_FLAG | 1 << 0 | Zeroize UDS partition
916+| ZEROIZE_FE0_FLAG | 1 << 1 | Zeroize FE partition 0
917+| ZEROIZE_FE1_FLAG | 1 << 2 | Zeroize FE partition 1
918+| ZEROIZE_FE2_FLAG | 1 << 3 | Zeroize FE partition 2
919+| ZEROIZE_FE3_FLAG | 1 << 4 | Zeroize FE partition 3
920+
921+*Table: `ZEROIZE_UDS_FE` output arguments*
922+
923+| **Name** | **Type** | **Description**
924+| -------- | -------- | ---------------
925+| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian.
926+| dpe_result | u32 | Result code, 0 on success.
753927
754928 #### CM_SHA
755929
@@ -765,7 +939,7 @@
765939 | -------------- | ------------- | ---------------
766940 | chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
767941 | hash_algorithm | u32 | Hash algorithm: 1 = SHA-384, 2 = SHA-512. Value 0 is reserved and will return an error.
768-| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead).
942+| input_size | u32 | Size of input data in bytes. Maximum 16,372 bytes in subsystem mode (16 KB minus 12-byte header overhead). Passive mode supports up to 262,132 bytes (256 KB minus overhead) when using a passive-mode mailbox.
769943 | input | u8[input_size]| Input data to hash. Variable size up to the mailbox capacity.
770944
771945 *Table: `CM_SHA` output arguments*
@@ -794,7 +968,11 @@
794968
795969 Following is the sequence of steps that are performed to download the firmware image into the mailbox in SUBSYSTEM mode.
796970
797-1. On receiving the RI_DOWNLOAD_FIRMWARE mailbox command, set the RI PROT_CAP2 register version to 1.1 and the `Agent Capability` field bits:
971+ROM supports two commands for firmware download in SUBSYSTEM mode:
972+- **RI_DOWNLOAD_FIRMWARE** (Command Code: `0x5249_4644` / "RIFD"): Standard firmware download. After downloading and validating the firmware, the runtime will activate the MCU firmware immediately.
973+- **RI_DOWNLOAD_ENCRYPTED_FIRMWARE** (Command Code: `0x5249_4645` / "RIFE"): Encrypted firmware download. Sets the boot mode to `EncryptedFirmware`, which signals to the runtime that the MCU firmware is encrypted and should not be activated until it has been decrypted using the `CM_AES_GCM_DECRYPT_DMA` command.
974+
975+1. On receiving the RI_DOWNLOAD_FIRMWARE or RI_DOWNLOAD_ENCRYPTED_FIRMWARE mailbox command, set the RI PROT_CAP2 register version to 1.1 and the `Agent Capability` field bits:
798976 - `Device ID`
799977 - `Device Status`
800978 - `Recovery memory access / INDIRECT_CTRL support`
@@ -1050,7 +1228,7 @@
10501228 - **ICCM**
10511229
10521230 ### Launch FMC
1053-The ROM initializes and populates the Firmware Handoff Table (FHT) to relay essential parameters to the FMC. The format of the FHT is documented [here](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/fmc/README.md#firmware-handoff-table). Upon successful population, the ROM transfers execution control to the FMC.
1231+The ROM initializes and populates the Firmware Handoff Table (FHT) to relay essential parameters to the FMC. The format of the FHT is documented [here](https://github.com/chipsalliance/caliptra-sw/blob/main/fmc/README.md#firmware-handoff-table). Upon successful population, the ROM transfers execution control to the FMC.
10541232
10551233 ## Warm reset flow
10561234 ROM does not perform any DICE derivations or firmware validation during warm reset.
@@ -1088,8 +1266,9 @@
10881266 ROM performs the same initialization sequence as specified [here](#Initialization)
10891267
10901268 ### Error handling
1269+ROM reports fatal errors after zeroization and watchdog shutdown, without waiting for a recovery `DEVICE_RESET` request. `SS_STRAP_GENERIC[3][1]` is reserved and has no effect on fatal-error reporting.
1270+
10911271 The ROM executes the following operations:
1092- - Updates the `cptra_fw_error_fatal` and `cptra_fw_error_non_fatal` registers with the error code ROM_UNKNOWN_RESET_FLOW (0x01040020) error code.
10931272 - Zeroizes the following cryptographic hardware modules:
10941273 - Ecc384
10951274 - Hmac384
@@ -1098,6 +1277,7 @@
10981277 - Sha2-512-384Acc
10991278 - KeyVault
11001279 - Stops the WatchDog Timer.
1280+ - Updates the `cptra_fw_error_fatal` and `cptra_fw_error_non_fatal` registers with the error code ROM_UNKNOWN_RESET_FLOW (0x01040020).
11011281 - Enters an infinite loop, awaiting a reset.
11021282 <br><br>
11031283
@@ -1107,6 +1287,7 @@
11071287
11081288 The basic flow for validating the firmware involves the following:
11091289
1290+- Capturing the firmware manifest in protected internal memory and using that snapshot for all manifest authentication and subsequent image validation.
11101291 - Validating the manufacturer key descriptors in the preamble.
11111292 - Validating the active manufacturer keys with the corresponding hash in the key descriptors.
11121293 - Validating the owner key descriptors in the preamble.
@@ -1116,7 +1297,7 @@
11161297 - If the manufacturer-signed header marks the bundle as a debug image, requiring subsystem mode, asserted `SS_DEBUG_INTENT`, and clear `SS_STRAP_GENERIC[3][31]`.
11171298 - Validating the Manifest Header using the owner keys against the owner signatures.
11181299 - On the completion of these validations, it is assured that the header portion is authentic.
1119-- Loading the FMC and Rutime (RT) TOC entries from the mailbox.
1300+- Reading the FMC and Runtime (RT) TOC entries from the protected manifest snapshot.
11201301 - Validating the TOCs against the TOC hash in the header.
11211302 - On successful validation, it is assured that the TOCs are valid. The next step is to use the Hash entry in the TOCs to validate the image sections.
11221303 - Downloading the FMC Image portion of the firmware Image.
@@ -1164,11 +1345,12 @@
11641345 ### Preamble validation: Manufacturing key validation
11651346
11661347 - fuse_ecc_revocation serves as the bitmask for revoking ECC keys.
1167- - If bit-n is set, the nth key is disabled. All other higher bits that are zeros indicate the keys are still enabled.
1348+ - If bit-n is set, the nth key is disabled. All other bits that are zeros indicate the keys are still enabled.
11681349 - If all the bits are zeros, all ECC keys remain enabled.
11691350 - Ensure that the Active Key Index in the preamble is not disabled by the fuse_ecc_revocation fuse.
11701351 - If the key is disabled, the validation process fails.
1171-- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation.
1352+ - **Note: The last key index is never revoked, regardless of the fuse value.**
1353+- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation. The last key index for PQC keys is also never revoked.
11721354
11731355 ### Preamble validation: Validate the Owner key
11741356
@@ -1177,6 +1359,755 @@
11771359 - The validation process for owner public keys involves generating a SHA2-384 hash from the owner public keys within the preamble and comparing it to the hash stored in the fuse_owner_pk_hash register.
11781360 - If the computed hash matches the value in fuse_owner_pk_hash, the owner public keys are deemed valid.
11791361 - If there is a hash mismatch, the image validation process fails.
1362+
1363+### Public key hash byte ordering (dword reversal)
1364+
1365+**Important:** Hashes and ECC key coordinates stored in the firmware manifest and fuse registers use
1366+a **reversed-dword format** rather than the standard byte order defined by the SHA specification.
1367+
1368+In standard byte order, a SHA2-384 hash is a sequence of 48 bytes exactly as output by tools like
1369+OpenSSL or Python's `hashlib`. In reversed-dword format, the same 48 bytes are grouped into 12
1370+four-byte words (dwords) and the bytes within each dword are reversed.
1371+
1372+For example, if the standard SHA2-384 hash begins with `b1 7c a8 77 66 66 57 cc d1 00 e6 92 ...`:
1373+
1374+| Standard byte order | β†’ | Reversed-dword format |
1375+| ---------------------- | --- | ----------------------- |
1376+| `b1 7c a8 77` || `77 a8 7c b1` |
1377+| `66 66 57 cc` || `cc 57 66 66` |
1378+| `d1 00 e6 92` || `92 e6 00 d1` |
1379+| ... || ... |
1380+
1381+
1382+This reversed-dword format applies to:
1383+- **Individual public key hashes** in the ECC and PQC key descriptors within the preamble
1384+- **FUSE_VENDOR_PK_HASH** and **CPTRA_OWNER_PK_HASH** fuse/register values (which are `[u32; 12]` arrays)
1385+- **ECC public key coordinates** (X and Y), which are stored as `[u32; 12]` arrays in the preamble
1386+
1387+Note: LMS public key fields (`tree_type`, `otstype`, `id`, `digest`) follow the LMS specification
1388+encoding and are **not** subject to dword reversal. MLDSA public keys are stored as raw byte arrays
1389+and are also **not** subject to dword reversal.
1390+
1391+For a detailed description of byte ordering conventions for all mailbox cryptographic fields
1392+(including ECC, ML-DSA, and SHA digest fields with OpenSSL examples), see the
1393+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields)
1394+section in the Runtime README.
1395+
1396+### Computing public key hashes: step-by-step example
1397+
1398+The following example walks through the computation of the **vendor PK descriptor hash**
1399+using the test public keys from `image/fake-keys/src/lib.rs` with PQC key type **LMS (type 3)**.
1400+
1401+#### Step 1: Hash each vendor ECC public key
1402+
1403+Each ECC-384 public key has X and Y coordinates, each stored as `[u32; 12]`. To hash a key,
1404+serialize the struct to 96 bytes by writing each `u32` word in reversed-dword format, then
1405+compute SHA2-384 of those 96 bytes.
1406+
1407+**ECC Key 0:**
1408+```
1409+X (standard byte order): c69fe67f 97ea3e42 21a7a603 6c2e070d 1657327b c3f1e7c1
1410+ 8dccb9e4 ffda5c3f 4db0a1c0 567e0973 17bf4484 39696a07
1411+Y (standard byte order): c126b913 5fc82572 8f1cd403 19109430 994fe3e8 74a8b026
1412+ be14794d 27789964 7735fde8 328afd84 cd4d4aa8 72d40b42
1413+
1414+X (reversed-dword): 7fe69fc6 423eea97 03a6a721 0d072e6c 7b325716 c1e7f1c3
1415+ e4b9cc8d 3f5cdaff c0a1b04d 73097e56 8444bf17 076a6939
1416+Y (reversed-dword): 13b926c1 7225c85f 03d41c8f 30941019 e8e34f99 26b0a874
1417+ 4d7914be 64997827 e8fd3577 84fd8a32 a84a4dcd 420bd472
1418+
1419+Input to SHA384 = X_reversed || Y_reversed (96 bytes)
1420+SHA384 (standard): 84facd34 227de869 1fbb7d33 49306e0f 250a3659 53a6cc6b
1421+ 629d4616 32f73cfd 768152bb 8a03a255 5a1b1f1f c3923faa
1422+SHA384 (reversed-dword): 34cdfa84 69e87d22 337dbb1f 0f6e3049 59360a25 6bcca653
1423+ 16469d62 fd3cf732 bb528176 55a2038a 1f1f1b5a aa3f92c3
1424+```
1425+
1426+**ECC Key 1:**
1427+```
1428+X (standard): a6309750 f0a05ddb 956a7f86 2812ec4f ec454e95 3b53dbfb
1429+ 9eb54140 15ea7507 084af93c b7fa33fe 51811ad5 e754232e
1430+Y (standard): ef5a5987 7a0ce0be 2621d2a9 8bf3c5df af7b3d6d 97f24183
1431+ a4a42038 58c39b86 272ef548 e572b937 1ecf1994 1b8d4ea7
1432+
1433+SHA384 (standard): fe89195f 7fab8ebb 2818d935 837493c2 378525ef 686ed220
1434+ 09b9a399 f23f1f42 2f5ae1f3 ba1c3083 1a68a456 9c01fc96
1435+SHA384 (reversed-dword): 5f1989fe bb8eab7f 35d91828 c2937483 ef258537 20d26e68
1436+ 99a3b909 421f3ff2 f3e15a2f 83301cba 56a4681a 96fc019c
1437+```
1438+
1439+**ECC Key 2:**
1440+```
1441+X (standard): a0d25693 c4251e48 185615b0 a6c27f6d e62c39f5 a9a32f75
1442+ 9553226a 4d1926c1 7928910f b7adc1b6 89996733 10134881
1443+Y (standard): bbdf72d7 07c08100 d54fcdad b1567bb0 0522762b 76b8dc4a
1444+ 846c175a 3fbd0501 9bdc8118 4be5f33c bb21b41d 93a8c523
1445+
1446+SHA384 (standard): f397ba45 b5801ddf b732078d ffdf792f b584a73f b055acaf
1447+ ef39f31d 5b88c7d5 2753a45a 0c76b098 90d8e335 7be87f26
1448+SHA384 (reversed-dword): 45ba97f3 df1d80b5 8d0732b7 2f79dfff 3fa784b5 afac55b0
1449+ 1df339ef d5c7885b 5aa45327 98b0760c 35e3d890 267fe87b
1450+```
1451+
1452+**ECC Key 3:**
1453+```
1454+X (standard): 002a82b6 8e03e9a0 fd3b4c14 ca2cb3e8 14350a71 0e43956d
1455+ 21694fb4 f34485e8 f0e33583 f7ea142d 50e16f8b 0225bb95
1456+Y (standard): 5802641c 7c45a4a2 408e03a6 a4100a92 50fcc468 d238cd0d
1457+ 449cc3e5 1abc25e7 0b05c426 843dcd6f 944ef6ff fa53ec5b
1458+
1459+SHA384 (standard): 8ba8acb6 b98da9dc 8ffce0bc eba86454 4acbbd6e 3f31466e
1460+ 5d532565 0bfc9e3b c8afb2b5 c33e20f5 06992143 83f33bc1
1461+SHA384 (reversed-dword): b6aca88b dca98db9 bce0fc8f 5464a8eb 6ebdcb4a 6e46313f
1462+ 6525535d 3b9efc0b b5b2afc8 f5203ec3 43219906 c13bf383
1463+```
1464+
1465+#### Step 2: Hash each vendor LMS public key
1466+
1467+Each LMS public key is a 48-byte struct: `tree_type` (u32), `otstype` (u32), `id` (16 bytes),
1468+`digest` (24 bytes). The binary serialization is hashed directly.
1469+
1470+**LMS Key 0:**
1471+```
1472+tree_type=0x0000000c, otstype=0x00000007
1473+id: 4908a17b cadb1829 1e289058 d5a8e3e8
1474+digest: 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1475+
1476+Serialized (48 bytes): 0000000c 00000007 4908a17b cadb1829 1e289058 d5a8e3e8
1477+ 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1478+SHA384 (standard): fc2c1b6f 56f732d1 fd876f3f ef757cbb a2b1c64b cc148298
1479+ d7508262 4bdf27cb 23d6b5b6 7169c46f 50b7fc19 92068fec
1480+SHA384 (reversed-dword): 6f1b2cfc d132f756 3f6f87fd bb7c75ef 4bc6b1a2 988214cc
1481+ 628250d7 cb27df4b b6b5d623 6fc46971 19fcb750 ec8f0692
1482+```
1483+
1484+**LMS Key 1:**
1485+```
1486+tree_type=0x0000000c, otstype=0x00000007
1487+id: 7cb5369d 64e4281d 046e977c 70d4d0a3
1488+digest: 8ea4701d adf7d700 0564b7d6 1d1c9587 9dd6475c 9c3aae0b
1489+
1490+SHA384 (standard): 7b5811fd 8d2b0cf8 9851f12d d2a7c239 f4f3abc5 d928dcc0
1491+ 3b4b891d abbdc67f c7b88436 432e1544 a408bc9c bb503f6b
1492+SHA384 (reversed-dword): fd11587b f80c2b8d 2df15198 39c2a7d2 c5abf3f4 c0dc28d9
1493+ 1d894b3b 7fc6bdab 3684b8c7 44152e43 9cbc08a4 6b3f50bb
1494+```
1495+
1496+**LMS Key 2:**
1497+```
1498+tree_type=0x0000000c, otstype=0x00000007
1499+id: 2bbb4b72 c5b41e05 d2fabe76 f41704bd
1500+digest: dcb53f96 24d4c7b3 c9ae4d4c 0e41e08e 3b159396 0fe6a277
1501+
1502+SHA384 (standard): 7e08a494 6933d35a 42c0d7b0 0236b10b db14c100 3f82f6a9
1503+ 7d401cb8 e420a7fa 5aab12b3 c4e96bec 49aec770 225a8f88
1504+SHA384 (reversed-dword): 94a4087e 5ad33369 b0d7c042 0bb13602 00c114db a9f6823f
1505+ b81c407d faa720e4 b312ab5a ec6be9c4 70c7ae49 888f5a22
1506+```
1507+
1508+**LMS Key 3:**
1509+```
1510+tree_type=0x0000000c, otstype=0x00000007
1511+id: 42cba2e5 575b5235 7ea7aead ef54074c
1512+digest: 5aa60e27 69251599 3ae8e21f 27ccdded 8ffcd3d2 8efbdec2
1513+
1514+SHA384 (standard): d3734fbc ee2893a3 b1b6519b 6ec78fb8 d7425327 cde1f7aa
1515+ 23012c64 c635219f d4ab1c4d 1b023252 00042884 2e463dbb
1516+SHA384 (reversed-dword): bc4f73d3 a39328ee 9b51b6b1 b88fc76e 275342d7 aaf7e1cd
1517+ 642c0123 9f2135c6 4d1cabd4 5232021b 84280400 bb3d462e
1518+```
1519+
1520+#### Step 3: Build the ECC key descriptor (196 bytes)
1521+
1522+Concatenate the 4-byte header with the 4 key hashes (each in reversed-dword format):
1523+
1524+```
1525+Header (4 bytes): 01 00 00 04 (version=1, reserved=0, key_hash_count=4)
1526+ECC key 0 hash (48 bytes, reversed-dword): 34cdfa84 69e87d22 ... aa3f92c3
1527+ECC key 1 hash (48 bytes, reversed-dword): 5f1989fe bb8eab7f ... 96fc019c
1528+ECC key 2 hash (48 bytes, reversed-dword): 45ba97f3 df1d80b5 ... 267fe87b
1529+ECC key 3 hash (48 bytes, reversed-dword): b6aca88b dca98db9 ... c13bf383
1530+
1531+Total: 4 + (4 Γ— 48) = 196 bytes
1532+```
1533+
1534+#### Step 4: Build the PQC (LMS) key descriptor (1540 bytes)
1535+
1536+```
1537+Header (4 bytes): 01 00 03 20 (version=1, key_type=3=LMS, key_hash_count=32)
1538+LMS key 0 hash (48 bytes, reversed-dword): 6f1b2cfc d132f756 ... ec8f0692
1539+LMS key 1 hash (48 bytes, reversed-dword): fd11587b f80c2b8d ... 6b3f50bb
1540+LMS key 2 hash (48 bytes, reversed-dword): 94a4087e 5ad33369 ... 888f5a22
1541+LMS key 3 hash (48 bytes, reversed-dword): bc4f73d3 a39328ee ... bb3d462e
1542+ ... (keys 0-3 repeated 8 times to fill all 32 slots)
1543+
1544+Total: 4 + (32 Γ— 48) = 1540 bytes
1545+```
1546+
1547+#### Step 5: Compute the vendor PK descriptor hash
1548+
1549+```
1550+Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes
1551+
1552+SHA384 (standard byte order):
1553+ b17ca877 666657cc d100e692 6c7206b6 0c995cb6 8992c6c9
1554+ baefce72 8af05441 dee1ff41 5adfc187 e1e4edb4 d3b2d909
1555+
1556+As [u32; 12] fuse register value:
1557+ [0xb17ca877, 0x666657cc, 0xd100e692, 0x6c7206b6,
1558+ 0x0c995cb6, 0x8992c6c9, 0xbaefce72, 0x8af05441,
1559+ 0xdee1ff41, 0x5adfc187, 0xe1e4edb4, 0xd3b2d909]
1560+```
1561+
1562+### Computing public key hashes: MLDSA step-by-step example
1563+
1564+The following example walks through the same computation as the LMS example above, but
1565+using PQC key type **MLDSA (type 1)** with the test keys from `image/fake-keys/src/lib.rs`.
1566+
1567+#### MLDSA Step 1: Hash each vendor ECC public key
1568+
1569+The ECC keys and their hashes are identical to the LMS example β€” see
1570+[Step 1 above](#step-1-hash-each-vendor-ecc-public-key). The ECC key descriptor is
1571+independent of the PQC key type.
1572+
1573+#### MLDSA Step 2: Hash each vendor MLDSA public key
1574+
1575+Each MLDSA-87 public key is a 2592-byte array (`[u32; 648]`). When serialized via
1576+`as_bytes()`, each `u32` word is written in little-endian byte order β€” for example, the
1577+Rust value `0x3bf1c072` becomes bytes `72 c0 f1 3b` in memory. Unlike LMS keys, MLDSA
1578+keys are not subject to any additional encoding β€” these raw bytes are hashed directly
1579+with SHA2-384.
1580+
1581+**MLDSA Key 0:**
1582+```
1583+Size: 2592 bytes (648 u32 words)
1584+First 24 bytes: 72c0f13b 7d937e22 69b6988d 6daadc3a e78acd11 940cfc0d ...
1585+
1586+SHA384 (standard): f1097978 0adae470 dcd4eeb8 5749a2e4 2e70c055 ebac46e4
1587+ 07c2c404 b46473d8 189117ed 8c83dde4 9f941e6a 1b6c6d4c
1588+SHA384 (reversed-dword): 787909f1 70e4da0a b8eed4dc e4a24957 55c0702e e446aceb
1589+ 04c4c207 d87364b4 ed179118 e4dd838c 6a1e949f 4c6d6c1b
1590+```
1591+
1592+**MLDSA Key 1:**
1593+```
1594+Size: 2592 bytes (648 u32 words)
1595+First 24 bytes: f432346c 096d0ec9 04f8d925 1512236b e3fd1ccb bda9ed3a ...
1596+
1597+SHA384 (standard): a57b6f71 ffab9844 de49e9f7 ad61476b 7446e140 517d07b1
1598+ 81447acb a6d7166f 7b89f199 b6e36174 2d0ab01c 540d26de
1599+SHA384 (reversed-dword): 716f7ba5 4498abff f7e949de 6b4761ad 40e14674 b1077d51
1600+ cb7a4481 6f16d7a6 99f1897b 7461e3b6 1cb00a2d de260d54
1601+```
1602+
1603+**MLDSA Key 2:**
1604+```
1605+Size: 2592 bytes (648 u32 words)
1606+First 24 bytes: 2bc91a00 7d3e5a4f e6b3f2ec cb1aaa0d 278d9786 44b25fed ...
1607+
1608+SHA384 (standard): 7f2f3c55 e8dd2481 bbee17c1 5d5773a8 01a9c0a6 84b30e47
1609+ 0ae67ecd 1ec3e7ac 19273c71 feb6bb99 10d26dd0 4ace4298
1610+SHA384 (reversed-dword): 553c2f7f 8124dde8 c117eebb a873575d a6c0a901 470eb384
1611+ cd7ee60a ace7c31e 713c2719 99bbb6fe d06dd210 9842ce4a
1612+```
1613+
1614+**MLDSA Key 3:**
1615+```
1616+Size: 2592 bytes (648 u32 words)
1617+First 24 bytes: 378dcb02 a6db3481 d51e9913 14da1567 a211290e f4c3d02f ...
1618+
1619+SHA384 (standard): 79fbeb0a 6ebc354b ccf48dd1 5b6c9142 a62af0c5 198c0de1
1620+ 365fbcb0 b2463ee5 103ccae3 4504ab83 04b37886 5c9a28ae
1621+SHA384 (reversed-dword): 0aebfb79 4b35bc6e d18df4cc 42916c5b c5f02aa6 e10d8c19
1622+ b0bc5f36 e53e46b2 e3ca3c10 83ab0445 8678b304 ae289a5c
1623+```
1624+
1625+#### MLDSA Step 3: Build the ECC key descriptor (196 bytes)
1626+
1627+Same as the LMS example β€” the ECC descriptor is independent of PQC key type. See
1628+[Step 3 above](#step-3-build-the-ecc-key-descriptor-196-bytes).
1629+
1630+#### MLDSA Step 4: Build the PQC (MLDSA) key descriptor (1540 bytes)
1631+
1632+The PQC key descriptor struct always has 32 hash slots (`VENDOR_PQC_MAX_KEY_COUNT`).
1633+For MLDSA, only 4 keys are populated; the remaining 28 slots are zero-filled.
1634+
1635+```
1636+Header (4 bytes): 01 00 01 04 (version=1, key_type=1=MLDSA, key_hash_count=4)
1637+MLDSA key 0 hash (48 bytes, reversed-dword): 787909f1 70e4da0a ... 4c6d6c1b
1638+MLDSA key 1 hash (48 bytes, reversed-dword): 716f7ba5 4498abff ... de260d54
1639+MLDSA key 2 hash (48 bytes, reversed-dword): 553c2f7f 8124dde8 ... 9842ce4a
1640+MLDSA key 3 hash (48 bytes, reversed-dword): 0aebfb79 4b35bc6e ... ae289a5c
1641+ ... (keys 4-31 are zero-filled)
1642+
1643+Total: 4 + (32 Γ— 48) = 1540 bytes
1644+```
1645+
1646+#### MLDSA Step 5: Compute the vendor PK descriptor hash
1647+
1648+```
1649+Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes
1650+
1651+SHA384 (standard byte order):
1652+ 30399676 a17e3e97 3677b3ff 862f4bf2 d1932d88 4778453c
1653+ 376fe00d c93fb8aa 0770f3eb f3411a08 53e9c57e ce8a2980
1654+
1655+As [u32; 12] fuse register value:
1656+ [0x30399676, 0xa17e3e97, 0x3677b3ff, 0x862f4bf2,
1657+ 0xd1932d88, 0x4778453c, 0x376fe00d, 0xc93fb8aa,
1658+ 0x0770f3eb, 0xf3411a08, 0x53e9c57e, 0xce8a2980]
1659+```
1660+
1661+#### Owner PK hash
1662+
1663+The owner PK hash is SHA2-384 over the serialized `ImageOwnerPubKeys` struct, which contains:
1664+- `ecc_pub_key`: `{ x: [u32; 12], y: [u32; 12] }` β€” 96 bytes (in reversed-dword format)
1665+- `pqc_pub_key`: raw byte array of 2592 bytes (for LMS, only the first 48 bytes are meaningful;
1666+ the rest are zero-padded)
1667+
1668+Total: 2688 bytes. The SHA2-384 of these bytes is the owner PK hash.
1669+
1670+#### Summary of expected hash values using test keys
1671+
1672+Using the test keys from `image/fake-keys/src/lib.rs`:
1673+
1674+| Hash | PQC Type | Standard byte order (hex) |
1675+| ------ | ---------- | --------------------------- |
1676+| Vendor PK descriptor hash | LMS (type 3) | `b17ca877666657ccd100e6926c7206b60c995cb68992c6c9baefce728af05441dee1ff415adfc187e1e4edb4d3b2d909` |
1677+| Vendor PK descriptor hash | MLDSA (type 1) | `30399676a17e3e973677b3ff862f4bf2d1932d884778453c376fe00dc93fb8aa0770f3ebf3411a0853e9c57ece8a2980` |
1678+| Owner PK hash | LMS (type 3) | `1b179390e4e6c44422ed553e256c7d675cd93190cb49d88d485aa4ef3906cd492ab3ee3d3ba5f2c990ad13390fed4de5` |
1679+| Owner PK hash | MLDSA (type 1) | `48afdb073c5e0d4ee46490468ef81f2cf57249b6e76a28f5fca4de696a7d3e2ed3efc4e6774318543e95307a54988bd7` |
1680+
1681+
1682+To convert any of these standard byte order hashes to the `[u32; 12]` fuse register format, group
1683+the hex string into 8-character (4-byte) chunks and interpret each as a 32-bit word:
1684+- `b17ca877666657cc...` β†’ `[0xb17ca877, 0x666657cc, 0xd100e692, ...]`
1685+
1686+#### Python script to compute vendor and owner PK hashes
1687+
1688+The following Python script computes the vendor PK descriptor hash and owner PK hash from
1689+ECC PEM files and LMS or MLDSA binary key files:
1690+
1691+```python
1692+#!/usr/bin/env python3
1693+"""
1694+Compute the Caliptra vendor PK descriptor hash and owner PK hash
1695+from ECC (.pem) and LMS/MLDSA (.bin) public key files.
1696+
1697+Usage:
1698+ python3 compute_pk_hashes.py --pqc-key-type <1|3> \\
1699+ --vendor-ecc-pub-keys key0.pem key1.pem key2.pem key3.pem \\
1700+ --vendor-pqc-pub-keys pqc0.bin pqc1.bin ... \\
1701+ --owner-ecc-pub-key owner.pem \\
1702+ --owner-pqc-pub-key owner_pqc.bin
1703+
1704+PQC key type: 1 = MLDSA, 3 = LMS
1705+
1706+ECC public keys are PEM files (P-384).
1707+LMS public keys are 48-byte binary files (tree_type, otstype, id, digest).
1708+MLDSA public keys are 2592-byte binary files.
1709+"""
1710+import argparse
1711+import hashlib
1712+import struct
1713+import sys
1714+
1715+from cryptography.hazmat.primitives.serialization import load_pem_public_key
1716+
1717+# Sizes
1718+ECC_PUB_KEY_BYTES = 96 # 2 x 48-byte coordinates
1719+PQC_PUB_KEY_SLOT_BYTES = 2592 # MLDSA key size; LMS keys are 48 bytes, zero-padded
1720+LMS_PUB_KEY_BYTES = 48
1721+MLDSA_PUB_KEY_BYTES = 2592
1722+HASH_BYTES = 48 # SHA2-384
1723+
1724+VENDOR_ECC_MAX_KEYS = 4
1725+VENDOR_LMS_MAX_KEYS = 32
1726+VENDOR_MLDSA_MAX_KEYS = 32 # struct always allocates 32 slots; only first 4 are populated
1727+KEY_DESCRIPTOR_VERSION = 1
1728+
1729+
1730+def ecc_pub_key_to_reversed_dwords(pem_path: str) -> bytes:
1731+ """Read an ECC P-384 PEM public key and return 96 bytes in reversed-dword format."""
1732+ with open(pem_path, 'rb') as f:
1733+ pub_key = load_pem_public_key(f.read())
1734+ nums = pub_key.public_numbers()
1735+ x_bytes = nums.x.to_bytes(48, 'big')
1736+ y_bytes = nums.y.to_bytes(48, 'big')
1737+ return to_reversed_dwords(x_bytes) + to_reversed_dwords(y_bytes)
1738+
1739+
1740+def to_reversed_dwords(standard_bytes: bytes) -> bytes:
1741+ """Convert bytes from standard byte order to reversed-dword format.
1742+
1743+ Groups the input into 4-byte dwords and reverses the bytes within each dword.
1744+ """
1745+ assert len(standard_bytes) % 4 == 0
1746+ result = bytearray()
1747+ for i in range(0, len(standard_bytes), 4):
1748+ result.extend(standard_bytes[i:i+4][::-1])
1749+ return bytes(result)
1750+
1751+
1752+def sha384_reversed_dwords(data: bytes) -> bytes:
1753+ """Compute SHA2-384 and return the hash in reversed-dword format."""
1754+ h = hashlib.sha384(data).digest()
1755+ return to_reversed_dwords(h)
1756+
1757+
1758+def build_ecc_key_descriptor(ecc_pem_paths: list) -> bytes:
1759+ """Build the ECC key descriptor: header + key hashes."""
1760+ n = len(ecc_pem_paths)
1761+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, 0, n)
1762+ hashes = b''
1763+ for path in ecc_pem_paths:
1764+ key_bytes = ecc_pub_key_to_reversed_dwords(path)
1765+ hashes += sha384_reversed_dwords(key_bytes)
1766+ # Pad to VENDOR_ECC_MAX_KEYS slots
1767+ hashes += b'\x00' * (HASH_BYTES * (VENDOR_ECC_MAX_KEYS - n))
1768+ return header + hashes
1769+
1770+
1771+def build_pqc_key_descriptor(pqc_bin_paths: list, pqc_key_type: int) -> bytes:
1772+ """Build the PQC key descriptor: header + key hashes."""
1773+ n = len(pqc_bin_paths)
1774+ max_keys = VENDOR_LMS_MAX_KEYS if pqc_key_type == 3 else VENDOR_MLDSA_MAX_KEYS
1775+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, pqc_key_type, n)
1776+ hashes = b''
1777+ for path in pqc_bin_paths:
1778+ with open(path, 'rb') as f:
1779+ key_bytes = f.read()
1780+ hashes += sha384_reversed_dwords(key_bytes)
1781+ # Pad to max slots
1782+ hashes += b'\x00' * (HASH_BYTES * (max_keys - n))
1783+ return header + hashes
1784+
1785+
1786+def build_owner_pub_keys(ecc_pem_path: str, pqc_bin_path: str) -> bytes:
1787+ """Build the serialized ImageOwnerPubKeys struct."""
1788+ ecc_bytes = ecc_pub_key_to_reversed_dwords(ecc_pem_path)
1789+ with open(pqc_bin_path, 'rb') as f:
1790+ pqc_bytes = f.read()
1791+ # Pad PQC key to full slot size
1792+ pqc_padded = pqc_bytes + b'\x00' * (PQC_PUB_KEY_SLOT_BYTES - len(pqc_bytes))
1793+ return ecc_bytes + pqc_padded
1794+
1795+
1796+def hash_to_fuse_words(standard_hash: bytes) -> list:
1797+ """Convert a standard byte order hash to [u32; 12] fuse word format."""
1798+ return [int.from_bytes(standard_hash[i:i+4], 'big') for i in range(0, 48, 4)]
1799+
1800+
1801+def main():
1802+ parser = argparse.ArgumentParser(
1803+ description='Compute Caliptra vendor PK descriptor hash and owner PK hash')
1804+ parser.add_argument('--pqc-key-type', type=int, required=True, choices=[1, 3],
1805+ help='PQC key type: 1=MLDSA, 3=LMS')
1806+ parser.add_argument('--vendor-ecc-pub-keys', nargs='+', required=True,
1807+ help='Vendor ECC P-384 public key PEM files')
1808+ parser.add_argument('--vendor-pqc-pub-keys', nargs='+', required=True,
1809+ help='Vendor PQC (LMS .bin or MLDSA .bin) public key files')
1810+ parser.add_argument('--owner-ecc-pub-key',
1811+ help='Owner ECC P-384 public key PEM file')
1812+ parser.add_argument('--owner-pqc-pub-key',
1813+ help='Owner PQC (LMS .bin or MLDSA .bin) public key file')
1814+ args = parser.parse_args()
1815+
1816+ pqc_name = {1: 'MLDSA', 3: 'LMS'}[args.pqc_key_type]
1817+
1818+ # Build descriptors
1819+ ecc_desc = build_ecc_key_descriptor(args.vendor_ecc_pub_keys)
1820+ pqc_desc = build_pqc_key_descriptor(args.vendor_pqc_pub_keys, args.pqc_key_type)
1821+ vendor_pub_key_info = ecc_desc + pqc_desc
1822+
1823+ # Vendor PK descriptor hash (standard byte order)
1824+ vendor_hash = hashlib.sha384(vendor_pub_key_info).digest()
1825+ vendor_hex = vendor_hash.hex()
1826+ vendor_words = hash_to_fuse_words(vendor_hash)
1827+
1828+ print(f"PQC key type: {args.pqc_key_type} ({pqc_name})")
1829+ print()
1830+ print(f"Vendor PK descriptor hash (standard byte order):")
1831+ print(f" {vendor_hex}")
1832+ print(f"Vendor PK descriptor hash (fuse [u32; 12]):")
1833+ print(f" {['0x{:08x}'.format(w) for w in vendor_words]}")
1834+
1835+ if args.owner_ecc_pub_key and args.owner_pqc_pub_key:
1836+ owner_bytes = build_owner_pub_keys(args.owner_ecc_pub_key, args.owner_pqc_pub_key)
1837+ owner_hash = hashlib.sha384(owner_bytes).digest()
1838+ owner_hex = owner_hash.hex()
1839+ owner_words = hash_to_fuse_words(owner_hash)
1840+
1841+ print()
1842+ print(f"Owner PK hash (standard byte order):")
1843+ print(f" {owner_hex}")
1844+ print(f"Owner PK hash (fuse [u32; 12]):")
1845+ print(f" {['0x{:08x}'.format(w) for w in owner_words]}")
1846+
1847+
1848+if __name__ == '__main__':
1849+ main()
1850+```
1851+
1852+### Fuse value byte ordering
1853+
1854+This section documents the byte ordering convention for every multi-word fuse
1855+register. It uses the same style as the
1856+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields)
1857+section in the Runtime README: examples show the relationship between standard
1858+tool output (e.g. OpenSSL, Python `hashlib`) and the `u32` word values written
1859+to fuse registers.
1860+
1861+> **When adding a new multi-word fuse**, add an entry to the appropriate
1862+> category below so that SoC integrators have a single reference for all fuse
1863+> byte ordering.
1864+
1865+#### SHA digest fuses (big-endian words / reversed-dword)
1866+
1867+The following fuse registers store SHA digest values as `[u32; N]` arrays using
1868+the same **reversed-dword format** described in
1869+[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal).
1870+Each 4-byte group from the standard hash output (as produced by `openssl dgst`
1871+or Python's `hashlib`) is byte-reversed when stored as a `u32` word.
1872+
1873+| Fuse Register | Array Type | Hash Algorithm |
1874+| --- | --- | --- |
1875+| FUSE_VENDOR_PK_HASH | `[u32; 12]` | SHA2-384 of vendor public key descriptors |
1876+| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | `[u32; 16]` | SHA-512 of the manufacturing debug unlock token |
1877+
1878+
1879+Example β€” suppose `openssl dgst -sha512` produces a digest starting with:
1880+
1881+```
1882+openssl output: 86 9B A8 D5 AD 0F CF 82 02 E5 60 80 ...
1883+ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~
1884+Fuse register[0]: 0x869BA8D5 [1]: 0xAD0FCF82 [2]: 0x02E56080 ...
1885+```
1886+
1887+Each 4-byte group from the OpenSSL output maps directly to one fuse register
1888+word as a big-endian `u32` β€” the first byte of the group is the most-significant
1889+byte of the word.
1890+
1891+On the little-endian RISC-V bus the bytes within each register word appear
1892+reversed at byte addresses:
1893+
1894+```
1895+Fuse byte address: 0 1 2 3 4 5 6 7 8 9 A B ...
1896+Byte value: D5 A8 9B 86 82 CF 0F AD 80 60 E5 02 ...
1897+ ── register[0] ── ─── register[1] ── ── register[2] ──
1898+```
1899+
1900+##### Manufacturing debug unlock token: step-by-step
1901+
1902+1. Choose a 32-byte random secret (the raw token). This is what the SoC sends
1903+ over the mailbox to unlock debug.
1904+
1905+2. Compute SHA-512 of the raw token:
1906+ ```
1907+ $ printf '\xd8\x92\x2c\x55\x79\x2b\x73\x7f\x29\x13\xf3\xe5\xcb\xe6\x54\x75' \
1908+ '\x62\x52\x01\x6e\xae\xe9\x63\xa1\xdd\x4e\x75\x3a\xf7\x87\xf0\x96' \
1909+ | openssl dgst -sha512 -binary | xxd -p -c 64
1910+ 869ba8d5ad0fcf8202e560803281da659812ffa2fc28c2d5154cb645ee0c38ec
1911+ 4fd9dd8bb0be7deb193f625381383a91ab40bd920fcd9425919e63723c0bf7a8
1912+ ```
1913+
1914+3. Split into 4-byte groups and interpret each as a big-endian `u32` to get the
1915+ fuse word values:
1916+ ```
1917+ Fuse [u32; 16] = {
1918+ 0x869BA8D5, 0xAD0FCF82, 0x02E56080, 0x3281DA65,
1919+ 0x9812FFA2, 0xFC28C2D5, 0x154CB645, 0xEE0C38EC,
1920+ 0x4FD9DD8B, 0xB0BE7DEB, 0x193F6253, 0x81383A91,
1921+ 0xAB40BD92, 0x0FCD9425, 0x919E6372, 0x3C0BF7A8,
1922+ }
1923+ ```
1924+
1925+4. MCU or SoC manager writes these 16 words into the `FUSE_MANUF_DEBUG_UNLOCK_TOKEN` registers from fuses.
1926+
1927+#### Architectural register: CPTRA_OWNER_PK_HASH (big-endian words)
1928+
1929+**CPTRA_OWNER_PK_HASH** (`[u32; 12]`) uses the same reversed-dword format as
1930+FUSE_VENDOR_PK_HASH. See
1931+[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)
1932+for details and worked examples.
1933+
1934+##### Production debug unlock public key hashes: byte ordering
1935+
1936+The production debug unlock flow uses SHA2-384 hashes of the concatenated
1937+ECC and MLDSA public keys to authenticate debug unlock tokens. These hashes
1938+are stored in the MCI register bank at addresses computed from
1939+`SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET`.
1940+
1941+**Hash input construction:**
1942+
1943+The hash is SHA2-384 over the raw mailbox wire bytes of the concatenated
1944+ECC and MLDSA public keys from the `AUTH_DEBUG_UNLOCK_TOKEN` payload.
1945+The mailbox wire format for each key type is:
1946+
1947+- **ECC public key (96 bytes)**: Each 4-byte group of the X and Y
1948+ coordinates is **dword-reversed** from the standard OpenSSL output.
1949+
1950+ ```
1951+ openssl ec output: AB CD EF 01 23 45 67 89 ... (X, 48 bytes)
1952+ 11 22 33 44 55 66 77 88 ... (Y, 48 bytes)
1953+
1954+ Hash input (= mailbox wire bytes):
1955+ 01 EF CD AB 89 67 45 23 ... (X, dword-reversed)
1956+ 44 33 22 11 88 77 66 55 ... (Y, dword-reversed)
1957+ ```
1958+
1959+- **MLDSA public key (2592 bytes)**: The native MLDSA key bytes are
1960+ used **as-is** β€” no conversion.
1961+
1962+ ```
1963+ MLDSA keygen output: 72 C0 F1 3B 7D 93 7E 22 ...
1964+
1965+ Hash input (= mailbox wire bytes):
1966+ 72 C0 F1 3B 7D 93 7E 22 ... (identical)
1967+ ```
1968+
1969+To compute the same hash offline for fuse provisioning, reconstruct the
1970+mailbox wire bytes: dword-reverse the ECC coordinates, keep MLDSA native,
1971+concatenate, and hash:
1972+
1973+```
1974+ECC dword-reversed: 01 EF CD AB 89 67 45 23 ... (96 bytes)
1975+MLDSA native: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes)
1976+
1977+hash_input = ECC_dword_reversed || MLDSA_native_bytes (2688 bytes)
1978+
1979+$ openssl dgst -sha384 -binary combined.bin | xxd -p -c 48
1980+β†’ 3f7a2b91c4e8d0f5...
1981+```
1982+
1983+**Provisioning: OpenSSL example**
1984+
1985+To prepare `combined_keys.bin`, dword-reverse the ECC raw coordinates
1986+and concatenate with the native MLDSA key bytes. Then compute the hash:
1987+
1988+```
1989+$ openssl dgst -sha384 -binary combined_keys.bin | xxd -p -c 48
1990+3f7a2b91c4e8d0f5a1b2c3d4e5f60718293a4b5c6d7e8f90a0b1c2d3e4f5061728394a5b6c
1991+```
1992+
1993+Map the digest output to fuse register words β€” each 4-byte group becomes
1994+one `u32` fuse word (same convention as all other SHA digest fuses):
1995+
1996+```
1997+openssl output: 3f 7a 2b 91 c4 e8 d0 f5 a1 b2 c3 d4 ...
1998+ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~
1999+Fuse word[0]: 0x3F7A2B91 [1]: 0xC4E8D0F5 [2]: 0xA1B2C3D4 ...
2000+```
2001+
2002+Write these 12 words to the MCI register bank at offset:
2003+
2004+```
2005+SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET + ((level - 1) * 48)
2006+```
2007+
2008+**Mailbox payload: preparing fields from OpenSSL output**
2009+
2010+The `AUTH_DEBUG_UNLOCK_TOKEN` mailbox command fields use the byte order
2011+conventions described in
2012+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields).
2013+The table below summarizes how to convert OpenSSL tool output into the
2014+mailbox payload bytes for each field:
2015+
2016+- **ECC P-384 public key (big-endian words)**: Extract the raw X and Y
2017+ coordinates (48 bytes each) from the PEM key, then **dword-reverse**
2018+ each 4-byte group before writing to the mailbox.
2019+
2020+ ```
2021+ # Extract raw X||Y from PEM (96 bytes, big-endian):
2022+ $ openssl ec -pubin -in key.pem -outform DER 2>/dev/null \
2023+ | tail -c 96 | xxd -p -c 48
2024+
2025+ OpenSSL raw bytes: AB CD EF 01 23 45 67 89 ... (X, 48 bytes)
2026+ 11 22 33 44 55 66 77 88 ... (Y, 48 bytes)
2027+
2028+ Mailbox bytes: 01 EF CD AB 89 67 45 23 ... (X, dword-reversed)
2029+ 44 33 22 11 88 77 66 55 ... (Y, dword-reversed)
2030+ ```
2031+
2032+- **MLDSA-87 public key (little-endian words)**: Copy the raw key bytes
2033+ produced by an MLDSA implementation (e.g. OpenSSL 3.5+, `fips204` crate)
2034+ **directly** into the mailbox β€” no conversion needed.
2035+
2036+ ```
2037+ MLDSA key bytes: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes)
2038+ Mailbox bytes: 72 C0 F1 3B 7D 93 7E 22 ... (identical)
2039+ ```
2040+
2041+- **ECC P-384 signature (big-endian words)**: Same treatment as the public
2042+ key β€” dword-reverse each 4-byte group of the R and S coordinates.
2043+
2044+- **MLDSA-87 signature (little-endian words)**: Copy raw signature bytes
2045+ directly β€” no conversion needed. The trailing byte (byte 4628) is
2046+ reserved and should be zero.
2047+
2048+**Note:** The hash used for fuse provisioning is computed over the exact
2049+same bytes that appear on the mailbox wire. There is no additional
2050+transformation β€” the SHA accelerator's internal endianness handling is
2051+transparent and produces `SHA384(wire_bytes)`. Therefore the provisioning
2052+hash and the runtime verification hash are both computed over
2053+`ECC_dword_reversed || MLDSA_native`.
2054+
2055+#### SVN fuses (little-endian 128-bit bitmap)
2056+
2057+**FUSE_FIRMWARE_SVN** and **FUSE_SOC_MANIFEST_SVN** are 128-bit one-hot encoded bitmaps stored
2058+as `[u32; 4]`. These are **not** cryptographic values β€” the security version
2059+number equals the bit position of the highest set bit.
2060+
2061+The four words form a little-endian 128-bit integer: word\[0\] contains bits
2062+0–31, word\[1\] contains bits 32–63, and so on.
2063+
2064+Example β€” to program SVN 7, set bits 0 through 6:
2065+
2066+```
2067+FUSE_FIRMWARE_SVN[0] = 0x0000007F (bits 0-6 set)
2068+FUSE_FIRMWARE_SVN[1] = 0x00000000
2069+FUSE_FIRMWARE_SVN[2] = 0x00000000
2070+FUSE_FIRMWARE_SVN[3] = 0x00000000
2071+```
2072+
2073+Example β€” SVN 40 means bits 0 through 39 are set:
2074+
2075+```
2076+FUSE_FIRMWARE_SVN[0] = 0xFFFFFFFF (bits 0-31 set)
2077+FUSE_FIRMWARE_SVN[1] = 0x000000FF (bits 32-39 set)
2078+FUSE_FIRMWARE_SVN[2] = 0x00000000
2079+FUSE_FIRMWARE_SVN[3] = 0x00000000
2080+```
2081+
2082+#### Obfuscated seed fuses (big-endian words)
2083+
2084+**FUSE_UDS_SEED** (`[u32; 16]`), **FUSE_FIELD_ENTROPY** (`[u32; 8]`), and
2085+**FUSE_HEK_SEED** (`[u32; 8]`) are obfuscated secret values. They use the same
2086+**big-endian word** ordering as SHA digest fuses β€” each `u32` word maps to 4
2087+bytes in big-endian order.
2088+
2089+These values are consumed through an AES de-obfuscation step and are typically
2090+programmed by the manufacturing toolchain. If replicating values for test or
2091+simulation, use the same big-endian word convention when converting between byte
2092+arrays and `[u32; N]` arrays.
2093+
2094+#### Scalar and per-word fuses (no byte-ordering concern)
2095+
2096+The following fuse registers are single words or per-word indexed values with no
2097+multi-word byte ordering:
2098+
2099+| Register | Width | Notes |
2100+| --- | --- | --- |
2101+| FUSE_ECC_REVOCATION | 4 bits | Bitmask |
2102+| FUSE_LMS_REVOCATION | 32 bits | Bitmask |
2103+| FUSE_MLDSA_REVOCATION | 4 bits | Bitmask |
2104+| FUSE_ANTI_ROLLBACK_DISABLE | 1 bit | Boolean |
2105+| FUSE_PQC_KEY_TYPE | 2 bits | One-hot encoded |
2106+| FUSE_SOC_STEPPING_ID | 16 bits | Scalar |
2107+| FUSE_SOC_MANIFEST_MAX_SVN | 8 bits | Scalar |
2108+| FUSE_IDEVID_CERT_ATTR | 24 Γ— u32 | Per-word indexed; each word accessed individually |
2109+| FUSE_IDEVID_MANUF_HSM_ID | 4 Γ— u32 | Opaque identifier, used as-is |
2110+
11802111
11812112 ## Preamble validation steps
11822113
@@ -1201,10 +2132,20 @@
12012132 ## Table of contents validation
12022133
12032134 - At this point both the Preamble and the Header have been validated.
1204-- Load the TOC entries (FMC TOC and RT TOC) from the mailbox.
2135+- Read the TOC entries (FMC TOC and RT TOC) from the protected manifest snapshot.
12052136 - Compute the SHA2-384 hash of the complete TOC data.
12062137 - Compare the computed TOC hash with the hash embedded in the Header.
12072138 - If the hashes match, the TOC data is validated.
2139+- Require tightly packed image data: FMC immediately follows the manifest, and
2140+ Runtime immediately follows the checked end of FMC. A gap before Runtime is
2141+ rejected with `IMAGE_VERIFIER_ERR_RUNTIME_OFFSET_INVALID`. This source-layout
2142+ requirement does not require adjacent ICCM load addresses.
2143+- Check each image's `Offset + Size` for overflow and require the resulting end
2144+ offset to be within the supplied bundle length before hashing or loading its
2145+ payload. Trailing bundle padding remains permitted.
2146+- For external images, also check the absolute AXI source address and final byte
2147+ before DMA. Update reset rejects a wrapping staging bundle before fetching its
2148+ manifest. Source-address overflow reports `DRIVER_DMA_AXI_ADDRESS_OVERFLOW`.
12082149 - Ensure that Fw.Svn is greater than or equal to Fuse.Svn.
12092150
12102151 <br> *(Note: Same SVN Validation is done for the FMC and RT)