Repository: chipsalliance/caliptra-sw
Pull request #383 (2023-06-14 00:19:44)

Make sw-emulator DOE peripiheral consistent with RTL, and add bit-exact DOE test assertions
Reviewers: For context, I strongly recommend looking at each commit in this PR separately.

This PR does everything necessary to implement a DOE driver test that works on both verilator and sw-emulator and can deterministically validate that key-vault values written by DOE match the expected bits exactly, with both debug_locked=false and debug_locked=true.

As part of generating test vectors, I've included a standalone implementation (`DoeTestVectors::generate()`) of the cryptographic operations necessary to generate the expected DOE test's HMAC results from fuse values and silicon secrets, using only openssl. 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 41de954d..761044fd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -164,11 +164,14 @@ dependencies = [
  "bitfield",
  "bitflags 2.0.2",
  "caliptra-builder",
+ "caliptra-drivers-test-bin",
  "caliptra-error",
  "caliptra-hw-model",
+ "caliptra-hw-model-types",
  "caliptra-lms-types",
  "caliptra-registers",
  "cfg-if",
+ "openssl",
  "ureg",
  "zerocopy",
 ]
@@ -178,6 +181,7 @@ name = "caliptra-drivers-test-bin"
 version = "0.1.0"
 dependencies = [
  "caliptra-drivers",
+ "caliptra-error",
  "caliptra-kat",
  "caliptra-lms-types",
  "caliptra-registers",
diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml
index ed35b5be..8c8fae3f 100644
--- a/drivers/Cargo.toml
+++ b/drivers/Cargo.toml
@@ -23,5 +23,8 @@ emu = []
 verilator = ["caliptra-hw-model/verilator"]
 
 [dev-dependencies]
+caliptra-drivers-test-bin = { path = "test-fw" }
 caliptra-hw-model = { path = "../hw-model" }
+caliptra-hw-model-types = { path = "../hw-model/types" }
 caliptra-builder = { path = "../builder" }
+openssl = "0.10"
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index fe05940a..6a301206 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -8,6 +8,7 @@ use std::{
 };
 
 use caliptra_emu_bus::Bus;
+use caliptra_hw_model_types::DEFAULT_CPTRA_OBF_KEY;
 use zerocopy::{AsBytes, LayoutVerified, Unalign};
 
 use caliptra_registers::mbox;
@@ -96,6 +97,9 @@ pub struct InitParams<'a> {
 
     pub security_state: SecurityState,
 
+    // The silicon obfuscation key passed to caliptra_top.
+    pub cptra_obf_key: [u32; 8],
+
     pub trng_nibbles: Box<dyn Iterator<Item = u8>>,
 }
 
@@ -114,6 +118,7 @@ impl<'a> Default for InitParams<'a> {
             log_writer: Box::new(stdout()),
             security_state: *SecurityState::default()
                 .set_device_lifecycle(DeviceLifecycle::Unprovisioned),
+            cptra_obf_key: DEFAULT_CPTRA_OBF_KEY,
             trng_nibbles: rng,
         }
     }
diff --git a/hw-model/src/model_emulated.rs b/hw-model/src/model_emulated.rs
index d1c8971c..2aaec79f 100644
--- a/hw-model/src/model_emulated.rs
+++ b/hw-model/src/model_emulated.rs
@@ -87,6 +87,7 @@ impl crate::HwModel for ModelEmulated {
                 cpu_enabled_cloned.set(true);
             }),
             security_state: params.security_state,
+            cptra_obf_key: params.cptra_obf_key,
             ..CaliptraRootBusArgs::default()
         };
         let mut root_bus = CaliptraRootBus::new(&clock, bus_args);
