Repository: chipsalliance/caliptra-sw
Pull request #259 (2023-05-11 04:29:17)

[feat] ROM PCR logging
This change logs a PCR entry to DCCM when a PCR is extended.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index f0c9cca2..421472b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -446,6 +446,7 @@ version = "0.1.0"
 dependencies = [
  "caliptra-cpu",
  "caliptra-drivers",
+ "caliptra-registers",
  "caliptra-x509",
  "caliptra_common",
  "cfg-if",
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 227774ca..3d921038 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -8,6 +8,8 @@ pub mod hand_off;
 #[macro_use]
 pub mod printer;
 pub mod boot_status;
+pub mod pcr;
+
 ///merge imports
 pub use hand_off::{
     print_fht, report_handoff_error_and_halt, DataStore, DataVaultRegister, FirmwareHandoffTable,
@@ -15,4 +17,5 @@ pub use hand_off::{
 };
 
 pub use boot_status::RomBootStatus;
+pub use pcr::{PcrLogEntry, PcrLogEntryId};
 pub use printer::MutablePrinter;
diff --git a/common/src/pcr.rs b/common/src/pcr.rs
new file mode 100644
index 00000000..db76065a
--- /dev/null
+++ b/common/src/pcr.rs
@@ -0,0 +1,66 @@
+/*++
+Licensed under the Apache-2.0 license.
+
+File Name:
+
+    pcr.rs
+
+Abstract:
+
+    PCR-related types.
+
+--*/
+
+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 {
+    Invalid = 0,
+    DeviceLifecycle = 1,      // data size = 1 byte
+    DebugLocked = 2,          // data size = 1 byte
+    AntiRollbackDisabled = 3, // data size = 1 byte
+    VendorPubKeyHash = 4,     // data size = 48 bytes
+    OwnerPubKeyHash = 5,      // data size = 48 bytes
+    VendorPubKeyIndex = 6,    // data size = 1 byte
+    FmcTci = 7,               // data size = 48 bytes
+    FmcSvn = 8,               // data size = 1 byte
+}
+
+impl From<u16> for PcrLogEntryId {
+    /// Converts to this type from the input type.
+    fn from(id: u16) -> PcrLogEntryId {
+        match id {
+            1 => PcrLogEntryId::DeviceLifecycle,
+            2 => PcrLogEntryId::DebugLocked,
+            3 => PcrLogEntryId::AntiRollbackDisabled,
+            4 => PcrLogEntryId::VendorPubKeyHash,
+            5 => PcrLogEntryId::OwnerPubKeyHash,
+            6 => PcrLogEntryId::VendorPubKeyIndex,
+            7 => PcrLogEntryId::FmcTci,
+            8 => PcrLogEntryId::FmcSvn,
+            _ => PcrLogEntryId::Invalid,
+        }
+    }
+}
+
+/// PCR log entry
+#[repr(C)]
+#[derive(AsBytes, FromBytes, Default, Debug)]
+pub struct PcrLogEntry {
+    /// Entry identifier
+    pub id: u16,
+
+    /// PCR to which the data is being extended to.
+    pub pcr_id: u16,
+
+    // PCR data
+    pub pcr_data: [u32; 12],
+
+    pub reserved: [u8; 4],
+}
diff --git a/rom/dev/src/pcr.rs b/rom/dev/src/pcr.rs
index 6dd9ea15..488872b6 100644
--- a/rom/dev/src/pcr.rs
+++ b/rom/dev/src/pcr.rs
@@ -22,7 +22,23 @@ Note:
 --*/
 
 use crate::rom_env::RomEnv;
+use caliptra_common::{PcrLogEntry, PcrLogEntryId};
 use caliptra_drivers::{Array4x12, CaliptraResult, PcrId};
+use caliptra_error::caliptra_err_def;
+use zerocopy::AsBytes;
+
+caliptra_err_def! {
+    RomGlobal,
+    GlobalErr
+    {
+        PcrLogInvalidEntryId = 0x4,
+        PcrLogUnsupportedDataLength = 0x5,
+    }
+}
+
+extern "C" {
+    static mut PCR_LOG_ORG: u8;
+}
 
 /// Extend PCR0
 ///
@@ -39,25 +55,89 @@ pub fn extend_pcr0(env: &RomEnv) -> CaliptraResult<()> {
     // Lock the PCR from clear
     pcr_bank.map(|p| p.set_pcr_lock(caliptra_drivers::PcrId::PcrId0));
 
-    let extend = |data: Array4x12| {
+    let extend = |data: Array4x12, pcr_entry_id: PcrLogEntryId| {
         let bytes: &[u8; 48] = &data.into();
-        sha.map(|s| pcr_bank.map(|p| p.extend_pcr(PcrId::PcrId0, s, bytes)))
+        sha.map(|s| pcr_bank.map(|p| p.extend_pcr(PcrId::PcrId0, s, bytes)))?;
+        log_pcr(pcr_entry_id, PcrId::PcrId0, bytes)
     };
 
-    let extend_u8 = |data: u8| {
+    let extend_u8 = |data: u8, pcr_entry_id: PcrLogEntryId| {
         let bytes = &data.to_le_bytes();
-        sha.map(|s| pcr_bank.map(|p| p.extend_pcr(PcrId::PcrId0, s, bytes)))
+        sha.map(|s| pcr_bank.map(|p| p.extend_pcr(PcrId::PcrId0, s, bytes)))?;
+        log_pcr(pcr_entry_id, PcrId::PcrId0, bytes)
     };
 
-    extend_u8(env.dev_state().map(|d| d.lifecycle()) as u8)?;
-    extend_u8(env.dev_state().map(|d| d.debug_locked()) as u8)?;
-    extend_u8(env.fuse_bank().map(|f| f.anti_rollback_disable()) as u8)?;
-    extend(env.fuse_bank().map(|f| f.vendor_pub_key_hash()))?;
-    extend(env.data_vault().map(|d| d.owner_pk_hash()))?;
-    extend_u8(env.data_vault().map(|d| d.vendor_pk_index()) as u8)?;
-    extend(env.data_vault().map(|d| d.fmc_tci()))?;
-    extend_u8(env.data_vault().map(|d| d.fmc_svn()) as u8)?;
+    extend_u8(
+        env.dev_state().map(|d| d.lifecycle()) as u8,
+        PcrLogEntryId::DeviceLifecycle,
+    )?;
+    extend_u8(
+        env.dev_state().map(|d| d.debug_locked()) as u8,
+        PcrLogEntryId::DebugLocked,
+    )?;
+    extend_u8(
+        env.fuse_bank().map(|f| f.anti_rollback_disable()) as u8,
+        PcrLogEntryId::AntiRollbackDisabled,
+    )?;
+    extend(
+        env.fuse_bank().map(|f| f.vendor_pub_key_hash()),
+        PcrLogEntryId::VendorPubKeyHash,
+    )?;
+    extend(
+        env.data_vault().map(|d| d.owner_pk_hash()),
+        PcrLogEntryId::OwnerPubKeyHash,
+    )?;
+    extend_u8(
+        env.data_vault().map(|d| d.vendor_pk_index()) as u8,
+        PcrLogEntryId::VendorPubKeyIndex,
+    )?;
+    extend(env.data_vault().map(|d| d.fmc_tci()), PcrLogEntryId::FmcTci)?;
+    extend_u8(
+        env.data_vault().map(|d| d.fmc_svn()) as u8,
+        PcrLogEntryId::FmcSvn,
+    )?;
 
     // TODO: Check PCR0 != 0
     Ok(())
 }
+
+/// Log PCR data
+///
+/// # Arguments
+/// * `pcr_entry_id` - PCR log entry ID
+/// * `pcr_id` - PCR ID
+/// * `data` - PCR data
+///
+/// # Return Value
+/// * `Ok(())` - Success
+/// * `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<()> {
+    if pcr_entry_id == PcrLogEntryId::Invalid {
+        raise_err!(PcrLogInvalidEntryId);
+    }
+
+    if data.len() > 48 {
+        raise_err!(PcrLogUnsupportedDataLength);
+    }
+
+    // Create a PCR log entry
+    let mut pcr_log_entry = PcrLogEntry {
+        id: pcr_entry_id as u16,
+        pcr_id: pcr_id as u16,
+        ..Default::default()
+    };
+    pcr_log_entry.pcr_data.as_bytes_mut()[..data.len()].copy_from_slice(data);
+
+    let dst = unsafe {
+        let offset = core::mem::size_of::<PcrLogEntry>() * (pcr_entry_id as usize - 1);
+        let ptr = (&mut PCR_LOG_ORG as *mut u8).add(offset);
+        core::slice::from_raw_parts_mut(ptr, core::mem::size_of::<PcrLogEntry>())
+    };
+
+    // Store log entry
+    dst.copy_from_slice(pcr_log_entry.as_bytes());
+
+    Ok(())
+}


Test patch:
diff --git a/rom/dev/tests/helpers.rs b/rom/dev/tests/helpers.rs
index 2b200d03..4ba1b42a 100644
--- a/rom/dev/tests/helpers.rs
+++ b/rom/dev/tests/helpers.rs
@@ -56,6 +56,13 @@ pub fn get_csr(hw: &mut DefaultHwModel) -> Result<Vec<u8>, ModelError> {
     Ok(result)
 }
 
+pub fn change_dword_endianess(data: &mut [u8]) {
+    for idx in (0..data.len()).step_by(4) {
+        data.swap(idx, idx + 3);
+        data.swap(idx + 1, idx + 2);
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/rom/dev/tests/test_fmcalias_derivation.rs b/rom/dev/tests/test_fmcalias_derivation.rs
index 73d00dc6..a3e8ab18 100644
--- a/rom/dev/tests/test_fmcalias_derivation.rs
+++ b/rom/dev/tests/test_fmcalias_derivation.rs
@@ -1,8 +1,14 @@
 // Licensed under the Apache-2.0 license
 
-use caliptra_builder::ImageOptions;
-use caliptra_hw_model::{Fuses, HwModel, ModelError};
+use caliptra_builder::{FwId, ImageOptions, APP_WITH_UART, ROM_WITH_UART};
+use caliptra_common::PcrLogEntry;
+use caliptra_common::PcrLogEntryId::*;
+use caliptra_hw_model::{BootParams, Fuses, HwModel, InitParams, ModelError, SecurityState};
+use caliptra_image_fake_keys::VENDOR_CONFIG_KEY_1;
+use caliptra_image_gen::ImageGenerator;
+use caliptra_image_openssl::OsslCrypto;
 use caliptra_image_types::IMAGE_BYTE_SIZE;
+use zerocopy::{AsBytes, FromBytes};
 
 pub mod helpers;
 
@@ -53,3 +59,140 @@ fn test_firmware_gt_max_size() {
         INVALID_IMAGE_SIZE
     );
 }
+
+#[test]
+fn test_pcr_log() {
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let (_hw, image_bundle) =
+        helpers::build_hw_model_and_image_bundle(Fuses::default(), ImageOptions::default());
+    let mut vendor_pubkey_digest = gen
+        .vendor_pubkey_digest(&image_bundle.manifest.preamble)
+        .unwrap();
+
+    let mut 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"],
+    };
+
+    let fuses = Fuses {
+        anti_rollback_disable: true,
+        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,
+        fw_image: None,
+    })
+    .unwrap();
+
+    const FMC_SVN: u32 = 1;
+    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_output_contains("[exit] Launching FMC")
+        .unwrap();
+
+    let result = hw.mailbox_execute(0x1000_0000, &[]);
+    assert!(result.is_ok());
+
+    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, DeviceLifecycle as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 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, DebugLocked as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 0);
+    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, AntiRollbackDisabled as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 0);
+    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 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, VendorPubKeyHash as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 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, OwnerPubKeyHash as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 0);
+    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, VendorPubKeyIndex as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 0);
+    assert_eq!(
+        pcr_log_entry.pcr_data[0] as u8,
+        VENDOR_CONFIG_KEY_1.ecc_key_idx as u8
+    );
+
+    // 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, FmcTci as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 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, FmcSvn as u16);
+    assert_eq!(pcr_log_entry.pcr_id, 0);
+    assert_eq!(pcr_log_entry.pcr_data[0] as u8, FMC_SVN as u8);
+}
diff --git a/rom/dev/tools/test-fmc/Cargo.toml b/rom/dev/tools/test-fmc/Cargo.toml
index 338f78a3..f137cbf1 100644
--- a/rom/dev/tools/test-fmc/Cargo.toml
+++ b/rom/dev/tools/test-fmc/Cargo.toml
@@ -12,6 +12,7 @@ ufmt = "0.2.0"
 zerocopy = "0.6.1"
 caliptra-x509 = { version = "0.1.0", path = "../../../../x509" , default-features = false }
 caliptra-cpu = { version = "0.1.0", path = "../../../../cpu" }
