From 1a3573c9d447d520a891e76a963fb636db97d48e Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Thu, 17 Oct 2024 17:13:48 +0200 Subject: [PATCH 1/6] sw-emulator: Fix RRI and DMA - Fix FIFO operations - Make sure hw-mode also has a handle_dma bus operation - Hook up recovery image later, because with real hardware it's also not statically available. - Add more tests for derive handle_dma_fn - Update recovery register interface to have some registers as RW --- hw-model/src/bus_logger.rs | 3 + hw-model/src/lib.rs | 18 ++++- hw-model/src/model_emulated.rs | 5 ++ hw-model/src/model_fpga_realtime.rs | 5 ++ hw-model/src/model_verilated.rs | 5 ++ .../lib/derive/tests/derive_bus_test.rs | 12 +++- sw-emulator/lib/periph/src/dma.rs | 40 +++++++---- sw-emulator/lib/periph/src/recovery.rs | 72 ++++++++++++------- sw-emulator/lib/periph/src/root_bus.rs | 6 +- 9 files changed, 118 insertions(+), 48 deletions(-) diff --git a/hw-model/src/bus_logger.rs b/hw-model/src/bus_logger.rs index 8cdcd4770a..e38077da5d 100644 --- a/hw-model/src/bus_logger.rs +++ b/hw-model/src/bus_logger.rs @@ -130,4 +130,7 @@ impl Bus for BusLogger { fn update_reset(&mut self) { self.bus.update_reset(); } + fn handle_dma(&mut self) { + self.bus.handle_dma(); + } } diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs index facc505513..33ab98281d 100644 --- a/hw-model/src/lib.rs +++ b/hw-model/src/lib.rs @@ -244,6 +244,7 @@ fn trace_path_or_env(trace_path: Option) -> Option { pub struct BootParams<'a> { pub fuses: Fuses, + pub active_mode: bool, pub fw_image: Option<&'a [u8]>, pub initial_dbg_manuf_service_reg: u32, pub initial_repcnt_thresh_reg: Option, @@ -256,6 +257,7 @@ impl<'a> Default for BootParams<'a> { fn default() -> Self { Self { fuses: Default::default(), + active_mode: false, fw_image: Default::default(), initial_dbg_manuf_service_reg: Default::default(), initial_repcnt_thresh_reg: Default::default(), @@ -558,7 +560,11 @@ pub trait HwModel { } writeln!(self.output().logger(), "ready_for_fw is high")?; self.cover_fw_mage(fw_image); - self.upload_firmware(fw_image)?; + if boot_params.active_mode { + self.upload_firmware_rri(fw_image)?; + } else { + self.upload_firmware(fw_image)?; + } } Ok(()) @@ -1096,6 +1102,16 @@ pub trait HwModel { Ok(()) } + /// HW-model function to place the image in rri + fn put_firmware_in_rri(&mut self, firmware: &[u8]) -> Result<(), ModelError>; + + /// Upload fw image to RRI. + fn upload_firmware_rri(&mut self, firmware: &[u8]) -> Result<(), ModelError> { + self.put_firmware_in_rri(firmware)?; + // TODO Add method to inform caliptra to start fetching from the RRI + Ok(()) + } + fn wait_for_mailbox_receive(&mut self) -> Result, ModelError> where Self: Sized, diff --git a/hw-model/src/model_emulated.rs b/hw-model/src/model_emulated.rs index ec665beb43..4c200eba15 100644 --- a/hw-model/src/model_emulated.rs +++ b/hw-model/src/model_emulated.rs @@ -269,4 +269,9 @@ impl crate::HwModel for ModelEmulated { self.cpu.warm_reset(); self.step(); } + + fn put_firmware_in_rri(&mut self, firmware: &[u8]) -> Result<(), ModelError> { + self.cpu.bus.bus.recovery.cms_data = Some(Rc::new(firmware.to_vec())); + Ok(()) + } } diff --git a/hw-model/src/model_fpga_realtime.rs b/hw-model/src/model_fpga_realtime.rs index c822bc0584..55dcb3d104 100644 --- a/hw-model/src/model_fpga_realtime.rs +++ b/hw-model/src/model_fpga_realtime.rs @@ -17,6 +17,7 @@ use std::time::{Duration, Instant}; use uio::{UioDevice, UioError}; use crate::EtrngResponse; +use crate::ModelError; use crate::Output; use crate::{HwModel, SecurityState, TrngMode}; @@ -510,6 +511,10 @@ impl HwModel for ModelFpgaRealtime { .write_volatile(pauser); } } + + fn put_firmware_in_rri(&mut self, firmware: &[u8]) -> Result<(), ModelError> { + todo!() + } } impl ModelFpgaRealtime { diff --git a/hw-model/src/model_verilated.rs b/hw-model/src/model_verilated.rs index 09a278e429..111d9f5876 100644 --- a/hw-model/src/model_verilated.rs +++ b/hw-model/src/model_verilated.rs @@ -3,6 +3,7 @@ use crate::bus_logger::{BusLogger, LogFile, NullBus}; use crate::trace_path_or_env; use crate::EtrngResponse; +use crate::ModelError; use crate::{HwModel, TrngMode}; use caliptra_emu_bus::Bus; use caliptra_emu_types::{RvAddr, RvData, RvSize}; @@ -421,4 +422,8 @@ impl ModelVerilated { self.v.input.itrng_valid = false; } } + + fn put_firmware_in_rri(&mut self, firmware: &[u8]) -> Result<(), ModelError> { + todo!() + } } diff --git a/sw-emulator/lib/derive/tests/derive_bus_test.rs b/sw-emulator/lib/derive/tests/derive_bus_test.rs index da4dded5c1..ee6dd3c168 100644 --- a/sw-emulator/lib/derive/tests/derive_bus_test.rs +++ b/sw-emulator/lib/derive/tests/derive_bus_test.rs @@ -23,6 +23,7 @@ impl From for RvData { #[derive(Bus)] #[poll_fn(poll)] +#[handle_dma_fn(handle_dma)] #[allow(clippy::manual_non_exhaustive)] struct MyBus { pub log: Log, @@ -161,6 +162,10 @@ impl MyBus { fn poll(&self) { write!(self.log.w(), "poll; ").unwrap(); } + + fn handle_dma(&self) { + write!(self.log.w(), "dma; ").unwrap(); + } } #[test] @@ -517,7 +522,7 @@ fn test_write_dispatch() { } #[test] -fn test_poll() { +fn test_poll_and_dma() { let mut bus = MyBus { rom: Ram::new(vec![0u8; 65536]), sram: Ram::new(vec![0u8; 65536]), @@ -547,5 +552,8 @@ fn test_poll() { }; Bus::poll(&mut bus); assert_eq!(bus.log.take(), "poll; "); - assert_eq!(bus.fake.log.take(), "poll()\n") + assert_eq!(bus.fake.log.take(), "poll()\n"); + + Bus::handle_dma(&mut bus); + assert_eq!(bus.log.take(), "dma; "); } diff --git a/sw-emulator/lib/periph/src/dma.rs b/sw-emulator/lib/periph/src/dma.rs index 760b603469..472b1ba91f 100644 --- a/sw-emulator/lib/periph/src/dma.rs +++ b/sw-emulator/lib/periph/src/dma.rs @@ -21,7 +21,7 @@ use caliptra_emu_types::{RvAddr, RvData, RvSize}; use std::cell::RefCell; use std::collections::VecDeque; use std::rc::Rc; -use tock_registers::interfaces::{Readable, Writeable}; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; use tock_registers::register_bitfields; use crate::CaliptraRootBus; @@ -66,7 +66,7 @@ register_bitfields! [ /// Control Control [ GO OFFSET(0) NUMBITS(1) [], - FLUSH OFFSET(0) NUMBITS(1) [], + FLUSH OFFSET(1) NUMBITS(1) [], READ_ROUTE OFFSET(16) NUMBITS(2) [ DISABLE = 0b00, MAILBOX = 0b01, @@ -177,7 +177,7 @@ impl DmaRegs { pub fn new(clock: &Clock) -> Self { Self { name: ReadOnlyRegister::new(Self::NAME), - capabilities: ReadOnlyRegister::new(Self::FIFO_SIZE as u32), // MAX FIFO DEPTH + capabilities: ReadOnlyRegister::new(Self::FIFO_SIZE as u32 - 1), // MAX FIFO DEPTH control: ReadWriteRegister::new(0), status0: ReadOnlyRegister::new(0), status1: ReadOnlyRegister::new(0), @@ -206,6 +206,7 @@ impl DmaRegs { if self.control.reg.is_set(Control::FLUSH) { self.fifo.clear(); self.status0.reg.write(Status0::DMA_FSM_PRESENT_STATE::IDLE); + self.control.reg.set(0); } if self.control.reg.is_set(Control::GO) { @@ -222,7 +223,7 @@ impl DmaRegs { ); self.status0 .reg - .write(Status0::DMA_FSM_PRESENT_STATE::WAIT_DATA); + .write(Status0::BUSY::SET + Status0::DMA_FSM_PRESENT_STATE::WAIT_DATA); } Ok(()) @@ -238,17 +239,18 @@ impl DmaRegs { pub fn on_read_data(&mut self, size: RvSize) -> Result { let range = 0..size as usize; - let bytes = range.fold(0, |mut acc, b| { - acc |= self.fifo.pop_front().unwrap_or( + let bytes: RvData = range.fold(0, |mut acc, b| { + acc |= (self.fifo.pop_front().unwrap_or( // self.status0 // .reg // .write(Status0::DMA_FSM_PRESENT_STATE::ERROR); // TODO write status in interrupt 0, - ) << (8 * b); + ) as RvData) + << (8 * b); acc }); - Ok(bytes as RvData) + Ok(bytes) } fn write_to_mailbox(&mut self, data: Vec, root_bus: &mut CaliptraRootBus) { @@ -261,11 +263,11 @@ impl DmaRegs { .write_dlen(RvSize::Word, self.byte_count.reg.get()) .unwrap(); - assert_eq!(data.len(), self.byte_count.reg.get() as usize); + // assert_eq!(data.len(), self.byte_count.reg.get() as usize); data.chunks(RvSize::Word as usize).for_each(|c| { mailbox_regs - .write_din(RvSize::Word, u32::from_ne_bytes(c.try_into().unwrap())) + .write_din(RvSize::Word, u32::from_le_bytes(c.try_into().unwrap())) .unwrap() }); } @@ -278,7 +280,11 @@ impl DmaRegs { let read_data = // Special case for putting stuff image in the mailbox from recovery register interface if read_addr == Self::RRI_BASE + Self::RRI_FIFO_OFFSET && read_addr_fixed { - (*root_bus.recovery.cms_data).clone() + if let Some(data) = root_bus.recovery.cms_data.clone() { + (*data).clone() + } else { + vec![] + } } else { let range = read_addr..read_addr + self.byte_count.reg.get(); range @@ -290,14 +296,13 @@ impl DmaRegs { .unwrap() .to_le_bytes() }).collect() - }; match self.control.reg.read_as_enum(Control::READ_ROUTE) { Some(Control::READ_ROUTE::Value::MAILBOX) => { self.write_to_mailbox(read_data, root_bus); } Some(Control::READ_ROUTE::Value::AHB_FIFO) => { - if self.fifo.len() + read_data.len() <= Self::FIFO_SIZE { + if self.fifo.len() + read_data.len() > Self::FIFO_SIZE { self.status0 .reg .write(Status0::DMA_FSM_PRESENT_STATE::ERROR); @@ -307,6 +312,9 @@ impl DmaRegs { } else { read_data.iter().for_each(|b| self.fifo.push_back(*b)); } + self.status0 + .reg + .modify(Status0::FIFO_DEPTH.val(self.fifo.len() as u32)); } Some(Control::READ_ROUTE::Value::AXI_WR) => { todo!() @@ -343,7 +351,9 @@ impl DmaRegs { _ => {} } - self.status0.reg.write(Status0::DMA_FSM_PRESENT_STATE::DONE); + self.status0 + .reg + .modify(Status0::BUSY::CLEAR + Status0::DMA_FSM_PRESENT_STATE::DONE); } } @@ -370,6 +380,6 @@ mod tests { let mut dma = Dma::new(&clock); let capabilities = dma.read(RvSize::Word, CAPABILITIES_OFFSET).unwrap(); - assert_eq!(capabilities, 0x1000); + assert_eq!(capabilities, 0xfff); } } diff --git a/sw-emulator/lib/periph/src/recovery.rs b/sw-emulator/lib/periph/src/recovery.rs index ebc6f0c85c..d8575eecca 100644 --- a/sw-emulator/lib/periph/src/recovery.rs +++ b/sw-emulator/lib/periph/src/recovery.rs @@ -202,7 +202,7 @@ pub struct RecoveryRegisterInterface { // #[register_array(offset = 0x00)] // prot_cap_magic: [u8; 8], #[register(offset = 0x08)] - prot_cap_version: ReadOnlyRegister, + prot_cap_version: ReadWriteRegister, #[register(offset = 0x0c)] prot_cap_cms_timing: ReadOnlyRegister, @@ -217,7 +217,7 @@ pub struct RecoveryRegisterInterface { // #[register_array(offset = 0x12)] // device_id: [u8; 22], #[register(offset = 0x28)] - device_info: ReadOnlyRegister, + device_info: ReadWriteRegister, // #[register(offset = 0x2c)] // hearthbeat: ReadOnlyRegister, // TODO do we need hearthbeat? @@ -231,7 +231,7 @@ pub struct RecoveryRegisterInterface { recovery_control: ReadWriteRegister, #[register(offset = 0x38)] - recovery_status: ReadOnlyRegister, + recovery_status: ReadWriteRegister, #[register(offset = 0x3c)] hw_status: ReadOnlyRegister, @@ -261,7 +261,7 @@ pub struct RecoveryRegisterInterface { #[register(offset = 0x6c, read_fn = indirect_fifo_data_read)] indirect_fifo_data: ReadOnlyRegister, // indirect_fifo_data: ReadWriteRegisterArray, // TODO should be larger size but for FW with only read use just one dword - pub cms_data: Rc>, // TODO Multiple images? + pub cms_data: Option>>, // TODO Multiple images? } impl RecoveryRegisterInterface { @@ -269,10 +269,10 @@ impl RecoveryRegisterInterface { const MAJOR_VERSION: u32 = 0x01; const MINOR_VERSION: u32 = 0x00; - pub fn new(cms_data: Rc>) -> Self { + pub fn new() -> Self { Self { // prot_cap_magic: Self::MAGIC, - prot_cap_version: ReadOnlyRegister::new( + prot_cap_version: ReadWriteRegister::new( VersionCapabilities::MAJOR.val(Self::MAJOR_VERSION).value | VersionCapabilities::MINOR.val(Self::MINOR_VERSION).value, ), @@ -284,7 +284,7 @@ impl RecoveryRegisterInterface { // device_id_descriptor_type: ReadOnlyRegister::new(DidType::TYPE::UUID.value), // TODO // device_id_vendor_id_string_length: ReadOnlyRegister::new(0), // not supported // device_id: [0; 22], - device_info: ReadOnlyRegister::new( + device_info: ReadWriteRegister::new( DeviceInfo::STATUS::DeviceHealthy.value | DeviceInfo::ERROR::NoProtocolError.value | DeviceInfo::RECOVERY_REASON::NoBootFailureDetected.value, @@ -293,7 +293,7 @@ impl RecoveryRegisterInterface { // vendor_status_length: ReadOnlyRegister::new(0), device_reset: ReadWriteRegister::new(0), recovery_control: ReadWriteRegister::new(0), - recovery_status: ReadOnlyRegister::new(0), + recovery_status: ReadWriteRegister::new(0), hw_status: ReadOnlyRegister::new(0), // TODO indirect_fifo_ctrl: ReadWriteRegister::new(0), indirect_fifo_image_size: ReadOnlyRegister::new(0), @@ -303,7 +303,7 @@ impl RecoveryRegisterInterface { indirect_size: ReadOnlyRegister::new(FIFO_SIZE_DWORD.try_into().unwrap()), max_transfer_window: ReadOnlyRegister::new(0), indirect_fifo_data: ReadOnlyRegister::new(0), - cms_data, + cms_data: None, } } @@ -311,6 +311,14 @@ impl RecoveryRegisterInterface { if size != RvSize::Word { return Err(BusError::LoadAccessFault); } + let image = match &self.cms_data { + None => { + println!("No image set in RRI"); + return Ok(0xffff_ffff); + } + Some(x) => x, + }; + let cms = self.indirect_fifo_ctrl.reg.read(IndirectCtrl::CMS); if cms != 0 { println!("CMS {cms} not supported"); @@ -319,7 +327,7 @@ impl RecoveryRegisterInterface { let read_index = self.read_index.reg.get(); let address = read_index * 4; - let image_len: u32 = self.cms_data.len().try_into().unwrap(); + let image_len = image.len().try_into().unwrap(); if address >= image_len { return Ok(0xffff_ffff); }; @@ -331,7 +339,7 @@ impl RecoveryRegisterInterface { let address: usize = address.try_into().unwrap(); let range = address..(address + 4); - let data = &self.cms_data[range]; + let data = &image[range]; self.read_index.reg.set(read_index + 1); Ok(u32::from_le_bytes(data.try_into().unwrap())) } @@ -343,29 +351,42 @@ impl RecoveryRegisterInterface { } let load: ReadWriteRegister = ReadWriteRegister::new(val); if load.reg.is_set(IndirectCtrl::RESET) { - let cms = load.reg.read(IndirectCtrl::CMS); - if cms != 0 { + if let Some(image) = &self.cms_data { + let cms = load.reg.read(IndirectCtrl::CMS); + if cms != 0 { + self.indirect_fifo_status + .reg + .set(IndirectStatus::REGION_TYPE::UnsupportedRegion.value); + } else { + self.indirect_fifo_image_size + .reg + .set(image.len() as u32 / 4); // DWORD + self.indirect_fifo_status + .reg + .set(IndirectStatus::REGION_TYPE::CodeSpaceRecovery.value); + } + self.write_index.reg.set(0); + self.read_index.reg.set(0); self.indirect_fifo_status .reg - .set(IndirectStatus::REGION_TYPE::UnsupportedRegion.value); + .modify(IndirectStatus::FIFO_EMPTY::CLEAR + IndirectStatus::FIFO_FULL::CLEAR); } else { - self.indirect_fifo_image_size - .reg - .set(self.cms_data.len().try_into().unwrap()); + println!("No Image in RRI"); self.indirect_fifo_status .reg - .set(IndirectStatus::REGION_TYPE::CodeSpaceRecovery.value); + .set(IndirectStatus::REGION_TYPE::UnsupportedRegion.value); } - self.write_index.reg.set(0); - self.read_index.reg.set(0); - self.indirect_fifo_status - .reg - .modify(IndirectStatus::FIFO_EMPTY::CLEAR + IndirectStatus::FIFO_FULL::CLEAR); } Ok(()) } } +impl Default for RecoveryRegisterInterface { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use caliptra_emu_bus::Bus; @@ -383,14 +404,15 @@ mod tests { fn test_get_image() { let image = Rc::new(vec![0xab; 512]); let image_len = image.len(); - let mut rri = RecoveryRegisterInterface::new(image.clone()); + let mut rri = RecoveryRegisterInterface::new(); + rri.cms_data = Some(image.clone()); // Reset rri.write(RvSize::Word, INDIRECT_FIFO_CTRL, INDIRECT_FIFO_RESET) .unwrap(); let image_size = rri.read(RvSize::Word, INDIRECT_FIFO_IMAGE_SIZE).unwrap(); - assert_eq!(image_len, image_size.try_into().unwrap()); + assert_eq!(image_len, image_size as usize * 4); let mut read_image = Vec::new(); while rri.read(RvSize::Word, INDIRECT_FIFO_STATUS).unwrap() & 1 == 0 { diff --git a/sw-emulator/lib/periph/src/root_bus.rs b/sw-emulator/lib/periph/src/root_bus.rs index 281a1c8d82..f348542a16 100644 --- a/sw-emulator/lib/periph/src/root_bus.rs +++ b/sw-emulator/lib/periph/src/root_bus.rs @@ -27,7 +27,6 @@ use caliptra_emu_cpu::{Pic, PicMmioRegisters}; use caliptra_emu_derive::Bus; use caliptra_hw_model_types::{EtrngResponse, RandomEtrngResponses, RandomNibbles, SecurityState}; use std::path::PathBuf; -use std::rc::Rc; use tock_registers::registers::InMemoryRegister; /// Default Deobfuscation engine key @@ -210,7 +209,6 @@ impl From> for ActionCb { /// Caliptra Root Bus Arguments pub struct CaliptraRootBusArgs { pub rom: Vec, - pub recovery_image: Rc>, pub log_dir: PathBuf, // The security state wires provided to caliptra_top pub security_state: SecurityState, @@ -233,7 +231,6 @@ impl Default for CaliptraRootBusArgs { fn default() -> Self { Self { rom: Default::default(), - recovery_image: Default::default(), log_dir: Default::default(), security_state: Default::default(), tb_services_cb: Default::default(), @@ -326,7 +323,6 @@ impl CaliptraRootBus { let iccm = Iccm::new(clock); let pic = Pic::new(); let itrng_nibbles = args.itrng_nibbles.take(); - let recovery_image = args.recovery_image.clone(); let soc_reg = SocRegistersInternal::new(clock, mailbox.clone(), iccm.clone(), &pic, args); if !soc_reg.is_debug_locked() { // When debug is possible, the key-vault is initialized with a debug value... @@ -345,7 +341,7 @@ impl CaliptraRootBus { sha512, sha256: HashSha256::new(clock), ml_dsa87: MlDsa87::new(clock), - recovery: RecoveryRegisterInterface::new(recovery_image), + recovery: RecoveryRegisterInterface::new(), iccm, dccm: Ram::new(vec![0; Self::DCCM_SIZE]), uart: Uart::new(), From 976a273208c67800fa7f14a01bb8bb3db53cd6cd Mon Sep 17 00:00:00 2001 From: Arthur Heymans Date: Fri, 18 Oct 2024 19:43:56 +0200 Subject: [PATCH 2/6] Add DMA driver: This adds a DMA driver which can read and write to registers and put a payload into the mailbox. To be able to read that content from the mailbox a new helper function is added. Signed-off-by: Arthur Heymans --- builder/src/firmware.rs | 6 + drivers/src/dma.rs | 253 ++++++++++++++++++ drivers/src/lib.rs | 2 + drivers/test-fw/Cargo.toml | 4 + drivers/test-fw/src/bin/dma_tests.rs | 78 ++++++ .../tests/drivers_integration_tests/main.rs | 20 ++ error/src/lib.rs | 7 + sw-emulator/app/src/main.rs | 1 - 8 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 drivers/src/dma.rs create mode 100644 drivers/test-fw/src/bin/dma_tests.rs diff --git a/builder/src/firmware.rs b/builder/src/firmware.rs index 503a0b5238..83415d1bb7 100644 --- a/builder/src/firmware.rs +++ b/builder/src/firmware.rs @@ -159,6 +159,11 @@ pub mod driver_tests { features: &["emu"], }; + pub const DMA: FwId = FwId { + bin_name: "axi_dma_tests", + ..BASE_FWID + }; + pub const DOE: FwId = FwId { bin_name: "doe", ..BASE_FWID @@ -404,6 +409,7 @@ pub const REGISTERED_FW: &[&FwId] = &[ &hw_model_tests::TEST_DCCM_DOUBLE_BIT_ECC, &hw_model_tests::TEST_UNITIALIZED_READ, &hw_model_tests::TEST_PCR_EXTEND, + &driver_tests::DMA, &driver_tests::DOE, &driver_tests::ECC384, &driver_tests::ECC384_SIGN_VALIDATION_FAILURE, diff --git a/drivers/src/dma.rs b/drivers/src/dma.rs new file mode 100644 index 0000000000..73242d4a99 --- /dev/null +++ b/drivers/src/dma.rs @@ -0,0 +1,253 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + dma.rs + +Abstract: + + File contains API for DMA Widget operations + +--*/ + +use caliptra_error::{CaliptraError, CaliptraResult}; +use caliptra_registers::axi_dma::{ + enums::{RdRouteE, WrRouteE}, + AxiDmaReg, +}; +use zerocopy::AsBytes; + +pub enum DmaReadTarget { + Mbox, + AhbFifo, + AxiWr(usize), +} + +pub struct DmaReadTransaction { + pub read_addr: usize, + pub fixed_addr: bool, + pub length: u32, + pub target: DmaReadTarget, +} + +pub enum DmaWriteOrigin { + Mbox, + AhbFifo, + AxiRd(usize), +} + +pub struct DmaWriteTransaction { + pub write_addr: usize, + pub fixed_addr: bool, + pub length: u32, + pub origin: DmaWriteOrigin, +} + +/// Dma Widget +pub struct Dma { + dma: AxiDmaReg, +} + +impl Dma { + pub fn new(dma: AxiDmaReg) -> Self { + Self { dma } + } + + fn flush(&mut self) { + let dma = self.dma.regs_mut(); + + dma.ctrl().write(|c| c.flush(true)); + + // Wait till we're not busy and have no errors + while { + let status0 = dma.status0().read(); + status0.busy() || status0.error() + } {} + } + + fn setup_dma_read(&mut self, read_transaction: DmaReadTransaction) { + let dma = self.dma.regs_mut(); + + let read_addr: usize = read_transaction.read_addr; + #[cfg(target_pointer_width = "64")] + dma.src_addr_h().write(|_| (read_addr >> 32) as u32); + dma.src_addr_l().write(|_| (read_addr & 0xffff_ffff) as u32); + + if let DmaReadTarget::AxiWr(target_addr) = read_transaction.target { + #[cfg(target_pointer_width = "64")] + dma.dst_addr_h().write(|_| (target_addr >> 32) as u32); + dma.dst_addr_l() + .write(|_| (target_addr & 0xffff_ffff) as u32); + } + + dma.ctrl().modify(|c| { + c.rd_route(|_| match read_transaction.target { + DmaReadTarget::Mbox => RdRouteE::Mbox, + DmaReadTarget::AhbFifo => RdRouteE::AhbFifo, + DmaReadTarget::AxiWr(_) => RdRouteE::AxiWr, + }) + .rd_fixed(read_transaction.fixed_addr) + .wr_route(|_| match read_transaction.target { + DmaReadTarget::AxiWr(_) => WrRouteE::AxiRd, + _ => WrRouteE::Disable, + }) + }); + + dma.byte_count().write(|_| read_transaction.length); + } + + fn setup_dma_write(&mut self, write_transaction: DmaWriteTransaction) { + let dma = self.dma.regs_mut(); + + let write_addr = write_transaction.write_addr; + #[cfg(target_pointer_width = "64")] + dma.dst_addr_h().write(|_| (write_addr >> 32) as u32); + dma.dst_addr_l() + .write(|_| (write_addr & 0xffff_ffff) as u32); + + if let DmaWriteOrigin::AxiRd(origin_addr) = write_transaction.origin { + #[cfg(target_pointer_width = "64")] + dma.dst_addr_h().write(|_| (origin_addr >> 32) as u32); + dma.dst_addr_l() + .write(|_| (origin_addr & 0xffff_ffff) as u32); + } + + dma.ctrl().modify(|c| { + c.wr_route(|_| match write_transaction.origin { + DmaWriteOrigin::Mbox => WrRouteE::Mbox, + DmaWriteOrigin::AhbFifo => WrRouteE::AhbFifo, + DmaWriteOrigin::AxiRd(_) => WrRouteE::AxiRd, + }) + .wr_fixed(write_transaction.fixed_addr) + .rd_route(|_| match write_transaction.origin { + DmaWriteOrigin::AxiRd(_) => RdRouteE::AxiWr, + _ => RdRouteE::Disable, + }) + }); + + dma.byte_count().write(|_| write_transaction.length); + } + + pub fn dma_read_fifo(&mut self, read_data: &mut [u8]) -> CaliptraResult<()> { + let dma = self.dma.regs_mut(); + + let status = dma.status0().read(); + + if read_data.len() > status.fifo_depth() as usize { + return Err(CaliptraError::DRIVER_DMA_FIFO_UNDERRUN); + } + + read_data.chunks_mut(4).for_each(|word| { + let ptr = dma.read_data().ptr as *mut u8; + // Reg only exports u32 writes but we need finer grained access + unsafe { + ptr.copy_to_nonoverlapping(word.as_mut_ptr(), word.len()); + } + }); + + Ok(()) + } + + fn dma_write_fifo(&mut self, write_data: &[u8]) -> CaliptraResult<()> { + let dma = self.dma.regs_mut(); + + let max_fifo_depth = dma.cap().read().fifo_max_depth(); + let current_fifo_depth = dma.status0().read().fifo_depth(); + + if write_data.len() as u32 > max_fifo_depth - current_fifo_depth { + return Err(CaliptraError::DRIVER_DMA_FIFO_OVERRUN); + } + + write_data.chunks(4).for_each(|word| { + let ptr = dma.write_data().ptr as *mut u8; + // Reg only exports u32 writes but we need finer grained access + unsafe { + ptr.copy_from_nonoverlapping(word.as_ptr(), word.len()); + } + }); + + Ok(()) + } + + fn do_transaction(&mut self) -> CaliptraResult<()> { + let dma = self.dma.regs_mut(); + + let status0 = dma.status0().read(); + if status0.busy() { + return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ALREADY_BUSY); + } + + if status0.error() { + return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR); + } + + dma.ctrl().modify(|c| c.go(true)); + + while dma.status0().read().busy() { + if dma.status0().read().error() { + return Err(CaliptraError::DRIVER_DMA_TRANSACTION_ERROR); + } + } + + Ok(()) + } + + pub fn read_dword(&mut self, read_addr: usize) -> CaliptraResult { + let mut read_val: u32 = 0; + + self.flush(); + + let read_transaction = DmaReadTransaction { + read_addr, + fixed_addr: false, + length: core::mem::size_of::() as u32, + target: DmaReadTarget::AhbFifo, + }; + + self.setup_dma_read(read_transaction); + self.do_transaction()?; + self.dma_read_fifo(read_val.as_bytes_mut())?; + Ok(read_val) + } + + pub fn write_dword(&mut self, write_addr: usize, write_val: u32) -> CaliptraResult<()> { + self.flush(); + + let write_transaction = DmaWriteTransaction { + write_addr, + fixed_addr: false, + length: core::mem::size_of::() as u32, + origin: DmaWriteOrigin::AhbFifo, + }; + self.dma_write_fifo(write_val.as_bytes())?; + self.setup_dma_write(write_transaction); + self.do_transaction()?; + Ok(()) + } + + pub fn transfer_payload_to_mbox( + &mut self, + read_addr: usize, + payload_len_bytes: u32, + fixed_addr: bool, + block_size: u32, + ) -> CaliptraResult<()> { + self.flush(); + + let read_transaction = DmaReadTransaction { + read_addr, + fixed_addr, + length: payload_len_bytes, + target: DmaReadTarget::Mbox, + }; + self.setup_dma_read(read_transaction); + self.dma + .regs_mut() + .block_size() + .write(|f| f.size(block_size)); + self.do_transaction()?; + Ok(()) + } +} diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index f3ed62283b..9ae82bbee4 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -21,6 +21,7 @@ mod wait; mod bounded_address; mod csrng; mod data_vault; +mod dma; mod doe; mod ecc384; mod error_reporter; @@ -61,6 +62,7 @@ pub use csrng::{Csrng, HealthFailCounts as CsrngHealthFailCounts, Seed as CsrngS pub use data_vault::{ ColdResetEntry4, ColdResetEntry48, DataVault, WarmResetEntry4, WarmResetEntry48, }; +pub use dma::{Dma, DmaReadTarget, DmaReadTransaction, DmaWriteOrigin, DmaWriteTransaction}; pub use doe::DeobfuscationEngine; pub use ecc384::{ Ecc384, Ecc384PrivKeyIn, Ecc384PrivKeyOut, Ecc384PubKey, Ecc384Result, Ecc384Scalar, diff --git a/drivers/test-fw/Cargo.toml b/drivers/test-fw/Cargo.toml index e913821aab..7bb61459ed 100644 --- a/drivers/test-fw/Cargo.toml +++ b/drivers/test-fw/Cargo.toml @@ -174,3 +174,7 @@ name = "trng_driver_responder" path = "src/bin/trng_driver_responder.rs" required-features = ["riscv"] +[[bin]] +name = "axi_dma_tests" +path = "src/bin/dma_tests.rs" +required-features = ["riscv"] diff --git a/drivers/test-fw/src/bin/dma_tests.rs b/drivers/test-fw/src/bin/dma_tests.rs new file mode 100644 index 0000000000..d45eea6369 --- /dev/null +++ b/drivers/test-fw/src/bin/dma_tests.rs @@ -0,0 +1,78 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + dma_tests.rs + +Abstract: + + File contains test cases for DMA driver API + +--*/ + +#![no_std] +#![no_main] + +use caliptra_drivers::{Dma, Mailbox}; +use caliptra_registers::{axi_dma::AxiDmaReg, ecc::EccReg, mbox::MboxCsr}; +use caliptra_test_harness::test_suite; + +// We test that reading from a periph into the fifo works by reading ECC name +fn test_dma_read_from_periph() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let ecc_regs = unsafe { EccReg::new() }; + let ecc_name = ecc_regs.regs().name().ptr(); + + let dword = dma.read_dword(ecc_name as usize).unwrap(); + assert_eq!(dword.to_ne_bytes(), [0x70, 0x63, 0x65, 0x73]); // secp +} + +// We test that reading from a periph into the fifo works by reading ECC name +fn test_dma_write_to_periph() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let ecc_regs = unsafe { EccReg::new() }; + let ecc_iv = ecc_regs.regs().iv().at(0).ptr; + + let data: u32 = 0xdead_beef; + + dma.write_dword(ecc_iv as usize, data).unwrap(); + let dword = dma.read_dword(ecc_iv as usize).unwrap(); + assert_eq!(dword, data); +} + +fn test_read_rri_to_mailbox() { + let mut dma = unsafe { Dma::new(AxiDmaReg::new()) }; + + let test_image = [0xab; 512]; + let block_size = 256; + + // TODO use i3c generated regs + let rri_regs = 0x1003_806c; + + // Get mailbox lock + let mut mbox_driver = unsafe { Mailbox::new(MboxCsr::new()) }; + let mut txn = mbox_driver.try_start_send_txn().unwrap(); + txn.send_request(0xdead_beef, b"").unwrap(); + + dma.transfer_payload_to_mbox(rri_regs, test_image.len() as u32, true, block_size) + .unwrap(); + + // TODO need a way to get DLEN and raw mailbox content from txn + // let mut mbox_driver = unsafe { Mailbox::new(MboxCsr::new()) }; + // let rxn = mbox_driver.try_start_recv_txn().unwrap(); + // assert_eq!(rxn.dlen(), test_image.len() as u32); + // assert_eq!( + // rxn.raw_mailbox_contents().get(..test_image.len()).unwrap(), + // test_image + // ); +} + +test_suite! { + test_dma_read_from_periph, + test_dma_write_to_periph, + test_read_rri_to_mailbox, +} diff --git a/drivers/tests/drivers_integration_tests/main.rs b/drivers/tests/drivers_integration_tests/main.rs index 30bffd44ba..abbb0681b3 100644 --- a/drivers/tests/drivers_integration_tests/main.rs +++ b/drivers/tests/drivers_integration_tests/main.rs @@ -1124,3 +1124,23 @@ fn test_uart() { fn test_mailbox_txn_drop() { run_driver_test(&firmware::driver_tests::MBOX_SEND_TXN_DROP); } + +#[test] +fn test_dma() { + let rom = caliptra_builder::build_firmware_rom(&firmware::driver_tests::DMA).unwrap(); + let recovery_image = &[0xab; 512]; + + let init_params = InitParams { + rom: &rom, + ..default_init_params() + }; + + let boot_params = BootParams { + ..Default::default() + }; + + let mut model = caliptra_hw_model::new_unbooted(init_params).unwrap(); + model.put_firmware_in_rri(recovery_image).unwrap(); + model.boot(boot_params).unwrap(); + model.step_until_exit_success().unwrap(); +} diff --git a/error/src/lib.rs b/error/src/lib.rs index 9bf495fca1..4e87310ca0 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -321,6 +321,13 @@ impl CaliptraError { pub const DRIVER_HANDOFF_INVALID_WARM_RESET_ENTRY48: CaliptraError = CaliptraError::new_const(0x000D104); + /// DMA driver Errors + pub const DRIVER_DMA_TRANSACTION_ALREADY_BUSY: CaliptraError = + CaliptraError::new_const(0x0000f000); + pub const DRIVER_DMA_TRANSACTION_ERROR: CaliptraError = CaliptraError::new_const(0x0000f001); + pub const DRIVER_DMA_FIFO_UNDERRUN: CaliptraError = CaliptraError::new_const(0x0000f002); + pub const DRIVER_DMA_FIFO_OVERRUN: CaliptraError = CaliptraError::new_const(0x0000f003); + /// Runtime Errors pub const RUNTIME_INTERNAL: CaliptraError = CaliptraError::new_const(0x000E0001); pub const RUNTIME_UNIMPLEMENTED_COMMAND: CaliptraError = CaliptraError::new_const(0x000E0002); diff --git a/sw-emulator/app/src/main.rs b/sw-emulator/app/src/main.rs index 6ae42ccd76..81534e88ab 100644 --- a/sw-emulator/app/src/main.rs +++ b/sw-emulator/app/src/main.rs @@ -269,7 +269,6 @@ fn main() -> io::Result<()> { let bus_args = CaliptraRootBusArgs { rom: rom_buffer, - recovery_image: current_fw_buf.clone(), log_dir: args_log_dir.clone(), tb_services_cb: TbServicesCb::new(move |val| match val { 0x01 => exit(0xFF), From 1515e80b1e3f6c14cb022b3884b87d61d0ca2015 Mon Sep 17 00:00:00 2001 From: Vishal Mhatre Date: Mon, 21 Oct 2024 16:11:39 +0530 Subject: [PATCH 3/6] [feat] ROM changes to retrieve firmware image from Recovery Interface --- api/src/mailbox.rs | 3 + drivers/src/dma.rs | 3 + drivers/src/mailbox.rs | 7 + error/src/lib.rs | 2 + rom/dev/src/flow/cold_reset/fw_processor.rs | 164 +++++++++++++++++--- rom/dev/src/rom_env.rs | 13 +- 6 files changed, 166 insertions(+), 26 deletions(-) diff --git a/api/src/mailbox.rs b/api/src/mailbox.rs index 6eb6c0dcc5..eb5e7a84cf 100644 --- a/api/src/mailbox.rs +++ b/api/src/mailbox.rs @@ -47,6 +47,9 @@ impl CommandId { // The authorize and stash command. pub const AUTHORIZE_AND_STASH: Self = Self(0x4154_5348); // "ATSH" + + // The download firmware from recovery interface command. + pub const RI_DOWNLOAD_FIRMWARE: Self = Self(0x5249_4644); // "RIFD" } impl From for CommandId { diff --git a/drivers/src/dma.rs b/drivers/src/dma.rs index 73242d4a99..91c96e9dcb 100644 --- a/drivers/src/dma.rs +++ b/drivers/src/dma.rs @@ -243,6 +243,9 @@ impl Dma { target: DmaReadTarget::Mbox, }; self.setup_dma_read(read_transaction); + + // [TODO] Acquire the mailbox lock here. + self.dma .regs_mut() .block_size() diff --git a/drivers/src/mailbox.rs b/drivers/src/mailbox.rs index e6ee0e2569..965ef6f92d 100644 --- a/drivers/src/mailbox.rs +++ b/drivers/src/mailbox.rs @@ -86,6 +86,13 @@ impl Mailbox { } } + pub fn fake_recv_txn(&mut self) -> MailboxRecvTxn { + MailboxRecvTxn { + state: MailboxOpState::Execute, + mbox: &mut self.mbox, + } + } + /// Lets the caller peek into the mailbox without touching the transaction. pub fn peek_recv(&mut self) -> Option { let mbox = self.mbox.regs(); diff --git a/error/src/lib.rs b/error/src/lib.rs index 4e87310ca0..95dbea45c0 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -507,6 +507,8 @@ impl CaliptraError { pub const FW_PROC_MAILBOX_PROCESS_FAILURE: CaliptraError = CaliptraError::new_const(0x01020007); pub const FW_PROC_MAILBOX_STASH_MEASUREMENT_MAX_LIMIT: CaliptraError = CaliptraError::new_const(0x01020008); + pub const FW_PROC_MAILBOX_FW_LOAD_CMD_IN_ACTIVE_MODE: CaliptraError = + CaliptraError::new_const(0x01020009); /// FMC Alias Layer : Certificate Verification Failure. pub const FMC_ALIAS_CERT_VERIFY: CaliptraError = CaliptraError::new_const(0x01030001); diff --git a/rom/dev/src/flow/cold_reset/fw_processor.rs b/rom/dev/src/flow/cold_reset/fw_processor.rs index 059ac246f6..c702e504a5 100644 --- a/rom/dev/src/flow/cold_reset/fw_processor.rs +++ b/rom/dev/src/flow/cold_reset/fw_processor.rs @@ -11,6 +11,8 @@ Abstract: File contains the code to download and validate the firmware. --*/ +#![allow(dead_code)] + #[cfg(feature = "fake-rom")] use crate::flow::fake::FakeRomImageVerificationEnv; use crate::fuse::log_fuse_data; @@ -37,10 +39,30 @@ use caliptra_image_types::{ImageManifest, IMAGE_BYTE_SIZE}; use caliptra_image_verify::{ImageVerificationInfo, ImageVerificationLogInfo, ImageVerifier}; use caliptra_kat::KatsEnv; use caliptra_x509::{NotAfter, NotBefore}; -use core::mem::ManuallyDrop; +use core::mem::{size_of, ManuallyDrop}; use zerocopy::{AsBytes, LayoutVerified}; use zeroize::Zeroize; +// [TODO] Read this from HW. +const ACTIVE_MODE: bool = true; +// [TODO] Read this from HW. +const RRI_BASE: u32 = 0x1003_8000; +const PAYLOAD_AVAILABLE: bool = true; +const RECOVERY_INTERFACE_ADDRESS: u32 = 0x0; +const PROT_CAP_OFFSET: u32 = 0x0; +const PROT_CAP_AGENT_BYTE_10_OFFSET: u32 = PROT_CAP_OFFSET + 10; +const PROT_CAP_AGENT_BYTE_11_OFFSET: u32 = PROT_CAP_OFFSET + 11; +const DEVICE_ID_OFFSET: u32 = PROT_CAP_OFFSET + 0x10; +const DEVICE_STATUS_OFFSET: u32 = PROT_CAP_OFFSET + 0x28; +const DEVICE_RESET_OFFSET: u32 = PROT_CAP_OFFSET + 0x30; +const RECOVERY_CTRL_OFFSET: u32 = PROT_CAP_OFFSET + 0x34; +const RECOVERY_STATUS_OFFSET: u32 = PROT_CAP_OFFSET + 0x38; +const HW_STATUS_OFFSET: u32 = PROT_CAP_OFFSET + 0x3C; +const INDIRECT_FIFO_CTRL_OFFSET: u32 = PROT_CAP_OFFSET + 0x40; +const INDIRECT_FIFO_STATUS_OFFSET: u32 = PROT_CAP_OFFSET + 0x48; +const INDIRECT_FIFO_DATA_OFFSET: u32 = PROT_CAP_OFFSET + 0x6C; +const RECOVERY_DMA_BLOCK_SIZE_BYTES: u32 = 256; + #[derive(Debug, Default, Zeroize)] pub struct FwProcInfo { pub fmc_cert_valid_not_before: NotBefore, @@ -85,10 +107,11 @@ impl FirmwareProcessor { sha_acc_lock_state: ShaAccLockState::NotAcquired, }; // Process mailbox commands. - let mut txn = Self::process_mailbox_commands( + let (mut txn, image_size_bytes) = Self::process_mailbox_commands( &mut env.soc_ifc, &mut env.mbox, &mut env.pcr_bank, + &mut env.dma, &mut kats_env, env.persistent_data.get_mut(), )?; @@ -115,7 +138,7 @@ impl FirmwareProcessor { }; // Verify the image - let info = Self::verify_image(&mut venv, manifest, txn.dlen()); + let info = Self::verify_image(&mut venv, manifest, image_size_bytes); let info = okref(&info)?; Self::update_fuse_log(&mut env.persistent_data.get_mut().fuse_log, &info.log_info)?; @@ -174,9 +197,10 @@ impl FirmwareProcessor { soc_ifc: &mut SocIfc, mbox: &'a mut Mailbox, pcr_bank: &mut PcrBank, + dma: &mut Dma, env: &mut KatsEnv, persistent_data: &mut PersistentData, - ) -> CaliptraResult>> { + ) -> CaliptraResult<(ManuallyDrop>, u32)> { let mut self_test_in_progress = false; cprintln!("[fwproc] Waiting for Commands..."); @@ -190,6 +214,10 @@ impl FirmwareProcessor { // Handle FW load as a separate case due to the re-borrow explained below if txn.cmd() == CommandId::FIRMWARE_LOAD.into() { + if ACTIVE_MODE { + return Err(CaliptraError::FW_PROC_MAILBOX_FW_LOAD_CMD_IN_ACTIVE_MODE); + } + // Re-borrow mailbox to work around https://github.com/rust-lang/rust/issues/54663 let txn = mbox .peek_recv() @@ -199,14 +227,15 @@ impl FirmwareProcessor { // transaction will be completed by either handle_fatal_error() (on // failure) or by a manual complete call upon success. let txn = ManuallyDrop::new(txn.start_txn()); - if txn.dlen() == 0 || txn.dlen() > IMAGE_BYTE_SIZE as u32 { - cprintln!("Invalid Image of size {} bytes" txn.dlen()); + let image_size_bytes = txn.dlen(); + if image_size_bytes == 0 || image_size_bytes > IMAGE_BYTE_SIZE as u32 { + cprintln!("Invalid Image of size {} bytes", image_size_bytes); return Err(CaliptraError::FW_PROC_INVALID_IMAGE_SIZE); } - cprintln!("[fwproc] Received Image of size {} bytes" txn.dlen()); + cprintln!("[fwproc] Received Image of size {} bytes", image_size_bytes); report_boot_status(FwProcessorDownloadImageComplete.into()); - return Ok(txn); + return Ok((txn, image_size_bytes)); } // NOTE: We use ManuallyDrop here because any error here becomes a fatal error @@ -299,6 +328,25 @@ impl FirmwareProcessor { resp.populate_chksum(); txn.send_response(resp.as_bytes())?; } + CommandId::RI_DOWNLOAD_FIRMWARE => { + if ACTIVE_MODE == false { + cprintln!( + "[fwproc] RI_DOWNLOAD_FIRMWARE cmd not supported in passive mode" + ); + txn.complete(false)?; + return Err(CaliptraError::FW_PROC_MAILBOX_INVALID_COMMAND); + } + txn.complete(true)?; + // Download the firmware image from the recovery interface. + let image_size_bytes = Self::retrieve_image_from_ri(dma)?; + let txn = ManuallyDrop::new(mbox.fake_recv_txn()); + cprintln!( + "[fwproc] Received Image from Recovery Interface of size {} bytes", + image_size_bytes + ); + report_boot_status(FwProcessorDownloadImageComplete.into()); + return Ok((txn, image_size_bytes)); + } _ => { cprintln!("[fwproc] Invalid command received"); // Don't complete the transaction here; let the fatal @@ -322,7 +370,13 @@ impl FirmwareProcessor { txn: &mut MailboxRecvTxn, ) -> CaliptraResult { let manifest = &mut persistent_data.get_mut().manifest1; - txn.copy_request(manifest.as_bytes_mut())?; + if ACTIVE_MODE { + let raw_image = txn.raw_mailbox_contents(); + let buf = manifest.as_bytes_mut(); + buf.copy_from_slice(&raw_image[..buf.len()]); + } else { + txn.copy_request(manifest.as_bytes_mut())?; + } report_boot_status(FwProcessorManifestLoadComplete.into()); Ok(*manifest) } @@ -476,12 +530,20 @@ impl FirmwareProcessor { manifest.fmc.size ); - let fmc_dest = unsafe { - let addr = (manifest.fmc.load_addr) as *mut u32; - core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize / 4) - }; - - txn.copy_request(fmc_dest.as_bytes_mut())?; + if ACTIVE_MODE { + let raw_image = txn.raw_mailbox_contents(); + let fmc_dest = unsafe { + let addr = (manifest.fmc.load_addr) as *mut u8; + core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize) + }; + fmc_dest.copy_from_slice(&raw_image[size_of::()..][..fmc_dest.len()]); + } else { + let fmc_dest = unsafe { + let addr = (manifest.fmc.load_addr) as *mut u32; + core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize / 4) + }; + txn.copy_request(fmc_dest.as_bytes_mut())?; + } cprintln!( "[fwproc] Loading Runtime at address 0x{:08x} len {}", @@ -489,12 +551,25 @@ impl FirmwareProcessor { manifest.runtime.size ); - let runtime_dest = unsafe { - let addr = (manifest.runtime.load_addr) as *mut u32; - core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize / 4) - }; - - txn.copy_request(runtime_dest.as_bytes_mut())?; + if ACTIVE_MODE { + let runtime_dest = unsafe { + let addr = (manifest.runtime.load_addr) as *mut u8; + core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize) + }; + + txn.copy_request(runtime_dest.as_bytes_mut())?; + let raw_image = txn.raw_mailbox_contents(); + runtime_dest.copy_from_slice( + &raw_image[size_of::() + manifest.fmc.size as usize..] + [..runtime_dest.len()], + ); + } else { + let runtime_dest = unsafe { + let addr = (manifest.runtime.load_addr) as *mut u32; + core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize / 4) + }; + txn.copy_request(runtime_dest.as_bytes_mut())?; + } report_boot_status(FwProcessorLoadImageComplete.into()); Ok(()) @@ -674,7 +749,7 @@ impl FirmwareProcessor { Self::log_measurement(persistent_data, stash_measurement) } - /// Log mesaure data to the Stash Measurement log + /// Log measurement data to the Stash Measurement log /// /// # Arguments /// * `persistent_data` - Persistent data @@ -710,4 +785,49 @@ impl FirmwareProcessor { Ok(()) } + + /// Retrieve the fw image from the recovery interface and store it in the mailbox sram. + /// + /// # Arguments + /// * `dma` - DMA driver + /// + /// # Returns + /// * `()` - Ok + /// Error code on failure. + fn retrieve_image_from_ri(dma: &mut Dma) -> CaliptraResult { + // 1. Set PROT_CAP:Byte11 bit3 to 1 ('Flashless boot'). + let mut prot_cap_byte_11_val = dma.read_dword(PROT_CAP_AGENT_BYTE_11_OFFSET as usize)?; + prot_cap_byte_11_val |= 1 << 1; // Set bit 1 + dma.write_dword(PROT_CAP_AGENT_BYTE_11_OFFSET as usize, prot_cap_byte_11_val)?; + + // 2. Set DEVICE_STATUS:Byte0 to 0x3 ('Recovery mode - ready to accept recovery image'). + dma.write_dword(DEVICE_STATUS_OFFSET as usize, 0x3)?; + + // 3. Set DEVICE_STATUS:Byte[2:3] to 0x12 ('Recovery Reason Codes' 0x12 = 0 Flashless/Streaming Boot (FSB)). + dma.write_dword((DEVICE_RESET_OFFSET + 2) as usize, 0x12)?; + + // 4. Set RECOVERY_STATUS register:Byte0 Bit[3:0] to 0x1 ('Awaiting recovery image') & + // Byte0 Bit[7:4] to 0 (Recovery image index). + let mut recovery_status_val = dma.read_dword(RECOVERY_STATUS_OFFSET as usize)?; + recovery_status_val = (recovery_status_val & 0xFFFFFF00) | 0x1; + dma.write_dword(RECOVERY_STATUS_OFFSET as usize, recovery_status_val)?; + + // 5. Loop on the 'payload_available' signal for the recovery image details to be available. + while PAYLOAD_AVAILABLE == false { + // Wait for the payload available signal. + } + + // 6. Read the image size from INDIRECT_FIFO_CTRL register:Byte[2:5]. Image size in DWORDs. + let image_size_dword = dma.read_dword((INDIRECT_FIFO_CTRL_OFFSET + 2) as usize)?; + + // 7. Transfer the image from the recovery interface to the mailbox SRAM. + let image_size_bytes = image_size_dword * 4; + dma.transfer_payload_to_mbox( + (RRI_BASE + INDIRECT_FIFO_DATA_OFFSET) as usize, + image_size_bytes, + true, + RECOVERY_DMA_BLOCK_SIZE_BYTES, + )?; + Ok(image_size_bytes) + } } diff --git a/rom/dev/src/rom_env.rs b/rom/dev/src/rom_env.rs index e067aef06c..3c0e3b0283 100644 --- a/rom/dev/src/rom_env.rs +++ b/rom/dev/src/rom_env.rs @@ -17,14 +17,15 @@ Abstract: use crate::fht::FhtDataStore; use caliptra_drivers::{ - DataVault, DeobfuscationEngine, Ecc384, Hmac384, KeyVault, Lms, Mailbox, PcrBank, + DataVault, DeobfuscationEngine, Dma, Ecc384, Hmac384, KeyVault, Lms, Mailbox, PcrBank, PersistentDataAccessor, Sha1, Sha256, Sha2_512_384Acc, Sha384, SocIfc, Trng, }; use caliptra_error::CaliptraResult; use caliptra_registers::{ - csrng::CsrngReg, doe::DoeReg, dv::DvReg, ecc::EccReg, entropy_src::EntropySrcReg, - hmac::HmacReg, kv::KvReg, mbox::MboxCsr, pv::PvReg, sha256::Sha256Reg, sha512::Sha512Reg, - sha512_acc::Sha512AccCsr, soc_ifc::SocIfcReg, soc_ifc_trng::SocIfcTrngReg, + axi_dma::AxiDmaReg, csrng::CsrngReg, doe::DoeReg, dv::DvReg, ecc::EccReg, + entropy_src::EntropySrcReg, hmac::HmacReg, kv::KvReg, mbox::MboxCsr, pv::PvReg, + sha256::Sha256Reg, sha512::Sha512Reg, sha512_acc::Sha512AccCsr, soc_ifc::SocIfcReg, + soc_ifc_trng::SocIfcTrngReg, }; /// Rom Context @@ -76,6 +77,9 @@ pub struct RomEnv { // Mechanism to access the persistent data safely pub persistent_data: PersistentDataAccessor, + + /// DMA Widget + pub dma: Dma, } impl RomEnv { @@ -104,6 +108,7 @@ impl RomEnv { fht_data_store: FhtDataStore::default(), trng, persistent_data: PersistentDataAccessor::new(), + dma: Dma::new(AxiDmaReg::new()), }) } } From 7921b6e61d1ef5657a152a5c493ee04cf5dd8362 Mon Sep 17 00:00:00 2001 From: Vishal Mhatre <38512878+mhatrevi@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:53:31 +0530 Subject: [PATCH 4/6] [doc] ROM README.md update for firmware image 2.0 format (#1723) ROM README.md update for firmware image 2.0 format --- rom/dev/README.md | 102 +++++++++++++----------- rom/dev/doc/svg/preamble-validation.svg | 2 +- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/rom/dev/README.md b/rom/dev/README.md index f35bb9a3bd..cf9e0a2e1d 100644 --- a/rom/dev/README.md +++ b/rom/dev/README.md @@ -49,11 +49,10 @@ Following are the main FUSE & Architectural Registers used by the Caliptra ROM f | CPTRA_SECURITY_STATE | 32 | Security State of the device. Contains two fields:
**LIFECYCLE_STATE**: Unprovisioned, Manufacturing or Production
**DEBUG_ENABLED**: Boolean indicating if debug is enabled or not | | FUSE_UDS_SEED | 384 | Obfuscated UDS | | FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy | -| FUSE_KEY_MANIFEST_PK_HASH | 384 | Hash of the four ECC and thirty-two LMS Manufacturer Public Keys | +| FUSE_KEY_MANIFEST_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors | | FUSE_KEY_MANIFEST_PK_HASH_MASK | 32 | Manufacturer ECC Public Key Revocation Mask | -| FUSE_LMS_REVOCATION | 32 | Manufacturer LMS Public Key Revocation Mask | -| FUSE_LMS_VERIFY | 32 | LMS Verification flag:
**0** - Verify Caliptra firmware images with ECDSA-only
**1** - Verify Caliptra firmware images with both ECDSA and LMS | -| FUSE_OWNER_PK_HASH | 384 | Owner ECC and LMS Public Key Hash | +| FUSE_PQC_REVOCATION | 32 | Manufacturer LMS or MLDSA Public Key Revocation Mask | +| FUSE_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash | | FUSE_FMC_KEY_MANIFEST_SVN | 32 | FMC Security Version Number | | FUSE_RUNTIME_SVN | 128 | Runtime Security Version Number | | FUSE_ANTI_ROLLBACK_DISABLE | 1 | Disable SVN checking for FMC & Runtime when bit is set | @@ -69,7 +68,7 @@ The Caliptra Firmware image has two main components: - ### **Firmware images** The firmware manifest is a combination of preamble and a signed header. It has -public keys, signatures and table of contents which refer to the various +public keys, public key hashes, signatures and table of contents which refer to the various firmware images contained in the bundle. ![Firmware Image Bundle](doc/svg/fw-img-bundle.svg) @@ -83,30 +82,42 @@ Firmware manifest consists of preamble, header and table of contents. It is the unsigned portion of the manifest. Preamble contains the signing public keys and signatures. ROM is responsible for parsing the preamble. ROM performs the following steps: - Loads the preamble from the mailbox. -- Calculates the hash of the four Manufacturer ECC and thirty-two LMS (if LMS verification is enabled) Public Keys in the preamble and compares it against the hash in the fuse (FUSE_KEY_MANIFEST_PK_HASH). If the hashes do not match, the boot fails. -- Selects the appropriate Manufacturer Public Key(s) based on fuse (FUSE_KEY_MANIFEST_PK_HASH_MASK for ECC public key, FUSE_LMS_REVOCATION for LMS public key) +- Calculates the hash of ECC and LMS or MLDSA Public Key Descriptors in the preamble and compares it against the hash in the fuse (FUSE_KEY_MANIFEST_PK_HASH). If the hashes do not match, the boot fails. +- Verifies the active Manufacturer Public Key(s) based on fuse (FUSE_KEY_MANIFEST_PK_HASH_MASK for ECC public key, FUSE_PQC_REVOCATION for LMS or MLDSA public key) *Note: All fields are little endian unless specified* +*Public Key Descriptor* + +| Field | Size (bytes) | Description| +|-------|--------|------------| +| Key Descriptor Version | 1 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x | +| Intent | 1 | Type of the descriptor
0x1 - Vendor
0x2 - Owner | +| Key Type | 1 | Type of the key in the descriptor
0x1 - ECC
0x2 - LMS
0x3 - MLDSA | +| Key Hash Count | 1 | Number of public key hashes (n) | +| Public Key Hash(es) | 48 * n | List of SHA2-384 public key hashes | + +*Preamble* + | Field | Size (bytes) | Description| |-------|--------|------------| | Firmware Manifest Marker | 4 | Magic Number marking the start of the package manifest. The value must be 0x434D414E (‘CMAN’ in ASCII)| | Firmware Manifest Size | 4 | Size of the full manifest structure | -| Manufacturer ECC Public Key 1 | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature.
**X-Coordinate:** Public Key X-Coordinate (48 bytes)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes) | -| Manufacturer ECC Public Key 2 | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature.
**X-Coordinate:** Public Key X-Coordinate (48 bytes)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes) | -| Manufacturer ECC Public Key 3 | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature.
**X-Coordinate:** Public Key X-Coordinate (48 bytes)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes) | -| Manufacturer ECC Public Key 4 | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature.
**X-Coordinate:** Public Key X-Coordinate (48 bytes)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes) | -| Manufacturer LMS Public Key 1 | 48 | LMS public key used to verify the Firmware Manifest Header Signature.
**tree_type:** LMS Algorithm Type (4 bytes)
**otstype:** LMS Ots Algorithm Type (4 bytes)
**id:** (16 bytes)
**digest:** (24 bytes) | -| Manufacturer LMS Public Key 2 | 48 | LMS public key used to verify the Firmware Manifest Header Signature.
**tree_type:** LMS Algorithm Type (4 bytes)
**otstype:** LMS Ots Algorithm Type (4 bytes)
**id:** (16 bytes)
**digest:** (24 bytes) | -|... | | | -| ECC Public Key Index Hint | 4 | The hint to ROM to indicate which ECC public key it should first use. | -| LMS Public Key Index Hint | 4 | The hint to ROM to indicate which LMS public key it should first use. | -| Manufacturer ECC Signature | 96 | Manufacturer ECDSA P-384 signature of the Firmware Manifest header hashed using SHA2-384.
**R-Coordinate:** Random Point (48 bytes)
**S-Coordinate:** Proof (48 bytes) | -| Manufacturer LMS Signature | 1620 | Manufacturer LMS signature of the Firmware Manifest header hashed using SHA2-384.
**q:** Leaf of the Merkle tree where the OTS public key appears (4 bytes)
**ots:** Lmots Signature (1252 bytes)
**tree_type:** Lms Algorithm Type (4 bytes)
**tree_path:** Path through the tree from the leaf associated with the LM-OTS signature to the root. (360 bytes) | +| Firmware Manifest Type | 4 | **Byte0:** - Type
0x1 – ECC & LMS Keys
0x2 – ECC & MLDSA Keys
**Byte1-Byte3:** Reserved | +| Manufacturer Key Descriptor - 1 | Variable | Public Key Descriptor for ECC keys | +| Manufacturer Key Descriptor - 2 | Variable | Public Key Descriptor for LMS or MLDSA keys | +| Active Key Index - 1 | 4 | Public Key Hash Index for the active ECC key | +| Active Key - 1 | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature
**X-Coordinate:** Public Key X-Coordinate (48 bytes, big endian)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes, big endian) | +| Active Key Index - 2 | 4 | Public Key Hash Index for the active LMS or MLDSA key | +| Active Key - 2 | 48 or 2592 | LMS public key used to verify the Firmware Manifest Header Signature.
**tree_type:** LMS Algorithm Type (4 bytes, big endian) Must equal 12.
**otstype:** LM-OTS Algorithm Type (4 bytes, big endian) Must equal 7.
**id:** (16 bytes)
**digest:** (24 bytes)

