| @@ -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/294bcacbd34aca6e23ce7b652885e3236f7e7afe/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>294bcac</code> |
| 2 | +📄 Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/0a7d46b7cf8a9ccbfc195a8b148268df68d7b80f/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>0a7d46b</code> |
| 3 | 3 | </div> |
| 4 | 4 | |
| 5 | 5 | |
| 6 | | -# Caliptra - ROM Specification v2.0.2 |
| 6 | +# Caliptra - ROM Specification v2.1 |
| 7 | 7 | |
| 8 | 8 | *Spec Version: 1.0* |
| 9 | 9 | |
| @@ -61,13 +61,13 @@ |
| 61 | 61 | | :------------------------------ | :------------ | :----------------------------------------------------- | |
| 62 | 62 | | FUSE_UDS_SEED | 512 | Obfuscated UDS | |
| 63 | 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 | |
| 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 | 68 | | FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number | |
| 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 | |
| 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 | 71 | | FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization | |
| 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 | 73 | |
| @@ -75,7 +75,7 @@ |
| 75 | 75 | ### Architectural Registers |
| 76 | 76 | | Register | Width (bits) | Description | |
| 77 | 77 | | :------------------------------ | :------------ | :----------------------------------------------------- | |
| 78 | | -| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash | |
| 78 | +| 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 | 79 | |
| 80 | 80 | |
| 81 | 81 | ### Entropy Source Configuration Registers |
| @@ -159,7 +159,7 @@ |
| 159 | 159 | | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x | |
| 160 | 160 | | Reserved | 1 | Reserved | |
| 161 | 161 | | Key Hash Count | 1 | Number of valid public key hashes | |
| 162 | | -| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4 | |
| 162 | +| 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)). | |
| 163 | 163 | |
| 164 | 164 | |
| 165 | 165 | #### PQC Manufacturer Public Key Descriptor |
| @@ -169,7 +169,7 @@ |
| 169 | 169 | | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x | |
| 170 | 170 | | Key Type | 1 | Type of the key in the descriptor <br> 0x1 - MLDSA <br> 0x3 - LMS | |
| 171 | 171 | | Key Hash Count | 1 | Number of valid public key hashes | |
| 172 | | -| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. LMS: n = 32, MLDSA: n = 4 | |
| 172 | +| 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)). | |
| 173 | 173 | |
| 174 | 174 | |
| 175 | 175 | #### Header |
| @@ -707,24 +707,63 @@ |
| 707 | 707 | |
| 708 | 708 | #### Handling commands from mailbox |
| 709 | 709 | |
| 710 | | -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. |
| 711 | | - |
| 712 | | -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). |
| 713 | | -2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#version). |
| 714 | | -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). |
| 715 | | -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). |
| 716 | | -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). |
| 717 | | -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). |
| 718 | | -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). |
| 719 | | -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). |
| 720 | | -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) |
| 721 | | -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) |
| 722 | | -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) |
| 723 | | -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) |
| 710 | +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. |
| 711 | + |
| 712 | +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). |
| 713 | +2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#version). |
| 714 | +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). |
| 715 | +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). |
| 716 | +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). |
| 717 | +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). |
| 718 | +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). |
| 719 | +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) |
| 720 | +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) |
| 721 | +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) |
| 722 | +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) |
| 723 | +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) |
| 724 | 724 | 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. |
| 725 | | -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) |
| 726 | | -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) |
| 727 | | -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) |
| 725 | +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) |
| 726 | +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) |
| 727 | +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) |
| 728 | +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. |
| 729 | +18. **ZEROIZE_UDS_FE** |
| 730 | + |
| 731 | +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. |
| 732 | + |
| 733 | +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. |
| 734 | + |
| 735 | +The zeroization process follows these steps for each partition: |
| 736 | +1. Clears the zeroization marker first to mask potential ECC errors during power failures |
| 737 | +2. Zeroizes the seed data |
| 738 | +3. Clears the partition digest |
| 739 | + |
| 740 | +All operations are verified to return 0xFFFFFFFF before proceeding. |
| 741 | + |
| 742 | +Command Code: `0x5A45_5546` ("ZEUF") |
| 743 | + |
| 744 | +*Table: `ZEROIZE_UDS_FE` input arguments* |
| 745 | + |
| 746 | +| **Name** | **Type** | **Description** |
| 747 | +| -------- | -------- | --------------- |
| 748 | +| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
| 749 | +| flags | u32 | Partition flags. See ZEROIZE_UDS_FE_FLAGS below. |
| 750 | + |
| 751 | +*Table: `ZEROIZE_UDS_FE_FLAGS` input flags* |
| 752 | + |
| 753 | +| **Name** | **Value** | **Description** |
| 754 | +| ------------------ | --------- | --------------- |
| 755 | +| ZEROIZE_UDS_FLAG | 1 << 0 | Zeroize UDS partition |
| 756 | +| ZEROIZE_FE0_FLAG | 1 << 1 | Zeroize FE partition 0 |
| 757 | +| ZEROIZE_FE1_FLAG | 1 << 2 | Zeroize FE partition 1 |
| 758 | +| ZEROIZE_FE2_FLAG | 1 << 3 | Zeroize FE partition 2 |
| 759 | +| ZEROIZE_FE3_FLAG | 1 << 4 | Zeroize FE partition 3 |
| 760 | + |
| 761 | +*Table: `ZEROIZE_UDS_FE` output arguments* |
| 762 | + |
| 763 | +| **Name** | **Type** | **Description** |
| 764 | +| -------- | -------- | --------------- |
| 765 | +| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian. |
| 766 | +| dpe_result | u32 | Result code, 0 on success. |
| 728 | 767 | |
| 729 | 768 | #### CM_SHA |
| 730 | 769 | |
| @@ -740,7 +779,7 @@ |
| 740 | 779 | | -------------- | ------------- | --------------- |
| 741 | 780 | | chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
| 742 | 781 | | hash_algorithm | u32 | Hash algorithm: 1 = SHA-384, 2 = SHA-512. Value 0 is reserved and will return an error. |
| 743 | | -| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead). |
| 782 | +| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead) in passive mode, and 16,372 bytes in subsystem mode for 2.1 (16 KB minus overhead). |
| 744 | 783 | | input | u8[input_size]| Input data to hash. Variable size up to the mailbox capacity. |
| 745 | 784 | |
| 746 | 785 | *Table: `CM_SHA` output arguments* |
| @@ -769,7 +808,11 @@ |
| 769 | 808 | |
| 770 | 809 | Following is the sequence of steps that are performed to download the firmware image into the mailbox in SUBSYSTEM mode. |
| 771 | 810 | |
| 772 | | -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: |
| 811 | +ROM supports two commands for firmware download in SUBSYSTEM mode: |
| 812 | +- **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. |
| 813 | +- **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. |
| 814 | + |
| 815 | +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: |
| 773 | 816 | - `Device ID` |
| 774 | 817 | - `Device Status` |
| 775 | 818 | - `Push C-image support` |
| @@ -1023,7 +1066,7 @@ |
| 1023 | 1066 | - **ICCM** |
| 1024 | 1067 | |
| 1025 | 1068 | ### Launch FMC |
| 1026 | | -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. |
| 1069 | +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. |
| 1027 | 1070 | |
| 1028 | 1071 | ## Warm reset flow |
| 1029 | 1072 | ROM does not perform any DICE derivations or firmware validation during warm reset. |
| @@ -1136,11 +1179,12 @@ |
| 1136 | 1179 | ### Preamble validation: Manufacturing key validation |
| 1137 | 1180 | |
| 1138 | 1181 | - fuse_ecc_revocation serves as the bitmask for revoking ECC keys. |
| 1139 | | - - If bit-n is set, the nth key is disabled. All other higher bits that are zeros indicate the keys are still enabled. |
| 1182 | + - If bit-n is set, the nth key is disabled. All other bits that are zeros indicate the keys are still enabled. |
| 1140 | 1183 | - If all the bits are zeros, all ECC keys remain enabled. |
| 1141 | 1184 | - Ensure that the Active Key Index in the preamble is not disabled by the fuse_ecc_revocation fuse. |
| 1142 | 1185 | - If the key is disabled, the validation process fails. |
| 1143 | | -- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation. |
| 1186 | + - **Note: The last key index is never revoked, regardless of the fuse value.** |
| 1187 | +- 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. |
| 1144 | 1188 | |
| 1145 | 1189 | ### Preamble validation: Validate the Owner key |
| 1146 | 1190 | |
| @@ -1149,6 +1193,391 @@ |
| 1149 | 1193 | - 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. |
| 1150 | 1194 | - If the computed hash matches the value in fuse_owner_pk_hash, the owner public keys are deemed valid. |
| 1151 | 1195 | - If there is a hash mismatch, the image validation process fails. |
| 1196 | + |
| 1197 | +### Public key hash byte ordering (dword reversal) |
| 1198 | + |
| 1199 | +**Important:** Hashes and ECC key coordinates stored in the firmware manifest and fuse registers use |
| 1200 | +a **reversed-dword format** rather than the standard byte order defined by the SHA specification. |
| 1201 | + |
| 1202 | +In standard byte order, a SHA2-384 hash is a sequence of 48 bytes exactly as output by tools like |
| 1203 | +OpenSSL or Python's `hashlib`. In reversed-dword format, the same 48 bytes are grouped into 12 |
| 1204 | +four-byte words (dwords) and the bytes within each dword are reversed. |
| 1205 | + |
| 1206 | +For example, if the standard SHA2-384 hash begins with `b1 7c a8 77 66 66 57 cc d1 00 e6 92 ...`: |
| 1207 | + |
| 1208 | +| Standard byte order | → | Reversed-dword format | |
| 1209 | +| ---------------------- | --- | ----------------------- | |
| 1210 | +| `b1 7c a8 77` || `77 a8 7c b1` | |
| 1211 | +| `66 66 57 cc` || `cc 57 66 66` | |
| 1212 | +| `d1 00 e6 92` || `92 e6 00 d1` | |
| 1213 | +| ... || ... | |
| 1214 | + |
| 1215 | + |
| 1216 | +This reversed-dword format applies to: |
| 1217 | +- **Individual public key hashes** in the ECC and PQC key descriptors within the preamble |
| 1218 | +- **FUSE_VENDOR_PK_HASH** and **CPTRA_OWNER_PK_HASH** fuse/register values (which are `[u32; 12]` arrays) |
| 1219 | +- **ECC public key coordinates** (X and Y), which are stored as `[u32; 12]` arrays in the preamble |
| 1220 | + |
| 1221 | +Note: LMS public key fields (`tree_type`, `otstype`, `id`, `digest`) follow the LMS specification |
| 1222 | +encoding and are **not** subject to dword reversal. MLDSA public keys are stored as raw byte arrays |
| 1223 | +and are also **not** subject to dword reversal. |
| 1224 | + |
| 1225 | +### Computing public key hashes: step-by-step example |
| 1226 | + |
| 1227 | +The following example walks through the computation of the **vendor PK descriptor hash** |
| 1228 | +using the test public keys from `image/fake-keys/src/lib.rs` with PQC key type **LMS (type 3)**. |
| 1229 | + |
| 1230 | +#### Step 1: Hash each vendor ECC public key |
| 1231 | + |
| 1232 | +Each ECC-384 public key has X and Y coordinates, each stored as `[u32; 12]`. To hash a key, |
| 1233 | +serialize the struct to 96 bytes by writing each `u32` word in reversed-dword format, then |
| 1234 | +compute SHA2-384 of those 96 bytes. |
| 1235 | + |
| 1236 | +**ECC Key 0:** |
| 1237 | +``` |
| 1238 | +X (standard byte order): c69fe67f 97ea3e42 21a7a603 6c2e070d 1657327b c3f1e7c1 |
| 1239 | + 8dccb9e4 ffda5c3f 4db0a1c0 567e0973 17bf4484 39696a07 |
| 1240 | +Y (standard byte order): c126b913 5fc82572 8f1cd403 19109430 994fe3e8 74a8b026 |
| 1241 | + be14794d 27789964 7735fde8 328afd84 cd4d4aa8 72d40b42 |
| 1242 | + |
| 1243 | +X (reversed-dword): 7fe69fc6 423eea97 03a6a721 0d072e6c 7b325716 c1e7f1c3 |
| 1244 | + e4b9cc8d 3f5cdaff c0a1b04d 73097e56 8444bf17 076a6939 |
| 1245 | +Y (reversed-dword): 13b926c1 7225c85f 03d41c8f 30941019 e8e34f99 26b0a874 |
| 1246 | + 4d7914be 64997827 e8fd3577 84fd8a32 a84a4dcd 420bd472 |
| 1247 | + |
| 1248 | +Input to SHA384 = X_reversed || Y_reversed (96 bytes) |
| 1249 | +SHA384 (standard): 84facd34 227de869 1fbb7d33 49306e0f 250a3659 53a6cc6b |
| 1250 | + 629d4616 32f73cfd 768152bb 8a03a255 5a1b1f1f c3923faa |
| 1251 | +SHA384 (reversed-dword): 34cdfa84 69e87d22 337dbb1f 0f6e3049 59360a25 6bcca653 |
| 1252 | + 16469d62 fd3cf732 bb528176 55a2038a 1f1f1b5a aa3f92c3 |
| 1253 | +``` |
| 1254 | + |
| 1255 | +**ECC Key 1:** |
| 1256 | +``` |
| 1257 | +X (standard): a6309750 f0a05ddb 956a7f86 2812ec4f ec454e95 3b53dbfb |
| 1258 | + 9eb54140 15ea7507 084af93c b7fa33fe 51811ad5 e754232e |
| 1259 | +Y (standard): ef5a5987 7a0ce0be 2621d2a9 8bf3c5df af7b3d6d 97f24183 |
| 1260 | + a4a42038 58c39b86 272ef548 e572b937 1ecf1994 1b8d4ea7 |
| 1261 | + |
| 1262 | +SHA384 (standard): fe89195f 7fab8ebb 2818d935 837493c2 378525ef 686ed220 |
| 1263 | + 09b9a399 f23f1f42 2f5ae1f3 ba1c3083 1a68a456 9c01fc96 |
| 1264 | +SHA384 (reversed-dword): 5f1989fe bb8eab7f 35d91828 c2937483 ef258537 20d26e68 |
| 1265 | + 99a3b909 421f3ff2 f3e15a2f 83301cba 56a4681a 96fc019c |
| 1266 | +``` |
| 1267 | + |
| 1268 | +**ECC Key 2:** |
| 1269 | +``` |
| 1270 | +X (standard): a0d25693 c4251e48 185615b0 a6c27f6d e62c39f5 a9a32f75 |
| 1271 | + 9553226a 4d1926c1 7928910f b7adc1b6 89996733 10134881 |
| 1272 | +Y (standard): bbdf72d7 07c08100 d54fcdad b1567bb0 0522762b 76b8dc4a |
| 1273 | + 846c175a 3fbd0501 9bdc8118 4be5f33c bb21b41d 93a8c523 |
| 1274 | + |
| 1275 | +SHA384 (standard): f397ba45 b5801ddf b732078d ffdf792f b584a73f b055acaf |
| 1276 | + ef39f31d 5b88c7d5 2753a45a 0c76b098 90d8e335 7be87f26 |
| 1277 | +SHA384 (reversed-dword): 45ba97f3 df1d80b5 8d0732b7 2f79dfff 3fa784b5 afac55b0 |
| 1278 | + 1df339ef d5c7885b 5aa45327 98b0760c 35e3d890 267fe87b |
| 1279 | +``` |
| 1280 | + |
| 1281 | +**ECC Key 3:** |
| 1282 | +``` |
| 1283 | +X (standard): 002a82b6 8e03e9a0 fd3b4c14 ca2cb3e8 14350a71 0e43956d |
| 1284 | + 21694fb4 f34485e8 f0e33583 f7ea142d 50e16f8b 0225bb95 |
| 1285 | +Y (standard): 5802641c 7c45a4a2 408e03a6 a4100a92 50fcc468 d238cd0d |
| 1286 | + 449cc3e5 1abc25e7 0b05c426 843dcd6f 944ef6ff fa53ec5b |
| 1287 | + |
| 1288 | +SHA384 (standard): 8ba8acb6 b98da9dc 8ffce0bc eba86454 4acbbd6e 3f31466e |
| 1289 | + 5d532565 0bfc9e3b c8afb2b5 c33e20f5 06992143 83f33bc1 |
| 1290 | +SHA384 (reversed-dword): b6aca88b dca98db9 bce0fc8f 5464a8eb 6ebdcb4a 6e46313f |
| 1291 | + 6525535d 3b9efc0b b5b2afc8 f5203ec3 43219906 c13bf383 |
| 1292 | +``` |
| 1293 | + |
| 1294 | +#### Step 2: Hash each vendor LMS public key |
| 1295 | + |
| 1296 | +Each LMS public key is a 48-byte struct: `tree_type` (u32), `otstype` (u32), `id` (16 bytes), |
| 1297 | +`digest` (24 bytes). The binary serialization is hashed directly. |
| 1298 | + |
| 1299 | +**LMS Key 0:** |
| 1300 | +``` |
| 1301 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1302 | +id: 4908a17b cadb1829 1e289058 d5a8e3e8 |
| 1303 | +digest: 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6 |
| 1304 | + |
| 1305 | +Serialized (48 bytes): 0000000c 00000007 4908a17b cadb1829 1e289058 d5a8e3e8 |
| 1306 | + 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6 |
| 1307 | +SHA384 (standard): fc2c1b6f 56f732d1 fd876f3f ef757cbb a2b1c64b cc148298 |
| 1308 | + d7508262 4bdf27cb 23d6b5b6 7169c46f 50b7fc19 92068fec |
| 1309 | +SHA384 (reversed-dword): 6f1b2cfc d132f756 3f6f87fd bb7c75ef 4bc6b1a2 988214cc |
| 1310 | + 628250d7 cb27df4b b6b5d623 6fc46971 19fcb750 ec8f0692 |
| 1311 | +``` |
| 1312 | + |
| 1313 | +**LMS Key 1:** |
| 1314 | +``` |
| 1315 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1316 | +id: 7cb5369d 64e4281d 046e977c 70d4d0a3 |
| 1317 | +digest: 8ea4701d adf7d700 0564b7d6 1d1c9587 9dd6475c 9c3aae0b |
| 1318 | + |
| 1319 | +SHA384 (standard): 7b5811fd 8d2b0cf8 9851f12d d2a7c239 f4f3abc5 d928dcc0 |
| 1320 | + 3b4b891d abbdc67f c7b88436 432e1544 a408bc9c bb503f6b |
| 1321 | +SHA384 (reversed-dword): fd11587b f80c2b8d 2df15198 39c2a7d2 c5abf3f4 c0dc28d9 |
| 1322 | + 1d894b3b 7fc6bdab 3684b8c7 44152e43 9cbc08a4 6b3f50bb |
| 1323 | +``` |
| 1324 | + |
| 1325 | +**LMS Key 2:** |
| 1326 | +``` |
| 1327 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1328 | +id: 2bbb4b72 c5b41e05 d2fabe76 f41704bd |
| 1329 | +digest: dcb53f96 24d4c7b3 c9ae4d4c 0e41e08e 3b159396 0fe6a277 |
| 1330 | + |
| 1331 | +SHA384 (standard): 7e08a494 6933d35a 42c0d7b0 0236b10b db14c100 3f82f6a9 |
| 1332 | + 7d401cb8 e420a7fa 5aab12b3 c4e96bec 49aec770 225a8f88 |
| 1333 | +SHA384 (reversed-dword): 94a4087e 5ad33369 b0d7c042 0bb13602 00c114db a9f6823f |
| 1334 | + b81c407d faa720e4 b312ab5a ec6be9c4 70c7ae49 888f5a22 |
| 1335 | +``` |
| 1336 | + |
| 1337 | +**LMS Key 3:** |
| 1338 | +``` |
| 1339 | +tree_type=0x0000000c, otstype=0x00000007 |
| 1340 | +id: 42cba2e5 575b5235 7ea7aead ef54074c |
| 1341 | +digest: 5aa60e27 69251599 3ae8e21f 27ccdded 8ffcd3d2 8efbdec2 |
| 1342 | + |
| 1343 | +SHA384 (standard): d3734fbc ee2893a3 b1b6519b 6ec78fb8 d7425327 cde1f7aa |
| 1344 | + 23012c64 c635219f d4ab1c4d 1b023252 00042884 2e463dbb |
| 1345 | +SHA384 (reversed-dword): bc4f73d3 a39328ee 9b51b6b1 b88fc76e 275342d7 aaf7e1cd |
| 1346 | + 642c0123 9f2135c6 4d1cabd4 5232021b 84280400 bb3d462e |
| 1347 | +``` |
| 1348 | + |
| 1349 | +#### Step 3: Build the ECC key descriptor (196 bytes) |
| 1350 | + |
| 1351 | +Concatenate the 4-byte header with the 4 key hashes (each in reversed-dword format): |
| 1352 | + |
| 1353 | +``` |
| 1354 | +Header (4 bytes): 01 00 00 04 (version=1, reserved=0, key_hash_count=4) |
| 1355 | +ECC key 0 hash (48 bytes, reversed-dword): 34cdfa84 69e87d22 ... aa3f92c3 |
| 1356 | +ECC key 1 hash (48 bytes, reversed-dword): 5f1989fe bb8eab7f ... 96fc019c |
| 1357 | +ECC key 2 hash (48 bytes, reversed-dword): 45ba97f3 df1d80b5 ... 267fe87b |
| 1358 | +ECC key 3 hash (48 bytes, reversed-dword): b6aca88b dca98db9 ... c13bf383 |
| 1359 | + |
| 1360 | +Total: 4 + (4 × 48) = 196 bytes |
| 1361 | +``` |
| 1362 | + |
| 1363 | +#### Step 4: Build the PQC (LMS) key descriptor (1540 bytes) |
| 1364 | + |
| 1365 | +``` |
| 1366 | +Header (4 bytes): 01 00 03 20 (version=1, key_type=3=LMS, key_hash_count=32) |
| 1367 | +LMS key 0 hash (48 bytes, reversed-dword): 6f1b2cfc d132f756 ... ec8f0692 |
| 1368 | +LMS key 1 hash (48 bytes, reversed-dword): fd11587b f80c2b8d ... 6b3f50bb |
| 1369 | +LMS key 2 hash (48 bytes, reversed-dword): 94a4087e 5ad33369 ... 888f5a22 |
| 1370 | +LMS key 3 hash (48 bytes, reversed-dword): bc4f73d3 a39328ee ... bb3d462e |
| 1371 | + ... (keys 0-3 repeated 8 times to fill all 32 slots) |
| 1372 | + |
| 1373 | +Total: 4 + (32 × 48) = 1540 bytes |
| 1374 | +``` |
| 1375 | + |
| 1376 | +#### Step 5: Compute the vendor PK descriptor hash |
| 1377 | + |
| 1378 | +``` |
| 1379 | +Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes |
| 1380 | + |
| 1381 | +SHA384 (standard byte order): |
| 1382 | + b17ca877 666657cc d100e692 6c7206b6 0c995cb6 8992c6c9 |
| 1383 | + baefce72 8af05441 dee1ff41 5adfc187 e1e4edb4 d3b2d909 |
| 1384 | + |
| 1385 | +As [u32; 12] fuse register value: |
| 1386 | + [0xb17ca877, 0x666657cc, 0xd100e692, 0x6c7206b6, |
| 1387 | + 0x0c995cb6, 0x8992c6c9, 0xbaefce72, 0x8af05441, |
| 1388 | + 0xdee1ff41, 0x5adfc187, 0xe1e4edb4, 0xd3b2d909] |
| 1389 | +``` |
| 1390 | + |
| 1391 | +#### Owner PK hash |
| 1392 | + |
| 1393 | +The owner PK hash is SHA2-384 over the serialized `ImageOwnerPubKeys` struct, which contains: |
| 1394 | +- `ecc_pub_key`: `{ x: [u32; 12], y: [u32; 12] }` — 96 bytes (in reversed-dword format) |
| 1395 | +- `pqc_pub_key`: raw byte array of 2592 bytes (for LMS, only the first 48 bytes are meaningful; |
| 1396 | + the rest are zero-padded) |
| 1397 | + |
| 1398 | +Total: 2688 bytes. The SHA2-384 of these bytes is the owner PK hash. |
| 1399 | + |
| 1400 | +#### Summary of expected hash values using test keys |
| 1401 | + |
| 1402 | +Using the test keys from `image/fake-keys/src/lib.rs`: |
| 1403 | + |
| 1404 | +| Hash | PQC Type | Standard byte order (hex) | |
| 1405 | +| ------ | ---------- | --------------------------- | |
| 1406 | +| Vendor PK descriptor hash | LMS (type 3) | `b17ca877666657ccd100e6926c7206b60c995cb68992c6c9baefce728af05441dee1ff415adfc187e1e4edb4d3b2d909` | |
| 1407 | +| Vendor PK descriptor hash | MLDSA (type 1) | `30399676a17e3e973677b3ff862f4bf2d1932d884778453c376fe00dc93fb8aa0770f3ebf3411a0853e9c57ece8a2980` | |
| 1408 | +| Owner PK hash | LMS (type 3) | `1b179390e4e6c44422ed553e256c7d675cd93190cb49d88d485aa4ef3906cd492ab3ee3d3ba5f2c990ad13390fed4de5` | |
| 1409 | +| Owner PK hash | MLDSA (type 1) | `48afdb073c5e0d4ee46490468ef81f2cf57249b6e76a28f5fca4de696a7d3e2ed3efc4e6774318543e95307a54988bd7` | |
| 1410 | + |
| 1411 | + |
| 1412 | +To convert any of these standard byte order hashes to the `[u32; 12]` fuse register format, group |
| 1413 | +the hex string into 8-character (4-byte) chunks and interpret each as a 32-bit word: |
| 1414 | +- `b17ca877666657cc...` → `[0xb17ca877, 0x666657cc, 0xd100e692, ...]` |
| 1415 | + |
| 1416 | +#### Python script to compute vendor and owner PK hashes |
| 1417 | + |
| 1418 | +The following Python script computes the vendor PK descriptor hash and owner PK hash from |
| 1419 | +ECC PEM files and LMS or MLDSA binary key files: |
| 1420 | + |
| 1421 | +```python |
| 1422 | +#!/usr/bin/env python3 |
| 1423 | +""" |
| 1424 | +Compute the Caliptra vendor PK descriptor hash and owner PK hash |
| 1425 | +from ECC (.pem) and LMS/MLDSA (.bin) public key files. |
| 1426 | + |
| 1427 | +Usage: |
| 1428 | + python3 compute_pk_hashes.py --pqc-key-type <1|3> \\ |
| 1429 | + --vendor-ecc-pub-keys key0.pem key1.pem key2.pem key3.pem \\ |
| 1430 | + --vendor-pqc-pub-keys pqc0.bin pqc1.bin ... \\ |
| 1431 | + --owner-ecc-pub-key owner.pem \\ |
| 1432 | + --owner-pqc-pub-key owner_pqc.bin |
| 1433 | + |
| 1434 | +PQC key type: 1 = MLDSA, 3 = LMS |
| 1435 | + |
| 1436 | +ECC public keys are PEM files (P-384). |
| 1437 | +LMS public keys are 48-byte binary files (tree_type, otstype, id, digest). |
| 1438 | +MLDSA public keys are 2592-byte binary files. |
| 1439 | +""" |
| 1440 | +import argparse |
| 1441 | +import hashlib |
| 1442 | +import struct |
| 1443 | +import sys |
| 1444 | + |
| 1445 | +from cryptography.hazmat.primitives.serialization import load_pem_public_key |
| 1446 | + |
| 1447 | +# Sizes |
| 1448 | +ECC_PUB_KEY_BYTES = 96 # 2 x 48-byte coordinates |
| 1449 | +PQC_PUB_KEY_SLOT_BYTES = 2592 # MLDSA key size; LMS keys are 48 bytes, zero-padded |
| 1450 | +LMS_PUB_KEY_BYTES = 48 |
| 1451 | +MLDSA_PUB_KEY_BYTES = 2592 |
| 1452 | +HASH_BYTES = 48 # SHA2-384 |
| 1453 | + |
| 1454 | +VENDOR_ECC_MAX_KEYS = 4 |
| 1455 | +VENDOR_LMS_MAX_KEYS = 32 |
| 1456 | +VENDOR_MLDSA_MAX_KEYS = 32 # struct always allocates 32 slots; only first 4 are populated |
| 1457 | +KEY_DESCRIPTOR_VERSION = 1 |
| 1458 | + |
| 1459 | + |
| 1460 | +def ecc_pub_key_to_reversed_dwords(pem_path: str) -> bytes: |
| 1461 | + """Read an ECC P-384 PEM public key and return 96 bytes in reversed-dword format.""" |
| 1462 | + with open(pem_path, 'rb') as f: |
| 1463 | + pub_key = load_pem_public_key(f.read()) |
| 1464 | + nums = pub_key.public_numbers() |
| 1465 | + x_bytes = nums.x.to_bytes(48, 'big') |
| 1466 | + y_bytes = nums.y.to_bytes(48, 'big') |
| 1467 | + return to_reversed_dwords(x_bytes) + to_reversed_dwords(y_bytes) |
| 1468 | + |
| 1469 | + |
| 1470 | +def to_reversed_dwords(standard_bytes: bytes) -> bytes: |
| 1471 | + """Convert bytes from standard byte order to reversed-dword format. |
| 1472 | + |
| 1473 | + Groups the input into 4-byte dwords and reverses the bytes within each dword. |
| 1474 | + """ |
| 1475 | + assert len(standard_bytes) % 4 == 0 |
| 1476 | + result = bytearray() |
| 1477 | + for i in range(0, len(standard_bytes), 4): |
| 1478 | + result.extend(standard_bytes[i:i+4][::-1]) |
| 1479 | + return bytes(result) |
| 1480 | + |
| 1481 | + |
| 1482 | +def sha384_reversed_dwords(data: bytes) -> bytes: |
| 1483 | + """Compute SHA2-384 and return the hash in reversed-dword format.""" |
| 1484 | + h = hashlib.sha384(data).digest() |
| 1485 | + return to_reversed_dwords(h) |
| 1486 | + |
| 1487 | + |
| 1488 | +def build_ecc_key_descriptor(ecc_pem_paths: list) -> bytes: |
| 1489 | + """Build the ECC key descriptor: header + key hashes.""" |
| 1490 | + n = len(ecc_pem_paths) |
| 1491 | + header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, 0, n) |
| 1492 | + hashes = b'' |
| 1493 | + for path in ecc_pem_paths: |
| 1494 | + key_bytes = ecc_pub_key_to_reversed_dwords(path) |
| 1495 | + hashes += sha384_reversed_dwords(key_bytes) |
| 1496 | + # Pad to VENDOR_ECC_MAX_KEYS slots |
| 1497 | + hashes += b'\x00' * (HASH_BYTES * (VENDOR_ECC_MAX_KEYS - n)) |
| 1498 | + return header + hashes |
| 1499 | + |
| 1500 | + |
| 1501 | +def build_pqc_key_descriptor(pqc_bin_paths: list, pqc_key_type: int) -> bytes: |
| 1502 | + """Build the PQC key descriptor: header + key hashes.""" |
| 1503 | + n = len(pqc_bin_paths) |
| 1504 | + max_keys = VENDOR_LMS_MAX_KEYS if pqc_key_type == 3 else VENDOR_MLDSA_MAX_KEYS |
| 1505 | + header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, pqc_key_type, n) |
| 1506 | + hashes = b'' |
| 1507 | + for path in pqc_bin_paths: |
| 1508 | + with open(path, 'rb') as f: |
| 1509 | + key_bytes = f.read() |
| 1510 | + hashes += sha384_reversed_dwords(key_bytes) |
| 1511 | + # Pad to max slots |
| 1512 | + hashes += b'\x00' * (HASH_BYTES * (max_keys - n)) |
| 1513 | + return header + hashes |
| 1514 | + |
| 1515 | + |
| 1516 | +def build_owner_pub_keys(ecc_pem_path: str, pqc_bin_path: str) -> bytes: |
| 1517 | + """Build the serialized ImageOwnerPubKeys struct.""" |
| 1518 | + ecc_bytes = ecc_pub_key_to_reversed_dwords(ecc_pem_path) |
| 1519 | + with open(pqc_bin_path, 'rb') as f: |
| 1520 | + pqc_bytes = f.read() |
| 1521 | + # Pad PQC key to full slot size |
| 1522 | + pqc_padded = pqc_bytes + b'\x00' * (PQC_PUB_KEY_SLOT_BYTES - len(pqc_bytes)) |
| 1523 | + return ecc_bytes + pqc_padded |
| 1524 | + |
| 1525 | + |
| 1526 | +def hash_to_fuse_words(standard_hash: bytes) -> list: |
| 1527 | + """Convert a standard byte order hash to [u32; 12] fuse word format.""" |
| 1528 | + return [int.from_bytes(standard_hash[i:i+4], 'big') for i in range(0, 48, 4)] |
| 1529 | + |
| 1530 | + |
| 1531 | +def main(): |
| 1532 | + parser = argparse.ArgumentParser( |
| 1533 | + description='Compute Caliptra vendor PK descriptor hash and owner PK hash') |
| 1534 | + parser.add_argument('--pqc-key-type', type=int, required=True, choices=[1, 3], |
| 1535 | + help='PQC key type: 1=MLDSA, 3=LMS') |
| 1536 | + parser.add_argument('--vendor-ecc-pub-keys', nargs='+', required=True, |
| 1537 | + help='Vendor ECC P-384 public key PEM files') |
| 1538 | + parser.add_argument('--vendor-pqc-pub-keys', nargs='+', required=True, |
| 1539 | + help='Vendor PQC (LMS .bin or MLDSA .bin) public key files') |
| 1540 | + parser.add_argument('--owner-ecc-pub-key', |
| 1541 | + help='Owner ECC P-384 public key PEM file') |
| 1542 | + parser.add_argument('--owner-pqc-pub-key', |
| 1543 | + help='Owner PQC (LMS .bin or MLDSA .bin) public key file') |
| 1544 | + args = parser.parse_args() |
| 1545 | + |
| 1546 | + pqc_name = {1: 'MLDSA', 3: 'LMS'}[args.pqc_key_type] |
| 1547 | + |
| 1548 | + # Build descriptors |
| 1549 | + ecc_desc = build_ecc_key_descriptor(args.vendor_ecc_pub_keys) |
| 1550 | + pqc_desc = build_pqc_key_descriptor(args.vendor_pqc_pub_keys, args.pqc_key_type) |
| 1551 | + vendor_pub_key_info = ecc_desc + pqc_desc |
| 1552 | + |
| 1553 | + # Vendor PK descriptor hash (standard byte order) |
| 1554 | + vendor_hash = hashlib.sha384(vendor_pub_key_info).digest() |
| 1555 | + vendor_hex = vendor_hash.hex() |
| 1556 | + vendor_words = hash_to_fuse_words(vendor_hash) |
| 1557 | + |
| 1558 | + print(f"PQC key type: {args.pqc_key_type} ({pqc_name})") |
| 1559 | + print() |
| 1560 | + print(f"Vendor PK descriptor hash (standard byte order):") |
| 1561 | + print(f" {vendor_hex}") |
| 1562 | + print(f"Vendor PK descriptor hash (fuse [u32; 12]):") |
| 1563 | + print(f" {['0x{:08x}'.format(w) for w in vendor_words]}") |
| 1564 | + |
| 1565 | + if args.owner_ecc_pub_key and args.owner_pqc_pub_key: |
| 1566 | + owner_bytes = build_owner_pub_keys(args.owner_ecc_pub_key, args.owner_pqc_pub_key) |
| 1567 | + owner_hash = hashlib.sha384(owner_bytes).digest() |
| 1568 | + owner_hex = owner_hash.hex() |
| 1569 | + owner_words = hash_to_fuse_words(owner_hash) |
| 1570 | + |
| 1571 | + print() |
| 1572 | + print(f"Owner PK hash (standard byte order):") |
| 1573 | + print(f" {owner_hex}") |
| 1574 | + print(f"Owner PK hash (fuse [u32; 12]):") |
| 1575 | + print(f" {['0x{:08x}'.format(w) for w in owner_words]}") |
| 1576 | + |
| 1577 | + |
| 1578 | +if __name__ == '__main__': |
| 1579 | + main() |
| 1580 | +``` |
| 1152 | 1581 | |
| 1153 | 1582 | ## Preamble validation steps |
| 1154 | 1583 | |