+caliptra-registers = { path = "../../../../registers" }
 
 [build-dependencies]
 cfg-if = "1.0.0"
diff --git a/rom/dev/tools/test-fmc/src/main.rs b/rom/dev/tools/test-fmc/src/main.rs
index aa2a957a..7e6b747a 100644
--- a/rom/dev/tools/test-fmc/src/main.rs
+++ b/rom/dev/tools/test-fmc/src/main.rs
@@ -17,12 +17,15 @@ Abstract:
 extern "C" {
     static mut LDEVID_TBS_ORG: u8;
     static mut FMCALIAS_TBS_ORG: u8;
+    static mut PCR_LOG_ORG: u8;
 }
 
 use caliptra_common::FirmwareHandoffTable;
+use caliptra_common::{PcrLogEntry, PcrLogEntryId};
 use caliptra_drivers::DataVault;
 use caliptra_drivers::Mailbox;
 use caliptra_x509::{Ecdsa384CertBuilder, Ecdsa384Signature, FmcAliasCertTbs, LocalDevIdCertTbs};
+use zerocopy::AsBytes;
 use zerocopy::FromBytes;
 
 #[cfg(not(feature = "std"))]
@@ -57,6 +60,8 @@ pub extern "C" fn fmc_entry() -> ! {
 
     create_certs();
 
+    process_mailbox_commands();
+
     caliptra_drivers::ExitCtrl::exit(0)
 }
 