**OR**

MLDSA-87 public key used to verify the Firmware Manifest Header Signature.
(2592 bytes)| +| Manufacturer ECC Signature | 96 | Manufacturer ECC P-384 signature of the Firmware Manifest header hashed using SHA2-384.
**R-Coordinate:** Random Point (48 bytes)
**S-Coordinate:** Proof (48 bytes) | +| Manufacturer LMS or MLDSA Signature | 1620 or 4628 | Manufacturer LMS signature of the Firmware Manifest header hashed using SHA2-384.
**q:** Leaf of the Merkle tree where the OTS public key appears (4 bytes)
**ots:** Lmots Signature (1252 bytes)
**tree_type:** Lms Algorithm Type (4 bytes)
**tree_path:** Path through the tree from the leaf associated with the LM-OTS signature to the root. (360 bytes)

**OR**

Vendor MLDSA-87 signature of the Firmware Manifest header hashed using SHA2-512 (4627 bytes + 1 Reserved byte)| +| Owner Key Descriptor - 1 | Variable | Public Key Descriptor for ECC keys | +| Owner Key Descriptor - 2 | Variable | Public Key Descriptor for LMS or MLDSA keys | | Owner ECC Public Key | 96 | ECC P-384 public key used to verify the Firmware Manifest Header Signature.
**X-Coordinate:** Public Key X-Coordinate (48 bytes)
**Y-Coordinate:** Public Key Y-Coordinate (48 bytes)| -| Owner LMS Public Key | 48 | LMS public key used to verify the Firmware Manifest Header Signature.
**tree_type:** LMS Algorithm Type (4 bytes)
**otstype:** LMS Ots Algorithm Type (4 bytes)
**id:** (16 bytes)
**digest:** (24 bytes) | -| Owner ECC Signature | 96 | Manufacturer ECDSA P-384 signature of the Firmware Manifest header hashed using SHA2-384.
**R-Coordinate:** Random Point (48 bytes)
**S-Coordinate:** Proof (48 bytes) | -| Owner LMS Signature | 1620 | Owner LMS signature of the Firmware Manifest header hashed using SHA2-384.
**q:** Leaf of the Merkle tree where the OTS public key appears (4 bytes)
**ots:** Lmots Signature (1252 bytes)
**tree_type:** Lms Algorithm Type (4 bytes)
**tree_path:** Path through the tree from the leaf associated with the LM-OTS signature to the root. (360 bytes) | +| Owner LMS or MLDSA Public Key | 48 or 2592 | LMS public key used to verify the Firmware Manifest Header Signature.
**tree_type:** LMS Algorithm Type (4 bytes)
**otstype:** LMS Ots Algorithm Type (4 bytes)
**id:** (16 bytes)
**digest:** (24 bytes)

