Repository: chipsalliance/caliptra-sw
Pull request #482 (2023-07-17 13:11:42)

Extend both a current and journey PCR in ROM.
For consistency with FMC. Also adds tests to validate the log against the PCR values in hardware.

bluegate010: Tests currently failing due to https://github.com/chipsalliance/caliptra-sw/issues/483.

Patch:
diff --git a/common/src/boot_status.rs b/common/src/boot_status.rs
index 93165059..d86e9172 100644
--- a/common/src/boot_status.rs
+++ b/common/src/boot_status.rs
@@ -70,10 +70,11 @@ pub enum RomBootStatus {
     UpdateResetStarted = UPDATE_RESET_BOOT_STATUS_BASE,
     UpdateResetLoadManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 1,
     UpdateResetImageVerificationComplete = UPDATE_RESET_BOOT_STATUS_BASE + 2,
-    UpdateResetPopulateDataVaultComplete = UPDATE_RESET_BOOT_STATUS_BASE + 3,
-    UpdateResetLoadImageComplete = UPDATE_RESET_BOOT_STATUS_BASE + 4,
-    UpdateResetOverwriteManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 5,
-    UpdateResetComplete = UPDATE_RESET_BOOT_STATUS_BASE + 6,
+    UpdateResetExtendPcrComplete = UPDATE_RESET_BOOT_STATUS_BASE + 3,
+    UpdateResetPopulateDataVaultComplete = UPDATE_RESET_BOOT_STATUS_BASE + 4,
+    UpdateResetLoadImageComplete = UPDATE_RESET_BOOT_STATUS_BASE + 5,
+    UpdateResetOverwriteManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 6,
+    UpdateResetComplete = UPDATE_RESET_BOOT_STATUS_BASE + 7,
 
     // ROM Global Boot Statues
     KatStarted = ROM_GLOBAL_BOOT_STATUS_BASE,
diff --git a/common/src/pcr.rs b/common/src/pcr.rs
index 3f621941..5ba522f3 100644
--- a/common/src/pcr.rs
+++ b/common/src/pcr.rs
@@ -16,9 +16,6 @@ use zerocopy::{AsBytes, FromBytes};
 
 // PcrLogEntryId is used to identify the PCR entry and
 // the size of the data in PcrLogEntry::pcr_data.
-//
-// For valid entries, it is also used as the index into the PCR log as per the formula:
-//      log_entry_index = pcr_entry_id - 1
 #[repr(u16)]
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum PcrLogEntryId {
@@ -74,5 +71,26 @@ pub struct PcrLogEntry {
     pub reserved1: [u8; 4],
 }
 
+impl PcrLogEntry {
+    pub fn measured_data(&self) -> &[u8] {
+        let data_len = match PcrLogEntryId::from(self.id) {
+            PcrLogEntryId::Invalid => 0,
+            PcrLogEntryId::DeviceLifecycle => 1,
+            PcrLogEntryId::DebugLocked => 1,
+            PcrLogEntryId::AntiRollbackDisabled => 1,
+            PcrLogEntryId::VendorPubKeyHash => 48,
+            PcrLogEntryId::OwnerPubKeyHash => 48,
+            PcrLogEntryId::EccVendorPubKeyIndex => 1,
+            PcrLogEntryId::FmcTci => 48,
+            PcrLogEntryId::FmcSvn => 1,
+            PcrLogEntryId::FmcFuseSvn => 1,
+            PcrLogEntryId::LmsVendorPubKeyIndex => 1,
+            PcrLogEntryId::RomVerifyConfig => 1,
+        };
+
+        &self.pcr_data.as_bytes()[..data_len]
+    }
+}
+
 pub const RT_FW_CURRENT_PCR: PcrId = PcrId::PcrId3;
 pub const RT_FW_JOURNEY_PCR: PcrId = PcrId::PcrId2;
diff --git a/drivers/src/pcr_bank.rs b/drivers/src/pcr_bank.rs
index 4a27bc85..8343d2a7 100644
--- a/drivers/src/pcr_bank.rs
+++ b/drivers/src/pcr_bank.rs
@@ -66,14 +66,56 @@ impl From<PcrId> for usize {
     }
 }
 
+impl TryFrom<u8> for PcrId {
+    type Error = ();
+    fn try_from(original: u8) -> Result<Self, Self::Error> {
+        match original {
+            0 => Ok(Self::PcrId0),
+            1 => Ok(Self::PcrId1),
+            2 => Ok(Self::PcrId2),
+            3 => Ok(Self::PcrId3),
+            4 => Ok(Self::PcrId4),
+            5 => Ok(Self::PcrId5),
+            6 => Ok(Self::PcrId6),
+            7 => Ok(Self::PcrId7),
+            8 => Ok(Self::PcrId8),
+            9 => Ok(Self::PcrId9),
+            10 => Ok(Self::PcrId10),
+            11 => Ok(Self::PcrId11),
+            12 => Ok(Self::PcrId12),
+            13 => Ok(Self::PcrId13),
+            14 => Ok(Self::PcrId14),
+            15 => Ok(Self::PcrId15),
+            16 => Ok(Self::PcrId16),
+            17 => Ok(Self::PcrId17),
+            18 => Ok(Self::PcrId18),
+            19 => Ok(Self::PcrId19),
+            20 => Ok(Self::PcrId20),
+            21 => Ok(Self::PcrId21),
+            22 => Ok(Self::PcrId22),
+            23 => Ok(Self::PcrId23),
+            24 => Ok(Self::PcrId24),
+            25 => Ok(Self::PcrId25),
+            26 => Ok(Self::PcrId26),
+            27 => Ok(Self::PcrId27),
+            28 => Ok(Self::PcrId28),
+            29 => Ok(Self::PcrId29),
+            30 => Ok(Self::PcrId30),
+            31 => Ok(Self::PcrId31),
+            _ => Err(()),
+        }
+    }
+}
+
 /// Platform Configuration Register (PCR) Bank
 pub struct PcrBank {
     pv: PvReg,
+    pub log_index: usize, // The index of the next PCR log to be written to DCCM.
 }
 
 impl PcrBank {
     pub fn new(pv: PvReg) -> Self {
-        Self { pv }
+        Self { pv, log_index: 0 }
     }
     /// Erase all the pcrs in the pcr vault
     ///
diff --git a/error/src/lib.rs b/error/src/lib.rs
index 08248efd..88bee390 100644
--- a/error/src/lib.rs
+++ b/error/src/lib.rs
@@ -352,16 +352,17 @@ impl CaliptraError {
         CaliptraError::new_const(0x01050004);
     pub const ROM_GLOBAL_PCR_LOG_UNSUPPORTED_DATA_LENGTH: CaliptraError =
         CaliptraError::new_const(0x01050005);
+    pub const ROM_GLOBAL_PCR_LOG_EXHAUSTED: CaliptraError = CaliptraError::new_const(0x01050006);
 
     pub const ROM_GLOBAL_FUSE_LOG_INVALID_ENTRY_ID: CaliptraError =
-        CaliptraError::new_const(0x01050006);
-    pub const ROM_GLOBAL_FUSE_LOG_UNSUPPORTED_DATA_LENGTH: CaliptraError =
         CaliptraError::new_const(0x01050007);
+    pub const ROM_GLOBAL_FUSE_LOG_UNSUPPORTED_DATA_LENGTH: CaliptraError =
+        CaliptraError::new_const(0x01050008);
 
     pub const ROM_GLOBAL_UNSUPPORTED_LDEVID_TBS_SIZE: CaliptraError =
-        CaliptraError::new_const(0x01050008);
-    pub const ROM_GLOBAL_UNSUPPORTED_FMCALIAS_TBS_SIZE: CaliptraError =
         CaliptraError::new_const(0x01050009);
+    pub const ROM_GLOBAL_UNSUPPORTED_FMCALIAS_TBS_SIZE: CaliptraError =
+        CaliptraError::new_const(0x0105000A);
 
     /// ROM KAT Errors
     pub const ROM_KAT_SHA256_DIGEST_FAILURE: CaliptraError = CaliptraError::new_const(0x90010001);
diff --git a/rom/dev/README.md b/rom/dev/README.md
index f5501c0f..762c9a2c 100644
--- a/rom/dev/README.md
+++ b/rom/dev/README.md
@@ -212,7 +212,8 @@ The following sections define the various cryptographic primitives used by Calip
 | | `dv4_lock_wr(dv_slot)` | Write Lock the 4-byte data vault slot<br>Input<br>***dv_slot*** - data vault slot |
 | Platform Configuration Registers | `pcr_extend(pcr_slot, data)` | Perform PCR extend operation on a PCR with specified data<br>**Input**:<br>***pc_slot*** - PCR slot to hash extend<br>***data*** – data |
 | | `pcr_read(pcr_slot) -> measurement` | Read the PCR slot<br>**Input**:<br>***pc_slot*** - PCR slot to read<br>**Output**:<br>***measurement*** - Accumulated measurement |
-| | `pcr_lock_clr(pcr_slot)` | Lock for Clear PCR slot<br>**Input**:<br>***pcr_slot*** - pcr slot |
+| | `pcr_lock_clear(pcr_slot)` | Lock for Clear PCR slot<br>**Input**:<br>***pcr_slot*** - pcr slot |
+| | `pcr_clear(pcr_slot)` | Clear PCR slot<br>**Input**:<br>***pcr_slot*** - pcr slot |
 | X509 | `gen_tbs(type, pub_key) -> tbs` | Generate X509 Certificate or CSR `To Be Signed` portion<br>**Input**:<br>***type*** - Can be IDEVID_CSR, LDEVID_CERT or ALIAS_FMC_CERT<br>pub-key -public key<br>**Output**:<br>***tbs*** - DER encoded `To Be Signed` portion |
 <br>
 
@@ -439,17 +440,20 @@ Alias FMC Layer includes the measurement of the FMC and other security states. T
 
 **Actions:**
 
-1.	PCR0 is the Current PCR. PCR0 is locked for clear by the ROM on every reset. Subsequent layers may continue to extend PCR0 as runtime updates are performed.
-
-	`pcr_lock_clr(Pcr0)`
-	`pcr_extend(Pcr0, CPTRA_SECURITY_STATE.LIFECYCLE_STATE)`
-    `pcr_extend(Pcr0, CPTRA_SECURITY_STATE.DEBUG_ENABLED)`
-    `pcr_extend(Pcr0, FUSE_ANTI_ROLLBACK_DISABLE)`
-    `pcr_extend(Pcr0, MANUFACTURER_PK)`
-    `pcr_extend(Pcr0, FUSE_OWNER_PK_HASH)`
-    `pcr_extend(Pcr0, FMC_DIGEST)`
-    `pcr_extend(Pcr0, FMC_SVN)`
-    `pcr_extend(Pcr0, FMC_FUSE_SVN)` (or 0 if `FUSE_ANTI_ROLLBACK_DISABLE`)
+1.	PCR0 is the Current PCR. PCR 1 is the Journey PCR. PCR0 is cleared by ROM upon each warm reset, before it is extended with FMC measurements. PCR0 and PCR1 are locked for clear by the ROM on every reset. Subsequent layers may continue to extend PCR0 as runtime updates are performed.
+
+    `pcr_clear(Pcr0)`
+    `pcr_extend(Pcr0 && Pcr1, CPTRA_SECURITY_STATE.LIFECYCLE_STATE)`
+    `pcr_extend(Pcr0 && Pcr1, CPTRA_SECURITY_STATE.DEBUG_ENABLED)`
+    `pcr_extend(Pcr0 && Pcr1, FUSE_ANTI_ROLLBACK_DISABLE)`
+    `pcr_extend(Pcr0 && Pcr1, MANUFACTURER_PK)`
+    `pcr_extend(Pcr0 && Pcr1, FUSE_OWNER_PK_HASH)`
+    `pcr_extend(Pcr0 && Pcr1, FMC_DIGEST)`
+    `pcr_extend(Pcr0 && Pcr1, FMC_SVN)`
+    `pcr_extend(Pcr0 && Pcr1, FMC_FUSE_SVN)` (or 0 if `FUSE_ANTI_ROLLBACK_DISABLE`)
+    `pcr_extend(Pcr0 && Pcr1, LMS_VENDOR_PK_INDEX)`
+    `pcr_extend(Pcr0 && Pcr1, ROM_VERIFY_CONFIG)`
+    `pcr_lock_clear(Pcr0 && Pcr1)`
 
 2.	CDI for Alias is derived from PCR0. For the Alias FMC CDI Derivation,  LDevID CDI in Key Vault Slot6 is used as HMAC Key and contents of PCR0 are used as data. The resultant mac is stored back in Slot 6
 
diff --git a/rom/dev/src/flow/cold_reset/fw_processor.rs b/rom/dev/src/flow/cold_reset/fw_processor.rs
index 4d6685dc..fa2a99b0 100644
--- a/rom/dev/src/flow/cold_reset/fw_processor.rs
+++ b/rom/dev/src/flow/cold_reset/fw_processor.rs
@@ -80,8 +80,8 @@ impl FirmwareProcessor {
         // Populate data vault
         Self::populate_data_vault(venv.data_vault, info);
 
-        // Extend PCR0
-        pcr::extend_pcr0(&mut venv, info)?;
+        // Extend PCR0 and PCR1
+        pcr::extend_pcrs(&mut venv, info)?;
         report_boot_status(FwProcessorExtendPcrComplete.into());
 
         // Load the image
diff --git a/rom/dev/src/flow/update_reset.rs b/rom/dev/src/flow/update_reset.rs
index 4afb0316..35c36bdb 100644
--- a/rom/dev/src/flow/update_reset.rs
+++ b/rom/dev/src/flow/update_reset.rs
@@ -11,7 +11,7 @@ Abstract:
     File contains the implementation of update reset flow.
 
 --*/
-use crate::{cprintln, rom_env::RomEnv, verifier::RomImageVerificationEnv};
+use crate::{cprintln, pcr, rom_env::RomEnv, verifier::RomImageVerificationEnv};
 
 use caliptra_common::memory_layout::{MAN1_ORG, MAN2_ORG};
 use caliptra_common::FirmwareHandoffTable;
@@ -19,7 +19,7 @@ use caliptra_common::FirmwareHandoffTable;
 use caliptra_common::RomBootStatus::*;
 use caliptra_drivers::DataVault;
 use caliptra_drivers::{
-    report_boot_status, MailboxRecvTxn, ResetReason, WarmResetEntry4, WarmResetEntry48,
+    okref, report_boot_status, MailboxRecvTxn, ResetReason, WarmResetEntry4, WarmResetEntry48,
 };
 use caliptra_error::{CaliptraError, CaliptraResult};
 use caliptra_image_types::ImageManifest;
@@ -64,16 +64,21 @@ impl UpdateResetFlow {
             pcr_bank: &mut env.pcr_bank,
         };
 
-        let info = Self::verify_image(&mut venv, &manifest, recv_txn.dlen())?;
+        let info = Self::verify_image(&mut venv, &manifest, recv_txn.dlen());
+        let info = okref(&info)?;
         report_boot_status(UpdateResetImageVerificationComplete.into());
 
+        // Extend PCR0 and PCR1
+        pcr::extend_pcrs(&mut venv, info)?;
+        report_boot_status(UpdateResetExtendPcrComplete.into());
+
         cprintln!(
             "[update-reset] Image verified using Vendor ECC Key Index {}",
             info.vendor_ecc_pub_key_idx
         );
 
         // Populate data vault
-        Self::populate_data_vault(venv.data_vault, &info);
+        Self::populate_data_vault(venv.data_vault, info);
 
         Self::load_image(&manifest, recv_txn)?;
         report_boot_status(UpdateResetLoadImageComplete.into());
diff --git a/rom/dev/src/lock.rs b/rom/dev/src/lock.rs
index 5a036c77..ad724c68 100644
--- a/rom/dev/src/lock.rs
+++ b/rom/dev/src/lock.rs
@@ -35,9 +35,10 @@ pub fn lock_registers(env: &mut RomEnv, reset_reason: ResetReason) {
         lock_common_reg_set(env);
     }
 
-    // Lock the PCR0 from clear
-    cprintln!("[state] Locking PCR0");
+    // Lock PCR0 and PCR1 from clear
+    cprintln!("[state] Locking PCR0 and PCR1");
     env.pcr_bank.set_pcr_lock(caliptra_drivers::PcrId::PcrId0);
+    env.pcr_bank.set_pcr_lock(caliptra_drivers::PcrId::PcrId1);
 
     cprintln!("[state] Locking ICCM");
     env.soc_ifc.set_iccm_lock(true);
diff --git a/rom/dev/src/pcr.rs b/rom/dev/src/pcr.rs
index db877019..738ddf2d 100644
--- a/rom/dev/src/pcr.rs
+++ b/rom/dev/src/pcr.rs
@@ -38,28 +38,31 @@ struct PcrExtender<'a> {
 impl PcrExtender<'_> {
     fn extend(&mut self, data: Array4x12, pcr_entry_id: PcrLogEntryId) -> CaliptraResult<()> {
         let bytes: &[u8; 48] = &data.into();
-        self.pcr_bank
-            .extend_pcr(PcrId::PcrId0, self.sha384, bytes)?;
-        log_pcr(pcr_entry_id, PcrId::PcrId0, bytes)
+        self.extend_and_log(bytes, pcr_entry_id)
     }
     fn extend_u8(&mut self, data: u8, pcr_entry_id: PcrLogEntryId) -> CaliptraResult<()> {
         let bytes = &data.to_le_bytes();
-        self.pcr_bank
-            .extend_pcr(PcrId::PcrId0, self.sha384, bytes)?;
-        log_pcr(pcr_entry_id, PcrId::PcrId0, bytes)
+        self.extend_and_log(bytes, pcr_entry_id)
+    }
+    fn extend_and_log(&mut self, data: &[u8], pcr_entry_id: PcrLogEntryId) -> CaliptraResult<()> {
+        self.pcr_bank.extend_pcr(PcrId::PcrId0, self.sha384, data)?;
+        self.pcr_bank.extend_pcr(PcrId::PcrId1, self.sha384, data)?;
+
+        let pcr_ids: u32 = (1 << PcrId::PcrId0 as u8) | (1 << PcrId::PcrId1 as u8);
+        log_pcr(self.pcr_bank, pcr_entry_id, pcr_ids, data)
     }
 }
 
-/// Extend PCR0
+/// Extend PCR0 and PCR1
 ///
 /// # Arguments
 ///
 /// * `env` - ROM Environment
-pub(crate) fn extend_pcr0(
+pub(crate) fn extend_pcrs(
     env: &mut RomImageVerificationEnv,
     info: &ImageVerificationInfo,
 ) -> CaliptraResult<()> {
-    // Clear the PCR
+    // Clear the Current PCR, but do not clear the Journey PCR
     env.pcr_bank.erase_pcr(caliptra_drivers::PcrId::PcrId0)?;
 
     let mut pcr = PcrExtender {
@@ -106,8 +109,9 @@ pub(crate) fn extend_pcr0(
 /// Log PCR data
 ///
 /// # Arguments
+/// * `pcr_bank` - PCR bank
 /// * `pcr_entry_id` - PCR log entry ID
-/// * `pcr_id` - PCR ID
+/// * `pcr_ids` - bitmask of PCR indices
 /// * `data` - PCR data
 ///
 /// # Return Value
@@ -115,7 +119,12 @@ pub(crate) fn extend_pcr0(
 /// * `Err(GlobalErr::PcrLogInvalidEntryId)` - Invalid PCR log entry ID
 /// * `Err(GlobalErr::PcrLogUpsupportedDataLength)` - Unsupported data length
 ///
-pub fn log_pcr(pcr_entry_id: PcrLogEntryId, pcr_id: PcrId, data: &[u8]) -> CaliptraResult<()> {
+pub fn log_pcr(
+    pcr_bank: &mut PcrBank,
+    pcr_entry_id: PcrLogEntryId,
+    pcr_ids: u32,
+    data: &[u8],
+) -> CaliptraResult<()> {
     if pcr_entry_id == PcrLogEntryId::Invalid {
         return Err(CaliptraError::ROM_GLOBAL_PCR_LOG_INVALID_ENTRY_ID);
     }
@@ -124,21 +133,27 @@ pub fn log_pcr(pcr_entry_id: PcrLogEntryId, pcr_id: PcrId, data: &[u8]) -> Calip
         return Err(CaliptraError::ROM_GLOBAL_PCR_LOG_UNSUPPORTED_DATA_LENGTH);
     }
 
+    if pcr_bank.log_index * core::mem::size_of::<PcrLogEntry>() > PCR_LOG_SIZE {
+        return Err(CaliptraError::ROM_GLOBAL_PCR_LOG_EXHAUSTED);
+    }
+
     // Create a PCR log entry
     let mut pcr_log_entry = PcrLogEntry {
         id: pcr_entry_id as u16,
-        pcr_ids: 1 << (pcr_id as u8),
+        pcr_ids,
         ..Default::default()
     };
     pcr_log_entry.pcr_data.as_bytes_mut()[..data.len()].copy_from_slice(data);
 
     let dst: &mut [PcrLogEntry] = unsafe {
         let ptr = PCR_LOG_ORG as *mut PcrLogEntry;
-        core::slice::from_raw_parts_mut(ptr, PCR_LOG_SIZE / core::mem::size_of::<PcrLogEntry>())
+        let entry_ptr = ptr.add(pcr_bank.log_index);
+        pcr_bank.log_index += 1;
+        core::slice::from_raw_parts_mut(entry_ptr, 1)
     };
 
     // Store the log entry.
-    dst[pcr_entry_id as usize - 1] = pcr_log_entry;
+    dst[0] = pcr_log_entry;
 
     Ok(())
 }


Test patch:
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index 9d8087c0..ee88fd69 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -5,13 +5,14 @@ use caliptra_common::RomBootStatus::ColdResetComplete;
 use caliptra_common::RomBootStatus::*;
 use caliptra_common::{FirmwareHandoffTable, FuseLogEntry, FuseLogEntryId};
 use caliptra_common::{PcrLogEntry, PcrLogEntryId};
-use caliptra_drivers::{ColdResetEntry4, RomVerifyConfig};
+use caliptra_drivers::{ColdResetEntry4, PcrId, RomVerifyConfig};
 use caliptra_error::CaliptraError;
 use caliptra_hw_model::{BootParams, Fuses, HwModel, InitParams, ModelError, SecurityState};
 use caliptra_image_fake_keys::{OWNER_CONFIG, VENDOR_CONFIG_KEY_1};
 use caliptra_image_gen::ImageGenerator;
 use caliptra_image_openssl::OsslCrypto;
 use caliptra_image_types::IMAGE_BYTE_SIZE;
+use openssl::hash::{Hasher, MessageDigest};
 use zerocopy::{AsBytes, FromBytes};
 pub mod helpers;
 
@@ -68,6 +69,30 @@ fn test_firmware_gt_max_size() {
     );
 }
 
+const PCR_COUNT: usize = 32;
+const PCR_ENTRY_SIZE: usize = core::mem::size_of::<PcrLogEntry>();
+
+// Checks entries for both PCR0 and PCR1. Skips checking `data` if empty.
+fn check_pcr_log_entry(
+    pcr_entry_arr: &[u8],
+    pcr_entry_index: usize,
+    entry_id: PcrLogEntryId,
+    data: &[u8],
+) {
+    let offset = pcr_entry_index * PCR_ENTRY_SIZE;
+    let entry = PcrLogEntry::read_from_prefix(pcr_entry_arr[offset..].as_bytes()).unwrap();
+
+    assert_eq!(entry.id, entry_id as u16);
+    assert_eq!(
+        entry.pcr_ids,
+        (1 << PcrId::PcrId0 as u8) | (1 << PcrId::PcrId1 as u8)
+    );
+
+    if !data.is_empty() {
+        assert_eq!(entry.measured_data(), data);
+    }
+}
+
 #[test]
 fn test_pcr_log() {
     let gen = ImageGenerator::new(OsslCrypto::default());
@@ -84,7 +109,7 @@ fn test_pcr_log() {
     pub const TEST_FMC_WITH_UART: FwId = FwId {
         crate_name: "caliptra-rom-test-fmc",
         bin_name: "caliptra-rom-test-fmc",
-        features: &["emu"],
+        features: &["emu", "interactive_test_fmc"],
         workspace_dir: None,
     };
 
@@ -123,116 +148,82 @@ fn test_pcr_log() {
 
     hw.step_until_boot_status(ColdResetComplete.into(), true);
 
-    let result = hw.mailbox_execute(0x1000_0000, &[]);
-    assert!(result.is_ok());
+    let pcr_entry_arr = hw.mailbox_execute(0x1000_0000, &[]).unwrap().unwrap();
 
-    let pcr_entry_arr = result.unwrap().unwrap();
-    let mut pcr_log_entry_offset = 0;
-    // Check PCR entry for DeviceLifecycle.
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::DeviceLifecycle as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
     let device_lifecycle = hw
         .soc_ifc()
         .cptra_security_state()
         .read()
         .device_lifecycle();
-    assert_eq!(pcr_log_entry.pcr_data[0] as u8, device_lifecycle as u8);
-
-    // Check PCR entry for DebugLocked
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::DebugLocked as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        0,
+        PcrLogEntryId::DeviceLifecycle,
+        &[device_lifecycle as u8],
+    );
+
     let debug_locked = hw.soc_ifc().cptra_security_state().read().debug_locked();
-    assert_eq!((pcr_log_entry.pcr_data[0] as u8) != 0, debug_locked);
-
-    // Check PCR entry for AntiRollbackDisabled.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::AntiRollbackDisabled as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        1,
+        PcrLogEntryId::DebugLocked,
+        &[debug_locked as u8],
+    );
+
     let anti_rollback_disable = hw.soc_ifc().fuse_anti_rollback_disable().read().dis();
-    assert_eq!(
-        (pcr_log_entry.pcr_data[0] as u8) != 0,
-        anti_rollback_disable
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        2,
+        PcrLogEntryId::AntiRollbackDisabled,
+        &[anti_rollback_disable as u8],
     );
 
-    // Check PCR entry for VendorPubKeyHash.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::VendorPubKeyHash as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
     helpers::change_dword_endianess(vendor_pubkey_digest.as_bytes_mut());
-    assert_eq!(pcr_log_entry.pcr_data, vendor_pubkey_digest);
-
-    // Check PCR entry for OwnerPubKeyHash.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::OwnerPubKeyHash as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        3,
+        PcrLogEntryId::VendorPubKeyHash,
+        vendor_pubkey_digest.as_bytes(),
+    );
+
     helpers::change_dword_endianess(owner_pubkey_digest.as_bytes_mut());
-    assert_eq!(pcr_log_entry.pcr_data, owner_pubkey_digest);
-
-    // Check PCR entry for VendorPubKeyIndex.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::EccVendorPubKeyIndex as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(
-        pcr_log_entry.pcr_data[0] as u8,
-        VENDOR_CONFIG_KEY_1.ecc_key_idx as u8
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        4,
+        PcrLogEntryId::OwnerPubKeyHash,
+        owner_pubkey_digest.as_bytes(),
     );
 
-    // Check PCR entry for FmcTci.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcTci as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-
-    // Check PCR entry for FmcSvn.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcSvn as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(pcr_log_entry.pcr_data[0] as u8, FMC_SVN as u8);
-
-    // Check PCR entry for FmcFuseSvn.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcFuseSvn as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(pcr_log_entry.pcr_data[0] as u8, 0); // anti_rollback_disable is true
-
-    // Check PCR entry for LmsVendorPubKeyIndex.
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::LmsVendorPubKeyIndex as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(
-        pcr_log_entry.pcr_data[0] as u8,
-        VENDOR_CONFIG_KEY_1.lms_key_idx as u8
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        5,
+        PcrLogEntryId::EccVendorPubKeyIndex,
+        &[VENDOR_CONFIG_KEY_1.ecc_key_idx as u8],
     );
 
-    // Check Rom verify config
-    pcr_log_entry_offset += core::mem::size_of::<PcrLogEntry>();
-    let pcr_log_entry =
-        PcrLogEntry::read_from_prefix(pcr_entry_arr[pcr_log_entry_offset..].as_bytes()).unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::RomVerifyConfig as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(
-        pcr_log_entry.pcr_data[0] as u8,
-        RomVerifyConfig::EcdsaAndLms as u8
+    check_pcr_log_entry(&pcr_entry_arr, 6, PcrLogEntryId::FmcTci, &[]);
+
+    check_pcr_log_entry(&pcr_entry_arr, 7, PcrLogEntryId::FmcSvn, &[FMC_SVN as u8]);
+
+    check_pcr_log_entry(&pcr_entry_arr, 8, PcrLogEntryId::FmcFuseSvn, &[0_u8]);
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        9,
+        PcrLogEntryId::LmsVendorPubKeyIndex,
+        &[VENDOR_CONFIG_KEY_1.lms_key_idx as u8],
+    );
+
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        10,
+        PcrLogEntryId::RomVerifyConfig,
+        &[RomVerifyConfig::EcdsaAndLms as u8],
     );
 }
 
@@ -252,15 +243,18 @@ fn test_pcr_log_fmc_fuse_svn() {
     pub const TEST_FMC_WITH_UART: FwId = FwId {
         crate_name: "caliptra-rom-test-fmc",
         bin_name: "caliptra-rom-test-fmc",
-        features: &["emu"],
+        features: &["emu", "interactive_test_fmc"],
         workspace_dir: None,
     };
 
+    const FMC_SVN: u32 = 3;
+    const FMC_FUSE_SVN: u32 = 2;
+
     let fuses = Fuses {
         anti_rollback_disable: false,
         key_manifest_pk_hash: vendor_pubkey_digest,
         owner_pk_hash: owner_pubkey_digest,
-        fmc_key_manifest_svn: 0b11, // FMC Fuse SVN = 2
+        fmc_key_manifest_svn: FMC_FUSE_SVN,
         ..Default::default()
     };
     let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
@@ -275,7 +269,6 @@ fn test_pcr_log_fmc_fuse_svn() {
     })
     .unwrap();
 
-    const FMC_SVN: u32 = 2;
     let image_options = ImageOptions {
         vendor_config: VENDOR_CONFIG_KEY_1,
         fmc_svn: FMC_SVN,
@@ -291,21 +284,163 @@ fn test_pcr_log_fmc_fuse_svn() {
 
     hw.step_until_boot_status(ColdResetComplete.into(), true);
 
-    let result = hw.mailbox_execute(0x1000_0000, &[]);
-    assert!(result.is_ok());
+    let pcr_entry_arr = hw.mailbox_execute(0x1000_0000, &[]).unwrap().unwrap();
 
-    let pcr_entry_arr = result.unwrap().unwrap();
+    check_pcr_log_entry(&pcr_entry_arr, 7, PcrLogEntryId::FmcSvn, &[FMC_SVN as u8]);
 
-    // Check PCR entry for FmcFuseSvn.
-    let pcr_log_entry = PcrLogEntry::read_from_prefix(
-        pcr_entry_arr
-            [((PcrLogEntryId::FmcFuseSvn as usize - 1) * core::mem::size_of::<PcrLogEntry>())..]
-            .as_bytes(),
-    )
+    check_pcr_log_entry(
+        &pcr_entry_arr,
+        8,
+        PcrLogEntryId::FmcFuseSvn,
+        &[FMC_FUSE_SVN as u8],
+    );
+}
+
+// Computes the PCR from the log.
+fn hash_pcr_log_entries(initial_pcr: &[u8; 48], pcr_entry_arr: &[u8], pcr_id: PcrId) -> [u8; 48] {
+    let mut offset: usize = 0;
+    let mut pcr: [u8; 48] = *initial_pcr;
+
+    assert_eq!(pcr_entry_arr.len() % PCR_ENTRY_SIZE, 0);
+
+    loop {
+        if offset == pcr_entry_arr.len() {
+            break;
+        }
+
+        let entry = PcrLogEntry::read_from_prefix(pcr_entry_arr[offset..].as_bytes()).unwrap();
+        offset += PCR_ENTRY_SIZE;
+
+        if (entry.pcr_ids & (1 << pcr_id as u8)) == 0 {
+            continue;
+        }
+
+        let mut hasher = Hasher::new(MessageDigest::sha384()).unwrap();
+        hasher.update(&pcr).unwrap();
+        hasher.update(entry.measured_data()).unwrap();
+        let digest: &[u8] = &hasher.finish().unwrap();
+
+        pcr.copy_from_slice(digest);
+    }
+
+    pcr
+}
+
+#[test]
+fn test_pcr_log_across_update_reset() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let owner_pubkey_digest = gen
+        .owner_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    pub const TEST_FMC_WITH_UART: FwId = FwId {
+        crate_name: "caliptra-rom-test-fmc",
+        bin_name: "caliptra-rom-test-fmc",
+        features: &["emu", "interactive_test_fmc"],
+        workspace_dir: None,
+    };
+
+    const FMC_SVN: u32 = 2;
+    const FMC_FUSE_SVN: u32 = 1;
+
+    let fuses = Fuses {
+        anti_rollback_disable: false,
+        fmc_key_manifest_svn: FMC_FUSE_SVN,
+        key_manifest_pk_hash: vendor_pubkey_digest,
+        owner_pk_hash: owner_pubkey_digest,
+        ..Default::default()
+    };
+    let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state: SecurityState::from(fuses.life_cycle as u32),
+            ..Default::default()
+        },
+        fuses,
+        ..Default::default()
+    })
     .unwrap();
-    assert_eq!(pcr_log_entry.id, PcrLogEntryId::FmcFuseSvn as u16);
-    assert_eq!(pcr_log_entry.pcr_ids, 1 << 0);
-    assert_eq!(pcr_log_entry.pcr_data[0] as u8, FMC_SVN as u8); // anti_rollback_disable is false
+
+    let image_options = ImageOptions {
+        vendor_config: VENDOR_CONFIG_KEY_1,
+        fmc_svn: FMC_SVN,
+        ..Default::default()
+    };
+    let image_bundle =
+        caliptra_builder::build_and_sign_image(&TEST_FMC_WITH_UART, &APP_WITH_UART, image_options)
+            .unwrap();
+
+    assert!(hw
+        .upload_firmware(&image_bundle.to_bytes().unwrap())
+        .is_ok());
+
+    hw.step_until_boot_status(ColdResetComplete.into(), true);
+
+    let pcr_entry_arr = hw.mailbox_execute(0x1000_0000, &[]).unwrap().unwrap();
+
+    // Fetch and validate PCR values against the log.
+
+    let pcrs = hw.mailbox_execute(0x1000_0006, &[]).unwrap().unwrap();
+    assert_eq!(pcrs.len(), PCR_COUNT * 48);
+
+    let mut pcr0_from_hw: [u8; 48] = pcrs[0..48].try_into().unwrap();
+    let mut pcr1_from_hw: [u8; 48] = pcrs[48..96].try_into().unwrap();
+
+    helpers::change_dword_endianess(&mut pcr0_from_hw);
+    helpers::change_dword_endianess(&mut pcr1_from_hw);
+
+    let pcr0_from_log = hash_pcr_log_entries(&[0; 48], &pcr_entry_arr, PcrId::PcrId0);
+    let pcr1_from_log = hash_pcr_log_entries(&[0; 48], &pcr_entry_arr, PcrId::PcrId1);
+
+    assert_eq!(pcr0_from_log, pcr0_from_hw);
+    assert_eq!(pcr1_from_log, pcr1_from_hw);
+
+    // Ensure all other PCRs are empty.
+    for i in 2..PCR_COUNT {
+        let offset = i * 48;
+        assert_eq!(pcrs[offset..offset + 48], [0; 48]);
+    }
+
+    // Trigger an update reset.
+    hw.mailbox_execute(0x1000_0004, &[]).unwrap();
+    hw.step_until_boot_status(UpdateResetStarted.into(), true);
+    hw.upload_firmware(&image_bundle.to_bytes().unwrap())
+        .unwrap();
+    hw.step_until_boot_status(UpdateResetComplete.into(), true);
+
+    let pcr_entry_arr = hw.mailbox_execute(0x1000_0000, &[]).unwrap().unwrap();
+
+    // Fetch and validate PCR values against the log. PCR0 should represent the
+    // latest boot, while PCR1 should represent the whole journey.
+
+    let pcrs_after_reset = hw.mailbox_execute(0x1000_0006, &[]).unwrap().unwrap();
+    assert_eq!(pcrs_after_reset.len(), PCR_COUNT * 48);
+
+    let mut new_pcr0_from_hw: [u8; 48] = pcrs_after_reset[0..48].try_into().unwrap();
+    let mut new_pcr1_from_hw: [u8; 48] = pcrs_after_reset[48..96].try_into().unwrap();
+
+    helpers::change_dword_endianess(&mut new_pcr0_from_hw);
+    helpers::change_dword_endianess(&mut new_pcr1_from_hw);
+
+    let new_pcr0_from_log = hash_pcr_log_entries(&[0; 48], &pcr_entry_arr, PcrId::PcrId0);
+    let new_pcr1_from_log = hash_pcr_log_entries(&pcr1_from_log, &pcr_entry_arr, PcrId::PcrId1);
+
+    assert_eq!(new_pcr0_from_log, new_pcr0_from_hw);
+    assert_eq!(new_pcr1_from_log, new_pcr1_from_hw);
+
+    // Also ensure PCR locks are configured correctly.
+    let reset_checks = hw.mailbox_execute(0x1000_0007, &[]).unwrap().unwrap();
+    assert_eq!(reset_checks, [0; 4]);
+
+    let pcrs_after_clear = hw.mailbox_execute(0x1000_0006, &[]).unwrap().unwrap();
+    assert_eq!(pcrs_after_clear, pcrs_after_reset);
 }
 
 #[test]
diff --git a/rom/dev/tests/test_update_reset.rs b/rom/dev/tests/test_update_reset.rs
index a3c8e676..7c217de1 100644
--- a/rom/dev/tests/test_update_reset.rs
+++ b/rom/dev/tests/test_update_reset.rs
@@ -296,6 +296,7 @@ fn test_update_reset_boot_status() {
 
     hw.step_until_boot_status(UpdateResetLoadManifestComplete.into(), false);
     hw.step_until_boot_status(UpdateResetImageVerificationComplete.into(), false);
+    hw.step_until_boot_status(UpdateResetExtendPcrComplete.into(), false);
     hw.step_until_boot_status(UpdateResetPopulateDataVaultComplete.into(), false);
     hw.step_until_boot_status(UpdateResetLoadImageComplete.into(), false);
     hw.step_until_boot_status(UpdateResetOverwriteManifestComplete.into(), false);
diff --git a/rom/dev/tools/test-fmc/src/main.rs b/rom/dev/tools/test-fmc/src/main.rs
index 6f9e1327..1e64ddbb 100644
--- a/rom/dev/tools/test-fmc/src/main.rs
+++ b/rom/dev/tools/test-fmc/src/main.rs
@@ -21,8 +21,9 @@ use caliptra_common::FirmwareHandoffTable;
 use caliptra_common::{FuseLogEntry, FuseLogEntryId};
 use caliptra_common::{PcrLogEntry, PcrLogEntryId};
 use caliptra_drivers::ColdResetEntry4::*;
-use caliptra_drivers::{DataVault, Mailbox};
+use caliptra_drivers::{DataVault, Mailbox, PcrBank, PcrId};
 use caliptra_registers::dv::DvReg;
+use caliptra_registers::pv::PvReg;
 use caliptra_x509::{Ecdsa384CertBuilder, Ecdsa384Signature, FmcAliasCertTbs, LocalDevIdCertTbs};
 use core::ptr;
 use ureg::RealMmioMut;
@@ -218,6 +219,12 @@ fn process_mailbox_command(mbox: &caliptra_registers::mbox::RegisterBlock<RealMm
         0x1000_0005 => {
             read_datavault_coldresetentry4(mbox);
         }
+        0x1000_0006 => {
+            read_pcrs(mbox);
+        }
+        0x1000_0007 => {
+            try_to_reset_pcrs(mbox);
+        }
         _ => {}
     }
 }
@@ -284,6 +291,52 @@ fn read_pcr_log(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
     mbox.status().write(|w| w.status(|w| w.data_ready()));
 }
 
+fn swap_word_bytes_inplace(words: &mut [u32]) {
+    for word in words.iter_mut() {
+        *word = word.swap_bytes()
+    }
+}
+
+fn read_pcrs(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
+    let pcr_bank = unsafe { PcrBank::new(PvReg::new()) };
+    const PCR_COUNT: usize = 32;
+    for i in 0..PCR_COUNT {
+        let pcr = pcr_bank.read_pcr(PcrId::try_from(i as u8).unwrap());
+        let mut pcr_bytes: [u32; 12] = pcr.try_into().unwrap();
+
+        swap_word_bytes_inplace(&mut pcr_bytes);
+        send_to_mailbox(mbox, pcr.as_bytes(), false);
+    }
+
+    mbox.dlen().write(|_| (48 * PCR_COUNT).try_into().unwrap());
+    mbox.status().write(|w| w.status(|w| w.data_ready()));
+}
+
+// Returns a list of u8 values, 0 on success, 1 on failure:
+//   - Whether PCR0 is locked
+//   - Whether PCR1 is locked
+//   - Whether PCR2 is unlocked
+//   - Whether PCR3 is unlocked
+fn try_to_reset_pcrs(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
+    let mut pcr_bank = unsafe { PcrBank::new(PvReg::new()) };
+
+    let res0 = pcr_bank.erase_pcr(PcrId::PcrId0);
+    let res1 = pcr_bank.erase_pcr(PcrId::PcrId1);
+    let res2 = pcr_bank.erase_pcr(PcrId::PcrId2);
+    let res3 = pcr_bank.erase_pcr(PcrId::PcrId3);
+
+    let ret_vals: [u8; 4] = [
+        if res0.is_err() { 0 } else { 1 },
+        if res1.is_err() { 0 } else { 1 },
+        if res2.is_ok() { 0 } else { 1 },
+        if res3.is_ok() { 0 } else { 1 },
+    ];
+
+    send_to_mailbox(mbox, &ret_vals, false);
+    mbox.dlen().write(|_| ret_vals.len().try_into().unwrap());
+    mbox.status().write(|w| w.status(|w| w.data_ready()));
+}
+
 fn read_fuse_log(mbox: &caliptra_registers::mbox::RegisterBlock<RealMmioMut>) {
     let mut fuse_entry_count = 0;
     loop {