@@ -178,3 +183,73 @@ fn copy_tbs(tbs: &mut [u8], ldevid_tbs: bool) {
     };
     tbs.copy_from_slice(src);
 }
+
+fn get_pcr_entry(entry_index: usize) -> PcrLogEntry {
+    // Copy the pcr log entry from DCCM
+    let mut pcr_entry: [u8; core::mem::size_of::<PcrLogEntry>()] =
+        [0u8; core::mem::size_of::<PcrLogEntry>()];
+
+    let src = unsafe {
+        let offset = core::mem::size_of::<PcrLogEntry>() * entry_index;
+        let ptr = (&mut PCR_LOG_ORG as *mut u8).add(offset);
+        core::slice::from_raw_parts_mut(ptr, core::mem::size_of::<PcrLogEntry>())
+    };
+
+    pcr_entry.copy_from_slice(src);
+    PcrLogEntry::read_from_prefix(pcr_entry.as_bytes()).unwrap()
+}
+
+fn process_mailbox_commands() {
+    let mbox = caliptra_registers::mbox::RegisterBlock::mbox_csr();
+
+    let cmd = mbox.cmd().read();
+    cprintln!("[fmc] Received command: 0x{:08X}", cmd);
+    match cmd {
+        0x1000_0000 => {
+            read_pcr_log(&mbox);
+        }
+        0x1000_0001 => {
+            // TODO: Generate certs
+        }
+        _ => {
+            // No-op
+        }
+    }
+}
+
+fn read_pcr_log(mbox: &caliptra_registers::mbox::RegisterBlock) {
+    let mut pcr_entry_count = 0;
+    loop {
+        let pcr_entry = get_pcr_entry(pcr_entry_count);
+        if PcrLogEntryId::from(pcr_entry.id) == PcrLogEntryId::Invalid {
+            break;
+        }
+
+        pcr_entry_count += 1;
+        let pcr_entry_bytes = pcr_entry.as_bytes();
+        let word_size = core::mem::size_of::<u32>();
+        let remainder = pcr_entry_bytes.len() % word_size;
+        let n = pcr_entry_bytes.len() - remainder;
+
+        for idx in (0..n).step_by(word_size) {
+            mbox.datain().write(|_| {
+                u32::from_le_bytes(pcr_entry_bytes[idx..idx + word_size].try_into().unwrap())
+            });
+        }
+
+        if remainder > 0 {
+            let mut last_word = pcr_entry_bytes[n] as u32;
+            for idx in 1..remainder {
+                last_word |= (pcr_entry_bytes[n + idx] as u32) << (idx << 3);
+            }
+            mbox.datain().write(|_| last_word);
+        }
+    }
+
+    mbox.dlen().write(|_| {
+        (core::mem::size_of::<PcrLogEntry>() * pcr_entry_count)
+            .try_into()
+            .unwrap()
+    });
+    mbox.status().write(|w| w.status(|w| w.data_ready()));
+}