**OR**

MLDSA-87 public key used to verify the Firmware Manifest Header Signature.
(2592 bytes)| +| Owner ECC Signature | 96 | Manufacturer ECC P-384 signature of the Firmware Manifest header hashed using SHA2-384.
**R-Coordinate:** Random Point (48 bytes)
**S-Coordinate:** Proof (48 bytes) | +| Owner LMS or MLDSA Signature | 1620 or 4628 | Owner LMS signature of the Firmware Manifest header hashed using SHA2-384.
**q:** Leaf of the Merkle tree where the OTS public key appears (4 bytes)
**ots:** Lmots Signature (1252 bytes)
**tree_type:** Lms Algorithm Type (4 bytes)
**tree_path:** Path through the tree from the leaf associated with the LM-OTS signature to the root. (360 bytes)

**OR**

Owner MLDSA-87 signature of the Firmware Manifest header hashed using SHA2-512 (4627 bytes + 1 Reserved byte) | | Reserved | 8 | Reserved 8 bytes |
@@ -117,8 +128,8 @@ The header contains the security version and SHA2-384 hash of the table of conte | Field | Size (bytes) | Description| |-------|--------|------------| | Revision | 8 | 8-byte version of the firmware image bundle | -| Vendor ECC public key index | 4 | The hint to ROM to indicate which ECC public key it should first use. | -| Vendor LMS public key index | 4 | The hint to ROM to indicate which LMS public key it should first use. | +| Vendor ECC public key hash index | 4 | The hint to ROM to indicate which ECC public key hash it should use to validate the active ECC public key. | +| Vendor LMS or MLDSA public key hash index | 4 | The hint to ROM to indicate which LMS or MLDSA public key hash it should use to validate the active public key. | | Flags | 4 | Feature flags.
**Bit0:** - Interpret the pl0_pauser field. If not set, all PAUSERs are PL1
**Bit1-Bit31:** Reserved | | TOC Entry Count | 4 | Number of entries in TOC. | | PL0 PAUSER | 4 | The PAUSER with PL0 privileges. | @@ -539,13 +550,14 @@ Alias FMC Layer includes the measurement of the FMC and other security states. T The basic flow for validating the firmware involves the following: -- Validate the manufacturing keys in the preamble -- Validate the owner keys in the preamble -- Select the manufacturer keys +- Validate the manufacturing keys descriptors in the preamble. +- Validate the active manufacturing keys with the hash in the key descriptors. +- Validate the owner keys in the preamble. +- Validate the active manufacturer keys against the key revocation fuses. - Once both the validations are complete, download the header from the mailbox. -- Validate the Manifest Header using the selected Manufacturer keys against the manufacturer signature. -- Validate the Manifest Header using the owner key(s) against the owner signature. -- On the completion of the last two validations, it is assured that the header portion is authentic. +- Validate the Manifest Header using the active manufacturer keys against the manufacturer signatures. +- Validate the Manifest Header using the owner keys against the owner signatures. +- On the completion of these validations, it is assured that the header portion is authentic. - Load both the TOC entries from the mailbox. - Validate the downloaded TOC data against the TOC hash in the header. - This marks the TOC data as valid. The next step is to use the TOC Hash to validate image sections. @@ -568,11 +580,10 @@ The following are the pre-conditions that should be satisfied: - Caliptra has transitioned through the BOOTFSM and all the fuses that are required for the validation are already populated by SOC. - The FUSES programmed by the soc are - - fuse_key_manifest_pk_hash : This fuse contains the hash of the manufacturer keys present in preamble. + - fuse_key_manifest_pk_hash : This fuse contains the hash of the manufacturer key descriptors present in preamble. - fuse_key_manifest_pk_hash_mask : This is the bitmask of the ECC keys which are revoked. - - fuse_lms_revocation : This is the bitmask of the LMS keys which are revoked. - - fuse_owner_pk_hash : The hash of the owner public key(s) in preamble. - - fuse_lms_verify: This fuse indicates if verification with LMS key is enabled. + - fuse_pqc_revocation : This is the bitmask of the LMS or MLDSA keys which are revoked. + - fuse_owner_pk_hash : The hash of the owner public keys in preamble. - fuse_key_manifest_svn : Used in FMC validation to make sure that the version number is good. - fuse_runtime_svn : Used in RT validation to make sure that the runtime image's version number is good. - The SOC has written the data to the mailbox. @@ -584,28 +595,27 @@ The following are the pre-conditions that should be satisfied: ## Preamble validation: Validate the manufacturing keys - Load the preamble bytes from the mailbox. -- There are four ECC and thirty-two LMS manufacturing keys in the preamble. -- fuse_key_manifest_pk_hash is the fuse that contains the hash of all the ECC and LMS manufacturing keys. -- To validate the key region, take the hash of all the ECC and LMS keys and compare it against the hash in fuse. +- There is an ECC key descriptor and either LMS or MLDSA key descriptor in the preamble. The ECC descriptor contains up to four ECC public key hashes. The LMS key descriptor contains up to 32 public key hashes. The MLDSA key descriptor contains up to four MLDSA public key hashes. +- There is an ECC key and either LMS or MLDSA manufacturing key in the preamble. These are the active public keys. +- fuse_key_manifest_pk_hash is the fuse that contains the hash of the ECC and LMS or MLDSA manufacturing key descriptors. +- To validate the key region, take the hash of the ECC and LMS or MLDSA keys descriptors and compare it against the hash in fuse. - If the hash does not match, fail the image validation. -- If the hash matches, all the ECC and LMS keys are validated. +- If the hash matches, the ECC and LMS or MLDSA key descriptors are validated. +- Validate the active public keys against one of the hashes in the key descriptors as indicated by the active key indices. -### Preamble validation: Manufacturing key selection +### Preamble validation: Manufacturing key validation -- Since there are four ECC key slots in the preamble, we will need to select one key out of four. - fuse_key_manifest_pk_hash_mask is the mask which revokes an ECC key. - If bit-0 is set, that key is disabled. All other higher bits which are zeros, are still enabled. - If all the bits are zeros, all the keys are enabled. - If bit-0 and bit-1 are set, all higher slot bits (2 and 3) are enabled. -- Select the key using the Public Key Index Hint field in the preamble. This key should not be disabled using the fuse_key_manifest_pk_hash_mask fuse. +- Validate that the 'Active Key Index' in the preamble is not disabled in the fuse_key_manifest_pk_hash_mask fuse. - If the key is disabled, fail the validation. - - If the key is enabled, select the key. -- Repeat the above procedure for LMS keys using the fuse_lms_revocation for key revocation. -- At this time, we have validated all the four ECC and thirty-two LMS keys and selected the ECC and LMS key that will be used for validation of the header against the manufacturer header signature field. +- Repeat the above procedure for LMS or MLDSA keys using the fuse_pqc_revocation for key revocation. ### Preamble validation: Validate the owner key -- There is one slot each for the owner ECC and LMS keys in the image preamble. +- There is one slot each for the owner ECC and LMS or MLDSA keys in the image preamble. - fuse_owner_pk_hash contains the hash of the owner public keys. - The validation of owner public keys is done by hashing the owner public keys from the preamble and comparing the hash against the value in the fuse_owner_pk_hash. - If the hash matches, the owner public keys are valid. @@ -622,8 +632,8 @@ The following are the pre-conditions that should be satisfied: - First signature is generated using one of the manufacturing keys. - Second signature is generated using the owner public key. - To validate the header, hash and then verify that the ECC manufacturer signature in the preamble is for the hash. -- If the manufacturer signature matches, proceed with the owner signature validation. If the signature does not match, fail the validation. Repeat the same procedure with LMS manufacturer key if LMS verification is enabled. -- The hash is already generated. Verify the signature for the above hash using the ECC owner public key. Repeat the same procedure with LMS owner key if LMS verification is enabled. +- If the manufacturer signature matches, proceed with the owner signature validation. If the signature does not match, fail the validation. Repeat the same procedure with LMS or MLDSA manufacturer key. +- The hash is already generated. Verify the signature for the above hash using the ECC owner public key. Repeat the same procedure with LMS or MLDSA owner key. ## Header validation steps diff --git a/rom/dev/doc/svg/preamble-validation.svg b/rom/dev/doc/svg/preamble-validation.svg index 1d2ddcd36a..3f42f258ed 100644 --- a/rom/dev/doc/svg/preamble-validation.svg +++ b/rom/dev/doc/svg/preamble-validation.svg @@ -225,7 +225,7 @@ Y Process.65 - Generate Sha-384 hash of the Mfg Keys and match with the hash... + Generate Sha-384 hash of the Mfg Key Descriptors and match with the hash... From 28be4adb7b9a2946978a53fb4b09d6e18d06306c Mon Sep 17 00:00:00 2001 From: Vishal Mhatre <38512878+mhatrevi@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:30:38 +0530 Subject: [PATCH 5/6] [doc] ROM README update for MLDSA DICE derivation (#1730) [doc] ROM README update for MLDSA DICE derivation --- rom/dev/README.md | 283 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 198 insertions(+), 85 deletions(-) diff --git a/rom/dev/README.md b/rom/dev/README.md index cf9e0a2e1d..6c5e057f38 100644 --- a/rom/dev/README.md +++ b/rom/dev/README.md @@ -47,8 +47,8 @@ Following are the main FUSE & Architectural Registers used by the Caliptra ROM f | Register | Width (bits) | Description | | :------------------------------ | :------------| :----------------------------------------------------- | | CPTRA_SECURITY_STATE | 32 | Security State of the device. Contains two fields:
**LIFECYCLE_STATE**: Unprovisioned, Manufacturing or Production
**DEBUG_ENABLED**: Boolean indicating if debug is enabled or not | -| FUSE_UDS_SEED | 384 | Obfuscated UDS | -| FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy | +| FUSE_UDS_SEED | 512 | Obfuscated UDS | +| FUSE_FIELD_ENTROPY | 512 | Obfuscated Field Entropy | | FUSE_KEY_MANIFEST_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors | | FUSE_KEY_MANIFEST_PK_HASH_MASK | 32 | Manufacturer ECC Public Key Revocation Mask | | FUSE_PQC_REVOCATION | 32 | Manufacturer LMS or MLDSA Public Key Revocation Mask | @@ -168,11 +168,16 @@ The following sections define the various cryptographic primitives used by Calip | Deobfuscation Engine | `doe_decrypt_uds(kv_slot, iv)` | Decrypt UDS to the specified key vault slot with specified initialization vector
**Input**:
***kv_slot*** - key vault slot to decrypt the uds to
***iv*** - initialization vector | | | `doe_decrypt_fe(kv_slot, iv)` | Decrypt Field Entropy to the specified key vault slot with specified initialization vector
**Input**:
***kv_slot*** - key vault slot to decrypt the field entropy to
***iv*** - initialization vector | | | `doe_clear_secrets()` | Clear UDS Fuse Register, Field Entropy Fuse Register and Obfuscation key | -| Hashed Message Authentication Code | `hmac384_mac(key,data,mac_kv_slot)` | Calculate the mac using a caller provided key and data. The resultant mac is stored in key vault slot
**Input**:
***key*** - caller specified key
data - data
***mac_kv_slot*** - key vault slot to store the mac to | -| | `hmac384_mac(kv_slot,data,mac_kv_slot)` | Calculate the mac using a caller provided key and data. The resultant mac is stored in key vault slot
**Input**:
***kv_slot*** - key vault slot to use the key from
***data*** - data
***mac_kv_slot*** - key vault slot to store the mac to | +| Hashed Message Authentication Code | `hmac384_mac(key,data,mac_kv_slot)` | Calculate the MAC using a caller provided key and data. The resultant MAC is stored in key vault slot
**Input**:
***key*** - caller specified key
data - data
***mac_kv_slot*** - key vault slot to store the MAC to | +| | `hmac384_mac(kv_slot,data,mac_kv_slot)` | Calculate the MAC using a caller provided key and data. The resultant MAC is stored in key vault slot
**Input**:
***kv_slot*** - key vault slot to use the key from
***data*** - data
***mac_kv_slot*** - key vault slot to store the MAC to | +| | `hmac512_mac(key,data,mac_kv_slot)` | Calculate the MAC using a caller provided key and data. The resultant MAC is stored in key vault slot
**Input**:
***key*** - caller specified key
data - data
***mac_kv_slot*** - key vault slot to store the MAC to | +| | `hmac512_mac(kv_slot,data,mac_kv_slot)` | Calculate the MAC using a caller provided key and data. The resultant MAC is stored in key vault slot
**Input**:
***kv_slot*** - key vault slot to use the key from
***data*** - data
***mac_kv_slot*** - key vault slot to store the MAC to | | Elliptic Curve Cryptography | `ecc384_keygen(seed_kv_slot, priv_kv_slot) -> pub_key` | Generate ECC384 Key Pair.
**Input**:
***seed_key_slot*** - key vault slot to use as seed for key generation
***priv_kv_slot*** - key vault slot to store the private key to
**Output**:
***pub-key*** - public key associated with the private key | | | `ecc384_sign(priv_kv_slot, data) -> sig` | ECC384 signing operation
**Input**:
***priv_kv_slot*** - key vault slot to use a private key from
***data*** - data to sign
**Output**:
***sig*** - signature | | | `ecc384_verify(pub_key, data, sig) -> CaliptraResult>` | ECC384 verify operation
**Input**:
***pub-key*** -public key
data - data to verify
sig - signature
**Output**:
***Ecc384Result*** - verify.r value on success, else an error | +| Module-Lattice-Based Digital Signature Algorithm | `mldsa87_keygen(seed_kv_slot) -> pub_key` | Generate MLDSA87 Key Pair.
**Input**:
***seed_key_slot*** - key vault slot to use as seed for key generation
**Output**:
***pub-key*** - public key associated with the private key | +| | `mldsa87_sign(seed_kv_slot, data) -> sig` | MLDSA87 signing operation
**Input**:
***seed_kv_slot*** - key vault slot to use as seed for key generation for signing
***data*** - data to sign
**Output**:
***sig*** - signature | +| | `mldsa87_verify(pub_key, data, sig) -> MlDsa87Result` | MLDSA87 verify operation
**Input**:
***pub-key*** -public key
data - data to verify
sig - signature
**Output**:
***MlDsa87Result*** - '0xAAAAAAAA' value on success, '0x55555555' on error | | Secure Hash Algorithm | `sha384_digest(data) -> digest` | Calculate the digest of the data
**Input**:
***data*** - data to verify
**Output**:
***digest*** - digest of the data | | Key Vault | `kv_clear(kv_slot)` | Key Vault slot to clear
**Input**:
***kv_slot*** - key vault slot to clear | | Data Vault | `dv48_store(data, dv_slot)` | Store the 48-byte data in the specified data vault slot
**Input**:
***data*** - data to store
***dv_slot*** - data vault slot | @@ -237,9 +242,17 @@ Both UDS and Field Entropy are available only during cold reset of Caliptra. `doe_decrypt_uds(KvSlot0, DOE_IV)` + Recondition the decrypted UDS for MLDSA keypair generation and store it in Key Vault Slot 10: + + `hmac512(key: KvSlot0, value: b"uds_recon", MAC: KvSlot10)` + 2. Decrypt Field Entropy to Key Vault Slot 1 - `doe_decrypt_uds(KvSlot1, DOE_IV)` + `doe_decrypt_fe(KvSlot1, DOE_IV)` + + Recondition the decrypted FE for MLDSA keypair generation and store it in Key Vault Slot 11: + + `hmac512(key: KvSlot1, value: b"fe_recon", MAC: KvSlot11)` 3. Clear class secrets (Clears UDS, Field Entropy and Obfuscation Key cleared) @@ -253,50 +266,81 @@ Both UDS and Field Entropy are available only during cold reset of Caliptra. | Slot | Key Vault | PCR Bank | Data Vault 48 Byte (Sticky) | Data Vault 4 Byte (Sticky) | |------|-----------|----------|-----------------------------|----------------------------| -| 0 | UDS (48 bytes) | | | | -| 1 | Field Entropy (32 bytes) | | | | +| 0 | UDS for ECDSA (48 bytes) | | | | +| 1 | Field Entropy for ECDSA (32 bytes) | | | | +| 10 | Conditioned UDS for MLDSA | | | | +| 11 | Conditioned Field Entropy for MLDSA | | | | ### Initial Device ID DICE layer -Initial Device ID Layer is used to generate Manufacturer CDI & Private Key. This layer represents the manufacturer or silicon vendor DICE Identity. During manufacturing, ROM can be requested to create Certificate Signing Request (CSR) via JTAG. +Initial Device ID Layer is used to generate Manufacturer CDI & Private Key. This layer represents the manufacturer or silicon vendor DICE Identity. During manufacturing, ROM can be requested to create Certificate Signing Request (CSR) via JTAG. **Pre-conditions:** -- UDS is loaded in Key Vault Slot 0 +- UDS for ECDSA is in Key Vault Slot 0 +- Conditioned UDS for MLDSA is in Key Vault Slot 10 **Actions:** -1. Derive the CDI using ROM specified label and UDS in Slot 0 as data and store the resultant mac in KeySlot6 +1. Derive the ECDSA CDI using ROM specified label and UDS in Key Vault Slot 0 as data and store the resultant MAC in Key Vault Slot 6 `hmac384_kdf(KvSlot0, b"idevid_cdi", KvSlot6)` -2. Clear the UDS in key vault + Use the conditioned UDS to derive the MLDSA CDI and store the resultant MAC in Key Vault Slot 12 + + `hmac512_kdf(KvSlot10, b"idevid_mldsa_cdi", KvSlot12)` + +2. Clear the UDS(s) in key vault `kv_clear(KvSlot0)` -3. Derive ECC Key Pair using CDI in Key Vault Slot6 and store the generated private key in KeySlot7 + `kv_clear(KvSlot10)` + +3. Derive ECC Key Pair using CDI in Key Vault Slot 6 and store the generated private key in Key Vault Slot 7 + + `IDevIDSeedEcdsa = hmac384_kdf(KvSlot6, b"idevid_keygen", KvSlot3)` + + `IDevIdPubKeyEcdsa = ecc384_keygen(KvSlot3, KvSlot7)` - `IDevIDSeed = hmac384_kdf(KvSlot6, b"idevid_keygen", KvSlot3)` - `IDevIdPubKey = ecc384_keygen(KvSlot3, KvSlot7)` `kv_clear(KvSlot3)` -*(Note: Steps 4-7 are performed if CSR download is requested via CPTRA_DBG_MANUF_SERVICE_REG register)* + Derive the MLDSA Key Pair using the conditioned CDI in Key Vault Slot 12. -4. Generate the `To Be Signed` DER Blob of the IDevId CSR + `IDevIDSeedMldsa = hmac512_kdf(KvSlot12, b"idevid_mldsa_keygen", KvSlot10)` - `IDevIdTbs = gen_tbs(IDEVID_CSR, IDevIdPubKey)` + `IDevIdPubKeyMldsa = mldsa87_keygen(KvSlot10)` -5. Sign the IDevID `To Be Signed` DER Blob with IDevId Private Key in Key Vault Slot 7 +*(Note: Steps 4-11 are performed if CSR download is requested via CPTRA_DBG_MANUF_SERVICE_REG register)* - `IDevIdTbsDigest = sha384_digest(IDevIdTbs)` - `IDevIdCertSig = ecc384_sign(KvSlot7, IDevIdTbsDigest)` +4. Generate the `To Be Signed` DER Blob of the IDevId CSR with the ECDSA public key. + + `IDevIdTbsEcdsa = gen_tbs(IDEVID_CSR, IDevIdPubKeyEcdsa)` + +5. Sign the IDevID `To Be Signed` DER Blob with IDevId ECDSA Private Key in Key Vault Slot 7 + + `IDevIdTbsDigestEcdsa = sha384_digest(IDevIdTbsEcdsa)` + + `IDevIdCertSigEcdsa = ecc384_sign(KvSlot7, IDevIdTbsDigestEcdsa)` 6. Verify the signature of IDevID `To Be Signed` Blob - `IDevIdTbsDigest = sha384_digest(IDevIdTbs)` - `Result = ecc384_verify(IDevIdPubKey, IDevIdTbsDigest, IDevIdCertSig)` + `Result = ecc384_verify(IDevIdPubKeyEcdsa, IDevIdTbsDigestEcdsa, IDevIdCertSigEcdsa)` + +7. Generate the `To Be Signed` DER Blob of the IDevId CSR with the MLDSA public key. + + `IDevIdTbsMldsa = gen_tbs(IDEVID_CSR, IDevIdPubKeyMldsa)` + +8. Sign the IDevID `To Be Signed` DER Blob with IDevId MLDSA Private Key generated from the seed in Key Vault Slot 10. + + `IDevIdTbsDigestMldsa = sha512_digest(IDevIdTbsMldsa)` -7. Upload the CSR to mailbox and wait for JTAG to read the CSR out of the mailbox. + `IDevIdCertSigMldsa = mldsa87_sign(KvSlot10, IDevIdTbsDigestMldsa)` + +10. Verify the signature of IDevID `To Be Signed` Blob + + `Result = mldsa87_verify(IDevIdPubKeyMldsa, IDevIdTbsDigestMldsa, IDevIdCertSigMldsa)` + +11. Upload the CSR(s) to mailbox and wait for JTAG to read the CSR out of the mailbox. **Post-conditions:** @@ -305,8 +349,11 @@ Initial Device ID Layer is used to generate Manufacturer CDI & Private Key. Thi | Slot | Key Vault | PCR Bank | Data Vault 48 Byte (Sticky) | Data Vault 4 Byte (Sticky) | |------|-----------|----------|-----------------------------|----------------------------| | 1 |Field Entropy (32 bytes) | | | | -| 6 |IDevID CDI (48 bytes) | | | | -| 7 |IDevID Private Key (48 bytes) | | | | +| 6 |IDevID CDI - ECDSA (48 bytes) | | | | +| 7 |IDevID ECDSA Private Key (48 bytes) | | | | +| 10 |IDevID MLDSA Key Seed (32 bytes) | | | | +| 11 | Conditioned Field Entropy for MLDSA | | | | +| 12 |IDevID CDI - MLDSA (64 bytes) | | | | ### Local Device ID DICE layer @@ -315,72 +362,104 @@ Local Device ID Layer derives the Owner CDI & ECC Keys. This layer represents th **Pre-conditions:** - Field Entropy is loaded in Key Vault Slot 1 -- IDevID CDI is stored in Key Vault Slot 6 +- IDevID CDI - ECDSA is stored in Key Vault Slot 6 - IDevID Private Key is stored in Key Vault Slot 7 +- IDevID MLDSA Key Seed is stored in Key Vault Slot 10 +- Conditioned Field Entropy for MLDSA is stored in Key Vault Slot 11 +- IDevID CDI - MLDSA is stored in Key Vault Slot 12 **Actions:** -1. Derive the CDI using IDevID CDI in Key Vault Slot6 as HMAC Key and Field Entropy stored in Key Vault Slot1 as data. The resultant mac is stored back in Slot 6 +1. Derive the ECDSA CDI using IDevID CDI - ECDSA in Key Vault Slot 6 as HMAC Key and Field Entropy stored in Key Vault Slot1 as data. The resultant MAC is stored back in Key Vault Slot 6 `hmac384_mac(KvSlot6, b"ldevid_cdi", KvSlot6)` + `hmac384_mac(KvSlot6, KvSlot1, KvSlot6)` + Derive the MLDSA CDI using IDevID CDI - MLDSA in Key Vault Slot 12 as HMAC Key and condtioned Field Entropy stored in Key Vault Slot 11 as data. The resultant MAC is stored back in Slot 12 + + `hmac512_mac(KvSlot12, b"ldevid_mldsa_cdi", KvSlot12)` + + `hmac512_mac(KvSlot12, KvSlot11, KvSlot12)` + *(Note: this uses a pair of HMACs to incorporate the diversification label, rather than a single KDF invocation, due to hardware limitations when passing KV data to the HMAC hardware as a message.)* -2. Clear the Field Entropy in Key Vault Slot 1 +2. Clear the Field Entropy in Key Vault Slot 1 and 11 `kv_clear(KvSlot1)` -3. Derive ECC Key Pair using CDI in Key Vault Slot6 and store the generated private key in KeySlot5. + `kv_clear(KvSlot11)` + +3. Derive ECDSA Key Pair using CDI in Key Vault Slot 6 and store the generated private key in Key Vault Slot 5. `LDevIDSeed = hmac384_kdf(KvSlot6, b"ldevid_keygen", KvSlot3)` - `LDevIdPubKey = ecc384_keygen(KvSlot3, KvSlot5)` + + `LDevIdPubKeyEcdsa = ecc384_keygen(KvSlot3, KvSlot5)` + `kv_clear(KvSlot3)` -4. Store and lock (for write) the LDevID Public Key in Data Vault (48 bytes) Slot 2 and Slot 3 +4. Derive the MLDSA Key Pair using CDI in Key Vault Slot 12 and store the key generation seed in Key Vault Slot 13. + + `LDevIDSeed = hmac512_kdf(KvSlot12, b"ldevid_mldsa_keygen", KvSlot13)` + + `LDevIdPubKeyMldsa = mldsa87_keygen(KvSlot13)` - `dv48_store(LDevIdPubKey.X, Dv48Slot2)` - `dv48_lock_wr(Dv48Slot2)` - `dv48_store(LDevIdPubKey.Y, Dv48Slot3)` - `dv48_lock_wr(Dv48Slot3)` +5. Store and lock (for write) the LDevID ECDSA and MLDSA Public Keys in the DCCM -5. Generate the `To Be Signed` DER Blob of the LDevId Certificate + `[TBD]` - `LDevIdTbs = gen_cert_tbs(LDEVID_CERT, IDevIdPubKey, LDevIdPubKey)` +5. Generate the `To Be Signed` DER Blob of the ECDSA LDevId Certificate -6. Sign the LDevID `To Be Signed` DER Blob with IDevId Private Key in Key Vault Slot 7 + `LDevIdTbsEcdsa = gen_cert_tbs(LDEVID_CERT, IDevIdPubKeyEcdsa, LDevIdPubKeyEcdsa)` - `LDevIdTbsDigest = sha384_digest(LDevIdTbs)` - `LDevIdCertSig = ecc384_sign(KvSlot7, LDevIdTbsDigest)` +6. Sign the LDevID `To Be Signed` DER Blob with IDevId ECDSA Private Key in Key Vault Slot 7 -7. Clear the IDevId Private Key in Key Vault Slot 7 + `LDevIdTbsDigestEcdsa = sha384_digest(LDevIdTbsEcdsa)` + + `LDevIdCertSigEcdsa = ecc384_sign(KvSlot7, LDevIdTbsDigestEcdsa)` + +7. Clear the IDevId ECDSA Private Key in Key Vault Slot 7 `kv_clear(KvSlot7)` 8. Verify the signature of LDevID `To Be Signed` Blob - `LDevIdTbsDigest = sha384_digest(LDevIdTbs)` - `Result = ecc384_verify(LDevIdPubKey, LDevIdTbsDigest, LDevIdCertSig)` + `Result = ecc384_verify(IDevIdPubKeyEcdsa, LDevIdTbsDigestEcdsa, LDevIdCertSigEcdsa)` + +9. Generate the `To Be Signed` DER Blob of the MLDSA LDevId Certificate + + `LDevIdTbsMldsa = gen_cert_tbs(LDEVID_CERT, IDevIdPubKeyMldsa, LDevIdPubKeyMldsa)` -9. Store and lock (for write) the LDevID Certificate Signature in the sticky Data Vault (48 bytes) Slot 0 & Slot 1 +10. Sign the LDevID `To Be Signed` DER Blob with the IDevId MLDSA Private Key derived from the seed in Key Vault Slot 10 - `dv48_store(LDevIdCertSig.R, Dv48Slot0)` - `dv48_lock_wr(Dv48Slot0)` - `dv48_store(LDevIdCertSig.S, Dv48Slot1)` - `dv48_lock_wr(Dv48Slot1)` + `LDevIdTbsDigestMldsa = sha512_digest(LDevIdTbsMldsa)` + + `LDevIdCertSigMldsa = mldsa87_sign(KvSlot10, LDevIdTbsDigestMldsa)` + +11. Clear the IDevId Mldsa seed in Key Vault Slot 10 + + `kv_clear(KvSlot10)` + +12. Verify the signature of LDevID `To Be Signed` Blob + + `Result = mldsa87_verify(IDevIdPubKeyMldsa, LDevIdTbsDigestMldsa, LDevIdCertSigMldsa)` + +9. Store and lock (for write) the LDevID Certificate ECDSA and MLDSA Signatures in DCCM + + `[TBD]` **Post-conditions:** - Vault state as follows: -| Slot | Key Vault | PCR Bank | Data Vault 48 Byte (Sticky) | Data Vault 4 Byte (Sticky) | -|------|-----------|----------|-----------------------------|----------------------------| -| 0 | | | 🔒LDevID Cert Signature R | | -| 1 | | | 🔒LDevID Cert Signature S | | -| 2 | | | 🔒LDevID Pub Key X | | -| 3 | | | 🔒LDevID Pub Key Y | | -| 5 | LDevID Private Key (48 bytes) | | | | -| 6 | LDevID CDI (48 bytes) | | | | +| Slot | Key Vault | PCR Bank | DCCM | +|------|-----------|----------|------| +| X | | | 🔒LDevID Cert Signature | +| X | | | 🔒LDevID Pub Key | +| 5 | LDevID Private Key - ECDSA (48 bytes) | | | +| 6 | LDevID CDI - ECDSA (48 bytes) | | | +| 12 | LDevID CDI - MLDSA (48 bytes) | | | +| 13 | LDevID Key Pair Seed - MLDSA (48 bytes) | | | ### Handling commands from mailbox @@ -418,8 +497,10 @@ Alias FMC Layer includes the measurement of the FMC and other security states. T **Pre-conditions:** -- LDevID CDI is stored in Key Vault Slot 6 -- LDevID Private Key is stored in Key Vault Slot 5 +- LDevID CDI - ECDSA is stored in Key Vault Slot 6 +- LDevID ECDSA Private Key is stored in Key Vault Slot 5 +- LDevID CDI - MLDSA is stored in Key Vault Slot 12 +- LDevID Key Pair Seed is stored in Key Vault Slot 13 - Firmware Image Bundle is successfully loaded and verified from the Mailbox - ROM has following information from Firmware Image Bundle - FMC_DIGEST - Digest of the FMC @@ -450,69 +531,99 @@ Alias FMC Layer includes the measurement of the FMC and other security states. T 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. +2. CDI for Alias is derived from PCR0. For the Alias FMC CDI Derivation, LDevID CDI - ECDSA 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. `Pcr0Measurement = pcr_read(Pcr0)` - `hmac384_kdf(KvSlot6, b"fmc_alias_cdi", Pcr0Measurement, KvSlot6)` -3. Derive Alias FMC ECC Key Pair using CDI in Key Vault Slot6 and store the generated private key in KeySlot7. + `hmac384_kdf(KvSlot6, label: b"fmc_alias_cdi", context: Pcr0Measurement, KvSlot6)` + + LDevID CDI - MLDSA in Key Vault Slot 12 is used as HMAC Key and contents of PCR0 are used as data. The resultant MAC is stored back in Slot 12. + + `hmac512_kdf(KvSlot12, label: b"fmc_alias_mldsa_cdi", context: Pcr0Measurement, KvSlot12)` + +3. Derive Alias FMC ECDSA Key Pair using CDI in Key Vault Slot 6 and store the generated private key in Key Vault Slot 7. + + `AliasFmcSeedEcdsa = hmac384_kdf(KvSlot6, b"fmc_alias_keygen", KvSlot3)` + + `AliasFmcPubKeyEcdsa = ecc384_keygen(KvSlot3, KvSlot7)` - `AliasFmcSeed = hmac384_kdf(KvSlot6, b"fmc_alias_keygen", KvSlot3)` - `AliasFmcPubKey = ecc384_keygen(KvSlot3, KvSlot7)` `kv_clear(KvSlot3)` -4. Store and lock (for write) the FMC Public Key in Data Vault (48 bytes) Slot 6 & Slot 7 + Derive the Alias FMC MLDSA Key Pair using CDI in Key Vault Slot 12 and store the key pair generation seed in Key Vault Slot 14. + + `AliasFmcSeedMldsa = hmac512_kdf(KvSlot12, b"fmc_alias_mldsa_keygen", KvSlot14)` + + `AliasFmcPubKeyMldsa = mldsa87_keygen(KvSlot14)` + - `dv48_store(FmcPubKey.X, Dv48Slot6)` - `dv48_lock_wr(Dv48Slot6)` - `dv48_store(FmcPubKey.Y, Dv48Slot7)` - `dv48_lock_wr(Dv48Slot7)` +4. Store and lock (for write) the FMC ECDSA and MLDSA Public Keys in the DCCM -5. Generate the `To Be Signed` DER Blob of the Alias FMC Certificate + `[TBD]` - `AliasFmcTbs = gen_cert_tbs(ALIAS_FMC_CERT, LDevIdPubKey, AliasFmcPubKey)` +5. Generate the `To Be Signed` DER Blob of the ECDSA Alias FMC Certificate -6. Sign the Alias FMC `To Be Signed` DER Blob with LDevId Private Key in Key Vault Slot 5 + `AliasFmcTbsEcdsa = gen_cert_tbs(ALIAS_FMC_CERT, LDevIdPubKeyEcdsa, AliasFmcPubKeyEcdsa)` - `AliasFmcTbsDigest = sha384_digest(AliasFmcTbs)` - `AliasFmcTbsCertSig = ecc384_sign(KvSlot5, AliasFmcTbsDigest)` +6. Sign the Alias FMC `To Be Signed` DER Blob with the LDevId ECDSA Private Key in Key Vault Slot 5 + + `AliasFmcTbsDigestEcdsa = sha384_digest(AliasFmcTbsEcdsa)` + + `AliasFmcTbsCertSigEcdsa = ecc384_sign(KvSlot5, AliasFmcTbsDigestEcdsa)` 7. Clear the LDevId Private Key in Key Vault Slot 5 `kv_clear(KvSlot5)` -8. Verify the signature of Alias FMC `To Be Signed` Blob +8. Verify the signature of Alias FMC `To Be Signed` ECDSA Blob + + `Result = ecc384_verify(LDevIdPubKeyEcdsa, AliasFmcDigestEcdsa, AliasFmcTbsCertSigEcdsa)` + +9. Generate the `To Be Signed` DER Blob of the MLDSA Alias FMC Certificate + + `AliasFmcTbsMldsa = gen_cert_tbs(ALIAS_FMC_CERT, LDevIdPubKeyMldsa, AliasFmcPubKeyMldsa)` + +10. Sign the Alias FMC `To Be Signed` DER Blob with the LDevId MLDSA Private Key generated from the seed in Key Vault Slot 13 + + `AliasFmcTbsDigestMldsa = sha512_digest(AliasFmcTbsMldsa)` + + `AliasFmcTbsCertSigMldsa = mldsa87_sign(KvSlot13, AliasFmcTbsDigestMldsa)` - `AliasFmcTbsDigest = sha384_digest(AliasFmcTbs)` - `Result = ecc384_verify(AliasFmcPubKey, AliasFmcDigest , AliasFmcTbsCertSig)` +11. Clear the seed in Key Vault Slot 13 -9. Store and lock (for write) the LDevID Certificate Signature in the sticky Data Vault (48 bytes) Slot 4 and Slot 5 + `kv_clear(KvSlot13)` - `dv48_store(FmcTbsCertSig.R, Dv48Slot4)` - `dv48_lock_wr(Dv48Slot4)` +12. Verify the signature of Alias FMC `To Be Signed` MLDSA Blob - `dv48_store(FmcTbsCertSig.S, Dv48Slot5)` - `dv48_lock_wr(Dv48Slot5)` + `Result = mldsa87_verify(LDevIdPubKeyMldsa, AliasFmcDigestMldsa, AliasFmcTbsCertSigMldsa)` -10. Lock critical state needed for warm and update reset in Data Vault +13. Store and lock (for write) the Alias FMC Certificate ECDSA and MLDSA Signatures in the DCCM + + `[TBD]` + +14. Lock critical state needed for warm and update reset in the DCCM [TBD] `dv48_store(FMC_DIGEST, Dv48Slot8)` + `dv48_lock_wr(Dv48Slot8)` `dv4_store(FMC_SVN, Dv4Slot0)` + `dv4_lock_wr(Dv4Slot0)` `dv48_store(FUSE_OWNER_PK_HASH, Dv48Slot9)` + `dv48_lock_wr(Dv48Slot9)` `dv4_store(MANUFACTURER_ECC_PK_INDEX, Dv4Slot3)` + `dv4_lock_wr(Dv4Slot3)` `dv4_store(MANUFACTURER_LMS_PK_INDEX, Dv4Slot4)` + `dv4_lock_wr(Dv4Slot4)` - **Note**: If LMS validation is not enabled, a value of 0xFFFFFFFF is stored. `dv4_store(ROM_COLD_BOOT_STATUS, Dv4Slot1)` + `dv4_lock_wr(Dv4Slot1)` **Note**: A value of 0x140 is stored on a successful cold boot. @@ -528,10 +639,12 @@ Alias FMC Layer includes the measurement of the FMC and other security states. T | 3 | | 🔒LDevID Pub Key Y | 🔒Manufacturer ECC Public Key Index | | 4 | | 🔒Alias FMC Cert Signature R | 🔒Manufacturer LMS Public Key Index | | 5 | | 🔒Alias FMC Cert Signature S | | -| 6 | Alias FMC CDI (48 bytes) | 🔒Alias FMC Pub Key X | | -| 7 | Alias FMC Private Key (48 bytes) | 🔒Alias FMC Pub Key Y | | +| 6 | Alias FMC CDI - ECDSA (48 bytes) | 🔒Alias FMC Pub Key X | | +| 7 | Alias FMC Private Key - ECDSA (48 bytes) | 🔒Alias FMC Pub Key Y | | | 8 | | 🔒FMC Digest | | | 9 | | 🔒Owner PK Hash | | +| 12 | Alias FMC CDI - MLDSA | | | +| 14 | Alias FMC Key Pair Seed - MLDSA | | | ## Warm reset flow From e8b955818f5cae034be92db9fb116939aa30cfcd Mon Sep 17 00:00:00 2001 From: Vishal Mhatre <38512878+mhatrevi@users.noreply.github.com> Date: Sun, 27 Oct 2024 07:27:10 +0530 Subject: [PATCH 6/6] [doc] ROM README update for fuses (#1742) --- rom/dev/README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/rom/dev/README.md b/rom/dev/README.md index 6c5e057f38..c34a5b85bd 100644 --- a/rom/dev/README.md +++ b/rom/dev/README.md @@ -1,5 +1,5 @@ -# Caliptra - ROM Specification v1.0 +# Caliptra - ROM Specification v2.0 ## Scope @@ -44,20 +44,25 @@ following topics: Following are the main FUSE & Architectural Registers used by the Caliptra ROM for DICE Derivations: +### Fuse Registers | Register | Width (bits) | Description | | :------------------------------ | :------------| :----------------------------------------------------- | -| CPTRA_SECURITY_STATE | 32 | Security State of the device. Contains two fields:
**LIFECYCLE_STATE**: Unprovisioned, Manufacturing or Production
**DEBUG_ENABLED**: Boolean indicating if debug is enabled or not | | FUSE_UDS_SEED | 512 | Obfuscated UDS | | FUSE_FIELD_ENTROPY | 512 | Obfuscated Field Entropy | | FUSE_KEY_MANIFEST_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors | -| FUSE_KEY_MANIFEST_PK_HASH_MASK | 32 | Manufacturer ECC Public Key Revocation Mask | -| FUSE_PQC_REVOCATION | 32 | Manufacturer LMS or MLDSA Public Key Revocation Mask | +| FUSE_ECC_REVOCATION (FUSE_KEY_MANIFEST_PK_HASH_MASK) | 32 | Manufacturer ECC Public Key Revocation Mask | +| FUSE_LMS_REVOCATION | 32 | Manufacturer LMS Public Key Revocation Mask | +| FUSE_MLDSA_REVOCATION | 32 | Manufacturer MLDSA Public Key Revocation Mask | | FUSE_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash | | FUSE_FMC_KEY_MANIFEST_SVN | 32 | FMC Security Version Number | | FUSE_RUNTIME_SVN | 128 | Runtime Security Version Number | | FUSE_ANTI_ROLLBACK_DISABLE | 1 | Disable SVN checking for FMC & Runtime when bit is set | | FUSE_IDEVID_CERT_ATTR | 768 | FUSE containing information for generating IDEVID CSR
**Word 0**: X509 Key Id Algorithm (2 bits) 1: SHA1, 2: SHA256, 2: SHA384, 3: Fuse
**Word 1,2,3,4,5**: Subject Key Id
**Words 7,8**: Unique Endpoint ID | -| CPTRA_DBG_MANUF_SERVICE_REG | 16 | Manufacturing Services:
**Bit 0**: IDEVID CSR upload
**Bit 1**: Random Number Generator Unavailable
**Bit 15:8**: FIPS test hook code
**Bit 30**: Fake ROM enable in production lifecycle mode
**Bit 31**: Fake ROM image verify enable | +| MANUF_DEBUG_UNLOCK_TOKEN | 128 | Secret value for manufacturing debug unlock authorization | + +### Architectural Registers +Please refer to the following link for SOC interface registers: +https://chipsalliance.github.io/caliptra-rtl/main/external-regs/?p=caliptra_top_reg.generic_and_fuse_reg ## Firmware image bundle @@ -83,7 +88,7 @@ It is the unsigned portion of the manifest. Preamble contains the signing public - Loads the preamble from the mailbox. - Calculates the hash of ECC and LMS or MLDSA Public Key Descriptors in the preamble and compares it against the hash in the fuse (FUSE_KEY_MANIFEST_PK_HASH). If the hashes do not match, the boot fails. -- Verifies the active Manufacturer Public Key(s) based on fuse (FUSE_KEY_MANIFEST_PK_HASH_MASK for ECC public key, FUSE_PQC_REVOCATION for LMS or MLDSA public key) +- Verifies the active Manufacturer Public Key(s) based on fuse (FUSE_ECC_REVOCATION for ECC public key, FUSE_LMS_REVOCATION for LMS public key or FUSE_MLDSA_REVOCATION for MLDSA public key) *Note: All fields are little endian unless specified* @@ -694,8 +699,9 @@ The following are the pre-conditions that should be satisfied: - Caliptra has transitioned through the BOOTFSM and all the fuses that are required for the validation are already populated by SOC. - The FUSES programmed by the soc are - fuse_key_manifest_pk_hash : This fuse contains the hash of the manufacturer key descriptors present in preamble. - - fuse_key_manifest_pk_hash_mask : This is the bitmask of the ECC keys which are revoked. - - fuse_pqc_revocation : This is the bitmask of the LMS or MLDSA keys which are revoked. + - fuse_ecc_revocation : This is the bitmask of the ECC keys which are revoked. + - fuse_lms_revocation : This is the bitmask of the LMS keys which are revoked. + - fuse_mldsa_revocation : This is the bitmask of the MLDSA keys which are revoked. - fuse_owner_pk_hash : The hash of the owner public keys in preamble. - fuse_key_manifest_svn : Used in FMC validation to make sure that the version number is good. - fuse_runtime_svn : Used in RT validation to make sure that the runtime image's version number is good. @@ -718,13 +724,13 @@ The following are the pre-conditions that should be satisfied: ### Preamble validation: Manufacturing key validation -- fuse_key_manifest_pk_hash_mask is the mask which revokes an ECC key. +- fuse_ecc_revocation is the mask which revokes an ECC key. - If bit-0 is set, that key is disabled. All other higher bits which are zeros, are still enabled. - If all the bits are zeros, all the keys are enabled. - If bit-0 and bit-1 are set, all higher slot bits (2 and 3) are enabled. -- Validate that the 'Active Key Index' in the preamble is not disabled in the fuse_key_manifest_pk_hash_mask fuse. +- Validate that the 'Active Key Index' in the preamble is not disabled in the fuse_ecc_revocation fuse. - If the key is disabled, fail the validation. -- Repeat the above procedure for LMS or MLDSA keys using the fuse_pqc_revocation for key revocation. +- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses respectively for key revocation. ### Preamble validation: Validate the owner key