diff --git a/hw-model/src/model_verilated.rs b/hw-model/src/model_verilated.rs
index da12b56d..333aa180 100644
--- a/hw-model/src/model_verilated.rs
+++ b/hw-model/src/model_verilated.rs
@@ -141,6 +141,7 @@ impl crate::HwModel for ModelVerilated {
         let mut v = CaliptraVerilated::with_callbacks(
             caliptra_verilated::InitArgs {
                 security_state: u32::from(params.security_state),
+                cptra_obf_key: params.cptra_obf_key,
             },
             generic_load_cb,
             ahb_cb,
diff --git a/hw-model/types/src/lib.rs b/hw-model/types/src/lib.rs
index 0ccc81c1..6cd4b485 100644
--- a/hw-model/types/src/lib.rs
+++ b/hw-model/types/src/lib.rs
@@ -1,5 +1,23 @@
 // Licensed under the Apache-2.0 license
 
+// Rationale behind this choice
+//
+// * The constant should be easily recognizable in waveforms and debug logs
+// * Every word must be different to ensure that a "stuck word" bug is noticed.
+// * Each byte in a word must be unique to ensure an endianness bug is noticed.
+pub const DEFAULT_UDS_SEED: [u32; 12] = [
+    0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617, 0x18191a1b, 0x1c1d1e1f,
+    0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f,
+];
+
+pub const DEFAULT_FIELD_ENTROPY: [u32; 8] = [
+    0x80818283, 0x84858687, 0x88898a8b, 0x8c8d8e8f, 0x90919293, 0x94959697, 0x98999a9b, 0x9c9d9e9f,
+];
+
+pub const DEFAULT_CPTRA_OBF_KEY: [u32; 8] = [
+    0xa0a1a2a3, 0xb0b1b2b3, 0xc0c1c2c3, 0xd0d1d2d3, 0xe0e1e2e3, 0xf0f1f2f3, 0xa4a5a6a7, 0xb4b5b6b7,
+];
+
 // Based on device_lifecycle_e from RTL
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub enum DeviceLifecycle {
@@ -133,7 +151,6 @@ impl TryFrom<u32> for U4 {
     }
 }
 
-#[derive(Default)]
 pub struct Fuses {
     pub uds_seed: [u32; 12],
     pub field_entropy: [u32; 8],
@@ -147,6 +164,23 @@ pub struct Fuses {
     pub idevid_manuf_hsm_id: [u32; 4],
     pub life_cycle: DeviceLifecycle,
 }
+impl Default for Fuses {
+    fn default() -> Self {
+        Self {
+            uds_seed: DEFAULT_UDS_SEED,
+            field_entropy: DEFAULT_FIELD_ENTROPY,
+            key_manifest_pk_hash: Default::default(),
+            key_manifest_pk_hash_mask: Default::default(),
+            owner_pk_hash: Default::default(),
+            fmc_key_manifest_svn: Default::default(),
+            runtime_svn: Default::default(),
+            anti_rollback_disable: Default::default(),
+            idevid_cert_attr: Default::default(),
+            idevid_manuf_hsm_id: Default::default(),
+            life_cycle: Default::default(),
+        }
+    }
+}
 
 #[cfg(test)]
 mod test {
diff --git a/sw-emulator/lib/periph/src/doe.rs b/sw-emulator/lib/periph/src/doe.rs
index 80b756c5..fdfe1afb 100644
--- a/sw-emulator/lib/periph/src/doe.rs
+++ b/sw-emulator/lib/periph/src/doe.rs
@@ -12,6 +12,7 @@ Abstract:
 
 --*/
 
+use crate::helpers::bytes_swap_word_endian;
 use crate::{KeyVault, SocRegistersInternal};
 use caliptra_emu_bus::{
     ActionHandle, BusError, Clock, ReadOnlyRegister, ReadWriteMemory, ReadWriteRegister, Timer,
@@ -28,8 +29,8 @@ const DOE_IV_SIZE: usize = 16;
 /// The number of CPU clock cycles it takes to perform the hash update action.
 const DOE_OP_TICKS: u64 = 1000;
 
-// hmac_block_dest_valid
-const DOE_KEY_USAGE: u32 = 0x2;
+// hmac_key_dest_valid | hmac_block_dest_valid
+const DOE_KEY_USAGE: u32 = 0x3;
 
 register_bitfields! [
     u32,
@@ -179,7 +180,7 @@ impl Doe {
             &mut plain_uds[..cipher_uds.len()],
         );
         self.key_vault
-            .write_key(key_id, &plain_uds, DOE_KEY_USAGE)
+            .write_key(key_id, &bytes_swap_word_endian(&plain_uds), DOE_KEY_USAGE)
             .unwrap();
     }
 
@@ -189,8 +190,8 @@ impl Doe {
     ///
     /// * `key_id` - Key index to store the field entropy
     fn unscramble_fe(&mut self, key_id: u32) {
+        let mut plain_fe = [0_u8; 32];
         let cipher_fe = self.soc_reg.field_entropy();
-        let mut plain_fe = [0u8; 48];
         Aes256Cbc::decrypt(
             &self.soc_reg.doe_key(),
             self.iv.data(),
@@ -198,7 +199,7 @@ impl Doe {
             &mut plain_fe[..cipher_fe.len()],
         );
         self.key_vault
-            .write_key(key_id, &plain_fe, DOE_KEY_USAGE)
+            .write_key(key_id, &bytes_swap_word_endian(&plain_fe), DOE_KEY_USAGE)
             .unwrap();
     }
 
@@ -215,6 +216,7 @@ mod tests {
     use caliptra_emu_bus::Bus;
     use caliptra_emu_crypto::EndianessTransform;
     use caliptra_emu_types::RvAddr;
+    use caliptra_hw_model_types::SecurityState;
     use tock_registers::registers::InMemoryRegister;
 
     const OFFSET_IV: RvAddr = 0;
@@ -238,10 +240,10 @@ mod tests {
         iv.to_big_endian();
 
         const PLAIN_TEXT_UDS: [u8; 48] = [
-            0x6B, 0xC1, 0xBE, 0xE2, 0x2E, 0x40, 0x9F, 0x96, 0xE9, 0x3D, 0x7E, 0x11, 0x73, 0x93,
-            0x17, 0x2A, 0xAE, 0x2D, 0x8A, 0x57, 0x1E, 0x3, 0xAC, 0x9C, 0x9E, 0xB7, 0x6F, 0xAC,
-            0x45, 0xAF, 0x8E, 0x51, 0x30, 0xC8, 0x1C, 0x46, 0xA3, 0x5C, 0xE4, 0x11, 0xE5, 0xFB,
-            0xC1, 0x19, 0x1A, 0xA, 0x52, 0xEF,
+            0xE2, 0xBE, 0xC1, 0x6B, 0x96, 0x9F, 0x40, 0x2E, 0x11, 0x7E, 0x3D, 0xE9, 0x2A, 0x17,
+            0x93, 0x73, 0x57, 0x8A, 0x2D, 0xAE, 0x9C, 0xAC, 0x03, 0x1E, 0xAC, 0x6F, 0xB7, 0x9E,
+            0x51, 0x8E, 0xAF, 0x45, 0x46, 0x1C, 0xC8, 0x30, 0x11, 0xE4, 0x5C, 0xA3, 0x19, 0xC1,
+            0xFB, 0xE5, 0xEF, 0x52, 0x0A, 0x1A,
         ];
 
         let clock = Clock::new();
@@ -250,7 +252,10 @@ mod tests {
             &clock,
             MailboxInternal::new(MailboxRam::new()),
             Iccm::new(&clock),
-            CaliptraRootBusArgs::default(),
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(true),
+                ..CaliptraRootBusArgs::default()
+            },
         );
         let mut doe = Doe::new(&clock, key_vault.clone(), soc_reg);
 
@@ -284,32 +289,34 @@ mod tests {
             clock.increment_and_process_timer_actions(1, &mut doe);
         }
 
-        let mut key_usage = KeyUsage::default();
-        key_usage.set_hmac_data(true);
+        let mut ku_hmac_data = KeyUsage::default();
+        ku_hmac_data.set_hmac_data(true);
 
-        assert_eq!(
-            key_vault.read_key(2, key_usage).unwrap()[..48],
-            PLAIN_TEXT_UDS
-        );
+        let mut ku_hmac_key = KeyUsage::default();
+        ku_hmac_key.set_hmac_key(true);
+
+        assert_eq!(key_vault.read_key(2, ku_hmac_data).unwrap(), PLAIN_TEXT_UDS);
+        assert_eq!(key_vault.read_key(2, ku_hmac_key).unwrap(), PLAIN_TEXT_UDS);
     }
 
     #[test]
     fn test_deobfuscate_fe() {
-        const PLAIN_TEXT_FE: [u8; 64] = [
-            0xC6, 0x10, 0x65, 0x4D, 0xB4, 0xED, 0xA8, 0x53, 0xCF, 0x54, 0x6D, 0xEF, 0x52, 0x4E,
-            0xC1, 0x5F, 0x39, 0xEF, 0x9A, 0xB2, 0x4B, 0x12, 0x57, 0xAC, 0x30, 0xAB, 0x92, 0x10,
-            0xAD, 0xB1, 0x3E, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        const PLAIN_TEXT_FE: [u8; 48] = [
+            0x4D, 0x65, 0x10, 0xC6, 0x53, 0xA8, 0xED, 0xB4, 0xEF, 0x6D, 0x54, 0xCF, 0x5F, 0xC1,
+            0x4E, 0x52, 0xB2, 0x9A, 0xEF, 0x39, 0xAC, 0x57, 0x12, 0x4B, 0x10, 0x92, 0xAB, 0x30,
+            0xA0, 0x3E, 0xB1, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
         ];
-
         let clock = Clock::new();
         let key_vault = KeyVault::new();
         let soc_reg = SocRegistersInternal::new(
             &clock,
             MailboxInternal::new(MailboxRam::new()),
             Iccm::new(&clock),
-            CaliptraRootBusArgs::default(),
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(true),
+                ..CaliptraRootBusArgs::default()
+            },
         );
         let mut doe = Doe::new(&clock, key_vault.clone(), soc_reg);
 
@@ -346,13 +353,14 @@ mod tests {
             clock.increment_and_process_timer_actions(1, &mut doe);
         }
 
-        let mut key_usage = KeyUsage::default();
-        key_usage.set_hmac_data(true);
+        let mut ku_hmac_data = KeyUsage::default();
+        ku_hmac_data.set_hmac_data(true);
 
-        assert_eq!(
-            key_vault.read_key(3, key_usage).unwrap(),
-            PLAIN_TEXT_FE[..KeyVault::KEY_SIZE]
-        );
+        let mut ku_hmac_key = KeyUsage::default();
+        ku_hmac_key.set_hmac_key(true);
+
+        assert_eq!(key_vault.read_key(3, ku_hmac_data).unwrap(), PLAIN_TEXT_FE);
+        assert_eq!(key_vault.read_key(3, ku_hmac_key).unwrap(), PLAIN_TEXT_FE);
     }
 
     #[test]
@@ -366,7 +374,10 @@ mod tests {
             &clock,
             MailboxInternal::new(MailboxRam::new()),
             Iccm::new(&clock),
-            CaliptraRootBusArgs::default(),
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(true),
+                ..CaliptraRootBusArgs::default()
+            },
         );
         let mut doe = Doe::new(&clock, key_vault, soc_reg.clone());
         assert_ne!(soc_reg.uds(), expected_uds);
diff --git a/sw-emulator/lib/periph/src/helpers.rs b/sw-emulator/lib/periph/src/helpers.rs
index 7095bb5e..5bd26bf5 100644
--- a/sw-emulator/lib/periph/src/helpers.rs
+++ b/sw-emulator/lib/periph/src/helpers.rs
@@ -66,6 +66,14 @@ pub fn words_from_bytes_le<A: U32ArrayBytes>(arr: &A) -> A::WordArray {
     result
 }
 
+pub fn words_from_bytes_be<A: U32ArrayBytes>(arr: &A) -> A::WordArray {
+    let mut result = A::WordArray::default();
+    for i in 0..result.as_mut().len() {
+        result.as_mut()[i] = u32::from_be_bytes(arr.as_ref()[i * 4..][..4].try_into().unwrap())
+    }
+    result
+}
+
 pub fn bytes_from_words_le<A: U32Array>(arr: &A) -> A::ByteArray {
     let mut result = A::default_result();
     for i in 0..arr.as_ref().len() {
@@ -74,6 +82,23 @@ pub fn bytes_from_words_le<A: U32Array>(arr: &A) -> A::ByteArray {
     result
 }
 
+pub fn bytes_from_words_be<A: U32Array>(arr: &A) -> A::ByteArray {
+    let mut result = A::default_result();
+    for i in 0..arr.as_ref().len() {
+        result.as_mut()[i * 4..][..4].copy_from_slice(&arr.as_ref()[i].to_be_bytes());
+    }
+    result
+}
+
+pub fn bytes_swap_word_endian<A: U32ArrayBytes>(
+    arr: &A,
+) -> <<A as U32ArrayBytes>::WordArray as U32Array>::ByteArray
+where
+    <A as U32ArrayBytes>::WordArray: U32Array,
+{
+    bytes_from_words_le(&words_from_bytes_be(arr))
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -105,6 +130,33 @@ mod tests {
         );
     }
 
+    #[test]
+    pub fn test_words_from_bytes_be() {
+        assert_eq!(
+            [
+                0x967945eb, 0x37c5cd70, 0x6f4f6895, 0x3e0a32de, 0x488d5abe, 0xe13f5cd2, 0xda1d0547,
+                0x611dcb2f, 0xffabba5a, 0x0ec341d0, 0x55b9181a, 0xd4957411,
+            ],
+            words_from_bytes_be(&[
+                0x96, 0x79, 0x45, 0xeb, 0x37, 0xc5, 0xcd, 0x70, 0x6f, 0x4f, 0x68, 0x95, 0x3e, 0x0a,
+                0x32, 0xde, 0x48, 0x8d, 0x5a, 0xbe, 0xe1, 0x3f, 0x5c, 0xd2, 0xda, 0x1d, 0x05, 0x47,
+                0x61, 0x1d, 0xcb, 0x2f, 0xff, 0xab, 0xba, 0x5a, 0x0e, 0xc3, 0x41, 0xd0, 0x55, 0xb9,
+                0x18, 0x1a, 0xd4, 0x95, 0x74, 0x11
+            ])
+        );
+        assert_eq!(
+            [
+                0xa53a536d, 0x556906b8, 0x8452d5b1, 0xe8ad4ae4, 0xd2d49a27, 0x61e69dde, 0x77a0fb13,
+                0xfd6e6a07,
+            ],
+            words_from_bytes_be(&[
+                0xa5, 0x3a, 0x53, 0x6d, 0x55, 0x69, 0x06, 0xb8, 0x84, 0x52, 0xd5, 0xb1, 0xe8, 0xad,
+                0x4a, 0xe4, 0xd2, 0xd4, 0x9a, 0x27, 0x61, 0xe6, 0x9d, 0xde, 0x77, 0xa0, 0xfb, 0x13,
+                0xfd, 0x6e, 0x6a, 0x7
+            ])
+        );
+    }
+
     #[test]
     pub fn test_bytes_from_words_le() {
         assert_eq!(
@@ -131,4 +183,61 @@ mod tests {
             ])
         );
     }
+
+    #[test]
+    pub fn test_bytes_from_words_be() {
+        assert_eq!(
+            [
+                0xeb, 0x45, 0x79, 0x96, 0x70, 0xcd, 0xc5, 0x37, 0x95, 0x68, 0x4f, 0x6f, 0xde, 0x32,
+                0x0a, 0x3e, 0xbe, 0x5a, 0x8d, 0x48, 0xd2, 0x5c, 0x3f, 0xe1, 0x47, 0x05, 0x1d, 0xda,
+                0x2f, 0xcb, 0x1d, 0x61, 0x5a, 0xba, 0xab, 0xff, 0xd0, 0x41, 0xc3, 0x0e, 0x1a, 0x18,
+                0xb9, 0x55, 0x11, 0x74, 0x95, 0xd4
+            ],
+            bytes_from_words_be(&[
+                0xeb457996, 0x70cdc537, 0x95684f6f, 0xde320a3e, 0xbe5a8d48, 0xd25c3fe1, 0x47051dda,
+                0x2fcb1d61, 0x5abaabff, 0xd041c30e, 0x1a18b955, 0x117495d4,
+            ])
+        );
+        assert_eq!(
+            [
+                0x6d, 0x53, 0x3a, 0xa5, 0xb8, 0x06, 0x69, 0x55, 0xb1, 0xd5, 0x52, 0x84, 0xe4, 0x4a,
+                0xad, 0xe8, 0x27, 0x9a, 0xd4, 0xd2, 0xde, 0x9d, 0xe6, 0x61, 0x13, 0xfb, 0xa0, 0x77,
+                0x07, 0x6a, 0x6e, 0xfd
+            ],
+            bytes_from_words_be(&[
+                0x6d533aa5, 0xb8066955, 0xb1d55284, 0xe44aade8, 0x279ad4d2, 0xde9de661, 0x13fba077,
+                0x76a6efd,
+            ])
+        );
+    }
+
+    #[test]
+    pub fn test_bytes_swap_word_endian() {
+        assert_eq!(
+            [
+                0xeb, 0x45, 0x79, 0x96, 0x70, 0xcd, 0xc5, 0x37, 0x95, 0x68, 0x4f, 0x6f, 0xde, 0x32,
+                0x0a, 0x3e, 0xbe, 0x5a, 0x8d, 0x48, 0xd2, 0x5c, 0x3f, 0xe1, 0x47, 0x05, 0x1d, 0xda,
+                0x2f, 0xcb, 0x1d, 0x61, 0x5a, 0xba, 0xab, 0xff, 0xd0, 0x41, 0xc3, 0x0e, 0x1a, 0x18,
+                0xb9, 0x55, 0x11, 0x74, 0x95, 0xd4
+            ],
+            bytes_swap_word_endian(&[
+                0x96, 0x79, 0x45, 0xeb, 0x37, 0xc5, 0xcd, 0x70, 0x6f, 0x4f, 0x68, 0x95, 0x3e, 0x0a,
+                0x32, 0xde, 0x48, 0x8d, 0x5a, 0xbe, 0xe1, 0x3f, 0x5c, 0xd2, 0xda, 0x1d, 0x05, 0x47,
+                0x61, 0x1d, 0xcb, 0x2f, 0xff, 0xab, 0xba, 0x5a, 0x0e, 0xc3, 0x41, 0xd0, 0x55, 0xb9,
+                0x18, 0x1a, 0xd4, 0x95, 0x74, 0x11
+            ])
+        );
+        assert_eq!(
+            [
+                0x6d, 0x53, 0x3a, 0xa5, 0xb8, 0x06, 0x69, 0x55, 0xb1, 0xd5, 0x52, 0x84, 0xe4, 0x4a,
+                0xad, 0xe8, 0x27, 0x9a, 0xd4, 0xd2, 0xde, 0x9d, 0xe6, 0x61, 0x13, 0xfb, 0xa0, 0x77,
+                0x07, 0x6a, 0x6e, 0xfd
+            ],
+            bytes_swap_word_endian(&[
+                0xa5, 0x3a, 0x53, 0x6d, 0x55, 0x69, 0x06, 0xb8, 0x84, 0x52, 0xd5, 0xb1, 0xe8, 0xad,
+                0x4a, 0xe4, 0xd2, 0xd4, 0x9a, 0x27, 0x61, 0xe6, 0x9d, 0xde, 0x77, 0xa0, 0xfb, 0x13,
+                0xfd, 0x6e, 0x6a, 0x7
+            ])
+        );
+    }
 }
diff --git a/sw-emulator/lib/periph/src/hmac_sha384.rs b/sw-emulator/lib/periph/src/hmac_sha384.rs
index 6f6526aa..7162a822 100644
--- a/sw-emulator/lib/periph/src/hmac_sha384.rs
+++ b/sw-emulator/lib/periph/src/hmac_sha384.rs
@@ -480,28 +480,23 @@ impl HmacSha384 {
         let mut key_usage = KeyUsage::default();
         key_usage.set_hmac_data(true);
 
-        let result = self.key_vault.read_key(key_id, key_usage);
-        let (block_read_result, data) = match result.err() {
-            Some(BusError::LoadAccessFault) | Some(BusError::LoadAddrMisaligned) => {
-                (KeyReadStatus::ERROR::KV_READ_FAIL.value, None)
+        let error_code = match self.key_vault.read_key_as_data(key_id, key_usage) {
+            Err(BusError::LoadAccessFault) | Err(BusError::LoadAddrMisaligned) => {
+                KeyReadStatus::ERROR::KV_READ_FAIL.value
             }
-            Some(BusError::StoreAccessFault) | Some(BusError::StoreAddrMisaligned) => {
-                (KeyReadStatus::ERROR::KV_WRITE_FAIL.value, None)
+            Err(BusError::StoreAccessFault) | Err(BusError::StoreAddrMisaligned) => {
+                KeyReadStatus::ERROR::KV_WRITE_FAIL.value
+            }
+            Ok(data) => {
+                self.format_block(&data);
+                KeyReadStatus::ERROR::KV_SUCCESS.value
             }
-            None => (
-                KeyReadStatus::ERROR::KV_SUCCESS.value,
-                Some(result.unwrap()),
-            ),
         };
 
-        if let Some(data) = &data {
-            self.format_block(data);
-        }
-
         self.block_read_status.reg.modify(
             KeyReadStatus::READY::SET
                 + KeyReadStatus::VALID::SET
-                + KeyReadStatus::ERROR.val(block_read_result),
+                + KeyReadStatus::ERROR.val(error_code),
         );
     }
 
@@ -516,7 +511,7 @@ impl HmacSha384 {
     /// # Error
     ///
     /// * `None`
-    fn format_block(&mut self, data: &[u8; KeyVault::KEY_SIZE]) {
+    fn format_block(&mut self, data: &[u8]) {
         let mut block_arr = [0u8; HMAC_BLOCK_SIZE];
         block_arr[..data.len()].copy_from_slice(&data[..data.len()]);
         block_arr.to_little_endian();
@@ -733,9 +728,7 @@ mod tests {
             }
         }
 
-        if block_via_kv {
-            assert_eq!(data.len(), HMAC_KEY_SIZE);
-        } else {
+        if !block_via_kv {
             // Compute the total bytes and total blocks required for the final message.
             block_arr[..data.len()].copy_from_slice(data);
             block_arr[data.len()] = 1 << 7;
@@ -789,13 +782,12 @@ mod tests {
         }
 
         if block_via_kv {
-            let mut block: [u8; KeyVault::KEY_SIZE] = [0; KeyVault::KEY_SIZE];
-            block[..data.len()].copy_from_slice(data);
-            block.to_big_endian(); // Keys are stored in big-endian format.
+            let mut data = data.to_vec();
+            data.to_big_endian(); // Keys are stored in big-endian format.
             let mut key_usage = KeyUsage::default();
             key_usage.set_hmac_data(true);
             key_vault
-                .write_key(block_id, &block, u32::from(key_usage))
+                .write_key(block_id, &data, u32::from(key_usage))
                 .unwrap();
 
             if block_read_disallowed {
@@ -1235,6 +1227,40 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_hmac_sha384_kv_small_block_read() {
+        // This is similar to what happens when the 32-byte field-entropy is
+        // used as hmac data.
+        let key: [u8; 48] = [
+            0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65,
+            0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65,
+            0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65,
+            0x66, 0x65, 0x4a, 0x65, 0x66, 0x65,
+        ];
+
+        let data: [u8; 32] = [
+            0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65,
+            0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65, 0x4a, 0x65, 0x66, 0x65,
+            0x4a, 0x65, 0x66, 0x65,
+        ];
+
+        let result: [u8; 48] = [
+            0x4f, 0x60, 0xdd, 0x1f, 0x5, 0xc1, 0x0, 0x1c, 0xfd, 0x14, 0x6f, 0x41, 0xb4, 0x4b, 0xaa,
+            0x80, 0x24, 0x65, 0xc, 0xd, 0xe9, 0x47, 0x3d, 0x9e, 0x16, 0x9d, 0x3b, 0x51, 0xf3, 0x3c,
+            0x6e, 0x83, 0xb2, 0xa9, 0x37, 0x5f, 0x37, 0x87, 0x9f, 0x75, 0x11, 0x87, 0x68, 0xa5,
+            0x68, 0x7c, 0x42, 0x49,
+        ];
+
+        for key_id in 0..8 {
+            test_hmac(
+                &mut key.clone(),
+                &data,
+                &result,
+                &[KeyVaultAction::BlockFromVault(key_id)],
+            );
+        }
+    }
+
     #[test]
     fn test_hmac_sha384_kv_block_read_fail() {
         let key: [u8; 48] = [
diff --git a/sw-emulator/lib/periph/src/key_vault.rs b/sw-emulator/lib/periph/src/key_vault.rs
index 19e62c35..a30f343d 100644
--- a/sw-emulator/lib/periph/src/key_vault.rs
+++ b/sw-emulator/lib/periph/src/key_vault.rs
@@ -172,13 +172,16 @@ impl KeyVault {
         self.regs.borrow().read_key(key_id, desired_usage)
     }
 
-    /// Internal emulator interface to write key to key vault
-    pub fn write_key(
-        &mut self,
+    pub fn read_key_as_data(
+        &self,
         key_id: u32,
-        key: &[u8; KeyVault::KEY_SIZE],
-        key_usage: u32,
-    ) -> Result<(), BusError> {
+        desired_usage: KeyUsage,
+    ) -> Result<Vec<u8>, BusError> {
+        self.regs.borrow().read_key_as_data(key_id, desired_usage)
+    }
+
+    /// Internal emulator interface to write key to key vault
+    pub fn write_key(&mut self, key_id: u32, key: &[u8], key_usage: u32) -> Result<(), BusError> {
         self.regs.borrow_mut().write_key(key_id, key, key_usage)
     }
 
@@ -195,6 +198,12 @@ impl KeyVault {
     ) -> Result<(), BusError> {
         self.regs.borrow_mut().write_pcr(pcr_id, pcr)
     }
+
+    pub fn clear_keys_with_debug_values(&mut self, sel_debug_value: bool) {
+        self.regs
+            .borrow_mut()
+            .clear_with_debug_values(sel_debug_value);
+    }
 }
 impl Default for KeyVault {
     fn default() -> Self {
@@ -481,12 +490,23 @@ impl KeyVaultRegs {
         Ok(key)
     }
 
-    pub fn write_key(
-        &mut self,
+    pub fn read_key_as_data(
+        &self,
         key_id: u32,
-        key: &[u8; KeyVault::KEY_SIZE],
-        key_usage: u32,
-    ) -> Result<(), BusError> {
+        desired_usage: KeyUsage,
+    ) -> Result<Vec<u8>, BusError> {
+        let mut result: Vec<u8> = self.read_key(key_id, desired_usage)?.into();
+        let key_ctrl_reg = &self.key_control[key_id as usize];
+        let last_dword = key_ctrl_reg.read(KV_CONTROL::LAST_DWORD);
+        result.resize((last_dword + 1) as usize * 4, 0);
+        Ok(result)
+    }
+
+    pub fn write_key(&mut self, key_id: u32, key: &[u8], key_usage: u32) -> Result<(), BusError> {
+        if key.len() > KeyVault::KEY_SIZE || key.len() % 4 != 0 {
+            Err(BusError::StoreAccessFault)?
+        }
+        let key_wordlen = key.len() / 4;
         let key_ctrl_reg = &mut self.key_control[key_id as usize];
         if key_ctrl_reg.read(KV_CONTROL::WRITE_LOCK) != 0
             || key_ctrl_reg.read(KV_CONTROL::USE_LOCK) != 0
@@ -494,15 +514,23 @@ impl KeyVaultRegs {
             Err(BusError::StoreAccessFault)?
         }
         let key_start = key_id as usize * KeyVault::KEY_SIZE;
-        let key_end = key_start + KeyVault::KEY_SIZE;
+        let key_end = key_start + key.len();
         self.keys.data_mut()[key_start..key_end].copy_from_slice(key);
 
         // Update the key usage.
         key_ctrl_reg.modify(KV_CONTROL::USAGE.val(key_usage));
 
+        // Update the last dword in the key
+        key_ctrl_reg.modify(KV_CONTROL::LAST_DWORD.val(key_wordlen as u32 - 1));
+
         Ok(())
     }
 
+    pub fn clear_with_debug_values(&mut self, sel_debug_value: bool) {
+        let fill_byte = if sel_debug_value { 0x55 } else { 0xaa };
+        self.keys.data_mut().fill(fill_byte);
+    }
+
     pub fn read_pcr(&self, pcr_id: u32) -> [u8; constants::PCR_SIZE_BYTES] {
         let pcr_start = pcr_id as usize * constants::PCR_SIZE_WORDS;
         let mut pcr = [0u32; constants::PCR_SIZE_WORDS];
@@ -699,6 +727,53 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_key_private_read_write_small() {
+        // In this case, only 8 of the total 12 words in the key-entry are
+        // written to, so when they are retrieved as data, only those 8 words
+        // should be returned.
+        let expected: [u8; 32] = [
+            0x11, 0x65, 0xb3, 0x40, 0x6f, 0xf0, 0xb5, 0x2a, 0x3d, 0x24, 0x72, 0x1f, 0x78, 0x54,
+            0x62, 0xca, 0x22, 0x76, 0xc9, 0xf4, 0x54, 0xa1, 0x16, 0xc2, 0xb2, 0xba, 0x20, 0x17,
+            0x1a, 0x79, 0x05, 0xea,
+        ];
+
+        let mut vault = KeyVault::new();
+        let mut key_usage = KeyUsage::default();
+        key_usage.set_hmac_data(true); // dummy usage.
+
+        for idx in 0..KeyVault::KEY_COUNT {
+            vault
+                .write_key(idx, &expected, u32::from(key_usage))
+                .unwrap();
+            let returned = vault.read_key_as_data(idx, key_usage).unwrap();
+            assert_eq!(&returned, &expected);
+
+            assert_eq!(
+                vault.read_key(idx, key_usage).unwrap(),
+                [
+                    0x11, 0x65, 0xb3, 0x40, 0x6f, 0xf0, 0xb5, 0x2a, 0x3d, 0x24, 0x72, 0x1f, 0x78,
+                    0x54, 0x62, 0xca, 0x22, 0x76, 0xc9, 0xf4, 0x54, 0xa1, 0x16, 0xc2, 0xb2, 0xba,
+                    0x20, 0x17, 0x1a, 0x79, 0x05, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+                ]
+            );
+        }
+    }
+
+    #[test]
+    fn test_key_clear_with_debug_values() {
+        let mut vault = KeyVault::new();
+
+        vault.clear_keys_with_debug_values(false);
+        let key_mem: Vec<u8> = vault.regs.borrow().keys.data().to_vec();
+        assert_eq!(key_mem, vec![0xaa; key_mem.len()]);
+
+        vault.clear_keys_with_debug_values(true);
+        let key_mem: Vec<u8> = vault.regs.borrow().keys.data().to_vec();
+        assert_eq!(key_mem, vec![0x55; key_mem.len()]);
+    }
+
     #[test]
     fn test_pcr_read_write() {
         let expected: [u8; constants::PCR_SIZE_BYTES] = [
diff --git a/sw-emulator/lib/periph/src/root_bus.rs b/sw-emulator/lib/periph/src/root_bus.rs
index cb4a6278..f1e2aff2 100644
--- a/sw-emulator/lib/periph/src/root_bus.rs
+++ b/sw-emulator/lib/periph/src/root_bus.rs
@@ -13,6 +13,7 @@ Abstract:
 --*/
 
 use crate::{
+    helpers::words_from_bytes_be,
     iccm::Iccm,
     soc_reg::{DebugManufService, SocRegistersExternal},
     AsymEcc384, Csrng, Doe, EmuCtrl, HashSha256, HashSha512, HmacSha384, KeyVault, MailboxExternal,
@@ -24,6 +25,12 @@ use caliptra_hw_model_types::SecurityState;
 use std::path::PathBuf;
 use tock_registers::registers::InMemoryRegister;
 
+/// Default Deobfuscation engine key
+pub const DEFAULT_DOE_KEY: [u8; 32] = [
+    0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81,
+    0x1F, 0x35, 0x2C, 0x7, 0x3B, 0x61, 0x8, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x9, 0x14, 0xDF, 0xF4,
+];
+
 pub struct TbServicesCb(pub Box<dyn FnMut(u8)>);
 impl TbServicesCb {
     pub fn new(f: impl FnMut(u8) + 'static) -> Self {
@@ -196,7 +203,7 @@ impl From<Box<dyn FnMut() + 'static>> for ActionCb {
 }
 
 /// Caliptra Root Bus Arguments
-#[derive(Default, Debug)]
+#[derive(Debug)]
 pub struct CaliptraRootBusArgs {
     pub rom: Vec<u8>,
     pub log_dir: PathBuf,
@@ -210,6 +217,24 @@ pub struct CaliptraRootBusArgs {
     pub upload_update_fw: UploadUpdateFwCb,
     pub bootfsm_go_cb: ActionCb,
     pub download_idevid_csr_cb: DownloadIdevidCsrCb,
+
+    // The obfuscation key, as passed to caliptra-top
+    pub cptra_obf_key: [u32; 8],
+}
+impl Default for CaliptraRootBusArgs {
+    fn default() -> Self {
+        Self {
+            rom: Default::default(),
+            log_dir: Default::default(),
+            security_state: Default::default(),
+            tb_services_cb: Default::default(),
+            ready_for_fw_cb: Default::default(),
+            upload_update_fw: Default::default(),
+            bootfsm_go_cb: Default::default(),
+            download_idevid_csr_cb: Default::default(),
+            cptra_obf_key: words_from_bytes_be(&DEFAULT_DOE_KEY),
+        }
+    }
 }
 
 #[derive(Bus)]
@@ -269,12 +294,17 @@ impl CaliptraRootBus {
     pub const DCCM_SIZE: usize = 128 * 1024;
 
     pub fn new(clock: &Clock, mut args: CaliptraRootBusArgs) -> Self {
-        let key_vault = KeyVault::new();
+        let mut key_vault = KeyVault::new();
         let mailbox_ram = MailboxRam::new();
         let mailbox = MailboxInternal::new(mailbox_ram.clone());
         let rom = Rom::new(std::mem::take(&mut args.rom));
         let iccm = Iccm::new(clock);
         let soc_reg = SocRegistersInternal::new(clock, mailbox.clone(), iccm.clone(), args);
+        if !soc_reg.is_debug_locked() {
+            // When debug is possible, the key-vault is initialized with a debug value...
+            // This is necessary to match the behavior of the RTL.
+            key_vault.clear_keys_with_debug_values(false);
+        }
 
         Self {
             rom,
@@ -316,3 +346,72 @@ pub struct SocToCaliptraBus {
     #[peripheral(offset = 0x3003_0000, mask = 0x0000_ffff)]
     soc_ifc: SocRegistersExternal,
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::KeyUsage;
+
+    use super::*;
+
+    #[test]
+    fn test_keyvault_init_val_in_debug_unlocked_mode() {
+        let clock = Clock::new();
+        let mut root_bus = CaliptraRootBus::new(
+            &clock,
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(false),
+                ..CaliptraRootBusArgs::default()
+            },
+        );
+        let mut key_usage = KeyUsage::default();
+        key_usage.set_hmac_key(true);
+
+        root_bus
+            .key_vault
+            .write_key(1, &[0x00, 0x11, 0x22, 0x33], key_usage.into())
+            .unwrap();
+
+        // The key-entry will still have the "init data" in the unwritten words.
+        // See chipsalliace/caliptra-rtl#114
+        assert_eq!(
+            root_bus.key_vault.read_key(1, key_usage).unwrap(),
+            [
+                0x00_u8, 0x11, 0x22, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+                0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+                0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+                0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa
+            ]
+        );
+    }
+
+    #[test]
+    fn test_keyvault_init_val_in_debug_locked_mode() {
+        let clock = Clock::new();
+        let mut root_bus = CaliptraRootBus::new(
+            &clock,
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(true),
+                ..CaliptraRootBusArgs::default()
+            },
+        );
+        let mut key_usage = KeyUsage::default();
+        key_usage.set_hmac_key(true);
+
+        root_bus
+            .key_vault
+            .write_key(1, &[0x00, 0x11, 0x22, 0x33], key_usage.into())
+            .unwrap();
+
+        // The key-entry will still have the "init data" in the unwritten words.
+        // See chipsalliace/caliptra-rtl#114
+        assert_eq!(
+            root_bus.key_vault.read_key(1, key_usage).unwrap(),
+            [
+                0x00_u8, 0x11, 0x22, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+            ]
+        );
+    }
+}
diff --git a/sw-emulator/lib/periph/src/soc_reg.rs b/sw-emulator/lib/periph/src/soc_reg.rs
index 7b6db858..9d375659 100644
--- a/sw-emulator/lib/periph/src/soc_reg.rs
+++ b/sw-emulator/lib/periph/src/soc_reg.rs
@@ -12,13 +12,13 @@ Abstract:
 
 --*/
 
-use crate::helpers::{bytes_from_words_le, words_from_bytes_le};
+use crate::helpers::{bytes_from_words_be, words_from_bytes_be};
 use crate::root_bus::ReadyForFwCbArgs;
 use crate::{CaliptraRootBusArgs, Iccm, MailboxInternal};
 use caliptra_emu_bus::BusError::{LoadAccessFault, StoreAccessFault};
 use caliptra_emu_bus::{
-    ActionHandle, Bus, BusError, Clock, ReadOnlyMemory, ReadOnlyRegister, ReadWriteRegister,
-    Register, Timer, TimerAction,
+    ActionHandle, Bus, BusError, Clock, ReadOnlyRegister, ReadWriteRegister, Register, Timer,
+    TimerAction,
 };
 use caliptra_emu_derive::Bus;
 use caliptra_emu_types::{RvAddr, RvData, RvSize};
@@ -195,20 +195,36 @@ impl SocRegistersInternal {
             ))),
         }
     }
+    pub fn is_debug_locked(&self) -> bool {
+        let reg = &self.regs.borrow().cptra_security_state.reg;
+        reg.read(SecurityState::DEBUG_LOCKED) != 0
+    }
 
     /// Get Unique device secret
     pub fn uds(&self) -> [u8; FUSE_UDS_SEED_SIZE] {
-        bytes_from_words_le(&self.regs.borrow().fuse_uds_seed)
+        if self.is_debug_locked() {
+            bytes_from_words_be(&self.regs.borrow().fuse_uds_seed)
+        } else {
+            [0xff_u8; FUSE_UDS_SEED_SIZE]
+        }
     }
 
     // Get field entropy
     pub fn field_entropy(&self) -> [u8; FUSE_FIELD_ENTROPY_SIZE] {
-        bytes_from_words_le(&self.regs.borrow().fuse_field_entropy)
+        if self.is_debug_locked() {
+            bytes_from_words_be(&self.regs.borrow().fuse_field_entropy)
+        } else {
+            [0xff_u8; FUSE_FIELD_ENTROPY_SIZE]
+        }
     }
 
     /// Get deobfuscation engine key
     pub fn doe_key(&self) -> [u8; INTERNAL_OBF_KEY_SIZE] {
-        *self.regs.borrow().internal_obf_key.data()
+        if self.is_debug_locked() {
+            bytes_from_words_be(&self.regs.borrow().internal_obf_key)
+        } else {
+            [0xff_u8; INTERNAL_OBF_KEY_SIZE]
+        }
     }
 
     /// Clear secrets
@@ -416,7 +432,7 @@ struct SocRegistersImpl {
     fuse_life_cycle: u32,
 
     /// INTERNAL_OBF_KEY Register
-    internal_obf_key: ReadOnlyMemory<INTERNAL_OBF_KEY_SIZE>,
+    internal_obf_key: [u32; 8],
 
     /// INTERNAL_ICCM_LOCK Register
     #[register(offset = 0x0620, write_fn = on_write_iccm_lock)]
@@ -472,13 +488,6 @@ struct SocRegistersImpl {
 }
 
 impl SocRegistersImpl {
-    /// Default Deobfuscation engine key
-    const DOE_KEY: [u8; INTERNAL_OBF_KEY_SIZE] = [
-        0x60, 0x3D, 0xEB, 0x10, 0x15, 0xCA, 0x71, 0xBE, 0x2B, 0x73, 0xAE, 0xF0, 0x85, 0x7D, 0x77,
-        0x81, 0x1F, 0x35, 0x2C, 0x7, 0x3B, 0x61, 0x8, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x9, 0x14,
-        0xDF, 0xF4,
-    ];
-
     /// Default unique device secret
     const UDS: [u8; FUSE_UDS_SEED_SIZE] = [
         0xF5, 0x8C, 0x4C, 0x4, 0xD6, 0xE5, 0xF1, 0xBA, 0x77, 0x9E, 0xAB, 0xFB, 0x5F, 0x7B, 0xFB,
@@ -524,7 +533,7 @@ impl SocRegistersImpl {
             cptra_clk_gating_en: ReadOnlyRegister::new(0),
             cptra_generic_input_wires: Default::default(),
             cptra_generic_output_wires: Default::default(),
-            fuse_uds_seed: words_from_bytes_le(&Self::UDS),
+            fuse_uds_seed: words_from_bytes_be(&Self::UDS),
             fuse_field_entropy: [0xffff_ffff; 8],
             fuse_vendor_pk_hash: Default::default(),
             fuse_vendor_pk_hash_mask: ReadWriteRegister::new(0),
@@ -535,7 +544,7 @@ impl SocRegistersImpl {
             fuse_idevid_cert_attr: Default::default(),
             fuse_idevid_manuf_hsm_id: Default::default(),
             fuse_life_cycle: Default::default(),
-            internal_obf_key: ReadOnlyMemory::new_with_data(Self::DOE_KEY),
+            internal_obf_key: args.cptra_obf_key,
             internal_iccm_lock: ReadWriteRegister::new(0),
             internal_fw_update_reset: ReadWriteRegister::new(0),
             internal_fw_update_reset_wait_cycles: ReadWriteRegister::new(0),
@@ -563,7 +572,7 @@ impl SocRegistersImpl {
     fn clear_secrets(&mut self) {
         self.fuse_uds_seed = [0u32; 12];
         self.fuse_field_entropy = [0u32; 8];
-        self.internal_obf_key.data_mut().fill(0);
+        self.internal_obf_key = [0u32; 8];
     }
 
     fn on_write_bootfsm_go(&mut self, _size: RvSize, val: RvData) -> Result<(), BusError> {
@@ -1021,4 +1030,42 @@ mod tests {
 
         assert_eq!(&*output.borrow(), &vec![b'h', b'i', 0xff]);
     }
+
+    #[test]
+    fn test_secrets_when_debug_not_locked() {
+        use caliptra_hw_model_types::SecurityState;
+        let clock = Clock::new();
+        let soc = SocRegistersInternal::new(
+            &clock,
+            MailboxInternal::new(MailboxRam::new()),
+            Iccm::new(&clock),
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(false),
+                ..CaliptraRootBusArgs::default()
+            },
+        );
+        soc.external_regs().regs.borrow_mut().fuse_field_entropy = [0x33333333; 8];
+        assert_eq!(soc.uds(), [0xff_u8; 48]);
+        assert_eq!(soc.field_entropy(), [0xff_u8; 32]);
+        assert_eq!(soc.doe_key(), [0xff_u8; 32]);
+    }
+
+    #[test]
+    fn test_secrets_when_debug_locked() {
+        use caliptra_hw_model_types::SecurityState;
+        let clock = Clock::new();
+        let soc = SocRegistersInternal::new(
+            &clock,
+            MailboxInternal::new(MailboxRam::new()),
+            Iccm::new(&clock),
+            CaliptraRootBusArgs {
+                security_state: *SecurityState::default().set_debug_locked(true),
+                ..CaliptraRootBusArgs::default()
+            },
+        );
+        soc.external_regs().regs.borrow_mut().fuse_field_entropy = [0x33333333; 8];
+        assert_eq!(soc.uds(), SocRegistersImpl::UDS);
+        assert_eq!(soc.field_entropy(), [0x33_u8; 32]);
+        assert_eq!(soc.doe_key(), crate::root_bus::DEFAULT_DOE_KEY);
+    }
 }


Test patch:
diff --git a/drivers/test-fw/Cargo.toml b/drivers/test-fw/Cargo.toml
index 5927f742..d77ff1f0 100644
--- a/drivers/test-fw/Cargo.toml
+++ b/drivers/test-fw/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2021"
 
 [dependencies]
 caliptra-drivers = { path = "..", features=["emu"] }
+caliptra-error = { path = "../../error", default_features = false }
 caliptra-registers = { path = "../../registers" }
 caliptra-kat = { path = "../../kat" }
 caliptra-lms-types = { path = "../../lms-types" }
diff --git a/drivers/test-fw/build.rs b/drivers/test-fw/build.rs
index 325480e0..6c8d4f51 100644
--- a/drivers/test-fw/build.rs
+++ b/drivers/test-fw/build.rs
@@ -13,6 +13,8 @@ Abstract:
 --*/
 
 fn main() {
-    println!("cargo:rerun-if-changed=../../test-harness/scripts/rom.ld");
-    println!("cargo:rustc-link-arg=-Ttest-harness/scripts/rom.ld");
+    if cfg!(feature = "riscv") {
+        println!("cargo:rerun-if-changed=../../test-harness/scripts/rom.ld");
+        println!("cargo:rustc-link-arg=-Ttest-harness/scripts/rom.ld");
+    }
 }
diff --git a/drivers/test-fw/src/bin/doe_tests.rs b/drivers/test-fw/src/bin/doe_tests.rs
index 62a65be0..0ffc0b6f 100644
--- a/drivers/test-fw/src/bin/doe_tests.rs
+++ b/drivers/test-fw/src/bin/doe_tests.rs
@@ -15,37 +15,86 @@ Abstract:
 #![no_std]
 #![no_main]
 
-use caliptra_drivers::{Array4x4, DeobfuscationEngine, KeyId};
+use caliptra_drivers::{
+    Array4x12, Array4x4, DeobfuscationEngine, Hmac384, Hmac384Data, Hmac384Key, Hmac384Tag, KeyId,
+    KeyReadArgs, Mailbox,
+};
+use caliptra_drivers_test_bin::{DoeTestResults, DOE_TEST_HMAC_KEY, DOE_TEST_IV};
 
-use caliptra_registers::doe::DoeReg;
+use caliptra_registers::{doe::DoeReg, hmac::HmacReg, mbox::MboxCsr};
 use caliptra_test_harness::test_suite;
+use zerocopy::AsBytes;
 
-fn test_decrypt_uds() {
-    let iv = [0xFF_u8; 16];
-    let mut doe = unsafe { DeobfuscationEngine::new(DoeReg::new()) };
-    assert_eq!(
-        doe.decrypt_uds(&Array4x4::from(iv), KeyId::KeyId0).ok(),
-        Some(())
-    );
-}
+fn test_decrypt() {
+    let mut test_results = DoeTestResults::default();
 
-fn test_decrypt_field_entropy() {
-    let iv = [0xFF_u8; 16];
+    let mut hmac384 = Hmac384::new(unsafe { HmacReg::new() });
     let mut doe = unsafe { DeobfuscationEngine::new(DoeReg::new()) };
     assert_eq!(
-        doe.decrypt_field_entropy(&Array4x4::from(iv), KeyId::KeyId0)
+        doe.decrypt_uds(&Array4x4::from(DOE_TEST_IV), KeyId::KeyId0)
             .ok(),
         Some(())
     );
+    // Make sure the UDS can be used as a HMAC key
+    let mut result = Array4x12::default();
+    hmac384
+        .hmac(
+            Hmac384Key::Key(KeyReadArgs { id: KeyId::KeyId0 }),
+            Hmac384Data::Slice("Hello world!".as_bytes()),
+            Hmac384Tag::Array4x12(&mut result),
+        )
+        .unwrap();
+    test_results.hmac_uds_as_key = result.0;
+
+    // Make sure the UDS can be used as HMAC data
+    let mut result = Array4x12::default();
+    hmac384
+        .hmac(
+            Hmac384Key::Array4x12(&Array4x12::new(DOE_TEST_HMAC_KEY)),
+            Hmac384Data::Key(KeyReadArgs { id: KeyId::KeyId0 }),
+            Hmac384Tag::Array4x12(&mut result),
+        )
+        .unwrap();
+    test_results.hmac_uds_as_data = result.0;
+
+    doe.decrypt_field_entropy(&Array4x4::from(DOE_TEST_IV), KeyId::KeyId1)
+        .unwrap();
+
+    // Make sure the FE can be used as a HMAC key
+    let mut result = Array4x12::default();
+    hmac384
+        .hmac(
+            Hmac384Key::Key(KeyReadArgs { id: KeyId::KeyId1 }),
+            Hmac384Data::Slice("Hello world!".as_bytes()),
+            Hmac384Tag::Array4x12(&mut result),
+        )
+        .unwrap();
+    test_results.hmac_field_entropy_as_key = result.0;
+
+    // Make sure the FE can be used as HMAC data
+    let mut result = Array4x12::default();
+    hmac384
+        .hmac(
+            Hmac384Key::Array4x12(&Array4x12::new(DOE_TEST_HMAC_KEY)),
+            Hmac384Data::Key(KeyReadArgs { id: KeyId::KeyId1 }),
+            Hmac384Tag::Array4x12(&mut result),
+        )
+        .unwrap();
+    test_results.hmac_field_entropy_as_data = result.0;
+
+    let mut mbox = Mailbox::new(unsafe { MboxCsr::new() });
+    mbox.try_start_send_txn()
+        .unwrap()
+        .send_request(0, test_results.as_bytes())
+        .unwrap();
 }
 
 fn test_clear_secrets() {
     let mut doe = unsafe { DeobfuscationEngine::new(DoeReg::new()) };
-    assert_eq!(doe.clear_secrets().ok(), Some(()))
+    doe.clear_secrets().unwrap();
 }
 
 test_suite! {
-    test_decrypt_uds,
-    test_decrypt_field_entropy,
+    test_decrypt,
     test_clear_secrets,
 }
diff --git a/drivers/test-fw/src/lib.rs b/drivers/test-fw/src/lib.rs
new file mode 100644
index 00000000..b6de6d6a
--- /dev/null
+++ b/drivers/test-fw/src/lib.rs
@@ -0,0 +1,69 @@
+// Licensed under the Apache-2.0 license
+
+#![no_std]
+
+/// Code shared between the caliptra-drivers integration_test.rs (running on the
+/// host) and the test binaries (running inside the hw-model).
+use core::fmt::Debug;
+use zerocopy::{AsBytes, FromBytes};
+
+pub const DOE_TEST_IV: [u8; 16] = [
+    0xc6, 0xb4, 0x07, 0xa2, 0xd1, 0x19, 0xa3, 0x7d, 0xb7, 0xa5, 0xbd, 0xeb, 0x26, 0x21, 0x4a, 0xed,
+];
+
+pub const DOE_TEST_HMAC_KEY: [u32; 12] = [
+    0x15f4a700, 0xd79bd4e1, 0x0f92b714, 0x3a38d570, 0x7cf2ebb4, 0xab47cc6e, 0xa4827e80, 0x32e6d3b4,
+    0xc6879874, 0x0aa49a0f, 0x4e740e9c, 0x2c9f9aad,
+];
+
+pub struct HexWord(u32);
+impl Debug for HexWord {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "0x{:08x}", self.0)
+    }
+}
+
+pub struct HexWordSlice<'a>(&'a [u32]);
+impl Debug for HexWordSlice<'_> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let mut l = f.debug_list();
+        for val in self.0 {
+            l.entry(&HexWord(*val));
+        }
+        l.finish()
+    }
+}
+
+#[derive(AsBytes, Clone, Copy, Default, Eq, PartialEq, FromBytes)]
+#[repr(C)]
+pub struct DoeTestResults {
+    /// HMAC result of the UDS as key, and b"Hello world!" as data.
+    pub hmac_uds_as_key: [u32; 12],
+
+    /// HMAC result of HMAC_KEY as key, and UDS as data.
+    pub hmac_uds_as_data: [u32; 12],
+
+    // HMAC result of of the field entropy (including padding) as key, and
+    // b"Hello world" as data.
+    pub hmac_field_entropy_as_key: [u32; 12],
+
+    /// HMAC result of HMAC_KEY as key, and field entropy (excluding padding) as
+    /// data.
+    pub hmac_field_entropy_as_data: [u32; 12],
+}
+impl Debug for DoeTestResults {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("DoeTestResults")
+            .field("hmac_uds_as_key", &HexWordSlice(&self.hmac_uds_as_key))
+            .field("hmac_uds_as_data", &HexWordSlice(&self.hmac_uds_as_data))
+            .field(
+                "hmac_field_entropy_as_key",
+                &HexWordSlice(&self.hmac_field_entropy_as_key),
+            )
+            .field(
+                "hmac_field_entropy_as_data",
+                &HexWordSlice(&self.hmac_field_entropy_as_data),
+            )
+            .finish()
+    }
+}
diff --git a/drivers/tests/integration_tests.rs b/drivers/tests/integration_tests.rs
index 9a12556c..cbac4fd2 100644
--- a/drivers/tests/integration_tests.rs
+++ b/drivers/tests/integration_tests.rs
@@ -3,17 +3,26 @@
 use std::error::Error;
 
 use caliptra_builder::FwId;
-use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, ModelError};
+use caliptra_drivers_test_bin::DoeTestResults;
+use caliptra_hw_model::{
+    BootParams, DefaultHwModel, DeviceLifecycle, HwModel, InitParams, ModelError, SecurityState,
+};
 use caliptra_registers::mbox::enums::MboxStatusE;
+use openssl::{hash::MessageDigest, pkey::PKey};
+use zerocopy::{transmute, AsBytes, FromBytes};
 
-fn start_driver_test(test_bin_name: &'static str) -> Result<DefaultHwModel, Box<dyn Error>> {
-    let rom = caliptra_builder::build_firmware_rom(&FwId {
+fn build_test_rom(test_bin_name: &'static str) -> Vec<u8> {
+    caliptra_builder::build_firmware_rom(&FwId {
         crate_name: "caliptra-drivers-test-bin",
         bin_name: test_bin_name,
         features: &["emu"],
         ..Default::default()
     })
-    .unwrap();
+    .unwrap()
+}
+
+fn start_driver_test(test_bin_name: &'static str) -> Result<DefaultHwModel, Box<dyn Error>> {
+    let rom = build_test_rom(test_bin_name);
     caliptra_hw_model::new(BootParams {
         init_params: InitParams {
             rom: &rom,
@@ -29,9 +38,271 @@ fn run_driver_test(test_bin_name: &'static str) {
     model.step_until_exit_success().unwrap();
 }
 
+struct DoeInput {
+    // The DOE obfuscation key, as wired to caliptra_top
+    doe_obf_key: [u32; 8],
+
+    // The DOE initialization vector, as given to the DOE_IV register by the
+    // firmware.
+    doe_iv: [u8; 16],
+
+    // The UDS seed, as stored in the fuses
+    uds_seed: [u32; 12],
+
+    // The field entropy, as stored in the fuses
+    field_entropy_seed: [u32; 8],
+
+    // The initial value of key-vault entry words at startup
+    keyvault_initial_word_value: u32,
+}
+
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+struct DoeTestVectors {
+    // The decrypted UDS as stored in the key vault
+    uds: [u32; 12],
+
+    // The decrypted field entropy as stored in the key vault (with padding)
+    field_entropy: [u32; 12],
+
+    // The expected results of the HMAC operations performed by the test.
+    expected_test_results: DoeTestResults,
+}
+impl DoeTestVectors {
+    /// A standalone implementation of the cryptographic operations necessary to
+    /// generate the expected DOE test's HMAC results from fuse values and
+    /// silicon secrets, using only openssl. This independent implementation is
+    /// used to validate that the test-vector constants are correct.
+    fn generate(input: &DoeInput) -> Self {
+        use openssl::{cipher::Cipher, cipher_ctx::CipherCtx, sign::Signer};
+
+        fn swap_word_bytes(words: &[u32]) -> Vec<u32> {
+            words.iter().map(|word| word.swap_bytes()).collect()
+        }
+        fn swap_word_bytes_inplace(words: &mut [u32]) {
+            for word in words.iter_mut() {
+                *word = word.swap_bytes()
+            }
+        }
+        fn hmac384(key: &[u8], data: &[u8]) -> [u8; 48] {
+            let pkey = PKey::hmac(key).unwrap();
+            let mut signer = Signer::new(MessageDigest::sha384(), &pkey).unwrap();
+            signer.update(data).unwrap();
+            let mut result = [0u8; 48];
+            signer.sign(&mut result).unwrap();
+            result
+        }
+        fn aes256_decrypt_blocks(key: &[u8], iv: &[u8], input: &[u8]) -> Vec<u8> {
+            let cipher = Cipher::aes_256_cbc();
+            let mut ctx = CipherCtx::new().unwrap();
+            ctx.decrypt_init(Some(cipher), Some(key), Some(iv)).unwrap();
+            ctx.set_padding(false);
+            let mut result = vec![];
+            ctx.cipher_update_vec(input, &mut result).unwrap();
+            ctx.cipher_final_vec(&mut result).unwrap();
+            result
+        }
+
+        let mut result = Self::default();
+
+        result
+            .uds
+            .as_bytes_mut()
+            .copy_from_slice(&aes256_decrypt_blocks(
+                swap_word_bytes(&input.doe_obf_key).as_bytes(),
+                &input.doe_iv,
+                swap_word_bytes(&input.uds_seed).as_bytes(),
+            ));
+        swap_word_bytes_inplace(&mut result.uds);
+
+        result.expected_test_results.hmac_uds_as_key = transmute!(hmac384(
+            swap_word_bytes(&result.uds).as_bytes(),
+            "Hello world!".as_bytes()
+        ));
+        swap_word_bytes_inplace(&mut result.expected_test_results.hmac_uds_as_key);
+
+        result.expected_test_results.hmac_uds_as_data = transmute!(hmac384(
+            swap_word_bytes(&caliptra_drivers_test_bin::DOE_TEST_HMAC_KEY).as_bytes(),
+            swap_word_bytes(&result.uds).as_bytes()
+        ));
+        swap_word_bytes_inplace(&mut result.expected_test_results.hmac_uds_as_data);
+
+        // After reset, the key-vault registers are filled with a particular
+        // word, depending on the debug-locked mode.  The field entropy only
+        // takes up 8 of the 12 words, so the 4 remaining words keep their
+        // original value (their contents are used when the key-vault entry is
+        // used as a HMAC key, but not when used as HMAC
+        // data).
+        result.field_entropy = [input.keyvault_initial_word_value; 12];
+        result.field_entropy[0..8]
+            .as_bytes_mut()
+            .copy_from_slice(&aes256_decrypt_blocks(
+                swap_word_bytes(&input.doe_obf_key).as_bytes(),
+                &input.doe_iv,
+                swap_word_bytes(&input.field_entropy_seed).as_bytes(),
+            ));
+        swap_word_bytes_inplace(&mut result.field_entropy);
+
+        result.expected_test_results.hmac_field_entropy_as_key = transmute!(hmac384(
+            swap_word_bytes(&result.field_entropy).as_bytes(),
+            "Hello world!".as_bytes()
+        ));
+        swap_word_bytes_inplace(&mut result.expected_test_results.hmac_field_entropy_as_key);
+
+        result.expected_test_results.hmac_field_entropy_as_data = transmute!(hmac384(
+            swap_word_bytes(&caliptra_drivers_test_bin::DOE_TEST_HMAC_KEY).as_bytes(),
+            swap_word_bytes(&result.field_entropy[0..8]).as_bytes()
+        ));
+        swap_word_bytes_inplace(&mut result.expected_test_results.hmac_field_entropy_as_data);
+        result
+    }
+}
+
+const DOE_TEST_VECTORS_DEBUG_MODE: DoeTestVectors = DoeTestVectors {
+    // The decrypted UDS as stored in the key vault
+    uds: [
+        0x34aa667c, 0x0a52c71f, 0x977a1de2, 0x701ef611, 0x0de19e21, 0x24b49b9d, 0xdf205ff6,
+        0xa9c04303, 0x0de19e21, 0x24b49b9d, 0xdf205ff6, 0xa9c04303,
+    ],
+
+    // The decrypted field entropy as stored in the key vault (with padding)
+    field_entropy: [
+        0x34aa667c,
+        0x0a52c71f,
+        0x977a1de2,
+        0x701ef611,
+        0x0de19e21,
+        0x24b49b9d,
+        0xdf205ff6,
+        0xa9c04303,
+        0xaaaa_aaaa,
+        0xaaaa_aaaa,
+        0xaaaa_aaaa,
+        0xaaaa_aaaa,
+    ],
+
+    // The expected results of the HMAC operations performed by the test.
+    expected_test_results: DoeTestResults {
+        hmac_uds_as_key: [
+            0x4446d380, 0xd2cb5d96, 0xcf745d40, 0xbfe7dcdb, 0x58a8befe, 0x2ddc1eac, 0xbc93b36c,
+            0xccc277ab, 0xedc67ae7, 0x7e4e12a4, 0x106e0e34, 0xb065b021,
+        ],
+        hmac_uds_as_data: [
+            0xe507101b, 0xb5fc57e0, 0xa02d2cdf, 0xb5b4d5ba, 0x69535616, 0xcb9d3ab8, 0x5a571a66,
+            0xb5e76d47, 0x802e86ba, 0x2969e838, 0x36869873, 0xb6847c27,
+        ],
+        hmac_field_entropy_as_key: [
+            0x683285f1, 0x27d26fc8, 0xe9e716c2, 0x0dc9c7fb, 0x9cad8b4c, 0xaeb167c5, 0xb402cf3b,
+            0x2e2c1745, 0x560bb884, 0xf592628f, 0x66db5c8f, 0x883086eb,
+        ],
+        hmac_field_entropy_as_data: [
+            0x4d2aec76, 0x7c73efbe, 0xb50aa67c, 0x89a684e3, 0x823834c4, 0x3429dea2, 0xf35cfdb0,
+            0xbfef4e6a, 0xa40dc572, 0xea82be07, 0xc93ef76a, 0xf955f845,
+        ],
+    },
+};
+
 #[test]
-fn test_doe() {
-    run_driver_test("doe");
+fn test_generate_doe_vectors_when_debug_not_locked() {
+    // When microcontroller debugging is possible, all the secrets are set by the hardware to
+    // 0xffff_ffff words.
+    let vectors = DoeTestVectors::generate(&DoeInput {
+        doe_obf_key: [0xffff_ffff_u32; 8],
+        doe_iv: caliptra_drivers_test_bin::DOE_TEST_IV,
+
+        uds_seed: [0xffff_ffff_u32; 12],
+        field_entropy_seed: [0xffff_ffff_u32; 8],
+
+        // In debug mode, this defaults to 0xaaaa_aaaa
+        keyvault_initial_word_value: 0xaaaa_aaaa,
+    });
+    assert_eq!(vectors, DOE_TEST_VECTORS_DEBUG_MODE);
+}
+
+#[test]
+fn test_doe_when_debug_not_locked() {
+    let rom = build_test_rom("doe");
+    let mut model = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state: *SecurityState::from(0)
+                .set_debug_locked(false)
+                .set_device_lifecycle(DeviceLifecycle::Unprovisioned),
+            ..Default::default()
+        },
+        ..Default::default()
+    })
+    .unwrap();
+
+    let txn = model.wait_for_mailbox_receive().unwrap();
+    let test_results = DoeTestResults::read_from(txn.req.data.as_slice()).unwrap();
+    assert_eq!(
+        test_results,
+        DOE_TEST_VECTORS_DEBUG_MODE.expected_test_results
+    )
+}
+
+const DOE_TEST_VECTORS: DoeTestVectors = DoeTestVectors {
+    uds: [
+        0x0b21f10f, 0x6963005e, 0x4884d93f, 0x1f91037a, 0x2d37ffe0, 0x3727b5e8, 0xb78b9608,
+        0x7e0e58d2, 0x420ce5ae, 0x4b1f04f8, 0x33b7af81, 0x72156bd8,
+    ],
+    field_entropy: [
+        0x3d75d35e, 0xbc44a31e, 0xad27aee5, 0x75cdd170, 0xe51dcaf4, 0x09c096ae, 0xa70ff448,
+        0x64834722, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    ],
+    expected_test_results: DoeTestResults {
+        hmac_uds_as_key: [
+            0xf1e6eebe, 0x17718892, 0x6b3482a4, 0x6ebdd31a, 0x1a64b1df, 0xf832d618, 0x5d209aeb,
+            0x3e22c6a5, 0xaf18b9da, 0x78767e58, 0x143b5932, 0xb94caa30,
+        ],
+        hmac_uds_as_data: [
+            0x255b90d3, 0xce58a455, 0x72ca9fbb, 0xb6f963b8, 0x8a9e809c, 0x101dadf8, 0x1e35d99c,
+            0x459e5648, 0x44ad895a, 0x6342b793, 0x73b5d82a, 0xa65a9e8a,
+        ],
+        hmac_field_entropy_as_key: [
+            0x4c904ff4, 0xe1b642b7, 0xdaf61d5c, 0x0ae649ad, 0x22411ddd, 0x288e0902, 0x2911effc,
+            0xd76b38f1, 0x0c6ea42e, 0xd1b53612, 0xf77d2515, 0x954d9088,
+        ],
+        hmac_field_entropy_as_data: [
+            0x9f6024ff, 0x68fd825a, 0xbad1ce52, 0x18ed486d, 0x4dd1edc2, 0xeacfeb0b, 0x8d5d8873,
+            0x896be4f5, 0x8f30e6fa, 0xcc1b11c3, 0x0df0bc6e, 0x8fa6b5ba,
+        ],
+    },
+};
+
+#[test]
+fn test_generate_doe_vectors_when_debug_locked() {
+    let vectors = DoeTestVectors::generate(&DoeInput {
+        doe_obf_key: caliptra_hw_model_types::DEFAULT_CPTRA_OBF_KEY,
+        doe_iv: caliptra_drivers_test_bin::DOE_TEST_IV,
+
+        uds_seed: caliptra_hw_model_types::DEFAULT_UDS_SEED,
+        field_entropy_seed: caliptra_hw_model_types::DEFAULT_FIELD_ENTROPY,
+
+        // in debug-locked mode, this defaults to 0
+        keyvault_initial_word_value: 0x0000_0000,
+    });
+    assert_eq!(DOE_TEST_VECTORS, vectors);
+}
+
+#[test]
+fn test_doe_when_debug_locked() {
+    let rom = build_test_rom("doe");
+    let mut model = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state: *SecurityState::from(0)
+                .set_debug_locked(true)
+                .set_device_lifecycle(DeviceLifecycle::Unprovisioned),
+            ..Default::default()
+        },
+        ..Default::default()
+    })
+    .unwrap();
+
+    let txn = model.wait_for_mailbox_receive().unwrap();
+    let test_results = DoeTestResults::read_from(txn.req.data.as_slice()).unwrap();
+    assert_eq!(test_results, DOE_TEST_VECTORS.expected_test_results)
 }
 
 #[test]
diff --git a/hw-latest/verilated/caliptra_verilated.cpp b/hw-latest/verilated/caliptra_verilated.cpp
index c230bda5..d84a4b7e 100644
--- a/hw-latest/verilated/caliptra_verilated.cpp
+++ b/hw-latest/verilated/caliptra_verilated.cpp
@@ -15,6 +15,7 @@ struct caliptra_verilated {
 struct caliptra_verilated* caliptra_verilated_new(struct caliptra_verilated_init_args* init_args) {
   auto result = new caliptra_verilated();
   result->v.security_state = init_args->security_state;
+  memcpy(result->v.cptra_obf_key, init_args->cptra_obf_key, sizeof(result->v.cptra_obf_key));
   return result;
 }
 void caliptra_verilated_destroy(struct caliptra_verilated* model) {
diff --git a/hw-latest/verilated/caliptra_verilated.h b/hw-latest/verilated/caliptra_verilated.h
index d0e2f623..016aef8c 100644
--- a/hw-latest/verilated/caliptra_verilated.h
+++ b/hw-latest/verilated/caliptra_verilated.h
@@ -60,6 +60,7 @@ struct caliptra_verilated_sig_out {
 
 struct caliptra_verilated_init_args {
   uint32_t security_state;
+  uint32_t cptra_obf_key[8];
 };
 
 // Constructs a new model. Model must eventually be destroyed with
diff --git a/hw-latest/verilated/src/bindings/real.rs b/hw-latest/verilated/src/bindings/real.rs
index 5738f146..92f8f4ad 100644
--- a/hw-latest/verilated/src/bindings/real.rs
+++ b/hw-latest/verilated/src/bindings/real.rs
@@ -55,6 +55,7 @@ pub struct caliptra_verilated_sig_out {
 #[derive(Debug, Default, Copy, Clone)]
 pub struct caliptra_verilated_init_args {
     pub security_state: u32,
+    pub cptra_obf_key: [u32; 8usize],
 }
 extern "C" {
     pub fn caliptra_verilated_new(
diff --git a/hw-latest/verilated/src/lib.rs b/hw-latest/verilated/src/lib.rs
index c3b0721d..3ed91df1 100644
--- a/hw-latest/verilated/src/lib.rs
+++ b/hw-latest/verilated/src/lib.rs
@@ -273,7 +273,10 @@ mod tests {
         if !cfg!(feature = "verilator") {
             return;
         }
-        let mut v = CaliptraVerilated::new(InitArgs { security_state: 0 });
+        let mut v = CaliptraVerilated::new(InitArgs {
+            security_state: 0,
+            cptra_obf_key: [0u32; 8],
+        });
 
         std::fs::remove_file("/tmp/caliptra_verilated_test.vcd").ok();
         std::fs::remove_file("/tmp/caliptra_verilated_test2.vcd").ok();
diff --git a/hw-model/test-fw/test_iccm_byte_write.rs b/hw-model/test-fw/test_iccm_byte_write.rs
index 203e2da2..b83f00d1 100644
--- a/hw-model/test-fw/test_iccm_byte_write.rs
+++ b/hw-model/test-fw/test_iccm_byte_write.rs
@@ -25,6 +25,10 @@ main:
     nop
     nop
     nop
+    nop
+    nop
+    nop
+    nop
     ret
 "#
 );
diff --git a/hw-model/test-fw/test_iccm_unaligned_write.rs b/hw-model/test-fw/test_iccm_unaligned_write.rs
index 6c71e1a2..d72f6b67 100644
--- a/hw-model/test-fw/test_iccm_unaligned_write.rs
+++ b/hw-model/test-fw/test_iccm_unaligned_write.rs
@@ -25,6 +25,10 @@ main:
     nop
     nop
     nop
+    nop
+    nop
+    nop
+    nop
     ret
 "#
 );
diff --git a/hw-model/tests/model_tests.rs b/hw-model/tests/model_tests.rs
index 812db083..5fab3cdb 100644
--- a/hw-model/tests/model_tests.rs
+++ b/hw-model/tests/model_tests.rs
@@ -46,7 +46,7 @@ fn test_iccm_byte_write_nmi_failure() {
     let nmi_info = harness::ExtErrorInfo::from(soc_ifc.cptra_fw_extended_error_info().read());
 
     // Exactly where the PC is when the NMI fires is a bit fuzzy...
-    assert!(nmi_info.mepc >= main_addr + 4 && nmi_info.mepc <= main_addr + 12);
+    assert!(nmi_info.mepc >= main_addr + 4 && nmi_info.mepc <= main_addr + 24);
     assert_eq!(nmi_info.mcause, harness::NMI_CAUSE_DBUS_STORE_ERROR);
 }
 
@@ -72,7 +72,7 @@ fn test_iccm_unaligned_write_nmi_failure() {
     let ext_info = harness::ExtErrorInfo::from(soc_ifc.cptra_fw_extended_error_info().read());
 
     // Exactly where the PC is when the NMI fires is a bit fuzzy...
-    assert!(ext_info.mepc >= main_addr + 4 && ext_info.mepc <= main_addr + 12);
+    assert!(ext_info.mepc >= main_addr + 4 && ext_info.mepc <= main_addr + 24);
     assert_eq!(ext_info.mcause, harness::NMI_CAUSE_DBUS_STORE_ERROR);
 }
 
diff --git a/test-harness/src/start.S b/test-harness/src/start.S
index 8d4a6d43..ce83c8c7 100644
--- a/test-harness/src/start.S
+++ b/test-harness/src/start.S
@@ -63,8 +63,16 @@ copy_data:
     j copy_data
 end_copy_data:
     
-    # Set side-effects in MRAC
-    li x1, 0xaaaaaaaa
+    # Setup MRAC
+    # ---------------------------------------------
+    # | Region       | Side Effect |  Cacheable   |      
+    # ---------------------------------------------
+    # | 0x0000_0000  |    0        |      1       |  
+    # | 0x4000_0000  |    0        |      0       |  
+    # | 0x5000_0000  |    0        |      0       |  
+    # | Others       |    1        |      0       |  
+    # ---------------------------------------------
+    li x1, 0xAAAAA0A9
     csrw 0x7c0, x1
 
     # call main entry point