| @@ -1,9 +1,9 @@ |
| 1 | 1 | <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/6199f735a0aed0d4e6dc5f031c31aece233e1d21/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>6199f73</code> |
| 2 | +π Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/dee2d5279be6edf0d310f00fcd4f4565505f47e8/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>dee2d52</code> |
| 3 | 3 | </div> |
| 4 | 4 | |
| 5 | 5 | |
| 6 | | -# Caliptra - ROM Specification v2.0.3 |
| 6 | +# Caliptra - ROM Specification v2.1 |
| 7 | 7 | |
| 8 | 8 | *Spec Version: 1.0* |
| 9 | 9 | |
| @@ -59,23 +59,28 @@ |
| 59 | 59 | ### Fuse Registers |
| 60 | 60 | | Register | Width (bits) | Description | |
| 61 | 61 | | :------------------------------ | :------------ | :----------------------------------------------------- | |
| 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). | |
| 65 | 65 | | FUSE_ECC_REVOCATION | 4 | Manufacturer ECC Public Key Revocation Mask | |
| 66 | 66 | | FUSE_LMS_REVOCATION | 32 | Manufacturer LMS Public Key Revocation Mask | |
| 67 | 67 | | 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). | |
| 69 | 69 | | 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). | |
| 72 | 72 | | 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). | |
| 73 | 78 | |
| 74 | 79 | |
| 75 | 80 | ### Architectural Registers |
| 76 | 81 | | Register | Width (bits) | Description | |
| 77 | 82 | | :------------------------------ | :------------ | :----------------------------------------------------- | |
| 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). | |
| 79 | 84 | |
| 80 | 85 | |
| 81 | 86 | ### Entropy Source Configuration Registers |
| @@ -84,21 +89,44 @@ |
| 84 | 89 | |
| 85 | 90 | | Register | Field/Bits | Description | |
| 86 | 91 | | :------------------------------- | :------------ | :------------------------------------------------------ | |
| 87 | | -| 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. | |
| 92 | +| 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. | |
| 88 | 93 | | SS_STRAP_GENERIC[2] | [16] | Entropy source single-bit mode. When set to 1, ROM enables `rng_bit_enable` and clears `threshold_scope`. | |
| 89 | 94 | | 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. | |
| 90 | 95 | | 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. | |
| 91 | | -| 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. | |
| 92 | | -| 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. | |
| 96 | +| 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. | |
| 97 | +| 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. | |
| 93 | 98 | | 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. | |
| 94 | 99 | | CPTRA_I_TRNG_ENTROPY_CONFIG_1 | [31:16] | Alert threshold (default: 2). Number of health check failures before an alert is triggered. | |
| 95 | 100 | |
| 96 | 101 | |
| 97 | 102 | **Notes:** |
| 98 | 103 | - If any threshold value is set to 0, the ROM uses the default value specified above. |
| 104 | +- 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. |
| 99 | 105 | - These configuration values are stored in persistent storage after first read to prevent malicious modification (reloaded on cold reset). |
| 100 | 106 | - In debug mode (`debug_locked == false`), entropy source configuration registers remain unlocked for characterization. |
| 101 | 107 | - In production mode, ROM locks the entropy source configuration after programming to prevent modification. |
| 108 | + |
| 109 | +### Stable Owner Key Root Derivation |
| 110 | + |
| 111 | +The Stable Owner Key feature is only available in subsystem mode when OCP LOCK is disabled and the following subsystem strap is set: |
| 112 | + |
| 113 | +| Register | Field/Bits | Description | |
| 114 | +| :------------------------------- | :--------- | :------------------------------------------------------ | |
| 115 | +| 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. | |
| 116 | + |
| 117 | + |
| 118 | +When the feature is available, ROM derives the Stable Owner Root Key during the IDevID stage before clearing DOE secrets: |
| 119 | + |
| 120 | +1. DOE decrypts the obfuscated HEK seed into `KEY_ID_HEK_SEED` (`KeyId14`) with HMAC block usage. |
| 121 | +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. |
| 122 | +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. |
| 123 | +4. ROM write-locks `KEY_ID_STABLE_OWNER` and erases the temporary `KEY_ID_HEK_SEED` slot. |
| 124 | + |
| 125 | +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. |
| 126 | + |
| 127 | +The following diagram summarizes the ROM-populated stable roots, including IDevID, LDevID, and the optional Owner root: |
| 128 | + |
| 129 | + |
| 102 | 130 | |
| 103 | 131 | For a comprehensive overview of the SOC interface registers, please refer to the following link:: |
| 104 | 132 | https://chipsalliance.github.io/caliptra-rtl/main/external-regs/?p=caliptra_top_reg.generic_and_fuse_reg |
| @@ -161,7 +189,7 @@ |
| 161 | 189 | | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x | |
| 162 | 190 | | Reserved | 1 | Reserved | |
| 163 | 191 | | Key Hash Count | 1 | Number of valid public key hashes | |
| 164 | | -| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4 | |
| 192 | +| 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)). | |
| 165 | 193 | |
| 166 | 194 | |
| 167 | 195 | #### PQC Manufacturer Public Key Descriptor |
| @@ -171,7 +199,7 @@ |
| 171 | 199 | | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x | |
| 172 | 200 | | Key Type | 1 | Type of the key in the descriptor <br> 0x1 - MLDSA <br> 0x3 - LMS | |
| 173 | 201 | | Key Hash Count | 1 | Number of valid public key hashes | |
| 174 | | -| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. LMS: n = 32, MLDSA: n = 4 | |
| 202 | +| 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)). | |
| 175 | 203 | |
| 176 | 204 | |
| 177 | 205 | #### Header |
| @@ -383,10 +411,10 @@ |
| 383 | 411 | | Unlock Level | 1 | Debug unlock Level (Number 1-8). | |
| 384 | 412 | | Reserved | 3 | Reserved field. | |
| 385 | 413 | | Challenge | 48 | Random number sent in `AUTH_DEBUG_UNLOCK_CHALLENGE` mailbox command payload. | |
| 386 | | -| 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) | |
| 387 | | -| MLDSA Public Key | 2592 | MLDSA-87 public key used to verify the Message Signature. | |
| 388 | | -| 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). | |
| 389 | | -| MLDSA Signature | 4628 | MLDSA signature of the Message hashed using SHA2-512. (4627 bytes + 1 Reserved byte). | |
| 414 | +| 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). | |
| 415 | +| 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). | |
| 416 | +| 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). | |
| 417 | +| 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). | |
| 390 | 418 | |
| 391 | 419 | |
| 392 | 420 | 7. On receiving this payload, ROM performs the following validations: |
| @@ -395,7 +423,7 @@ |
| 395 | 423 | - Calculates the address of the public key hash fuse as follows: <br> |
| 396 | 424 | **SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET register value + ( (Debug Unlock Level - 1) * SHA2-384 hash size (48 bytes) )** |
| 397 | 425 | - Retrieves the SHA2-384 hash (48 bytes) from the calculated address using DMA assist. |
| 398 | | - - Computes the SHA2-384 hash of the message formed by concatenating the ECC and MLDSA public keys in the payload. |
| 426 | + - 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. |
| 399 | 427 | - 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. |
| 400 | 428 | - Upon hash comparison failure, the ROM exits the payload validation flow and completes the mailbox command. |
| 401 | 429 | |
| @@ -563,11 +591,11 @@ |
| 563 | 591 | | Field | Size (bytes) | Description | |
| 564 | 592 | | ---------------- | -------------- | ------------------------------------------------------------------------------------------------- | |
| 565 | 593 | | Marker | 4 | Magic Number marking the start of the CSR payload. The value must be 0x435352 (βCSRβ in ASCII). | |
| 566 | | -| Size | 4 | Size of the entire CSR payload. Current size is 8272 bytes. | |
| 594 | +| Size | 4 | Size of the entire CSR payload. Current size is 8784 bytes. | |
| 567 | 595 | | ECC CSR Size | 4 | Size of the ECC CSR in bytes. | |
| 568 | 596 | | ECC CSR | 512 | ECC CSR buffer. Actual CSR size is indicated by 'ECC CSR Size'. | |
| 569 | 597 | | MLDSA CSR Size | 4 | Size of the MLDSA CSR in bytes. | |
| 570 | | -| MLDSA CSR | 7680 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. | |
| 598 | +| MLDSA CSR | 8192 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. | |
| 571 | 599 | | CSR MAC | 64 | HMAC-512 MAC, computed over the envelope bytes up to but not including this field. | |
| 572 | 600 | |
| 573 | 601 | |
| @@ -590,6 +618,105 @@ |
| 590 | 618 | | πIDevID Cert MLDSA Signature | |
| 591 | 619 | | πIDevID MLDSA Pub Key | |
| 592 | 620 | |
| 621 | + |
| 622 | +#### UEID (Unique Endpoint Identifier) |
| 623 | + |
| 624 | +The UEID is a 17-byte identifier that is embedded (as an X.509 extension) in the |
| 625 | +IDevID CSR, the LDevID certificate, and the FMC Alias certificate. Its value is |
| 626 | +derived entirely from fuses. |
| 627 | + |
| 628 | +##### Source fuses |
| 629 | + |
| 630 | +The UEID is assembled from 5 consecutive 32-bit words of the |
| 631 | +`FUSE_IDEVID_CERT_ATTR` fuse bank (see the [Fuse Registers](#fuse-registers) |
| 632 | +table): |
| 633 | + |
| 634 | +| Fuse word | `IdevidCertAttr` variant | Usage in UEID | |
| 635 | +| ----------- | ------------------------------- | ----------------------------------------- | |
| 636 | +| 11 | `UeidType` | UEID type byte (see RFC 9711 Β§4.2.1.1) | |
| 637 | +| 12 | `ManufacturerSerialNumber1` | First 4 bytes of the endpoint serial | |
| 638 | +| 13 | `ManufacturerSerialNumber2` | Next 4 bytes of the endpoint serial | |
| 639 | +| 14 | `ManufacturerSerialNumber3` | Next 4 bytes of the endpoint serial | |
| 640 | +| 15 | `ManufacturerSerialNumber4` | Last 4 bytes of the endpoint serial | |
| 641 | + |
| 642 | + |
| 643 | +Only the low byte of word 11 is used; the high 3 bytes of that word are |
| 644 | +discarded. Each of the four serial-number words is written to the UEID buffer |
| 645 | +in **little-endian** order (the natural byte order of the u32 register). |
| 646 | + |
| 647 | +##### Byte layout |
| 648 | + |
| 649 | +``` |
| 650 | + byte 0 byte 1 β byte 4 byte 5 β byte 8 byte 9 β byte 12 byte 13 β byte 16 |
| 651 | + ββββββββββββ ββββββββββββββββββ ββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ |
| 652 | + β UeidType β β MfgSerialNum1 β β MfgSerialNum2 β β MfgSerialNum3 β β MfgSerialNum4 β |
| 653 | + β (byte 0) β β (LE u32) β β (LE u32) β β (LE u32) β β (LE u32) β |
| 654 | + ββββββββββββ ββββββββββββββββββ ββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ |
| 655 | +``` |
| 656 | + |
| 657 | +This assembly is implemented in `caliptra_drivers::FuseBank::ueid` in |
| 658 | +`drivers/src/fuse_bank.rs`, returning a `[u8; 17]`. |
| 659 | + |
| 660 | +##### Placement in the certificate / CSR |
| 661 | + |
| 662 | +The 17-byte UEID is placed in the TCG DICE "Ueid" X.509 extension (OID |
| 663 | +`2.23.133.5.4.4`, not marked critical). The extension's `extnValue` |
| 664 | +`OCTET STRING` contains a DER-encoded `SEQUENCE { ueid OCTET STRING }`, as |
| 665 | +defined by the TCG DICE specification. The DER bytes written into the TBS |
| 666 | +template are: |
| 667 | + |
| 668 | +| DER bytes | Meaning | |
| 669 | +| --------------------------- | --------------------------------------------------------- | |
| 670 | +| `30 1F` | `SEQUENCE`, length 31 β the `Extension` | |
| 671 | +| `06 06 67 81 05 05 04 04` | `OID 2.23.133.5.4.4` (`tcg-dice-Ueid`) | |
| 672 | +| `04 15` | `OCTET STRING`, length 21 β the `extnValue` wrapper | |
| 673 | +| `30 13` | inner `SEQUENCE`, length 19 β the `TcgUeid` structure | |
| 674 | +| `04 11` | inner `OCTET STRING`, length 17 β the UEID value | |
| 675 | +| `XX XX β¦ XX` (17 B) | the 17 UEID bytes assembled above | |
| 676 | + |
| 677 | + |
| 678 | +The template slot for the 17 UEID bytes sits at a fixed offset in the TBS |
| 679 | +template (e.g. `UEID_OFFSET = 312` for `InitDevIdCsrTbsEcc384`); the ROM copies |
| 680 | +the UEID returned by `FuseBank::ueid` directly into that slot with no further |
| 681 | +transformation. See `x509/gen/src/x509.rs::make_tcg_ueid_ext` for the generator |
| 682 | +and `x509/build/*` for the resulting pre-baked templates. |
| 683 | + |
| 684 | +##### End-to-end example |
| 685 | + |
| 686 | +Given the following example fuse values (as programmed by the integration test |
| 687 | +`cert_test_with_ueid` in `rom/dev/tests/rom_integration_tests/test_image_validation.rs`): |
| 688 | + |
| 689 | +| Fuse word | Field | Value | |
| 690 | +| ----------- | -------------------------------- | --------------- | |
| 691 | +| 11 | `UeidType` | `0x0000_0001` | |
| 692 | +| 12 | `ManufacturerSerialNumber1` | `0x0403_0201` | |
| 693 | +| 13 | `ManufacturerSerialNumber2` | `0x0807_0605` | |
| 694 | +| 14 | `ManufacturerSerialNumber3` | `0x0C0B_0A09` | |
| 695 | +| 15 | `ManufacturerSerialNumber4` | `0x100F_0E0D` | |
| 696 | + |
| 697 | + |
| 698 | +Step-by-step: |
| 699 | + |
| 700 | +1. `FuseBank::ueid` reads the five fuse words and takes the low byte of word 11: |
| 701 | + `ueid_type = 0x01`. |
| 702 | +2. Each serial-number word is converted to little-endian bytes: |
| 703 | + - `0x04030201 β 01 02 03 04` |
| 704 | + - `0x08070605 β 05 06 07 08` |
| 705 | + - `0x0C0B0A09 β 09 0A 0B 0C` |
| 706 | + - `0x100F0E0D β 0D 0E 0F 10` |
| 707 | +3. The 17-byte UEID is: |
| 708 | + `01 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10` |
| 709 | + (byte 0 is the type; bytes 1β16 are the endpoint serial). |
| 710 | +4. The UEID is wrapped in the DER framing shown above and emitted verbatim in |
| 711 | + the IDevID CSR, LDevID certificate, and FMC Alias certificate. The resulting |
| 712 | + bytes on the wire for the Ueid extension are: |
| 713 | + `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`. |
| 714 | + |
| 715 | +The `cert_test_with_ueid` test programs exactly these fuses, boots the ROM, |
| 716 | +retrieves the IDevID ECC CSR, LDevID cert, and FMC Alias cert from the UART |
| 717 | +log, and asserts that the hex-encoded bytes |
| 718 | +`010102030405060708090A0B0C0D0E0F10` appear in all three β confirming both the |
| 719 | +fuse-to-UEID assembly and the DER placement described here. |
| 593 | 720 | |
| 594 | 721 | ### Local Device ID DICE layer |
| 595 | 722 | |
| @@ -709,24 +836,63 @@ |
| 709 | 836 | |
| 710 | 837 | #### Handling commands from mailbox |
| 711 | 838 | |
| 712 | | -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. |
| 713 | | - |
| 714 | | -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). |
| 715 | | -2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#version). |
| 716 | | -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). |
| 717 | | -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). |
| 718 | | -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). |
| 719 | | -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). |
| 720 | | -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). |
| 721 | | -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). |
| 722 | | -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) |
| 723 | | -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) |
| 724 | | -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) |
| 725 | | -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) |
| 839 | +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. |
| 840 | + |
| 841 | +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). |
| 842 | +2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#version). |
| 843 | +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). |
| 844 | +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). |
| 845 | +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). |
| 846 | +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). |
| 847 | +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). |
| 848 | +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) |
| 849 | +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) |
| 850 | +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) |
| 851 | +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) |
| 852 | +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) |
| 726 | 853 | 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. |
| 727 | | -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) |
| 728 | | -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) |
| 729 | | -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) |
| 854 | +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) |
| 855 | +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) |
| 856 | +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) |
| 857 | +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. |
| 858 | +18. **ZEROIZE_UDS_FE** |
| 859 | + |
| 860 | +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. |
| 861 | + |
| 862 | +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. |
| 863 | + |
| 864 | +The zeroization process follows these steps for each partition: |
| 865 | +1. Clears the zeroization marker first to mask potential ECC errors during power failures |
| 866 | +2. Zeroizes the seed data |
| 867 | +3. Clears the partition digest |
| 868 | + |
| 869 | +All operations are verified to return 0xFFFFFFFF before proceeding. |
| 870 | + |
| 871 | +Command Code: `0x5A45_5546` ("ZEUF") |
| 872 | + |
| 873 | +*Table: `ZEROIZE_UDS_FE` input arguments* |
| 874 | + |
| 875 | +| **Name** | **Type** | **Description** |
| 876 | +| -------- | -------- | --------------- |
| 877 | +| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
| 878 | +| flags | u32 | Partition flags. See ZEROIZE_UDS_FE_FLAGS below. |
| 879 | + |
| 880 | +*Table: `ZEROIZE_UDS_FE_FLAGS` input flags* |
| 881 | + |
| 882 | +| **Name** | **Value** | **Description** |
| 883 | +| ------------------ | --------- | --------------- |
| 884 | +| ZEROIZE_UDS_FLAG | 1 << 0 | Zeroize UDS partition |
| 885 | +| ZEROIZE_FE0_FLAG | 1 << 1 | Zeroize FE partition 0 |
| 886 | +| ZEROIZE_FE1_FLAG | 1 << 2 | Zeroize FE partition 1 |
| 887 | +| ZEROIZE_FE2_FLAG | 1 << 3 | Zeroize FE partition 2 |
| 888 | +| ZEROIZE_FE3_FLAG | 1 << 4 | Zeroize FE partition 3 |
| 889 | + |
| 890 | +*Table: `ZEROIZE_UDS_FE` output arguments* |
| 891 | + |
| 892 | +| **Name** | **Type** | **Description** |
| 893 | +| -------- | -------- | --------------- |
| 894 | +| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian. |
| 895 | +| dpe_result | u32 | Result code, 0 on success. |
| 730 | 896 | |
| 731 | 897 | #### CM_SHA |
| 732 | 898 | |
| @@ -742,7 +908,7 @@ |
| 742 | 908 | | -------------- | ------------- | --------------- |
| 743 | 909 | | chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
| 744 | 910 | | hash_algorithm | u32 | Hash algorithm: 1 = SHA-384, 2 = SHA-512. Value 0 is reserved and will return an error. |
| 745 | | -| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead). |
| 911 | +| 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. |
| 746 | 912 | | input | u8[input_size]| Input data to hash. Variable size up to the mailbox capacity. |
| 747 | 913 | |
| 748 | 914 | *Table: `CM_SHA` output arguments* |
| @@ -771,7 +937,11 @@ |
| 771 | 937 | |
| 772 | 938 | Following is the sequence of steps that are performed to download the firmware image into the mailbox in SUBSYSTEM mode. |
| 773 | 939 | |
| 774 | | -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: |
| 940 | +ROM supports two commands for firmware download in SUBSYSTEM mode: |
| 941 | +- **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. |
| 942 | +- **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. |
| 943 | + |
| 944 | +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: |
| 775 | 945 | - `Device ID` |
| 776 | 946 | - `Device Status` |
| 777 | 947 | - `Recovery memory access / INDIRECT_CTRL support` |
| @@ -1027,7 +1197,7 @@ |
| 1027 | 1197 | - **ICCM** |
| 1028 | 1198 | |
| 1029 | 1199 | ### Launch FMC |
| 1030 | | -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. |
| 1200 | +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. |
| 1031 | 1201 | |
| 1032 | 1202 | ## Warm reset flow |
| 1033 | 1203 | ROM does not perform any DICE derivations or firmware validation during warm reset. |
| @@ -1065,6 +1235,13 @@ |
| 1065 | 1235 | ROM performs the same initialization sequence as specified [here](#Initialization) |
| 1066 | 1236 | |
| 1067 | 1237 | ### Error handling |
| 1238 | +Fatal error reporting can be configured by the following subsystem strap: |
| 1239 | + |
| 1240 | +| Register | Field/Bits | Description | |
| 1241 | +| :------------------------------- | :--------- | :------------------------------------------------------ | |
| 1242 | +| SS_STRAP_GENERIC[3] | [1] | Wait for device reset before fatal error reporting. When set to 1 in subsystem mode, ROM waits for the recovery interface `DEVICE_RESET.RESET_CTRL` field to be set to `0x1` (`Reset Device`) before updating `CPTRA_FW_ERROR_FATAL` in the fatal error handler. When clear, ROM reports fatal errors immediately. | |
| 1243 | + |
| 1244 | + |
| 1068 | 1245 | The ROM executes the following operations: |
| 1069 | 1246 | - Updates the `cptra_fw_error_fatal` and `cptra_fw_error_non_fatal` registers with the error code ROM_UNKNOWN_RESET_FLOW (0x01040020) error code. |
| 1070 | 1247 | - Zeroizes the following cryptographic hardware modules: |
| @@ -1140,11 +1317,12 @@ |
| 1140 | 1317 | ### Preamble validation: Manufacturing key validation |
| 1141 | 1318 | |
| 1142 | 1319 | - fuse_ecc_revocation serves as the bitmask for revoking ECC keys. |
| 1143 | | - - If bit-n is set, the nth key is disabled. All other higher bits that are zeros indicate the keys are still enabled. |
| 1320 | + - If bit-n is set, the nth key is disabled. All other bits that are zeros indicate the keys are still enabled. |
| 1144 | 1321 | - If all the bits are zeros, all ECC keys remain enabled. |
| 1145 | 1322 | - Ensure that the Active Key Index in the preamble is not disabled by the fuse_ecc_revocation fuse. |
| 1146 | 1323 | - If the key is disabled, the validation process fails. |
| 1147 | | -- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation. |
| 1324 | + - **Note: The last key index is never revoked, regardless of the fuse value.** |
| 1325 | +- 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. |
| 1148 | 1326 | |
| 1149 | 1327 | ### Preamble validation: Validate the Owner key |
| 1150 | 1328 | |
| @@ -1153,6 +1331,755 @@ |
| 1153 | 1331 | - 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. |
| 1154 | 1332 | - If the computed hash matches the value in fuse_owner_pk_hash, the owner public keys are deemed valid. |
| 1155 | 1333 | - If there is a hash mismatch, the image validation process fails. |
| 1334 | + |
| 1335 | +### Public key hash byte ordering (dword reversal) |
| 1336 | + |
| 1337 | +**Important:** Hashes and ECC key coordinates stored in the firmware manifest and fuse registers use |
| 1338 | +a **reversed-dword format** rather than the standard byte order defined by the SHA specification. |
| 1339 | + |
| 1340 | +In standard byte order, a SHA2-384 hash is a sequence of 48 bytes exactly as output by tools like |
| 1341 | +OpenSSL or Python's `hashlib`. In reversed-dword format, the same 48 bytes are grouped into 12 |
| 1342 | +four-byte words (dwords) and the bytes within each dword are reversed. |
| 1343 | + |
| 1344 | +For example, if the standard SHA2-384 hash begins with `b1 7c a8 77 66 66 57 cc d1 00 e6 92 ...`: |
| 1345 | + |
| 1346 | +| Standard byte order | β | Reversed-dword format | |
| 1347 | +| ---------------------- | --- | ----------------------- | |
| 1348 | +| `b1 7c a8 77` || `77 a8 7c b1` | |
| 1349 | +| `66 66 57 cc` || `cc 57 66 66` | |
| 1350 | +| `d1 00 e6 92` || `92 e6 00 d1` | |
| 1351 | +| ... || ... | |
| 1352 | + |
| 1353 | + |
| 1354 | +This reversed-dword format applies to: |
| 1355 | +- **Individual public key hashes** in the ECC and PQC key descriptors within the preamble |
| 1356 | +- **FUSE_VENDOR_PK_HASH** and **CPTRA_OWNER_PK_HASH** fuse/register values (which are `[u32; 12]` arrays) |
| 1357 | +- **ECC public key coordinates** (X and Y), which are stored as `[u32; 12]` arrays in the preamble |
| 1358 | + |
| 1359 | +Note: LMS public key fields (`tree_type`, `otstype`, `id`, `digest`) follow the LMS specification |
| 1360 | +encoding and are **not** subject to dword reversal. MLDSA public keys are stored as raw byte arrays |
| 1361 | +and are also **not** subject to dword reversal. |
| 1362 | + |
| 1363 | +For a detailed description of byte ordering conventions for all mailbox cryptographic fields |
| 1364 | +(including ECC, ML-DSA, and SHA digest fields with OpenSSL examples), see the |
| 1365 | +[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields) |
| 1366 | +section in the Runtime README. |
| 1367 | + |
| 1368 | +### Computing public key hashes: step-by-step example |
| 1369 | + |
| 1370 | +The following example walks through the computation of the **vendor PK descriptor hash** |
| 1371 | +using the test public keys from `image/fake-keys/src/lib.rs` with PQC key type **LMS (type 3)**. |
| 1372 | + |
| 1373 | +#### Step 1: Hash each vendor ECC public key |
| 1374 | + |
| 1375 | +Each ECC-384 public key has X and Y coordinates, each stored as `[u32; 12]`. To hash a key, |
| 1376 | +serialize the struct to 96 bytes by writing each `u32` word in reversed-dword format, then |
| 1377 | +compute SHA2-384 of those 96 bytes. |
| 1378 | + |
| 1379 | +**ECC Key 0:** |
| 1380 | +``` |
| 1381 | +X (standard byte order): c69fe67f 97ea3e42 21a7a603 6c2e070d 1657327b c3f1e7c1 |
| 1382 | + 8dccb9e4 ffda5c3f 4db0a1c0 567e0973 17bf4484 39696a07 |
| 1383 | +Y (standard byte order): c126b913 5fc82572 8f1cd403 19109430 994fe3e8 74a8b026 |
| 1384 | + be14794d 27789964 7735fde8 328afd84 cd4d4aa8 72d40b42 |
| 1385 | + |
| 1386 | +X (reversed-dword): 7fe69fc6 423eea97 03a6a721 0d072e6c 7b325716 c1e7f1c3 |
| 1387 | + e4b9cc8d 3f5cdaff c0a1b04d 73097e56 8444bf17 076a6939 |
| 1388 | +Y (reversed-dword): 13b926c1 7225c85f 03d41c8f 30941019 e8e34f99 26b0a874 |
| 1389 | + 4d7914be 64997827 e8fd3577 84fd8a32 a84a4dcd 420bd472 |
| 1390 | + |
| 1391 | +Input to SHA384 = X_reversed || Y_reversed (96 bytes) |
| 1392 | +SHA384 (standard): 84facd34 227de869 1fbb7d33 49306e0f 250a3659 53a6cc6b |
| 1393 | + 629d4616 32f73cfd 768152bb 8a03a255 5a1b1f1f c3923faa |
| 1394 | +SHA384 (reversed-dword): 34cdfa84 69e87d22 337dbb1f 0f6e3049 59360a25 6bcca653 |
| 1395 | + 16469d62 fd3cf732 bb528176 55a2038a 1f1f1b5a aa3f92c3 |
| 1396 | +``` |
| 1397 | + |
| 1398 | +**ECC Key 1:** |
| 1399 | +``` |
| 1400 | +X (standard): a6309750 f0a05ddb 956a7f86 2812ec4f ec454e95 3b53dbfb |
| 1401 | + 9eb54140 15ea7507 084af93c b7fa33fe 51811ad5 e754232e |
| 1402 | +Y (standard): ef5a5987 7a0ce0be 2621d2a9 8bf3c5df af7b3d6d 97f24183 |
| 1403 | + a4a42038 58c39b86 272ef548 e572b937 1ecf1994 1b8d4ea7 |
| 1404 | + |
| 1405 | +SHA384 (standard): fe89195f 7fab8ebb 2818d935 837493c2 378525ef 686ed220 |
| 1406 | + 09b9a399 f23f1f42 2f5ae1f3 ba1c3083 1a68a456 9c01fc96 |
| 1407 | +SHA384 (reversed-dword): 5f1989fe bb8eab7f 35d91828 c2937483 ef258537 20d26e68 |
| 1408 | + 99a3b909 421f3ff2 f3e15a2f 83301cba 56a4681a 96fc019c |
| 1409 | +``` |
| 1410 | + |
| 1411 | +**ECC Key 2:** |
| 1412 | +``` |
| 1413 | +X (standard): a0d25693 c4251e48 185615b0 a6c27f6d e62c39f5 a9a32f75 |
| 1414 | + 9553226a 4d1926c1 7928910f b7adc1b6 89996733 10134881 |
| 1415 | +Y (standard): bbdf72d7 07c08100 d54fcdad b1567bb0 0522762b 76b8dc4a |
| 1416 | + 846c175a 3fbd0501 9bdc8118 4be5f33c bb21b41d 93a8c523 |
| 1417 | + |
| 1418 | +SHA384 (standard): f397ba45 b5801ddf b732078d ffdf792f b584a73f b055acaf |
| 1419 | + ef39f31d 5b88c7d5 2753a45a 0c76b098 90d8e335 7be87f26 |
| 1420 | +SHA384 (reversed-dword): 45ba97f3 df1d80b5 8d0732b7 2f79dfff 3fa784b5 afac55b0 |
| 1421 | + 1df339ef d5c7885b 5aa45327 98b0760c 35e3d890 267fe87b |
| 1422 | +``` |
| 1423 | + |
| 1424 | +**ECC Key 3:** |
| 1425 | +``` |
| 1426 | +X (standard): 002a82b6 8e03e9a0 fd3b4c14 ca2cb3e8 14350a71 0e43956d |
| 1427 | + 21694fb4 f34485e8 f0e33583 f7ea142d 50e16f8b 0225bb95 |
| 1428 | +Y (standard): 5802641c 7c45a4a2 408e03a6 a4100a92 50fcc468 d238cd0d |
| 1429 | + 449cc3e5 1abc25e7 0b05c426 843dcd6f 944ef6ff fa53ec5b |
| 1430 | + |
| 1431 | +SHA384 (standard): 8ba8acb6 b98da9dc 8ffce0bc eba86454 4acbbd6e 3f31466e |
| 1432 | + 5d532565 0bfc9e3b c8afb2b5 c33e20f5 06992143 83f33bc1 |
| 1433 | +SHA384 (reversed-dword): b6aca88b dca98db9 bce0fc8f 5464a8eb 6ebdcb4a 6e46313f |
| 1434 | + 6525535d 3b9efc0b b5b2afc8 f5203ec3 43219906 c13bf383 |
| 1435 | +``` |
| 1436 | + |
| 1437 | +#### Step 2: Hash each vendor LMS public key |
| 1438 | + |
| 1439 | +Each LMS public key is a 48-byte struct: `tree_type` (u32), `otstype` (u32), `id` (16 bytes), |
| 1440 | +`digest` (24 bytes). The binary serialization is hashed directly. |
| 1441 | + |
| 1442 | +**LMS Key 0:** |
| 1443 | +``` |
| 1444 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1445 | +id: 4908a17b cadb1829 1e289058 d5a8e3e8 |
| 1446 | +digest: 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6 |
| 1447 | + |
| 1448 | +Serialized (48 bytes): 0000000c 00000007 4908a17b cadb1829 1e289058 d5a8e3e8 |
| 1449 | + 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6 |
| 1450 | +SHA384 (standard): fc2c1b6f 56f732d1 fd876f3f ef757cbb a2b1c64b cc148298 |
| 1451 | + d7508262 4bdf27cb 23d6b5b6 7169c46f 50b7fc19 92068fec |
| 1452 | +SHA384 (reversed-dword): 6f1b2cfc d132f756 3f6f87fd bb7c75ef 4bc6b1a2 988214cc |
| 1453 | + 628250d7 cb27df4b b6b5d623 6fc46971 19fcb750 ec8f0692 |
| 1454 | +``` |
| 1455 | + |
| 1456 | +**LMS Key 1:** |
| 1457 | +``` |
| 1458 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1459 | +id: 7cb5369d 64e4281d 046e977c 70d4d0a3 |
| 1460 | +digest: 8ea4701d adf7d700 0564b7d6 1d1c9587 9dd6475c 9c3aae0b |
| 1461 | + |
| 1462 | +SHA384 (standard): 7b5811fd 8d2b0cf8 9851f12d d2a7c239 f4f3abc5 d928dcc0 |
| 1463 | + 3b4b891d abbdc67f c7b88436 432e1544 a408bc9c bb503f6b |
| 1464 | +SHA384 (reversed-dword): fd11587b f80c2b8d 2df15198 39c2a7d2 c5abf3f4 c0dc28d9 |
| 1465 | + 1d894b3b 7fc6bdab 3684b8c7 44152e43 9cbc08a4 6b3f50bb |
| 1466 | +``` |
| 1467 | + |
| 1468 | +**LMS Key 2:** |
| 1469 | +``` |
| 1470 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1471 | +id: 2bbb4b72 c5b41e05 d2fabe76 f41704bd |
| 1472 | +digest: dcb53f96 24d4c7b3 c9ae4d4c 0e41e08e 3b159396 0fe6a277 |
| 1473 | + |
| 1474 | +SHA384 (standard): 7e08a494 6933d35a 42c0d7b0 0236b10b db14c100 3f82f6a9 |
| 1475 | + 7d401cb8 e420a7fa 5aab12b3 c4e96bec 49aec770 225a8f88 |
| 1476 | +SHA384 (reversed-dword): 94a4087e 5ad33369 b0d7c042 0bb13602 00c114db a9f6823f |
| 1477 | + b81c407d faa720e4 b312ab5a ec6be9c4 70c7ae49 888f5a22 |
| 1478 | +``` |
| 1479 | + |
| 1480 | +**LMS Key 3:** |
| 1481 | +``` |
| 1482 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1483 | +id: 42cba2e5 575b5235 7ea7aead ef54074c |
| 1484 | +digest: 5aa60e27 69251599 3ae8e21f 27ccdded 8ffcd3d2 8efbdec2 |
| 1485 | + |
| 1486 | +SHA384 (standard): d3734fbc ee2893a3 b1b6519b 6ec78fb8 d7425327 cde1f7aa |
| 1487 | + 23012c64 c635219f d4ab1c4d 1b023252 00042884 2e463dbb |
| 1488 | +SHA384 (reversed-dword): bc4f73d3 a39328ee 9b51b6b1 b88fc76e 275342d7 aaf7e1cd |
| 1489 | + 642c0123 9f2135c6 4d1cabd4 5232021b 84280400 bb3d462e |
| 1490 | +``` |
| 1491 | + |
| 1492 | +#### Step 3: Build the ECC key descriptor (196 bytes) |
| 1493 | + |
| 1494 | +Concatenate the 4-byte header with the 4 key hashes (each in reversed-dword format): |
| 1495 | + |
| 1496 | +``` |
| 1497 | +Header (4 bytes): 01 00 00 04 (version=1, reserved=0, key_hash_count=4) |
| 1498 | +ECC key 0 hash (48 bytes, reversed-dword): 34cdfa84 69e87d22 ... aa3f92c3 |
| 1499 | +ECC key 1 hash (48 bytes, reversed-dword): 5f1989fe bb8eab7f ... 96fc019c |
| 1500 | +ECC key 2 hash (48 bytes, reversed-dword): 45ba97f3 df1d80b5 ... 267fe87b |
| 1501 | +ECC key 3 hash (48 bytes, reversed-dword): b6aca88b dca98db9 ... c13bf383 |
| 1502 | + |
| 1503 | +Total: 4 + (4 Γ 48) = 196 bytes |
| 1504 | +``` |
| 1505 | + |
| 1506 | +#### Step 4: Build the PQC (LMS) key descriptor (1540 bytes) |
| 1507 | + |
| 1508 | +``` |
| 1509 | +Header (4 bytes): 01 00 03 20 (version=1, key_type=3=LMS, key_hash_count=32) |
| 1510 | +LMS key 0 hash (48 bytes, reversed-dword): 6f1b2cfc d132f756 ... ec8f0692 |
| 1511 | +LMS key 1 hash (48 bytes, reversed-dword): fd11587b f80c2b8d ... 6b3f50bb |
| 1512 | +LMS key 2 hash (48 bytes, reversed-dword): 94a4087e 5ad33369 ... 888f5a22 |
| 1513 | +LMS key 3 hash (48 bytes, reversed-dword): bc4f73d3 a39328ee ... bb3d462e |
| 1514 | + ... (keys 0-3 repeated 8 times to fill all 32 slots) |
| 1515 | + |
| 1516 | +Total: 4 + (32 Γ 48) = 1540 bytes |
| 1517 | +``` |
| 1518 | + |
| 1519 | +#### Step 5: Compute the vendor PK descriptor hash |
| 1520 | + |
| 1521 | +``` |
| 1522 | +Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes |
| 1523 | + |
| 1524 | +SHA384 (standard byte order): |
| 1525 | + b17ca877 666657cc d100e692 6c7206b6 0c995cb6 8992c6c9 |
| 1526 | + baefce72 8af05441 dee1ff41 5adfc187 e1e4edb4 d3b2d909 |
| 1527 | + |
| 1528 | +As [u32; 12] fuse register value: |
| 1529 | + [0xb17ca877, 0x666657cc, 0xd100e692, 0x6c7206b6, |
| 1530 | + 0x0c995cb6, 0x8992c6c9, 0xbaefce72, 0x8af05441, |
| 1531 | + 0xdee1ff41, 0x5adfc187, 0xe1e4edb4, 0xd3b2d909] |
| 1532 | +``` |
| 1533 | + |
| 1534 | +### Computing public key hashes: MLDSA step-by-step example |
| 1535 | + |
| 1536 | +The following example walks through the same computation as the LMS example above, but |
| 1537 | +using PQC key type **MLDSA (type 1)** with the test keys from `image/fake-keys/src/lib.rs`. |
| 1538 | + |
| 1539 | +#### MLDSA Step 1: Hash each vendor ECC public key |
| 1540 | + |
| 1541 | +The ECC keys and their hashes are identical to the LMS example β see |
| 1542 | +[Step 1 above](#step-1-hash-each-vendor-ecc-public-key). The ECC key descriptor is |
| 1543 | +independent of the PQC key type. |
| 1544 | + |
| 1545 | +#### MLDSA Step 2: Hash each vendor MLDSA public key |
| 1546 | + |
| 1547 | +Each MLDSA-87 public key is a 2592-byte array (`[u32; 648]`). When serialized via |
| 1548 | +`as_bytes()`, each `u32` word is written in little-endian byte order β for example, the |
| 1549 | +Rust value `0x3bf1c072` becomes bytes `72 c0 f1 3b` in memory. Unlike LMS keys, MLDSA |
| 1550 | +keys are not subject to any additional encoding β these raw bytes are hashed directly |
| 1551 | +with SHA2-384. |
| 1552 | + |
| 1553 | +**MLDSA Key 0:** |
| 1554 | +``` |
| 1555 | +Size: 2592 bytes (648 u32 words) |
| 1556 | +First 24 bytes: 72c0f13b 7d937e22 69b6988d 6daadc3a e78acd11 940cfc0d ... |
| 1557 | + |
| 1558 | +SHA384 (standard): f1097978 0adae470 dcd4eeb8 5749a2e4 2e70c055 ebac46e4 |
| 1559 | + 07c2c404 b46473d8 189117ed 8c83dde4 9f941e6a 1b6c6d4c |
| 1560 | +SHA384 (reversed-dword): 787909f1 70e4da0a b8eed4dc e4a24957 55c0702e e446aceb |
| 1561 | + 04c4c207 d87364b4 ed179118 e4dd838c 6a1e949f 4c6d6c1b |
| 1562 | +``` |
| 1563 | + |
| 1564 | +**MLDSA Key 1:** |
| 1565 | +``` |
| 1566 | +Size: 2592 bytes (648 u32 words) |
| 1567 | +First 24 bytes: f432346c 096d0ec9 04f8d925 1512236b e3fd1ccb bda9ed3a ... |
| 1568 | + |
| 1569 | +SHA384 (standard): a57b6f71 ffab9844 de49e9f7 ad61476b 7446e140 517d07b1 |
| 1570 | + 81447acb a6d7166f 7b89f199 b6e36174 2d0ab01c 540d26de |
| 1571 | +SHA384 (reversed-dword): 716f7ba5 4498abff f7e949de 6b4761ad 40e14674 b1077d51 |
| 1572 | + cb7a4481 6f16d7a6 99f1897b 7461e3b6 1cb00a2d de260d54 |
| 1573 | +``` |
| 1574 | + |
| 1575 | +**MLDSA Key 2:** |
| 1576 | +``` |
| 1577 | +Size: 2592 bytes (648 u32 words) |
| 1578 | +First 24 bytes: 2bc91a00 7d3e5a4f e6b3f2ec cb1aaa0d 278d9786 44b25fed ... |
| 1579 | + |
| 1580 | +SHA384 (standard): 7f2f3c55 e8dd2481 bbee17c1 5d5773a8 01a9c0a6 84b30e47 |
| 1581 | + 0ae67ecd 1ec3e7ac 19273c71 feb6bb99 10d26dd0 4ace4298 |
| 1582 | +SHA384 (reversed-dword): 553c2f7f 8124dde8 c117eebb a873575d a6c0a901 470eb384 |
| 1583 | + cd7ee60a ace7c31e 713c2719 99bbb6fe d06dd210 9842ce4a |
| 1584 | +``` |
| 1585 | + |
| 1586 | +**MLDSA Key 3:** |
| 1587 | +``` |
| 1588 | +Size: 2592 bytes (648 u32 words) |
| 1589 | +First 24 bytes: 378dcb02 a6db3481 d51e9913 14da1567 a211290e f4c3d02f ... |
| 1590 | + |
| 1591 | +SHA384 (standard): 79fbeb0a 6ebc354b ccf48dd1 5b6c9142 a62af0c5 198c0de1 |
| 1592 | + 365fbcb0 b2463ee5 103ccae3 4504ab83 04b37886 5c9a28ae |
| 1593 | +SHA384 (reversed-dword): 0aebfb79 4b35bc6e d18df4cc 42916c5b c5f02aa6 e10d8c19 |
| 1594 | + b0bc5f36 e53e46b2 e3ca3c10 83ab0445 8678b304 ae289a5c |
| 1595 | +``` |
| 1596 | + |
| 1597 | +#### MLDSA Step 3: Build the ECC key descriptor (196 bytes) |
| 1598 | + |
| 1599 | +Same as the LMS example β the ECC descriptor is independent of PQC key type. See |
| 1600 | +[Step 3 above](#step-3-build-the-ecc-key-descriptor-196-bytes). |
| 1601 | + |
| 1602 | +#### MLDSA Step 4: Build the PQC (MLDSA) key descriptor (1540 bytes) |
| 1603 | + |
| 1604 | +The PQC key descriptor struct always has 32 hash slots (`VENDOR_PQC_MAX_KEY_COUNT`). |
| 1605 | +For MLDSA, only 4 keys are populated; the remaining 28 slots are zero-filled. |
| 1606 | + |
| 1607 | +``` |
| 1608 | +Header (4 bytes): 01 00 01 04 (version=1, key_type=1=MLDSA, key_hash_count=4) |
| 1609 | +MLDSA key 0 hash (48 bytes, reversed-dword): 787909f1 70e4da0a ... 4c6d6c1b |
| 1610 | +MLDSA key 1 hash (48 bytes, reversed-dword): 716f7ba5 4498abff ... de260d54 |
| 1611 | +MLDSA key 2 hash (48 bytes, reversed-dword): 553c2f7f 8124dde8 ... 9842ce4a |
| 1612 | +MLDSA key 3 hash (48 bytes, reversed-dword): 0aebfb79 4b35bc6e ... ae289a5c |
| 1613 | + ... (keys 4-31 are zero-filled) |
| 1614 | + |
| 1615 | +Total: 4 + (32 Γ 48) = 1540 bytes |
| 1616 | +``` |
| 1617 | + |
| 1618 | +#### MLDSA Step 5: Compute the vendor PK descriptor hash |
| 1619 | + |
| 1620 | +``` |
| 1621 | +Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes |
| 1622 | + |
| 1623 | +SHA384 (standard byte order): |
| 1624 | + 30399676 a17e3e97 3677b3ff 862f4bf2 d1932d88 4778453c |
| 1625 | + 376fe00d c93fb8aa 0770f3eb f3411a08 53e9c57e ce8a2980 |
| 1626 | + |
| 1627 | +As [u32; 12] fuse register value: |
| 1628 | + [0x30399676, 0xa17e3e97, 0x3677b3ff, 0x862f4bf2, |
| 1629 | + 0xd1932d88, 0x4778453c, 0x376fe00d, 0xc93fb8aa, |
| 1630 | + 0x0770f3eb, 0xf3411a08, 0x53e9c57e, 0xce8a2980] |
| 1631 | +``` |
| 1632 | + |
| 1633 | +#### Owner PK hash |
| 1634 | + |
| 1635 | +The owner PK hash is SHA2-384 over the serialized `ImageOwnerPubKeys` struct, which contains: |
| 1636 | +- `ecc_pub_key`: `{ x: [u32; 12], y: [u32; 12] }` β 96 bytes (in reversed-dword format) |
| 1637 | +- `pqc_pub_key`: raw byte array of 2592 bytes (for LMS, only the first 48 bytes are meaningful; |
| 1638 | + the rest are zero-padded) |
| 1639 | + |
| 1640 | +Total: 2688 bytes. The SHA2-384 of these bytes is the owner PK hash. |
| 1641 | + |
| 1642 | +#### Summary of expected hash values using test keys |
| 1643 | + |
| 1644 | +Using the test keys from `image/fake-keys/src/lib.rs`: |
| 1645 | + |
| 1646 | +| Hash | PQC Type | Standard byte order (hex) | |
| 1647 | +| ------ | ---------- | --------------------------- | |
| 1648 | +| Vendor PK descriptor hash | LMS (type 3) | `b17ca877666657ccd100e6926c7206b60c995cb68992c6c9baefce728af05441dee1ff415adfc187e1e4edb4d3b2d909` | |
| 1649 | +| Vendor PK descriptor hash | MLDSA (type 1) | `30399676a17e3e973677b3ff862f4bf2d1932d884778453c376fe00dc93fb8aa0770f3ebf3411a0853e9c57ece8a2980` | |
| 1650 | +| Owner PK hash | LMS (type 3) | `1b179390e4e6c44422ed553e256c7d675cd93190cb49d88d485aa4ef3906cd492ab3ee3d3ba5f2c990ad13390fed4de5` | |
| 1651 | +| Owner PK hash | MLDSA (type 1) | `48afdb073c5e0d4ee46490468ef81f2cf57249b6e76a28f5fca4de696a7d3e2ed3efc4e6774318543e95307a54988bd7` | |
| 1652 | + |
| 1653 | + |
| 1654 | +To convert any of these standard byte order hashes to the `[u32; 12]` fuse register format, group |
| 1655 | +the hex string into 8-character (4-byte) chunks and interpret each as a 32-bit word: |
| 1656 | +- `b17ca877666657cc...` β `[0xb17ca877, 0x666657cc, 0xd100e692, ...]` |
| 1657 | + |
| 1658 | +#### Python script to compute vendor and owner PK hashes |
| 1659 | + |
| 1660 | +The following Python script computes the vendor PK descriptor hash and owner PK hash from |
| 1661 | +ECC PEM files and LMS or MLDSA binary key files: |
| 1662 | + |
| 1663 | +```python |
| 1664 | +#!/usr/bin/env python3 |
| 1665 | +""" |
| 1666 | +Compute the Caliptra vendor PK descriptor hash and owner PK hash |
| 1667 | +from ECC (.pem) and LMS/MLDSA (.bin) public key files. |
| 1668 | + |
| 1669 | +Usage: |
| 1670 | + python3 compute_pk_hashes.py --pqc-key-type <1|3> \\ |
| 1671 | + --vendor-ecc-pub-keys key0.pem key1.pem key2.pem key3.pem \\ |
| 1672 | + --vendor-pqc-pub-keys pqc0.bin pqc1.bin ... \\ |
| 1673 | + --owner-ecc-pub-key owner.pem \\ |
| 1674 | + --owner-pqc-pub-key owner_pqc.bin |
| 1675 | + |
| 1676 | +PQC key type: 1 = MLDSA, 3 = LMS |
| 1677 | + |
| 1678 | +ECC public keys are PEM files (P-384). |
| 1679 | +LMS public keys are 48-byte binary files (tree_type, otstype, id, digest). |
| 1680 | +MLDSA public keys are 2592-byte binary files. |
| 1681 | +""" |
| 1682 | +import argparse |
| 1683 | +import hashlib |
| 1684 | +import struct |
| 1685 | +import sys |
| 1686 | + |
| 1687 | +from cryptography.hazmat.primitives.serialization import load_pem_public_key |
| 1688 | + |
| 1689 | +# Sizes |
| 1690 | +ECC_PUB_KEY_BYTES = 96 # 2 x 48-byte coordinates |
| 1691 | +PQC_PUB_KEY_SLOT_BYTES = 2592 # MLDSA key size; LMS keys are 48 bytes, zero-padded |
| 1692 | +LMS_PUB_KEY_BYTES = 48 |
| 1693 | +MLDSA_PUB_KEY_BYTES = 2592 |
| 1694 | +HASH_BYTES = 48 # SHA2-384 |
| 1695 | + |
| 1696 | +VENDOR_ECC_MAX_KEYS = 4 |
| 1697 | +VENDOR_LMS_MAX_KEYS = 32 |
| 1698 | +VENDOR_MLDSA_MAX_KEYS = 32 # struct always allocates 32 slots; only first 4 are populated |
| 1699 | +KEY_DESCRIPTOR_VERSION = 1 |
| 1700 | + |
| 1701 | + |
| 1702 | +def ecc_pub_key_to_reversed_dwords(pem_path: str) -> bytes: |
| 1703 | + """Read an ECC P-384 PEM public key and return 96 bytes in reversed-dword format.""" |
| 1704 | + with open(pem_path, 'rb') as f: |
| 1705 | + pub_key = load_pem_public_key(f.read()) |
| 1706 | + nums = pub_key.public_numbers() |
| 1707 | + x_bytes = nums.x.to_bytes(48, 'big') |
| 1708 | + y_bytes = nums.y.to_bytes(48, 'big') |
| 1709 | + return to_reversed_dwords(x_bytes) + to_reversed_dwords(y_bytes) |
| 1710 | + |
| 1711 | + |
| 1712 | +def to_reversed_dwords(standard_bytes: bytes) -> bytes: |
| 1713 | + """Convert bytes from standard byte order to reversed-dword format. |
| 1714 | + |
| 1715 | + Groups the input into 4-byte dwords and reverses the bytes within each dword. |
| 1716 | + """ |
| 1717 | + assert len(standard_bytes) % 4 == 0 |
| 1718 | + result = bytearray() |
| 1719 | + for i in range(0, len(standard_bytes), 4): |
| 1720 | + result.extend(standard_bytes[i:i+4][::-1]) |
| 1721 | + return bytes(result) |
| 1722 | + |
| 1723 | + |
| 1724 | +def sha384_reversed_dwords(data: bytes) -> bytes: |
| 1725 | + """Compute SHA2-384 and return the hash in reversed-dword format.""" |
| 1726 | + h = hashlib.sha384(data).digest() |
| 1727 | + return to_reversed_dwords(h) |
| 1728 | + |
| 1729 | + |
| 1730 | +def build_ecc_key_descriptor(ecc_pem_paths: list) -> bytes: |
| 1731 | + """Build the ECC key descriptor: header + key hashes.""" |
| 1732 | + n = len(ecc_pem_paths) |
| 1733 | + header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, 0, n) |
| 1734 | + hashes = b'' |
| 1735 | + for path in ecc_pem_paths: |
| 1736 | + key_bytes = ecc_pub_key_to_reversed_dwords(path) |
| 1737 | + hashes += sha384_reversed_dwords(key_bytes) |
| 1738 | + # Pad to VENDOR_ECC_MAX_KEYS slots |
| 1739 | + hashes += b'\x00' * (HASH_BYTES * (VENDOR_ECC_MAX_KEYS - n)) |
| 1740 | + return header + hashes |
| 1741 | + |
| 1742 | + |
| 1743 | +def build_pqc_key_descriptor(pqc_bin_paths: list, pqc_key_type: int) -> bytes: |
| 1744 | + """Build the PQC key descriptor: header + key hashes.""" |
| 1745 | + n = len(pqc_bin_paths) |
| 1746 | + max_keys = VENDOR_LMS_MAX_KEYS if pqc_key_type == 3 else VENDOR_MLDSA_MAX_KEYS |
| 1747 | + header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, pqc_key_type, n) |
| 1748 | + hashes = b'' |
| 1749 | + for path in pqc_bin_paths: |
| 1750 | + with open(path, 'rb') as f: |
| 1751 | + key_bytes = f.read() |
| 1752 | + hashes += sha384_reversed_dwords(key_bytes) |
| 1753 | + # Pad to max slots |
| 1754 | + hashes += b'\x00' * (HASH_BYTES * (max_keys - n)) |
| 1755 | + return header + hashes |
| 1756 | + |
| 1757 | + |
| 1758 | +def build_owner_pub_keys(ecc_pem_path: str, pqc_bin_path: str) -> bytes: |
| 1759 | + """Build the serialized ImageOwnerPubKeys struct.""" |
| 1760 | + ecc_bytes = ecc_pub_key_to_reversed_dwords(ecc_pem_path) |
| 1761 | + with open(pqc_bin_path, 'rb') as f: |
| 1762 | + pqc_bytes = f.read() |
| 1763 | + # Pad PQC key to full slot size |
| 1764 | + pqc_padded = pqc_bytes + b'\x00' * (PQC_PUB_KEY_SLOT_BYTES - len(pqc_bytes)) |
| 1765 | + return ecc_bytes + pqc_padded |
| 1766 | + |
| 1767 | + |
| 1768 | +def hash_to_fuse_words(standard_hash: bytes) -> list: |
| 1769 | + """Convert a standard byte order hash to [u32; 12] fuse word format.""" |
| 1770 | + return [int.from_bytes(standard_hash[i:i+4], 'big') for i in range(0, 48, 4)] |
| 1771 | + |
| 1772 | + |
| 1773 | +def main(): |
| 1774 | + parser = argparse.ArgumentParser( |
| 1775 | + description='Compute Caliptra vendor PK descriptor hash and owner PK hash') |
| 1776 | + parser.add_argument('--pqc-key-type', type=int, required=True, choices=[1, 3], |
| 1777 | + help='PQC key type: 1=MLDSA, 3=LMS') |
| 1778 | + parser.add_argument('--vendor-ecc-pub-keys', nargs='+', required=True, |
| 1779 | + help='Vendor ECC P-384 public key PEM files') |
| 1780 | + parser.add_argument('--vendor-pqc-pub-keys', nargs='+', required=True, |
| 1781 | + help='Vendor PQC (LMS .bin or MLDSA .bin) public key files') |
| 1782 | + parser.add_argument('--owner-ecc-pub-key', |
| 1783 | + help='Owner ECC P-384 public key PEM file') |
| 1784 | + parser.add_argument('--owner-pqc-pub-key', |
| 1785 | + help='Owner PQC (LMS .bin or MLDSA .bin) public key file') |
| 1786 | + args = parser.parse_args() |
| 1787 | + |
| 1788 | + pqc_name = {1: 'MLDSA', 3: 'LMS'}[args.pqc_key_type] |
| 1789 | + |
| 1790 | + # Build descriptors |
| 1791 | + ecc_desc = build_ecc_key_descriptor(args.vendor_ecc_pub_keys) |
| 1792 | + pqc_desc = build_pqc_key_descriptor(args.vendor_pqc_pub_keys, args.pqc_key_type) |
| 1793 | + vendor_pub_key_info = ecc_desc + pqc_desc |
| 1794 | + |
| 1795 | + # Vendor PK descriptor hash (standard byte order) |
| 1796 | + vendor_hash = hashlib.sha384(vendor_pub_key_info).digest() |
| 1797 | + vendor_hex = vendor_hash.hex() |
| 1798 | + vendor_words = hash_to_fuse_words(vendor_hash) |
| 1799 | + |
| 1800 | + print(f"PQC key type: {args.pqc_key_type} ({pqc_name})") |
| 1801 | + print() |
| 1802 | + print(f"Vendor PK descriptor hash (standard byte order):") |
| 1803 | + print(f" {vendor_hex}") |
| 1804 | + print(f"Vendor PK descriptor hash (fuse [u32; 12]):") |
| 1805 | + print(f" {['0x{:08x}'.format(w) for w in vendor_words]}") |
| 1806 | + |
| 1807 | + if args.owner_ecc_pub_key and args.owner_pqc_pub_key: |
| 1808 | + owner_bytes = build_owner_pub_keys(args.owner_ecc_pub_key, args.owner_pqc_pub_key) |
| 1809 | + owner_hash = hashlib.sha384(owner_bytes).digest() |
| 1810 | + owner_hex = owner_hash.hex() |
| 1811 | + owner_words = hash_to_fuse_words(owner_hash) |
| 1812 | + |
| 1813 | + print() |
| 1814 | + print(f"Owner PK hash (standard byte order):") |
| 1815 | + print(f" {owner_hex}") |
| 1816 | + print(f"Owner PK hash (fuse [u32; 12]):") |
| 1817 | + print(f" {['0x{:08x}'.format(w) for w in owner_words]}") |
| 1818 | + |
| 1819 | + |
| 1820 | +if __name__ == '__main__': |
| 1821 | + main() |
| 1822 | +``` |
| 1823 | + |
| 1824 | +### Fuse value byte ordering |
| 1825 | + |
| 1826 | +This section documents the byte ordering convention for every multi-word fuse |
| 1827 | +register. It uses the same style as the |
| 1828 | +[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields) |
| 1829 | +section in the Runtime README: examples show the relationship between standard |
| 1830 | +tool output (e.g. OpenSSL, Python `hashlib`) and the `u32` word values written |
| 1831 | +to fuse registers. |
| 1832 | + |
| 1833 | +> **When adding a new multi-word fuse**, add an entry to the appropriate |
| 1834 | +> category below so that SoC integrators have a single reference for all fuse |
| 1835 | +> byte ordering. |
| 1836 | + |
| 1837 | +#### SHA digest fuses (big-endian words / reversed-dword) |
| 1838 | + |
| 1839 | +The following fuse registers store SHA digest values as `[u32; N]` arrays using |
| 1840 | +the same **reversed-dword format** described in |
| 1841 | +[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal). |
| 1842 | +Each 4-byte group from the standard hash output (as produced by `openssl dgst` |
| 1843 | +or Python's `hashlib`) is byte-reversed when stored as a `u32` word. |
| 1844 | + |
| 1845 | +| Fuse Register | Array Type | Hash Algorithm | |
| 1846 | +| --- | --- | --- | |
| 1847 | +| FUSE_VENDOR_PK_HASH | `[u32; 12]` | SHA2-384 of vendor public key descriptors | |
| 1848 | +| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | `[u32; 16]` | SHA-512 of the manufacturing debug unlock token | |
| 1849 | + |
| 1850 | + |
| 1851 | +Example β suppose `openssl dgst -sha512` produces a digest starting with: |
| 1852 | + |
| 1853 | +``` |
| 1854 | +openssl output: 86 9B A8 D5 AD 0F CF 82 02 E5 60 80 ... |
| 1855 | + ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~ |
| 1856 | +Fuse register[0]: 0x869BA8D5 [1]: 0xAD0FCF82 [2]: 0x02E56080 ... |
| 1857 | +``` |
| 1858 | + |
| 1859 | +Each 4-byte group from the OpenSSL output maps directly to one fuse register |
| 1860 | +word as a big-endian `u32` β the first byte of the group is the most-significant |
| 1861 | +byte of the word. |
| 1862 | + |
| 1863 | +On the little-endian RISC-V bus the bytes within each register word appear |
| 1864 | +reversed at byte addresses: |
| 1865 | + |
| 1866 | +``` |
| 1867 | +Fuse byte address: 0 1 2 3 4 5 6 7 8 9 A B ... |
| 1868 | +Byte value: D5 A8 9B 86 82 CF 0F AD 80 60 E5 02 ... |
| 1869 | + ββ register[0] ββ βββ register[1] ββ ββ register[2] ββ |
| 1870 | +``` |
| 1871 | + |
| 1872 | +##### Manufacturing debug unlock token: step-by-step |
| 1873 | + |
| 1874 | +1. Choose a 32-byte random secret (the raw token). This is what the SoC sends |
| 1875 | + over the mailbox to unlock debug. |
| 1876 | + |
| 1877 | +2. Compute SHA-512 of the raw token: |
| 1878 | + ``` |
| 1879 | + $ printf '\xd8\x92\x2c\x55\x79\x2b\x73\x7f\x29\x13\xf3\xe5\xcb\xe6\x54\x75' \ |
| 1880 | + '\x62\x52\x01\x6e\xae\xe9\x63\xa1\xdd\x4e\x75\x3a\xf7\x87\xf0\x96' \ |
| 1881 | + | openssl dgst -sha512 -binary | xxd -p -c 64 |
| 1882 | + 869ba8d5ad0fcf8202e560803281da659812ffa2fc28c2d5154cb645ee0c38ec |
| 1883 | + 4fd9dd8bb0be7deb193f625381383a91ab40bd920fcd9425919e63723c0bf7a8 |
| 1884 | + ``` |
| 1885 | + |
| 1886 | +3. Split into 4-byte groups and interpret each as a big-endian `u32` to get the |
| 1887 | + fuse word values: |
| 1888 | + ``` |
| 1889 | + Fuse [u32; 16] = { |
| 1890 | + 0x869BA8D5, 0xAD0FCF82, 0x02E56080, 0x3281DA65, |
| 1891 | + 0x9812FFA2, 0xFC28C2D5, 0x154CB645, 0xEE0C38EC, |
| 1892 | + 0x4FD9DD8B, 0xB0BE7DEB, 0x193F6253, 0x81383A91, |
| 1893 | + 0xAB40BD92, 0x0FCD9425, 0x919E6372, 0x3C0BF7A8, |
| 1894 | + } |
| 1895 | + ``` |
| 1896 | + |
| 1897 | +4. MCU or SoC manager writes these 16 words into the `FUSE_MANUF_DEBUG_UNLOCK_TOKEN` registers from fuses. |
| 1898 | + |
| 1899 | +#### Architectural register: CPTRA_OWNER_PK_HASH (big-endian words) |
| 1900 | + |
| 1901 | +**CPTRA_OWNER_PK_HASH** (`[u32; 12]`) uses the same reversed-dword format as |
| 1902 | +FUSE_VENDOR_PK_HASH. See |
| 1903 | +[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal) |
| 1904 | +for details and worked examples. |
| 1905 | + |
| 1906 | +##### Production debug unlock public key hashes: byte ordering |
| 1907 | + |
| 1908 | +The production debug unlock flow uses SHA2-384 hashes of the concatenated |
| 1909 | +ECC and MLDSA public keys to authenticate debug unlock tokens. These hashes |
| 1910 | +are stored in the MCI register bank at addresses computed from |
| 1911 | +`SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET`. |
| 1912 | + |
| 1913 | +**Hash input construction:** |
| 1914 | + |
| 1915 | +The hash is SHA2-384 over the raw mailbox wire bytes of the concatenated |
| 1916 | +ECC and MLDSA public keys from the `AUTH_DEBUG_UNLOCK_TOKEN` payload. |
| 1917 | +The mailbox wire format for each key type is: |
| 1918 | + |
| 1919 | +- **ECC public key (96 bytes)**: Each 4-byte group of the X and Y |
| 1920 | + coordinates is **dword-reversed** from the standard OpenSSL output. |
| 1921 | + |
| 1922 | + ``` |
| 1923 | + openssl ec output: AB CD EF 01 23 45 67 89 ... (X, 48 bytes) |
| 1924 | + 11 22 33 44 55 66 77 88 ... (Y, 48 bytes) |
| 1925 | + |
| 1926 | + Hash input (= mailbox wire bytes): |
| 1927 | + 01 EF CD AB 89 67 45 23 ... (X, dword-reversed) |
| 1928 | + 44 33 22 11 88 77 66 55 ... (Y, dword-reversed) |
| 1929 | + ``` |
| 1930 | + |
| 1931 | +- **MLDSA public key (2592 bytes)**: The native MLDSA key bytes are |
| 1932 | + used **as-is** β no conversion. |
| 1933 | + |
| 1934 | + ``` |
| 1935 | + MLDSA keygen output: 72 C0 F1 3B 7D 93 7E 22 ... |
| 1936 | + |
| 1937 | + Hash input (= mailbox wire bytes): |
| 1938 | + 72 C0 F1 3B 7D 93 7E 22 ... (identical) |
| 1939 | + ``` |
| 1940 | + |
| 1941 | +To compute the same hash offline for fuse provisioning, reconstruct the |
| 1942 | +mailbox wire bytes: dword-reverse the ECC coordinates, keep MLDSA native, |
| 1943 | +concatenate, and hash: |
| 1944 | + |
| 1945 | +``` |
| 1946 | +ECC dword-reversed: 01 EF CD AB 89 67 45 23 ... (96 bytes) |
| 1947 | +MLDSA native: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes) |
| 1948 | + |
| 1949 | +hash_input = ECC_dword_reversed || MLDSA_native_bytes (2688 bytes) |
| 1950 | + |
| 1951 | +$ openssl dgst -sha384 -binary combined.bin | xxd -p -c 48 |
| 1952 | +β 3f7a2b91c4e8d0f5... |
| 1953 | +``` |
| 1954 | + |
| 1955 | +**Provisioning: OpenSSL example** |
| 1956 | + |
| 1957 | +To prepare `combined_keys.bin`, dword-reverse the ECC raw coordinates |
| 1958 | +and concatenate with the native MLDSA key bytes. Then compute the hash: |
| 1959 | + |
| 1960 | +``` |
| 1961 | +$ openssl dgst -sha384 -binary combined_keys.bin | xxd -p -c 48 |
| 1962 | +3f7a2b91c4e8d0f5a1b2c3d4e5f60718293a4b5c6d7e8f90a0b1c2d3e4f5061728394a5b6c |
| 1963 | +``` |
| 1964 | + |
| 1965 | +Map the digest output to fuse register words β each 4-byte group becomes |
| 1966 | +one `u32` fuse word (same convention as all other SHA digest fuses): |
| 1967 | + |
| 1968 | +``` |
| 1969 | +openssl output: 3f 7a 2b 91 c4 e8 d0 f5 a1 b2 c3 d4 ... |
| 1970 | + ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~ |
| 1971 | +Fuse word[0]: 0x3F7A2B91 [1]: 0xC4E8D0F5 [2]: 0xA1B2C3D4 ... |
| 1972 | +``` |
| 1973 | + |
| 1974 | +Write these 12 words to the MCI register bank at offset: |
| 1975 | + |
| 1976 | +``` |
| 1977 | +SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET + ((level - 1) * 48) |
| 1978 | +``` |
| 1979 | + |
| 1980 | +**Mailbox payload: preparing fields from OpenSSL output** |
| 1981 | + |
| 1982 | +The `AUTH_DEBUG_UNLOCK_TOKEN` mailbox command fields use the byte order |
| 1983 | +conventions described in |
| 1984 | +[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
| 1985 | +The table below summarizes how to convert OpenSSL tool output into the |
| 1986 | +mailbox payload bytes for each field: |
| 1987 | + |
| 1988 | +- **ECC P-384 public key (big-endian words)**: Extract the raw X and Y |
| 1989 | + coordinates (48 bytes each) from the PEM key, then **dword-reverse** |
| 1990 | + each 4-byte group before writing to the mailbox. |
| 1991 | + |
| 1992 | + ``` |
| 1993 | + # Extract raw X||Y from PEM (96 bytes, big-endian): |
| 1994 | + $ openssl ec -pubin -in key.pem -outform DER 2>/dev/null \ |
| 1995 | + | tail -c 96 | xxd -p -c 48 |
| 1996 | + |
| 1997 | + OpenSSL raw bytes: AB CD EF 01 23 45 67 89 ... (X, 48 bytes) |
| 1998 | + 11 22 33 44 55 66 77 88 ... (Y, 48 bytes) |
| 1999 | + |
| 2000 | + Mailbox bytes: 01 EF CD AB 89 67 45 23 ... (X, dword-reversed) |
| 2001 | + 44 33 22 11 88 77 66 55 ... (Y, dword-reversed) |
| 2002 | + ``` |
| 2003 | + |
| 2004 | +- **MLDSA-87 public key (little-endian words)**: Copy the raw key bytes |
| 2005 | + produced by an MLDSA implementation (e.g. OpenSSL 3.5+, `fips204` crate) |
| 2006 | + **directly** into the mailbox β no conversion needed. |
| 2007 | + |
| 2008 | + ``` |
| 2009 | + MLDSA key bytes: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes) |
| 2010 | + Mailbox bytes: 72 C0 F1 3B 7D 93 7E 22 ... (identical) |
| 2011 | + ``` |
| 2012 | + |
| 2013 | +- **ECC P-384 signature (big-endian words)**: Same treatment as the public |
| 2014 | + key β dword-reverse each 4-byte group of the R and S coordinates. |
| 2015 | + |
| 2016 | +- **MLDSA-87 signature (little-endian words)**: Copy raw signature bytes |
| 2017 | + directly β no conversion needed. The trailing byte (byte 4628) is |
| 2018 | + reserved and should be zero. |
| 2019 | + |
| 2020 | +**Note:** The hash used for fuse provisioning is computed over the exact |
| 2021 | +same bytes that appear on the mailbox wire. There is no additional |
| 2022 | +transformation β the SHA accelerator's internal endianness handling is |
| 2023 | +transparent and produces `SHA384(wire_bytes)`. Therefore the provisioning |
| 2024 | +hash and the runtime verification hash are both computed over |
| 2025 | +`ECC_dword_reversed || MLDSA_native`. |
| 2026 | + |
| 2027 | +#### SVN fuses (little-endian 128-bit bitmap) |
| 2028 | + |
| 2029 | +**FUSE_FIRMWARE_SVN** and **FUSE_SOC_MANIFEST_SVN** are 128-bit one-hot encoded bitmaps stored |
| 2030 | +as `[u32; 4]`. These are **not** cryptographic values β the security version |
| 2031 | +number equals the bit position of the highest set bit. |
| 2032 | + |
| 2033 | +The four words form a little-endian 128-bit integer: word\[0\] contains bits |
| 2034 | +0β31, word\[1\] contains bits 32β63, and so on. |
| 2035 | + |
| 2036 | +Example β to program SVN 7, set bits 0 through 6: |
| 2037 | + |
| 2038 | +``` |
| 2039 | +FUSE_FIRMWARE_SVN[0] = 0x0000007F (bits 0-6 set) |
| 2040 | +FUSE_FIRMWARE_SVN[1] = 0x00000000 |
| 2041 | +FUSE_FIRMWARE_SVN[2] = 0x00000000 |
| 2042 | +FUSE_FIRMWARE_SVN[3] = 0x00000000 |
| 2043 | +``` |
| 2044 | + |
| 2045 | +Example β SVN 40 means bits 0 through 39 are set: |
| 2046 | + |
| 2047 | +``` |
| 2048 | +FUSE_FIRMWARE_SVN[0] = 0xFFFFFFFF (bits 0-31 set) |
| 2049 | +FUSE_FIRMWARE_SVN[1] = 0x000000FF (bits 32-39 set) |
| 2050 | +FUSE_FIRMWARE_SVN[2] = 0x00000000 |
| 2051 | +FUSE_FIRMWARE_SVN[3] = 0x00000000 |
| 2052 | +``` |
| 2053 | + |
| 2054 | +#### Obfuscated seed fuses (big-endian words) |
| 2055 | + |
| 2056 | +**FUSE_UDS_SEED** (`[u32; 16]`), **FUSE_FIELD_ENTROPY** (`[u32; 8]`), and |
| 2057 | +**FUSE_HEK_SEED** (`[u32; 8]`) are obfuscated secret values. They use the same |
| 2058 | +**big-endian word** ordering as SHA digest fuses β each `u32` word maps to 4 |
| 2059 | +bytes in big-endian order. |
| 2060 | + |
| 2061 | +These values are consumed through an AES de-obfuscation step and are typically |
| 2062 | +programmed by the manufacturing toolchain. If replicating values for test or |
| 2063 | +simulation, use the same big-endian word convention when converting between byte |
| 2064 | +arrays and `[u32; N]` arrays. |
| 2065 | + |
| 2066 | +#### Scalar and per-word fuses (no byte-ordering concern) |
| 2067 | + |
| 2068 | +The following fuse registers are single words or per-word indexed values with no |
| 2069 | +multi-word byte ordering: |
| 2070 | + |
| 2071 | +| Register | Width | Notes | |
| 2072 | +| --- | --- | --- | |
| 2073 | +| FUSE_ECC_REVOCATION | 4 bits | Bitmask | |
| 2074 | +| FUSE_LMS_REVOCATION | 32 bits | Bitmask | |
| 2075 | +| FUSE_MLDSA_REVOCATION | 4 bits | Bitmask | |
| 2076 | +| FUSE_ANTI_ROLLBACK_DISABLE | 1 bit | Boolean | |
| 2077 | +| FUSE_PQC_KEY_TYPE | 2 bits | One-hot encoded | |
| 2078 | +| FUSE_SOC_STEPPING_ID | 16 bits | Scalar | |
| 2079 | +| FUSE_SOC_MANIFEST_MAX_SVN | 8 bits | Scalar | |
| 2080 | +| FUSE_IDEVID_CERT_ATTR | 24 Γ u32 | Per-word indexed; each word accessed individually | |
| 2081 | +| FUSE_IDEVID_MANUF_HSM_ID | 4 Γ u32 | Opaque identifier, used as-is | |
| 2082 | + |
| 1156 | 2083 | |
| 1157 | 2084 | ## Preamble validation steps |
| 1158 | 2085 | |