diff --git a/Cargo.lock b/Cargo.lock index 49d9d08803..7eba474eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,7 @@ dependencies = [ "caliptra-drivers", "caliptra-registers", "caliptra-test-harness", + "ufmt", ] [[package]] diff --git a/api/src/mailbox.rs b/api/src/mailbox.rs index a8a4babdb2..c0f6a5929f 100644 --- a/api/src/mailbox.rs +++ b/api/src/mailbox.rs @@ -50,6 +50,13 @@ impl From for u32 { } } +/// A trait implemented by request types. Describes the associated command ID +/// and response type. +pub trait Request: AsBytes + FromBytes { + const ID: CommandId; + type Resp: FromBytes; +} + // Contains all the possible mailbox response structs #[cfg_attr(test, derive(PartialEq, Debug, Eq))] #[allow(clippy::large_enum_variant)] @@ -292,6 +299,16 @@ pub struct GetIdevInfoResp { pub idev_pub_y: [u8; 48], } +#[repr(C)] +#[derive(Default, Debug, AsBytes, FromBytes, PartialEq, Eq)] +pub struct TestOnlyGetLdevCertReq { + header: MailboxReqHeader, +} +impl Request for TestOnlyGetLdevCertReq { + const ID: CommandId = CommandId::TEST_ONLY_GET_LDEV_CERT; + type Resp = GetLdevCertResp; +} + // GET_LDEV_CERT // No command-specific input args #[repr(C)] @@ -303,6 +320,10 @@ pub struct GetLdevCertResp { } impl GetLdevCertResp { pub const DATA_MAX_SIZE: usize = 1024; + + pub fn data(&self) -> Option<&[u8]> { + self.data.get(..self.data_size as usize) + } } // ECDSA384_SIGNATURE_VERIFY @@ -315,6 +336,10 @@ pub struct EcdsaVerifyReq { pub signature_r: [u8; 48], pub signature_s: [u8; 48], } +impl Request for EcdsaVerifyReq { + const ID: CommandId = CommandId::ECDSA384_VERIFY; + type Resp = MailboxRespHeader; +} // No command-specific output args // TEST_ONLY_HMAC384_SIGNATURE_VERIFY @@ -327,6 +352,10 @@ pub struct HmacVerifyReq { pub len: u32, pub msg: [u8; 256], } +impl Request for HmacVerifyReq { + const ID: CommandId = CommandId::TEST_ONLY_HMAC384_VERIFY; + type Resp = MailboxRespHeader; +} // No command-specific output args // STASH_MEASUREMENT @@ -350,6 +379,10 @@ impl Default for StashMeasurementReq { } } } +impl Request for StashMeasurementReq { + const ID: CommandId = CommandId::STASH_MEASUREMENT; + type Resp = StashMeasurementResp; +} #[repr(C)] #[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)] @@ -384,6 +417,10 @@ impl Default for InvokeDpeReq { } } } +impl Request for InvokeDpeReq { + const ID: CommandId = CommandId::INVOKE_DPE; + type Resp = InvokeDpeResp; +} #[repr(C)] #[derive(Debug, AsBytes, FromBytes, PartialEq, Eq)] @@ -417,6 +454,16 @@ impl Default for InvokeDpeResp { } } +#[repr(C)] +#[derive(Debug, Default, AsBytes, FromBytes, PartialEq, Eq)] +pub struct TestOnlyGetFmcAliasCertReq { + header: MailboxReqHeader, +} +impl Request for TestOnlyGetFmcAliasCertReq { + const ID: CommandId = CommandId::TEST_ONLY_GET_FMC_ALIAS_CERT; + type Resp = GetLdevCertResp; +} + // TEST_ONLY_GET_FMC_ALIAS_CERT // No command-specific input args #[repr(C)] @@ -428,6 +475,10 @@ pub struct TestGetFmcAliasCertResp { } impl TestGetFmcAliasCertResp { pub const DATA_MAX_SIZE: usize = 1024; + + pub fn data(&self) -> Option<&[u8]> { + self.data.get(..self.data_size as usize) + } } // FIPS_SELF_TEST diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs index 4258a9c38a..1dbec749c3 100644 --- a/hw-model/src/lib.rs +++ b/hw-model/src/lib.rs @@ -1,5 +1,6 @@ // Licensed under the Apache-2.0 license +use std::mem; use std::path::PathBuf; use std::str::FromStr; use std::{ @@ -8,6 +9,8 @@ use std::{ io::{stdout, ErrorKind, Write}, }; +use api::calc_checksum; +use api::mailbox::{MailboxReqHeader, MailboxRespHeader}; use caliptra_api as api; use caliptra_emu_bus::Bus; use caliptra_hw_model_types::{ @@ -237,6 +240,12 @@ pub enum ModelError { UnableToLockSha512Acc, UploadMeasurementResponseError, UnableToReadMailbox, + MailboxNoResponseData, + MailboxReqTypeTooSmall, + MailboxRespTypeTooSmall, + MailboxUnexpectedResponseLen { expected: u32, actual: u32 }, + MailboxRespInvalidChecksum { expected: i32, actual: i32 }, + MailboxRespInvalidFipsStatus(u32), } impl Error for ModelError {} impl Display for ModelError { @@ -268,6 +277,33 @@ impl Display for ModelError { write!(f, "Error in response after uploading measurement") } ModelError::UnableToReadMailbox => write!(f, "Unable to read mailbox regs"), + ModelError::MailboxNoResponseData => { + write!(f, "Expected response data but none was found") + } + ModelError::MailboxReqTypeTooSmall => { + write!(f, "Mailbox request type too small to contain header") + } + ModelError::MailboxRespTypeTooSmall => { + write!(f, "Mailbox response type too small to contain header") + } + ModelError::MailboxUnexpectedResponseLen { expected, actual } => { + write!( + f, + "Expected mailbox response lenth of {expected}, was {actual}" + ) + } + ModelError::MailboxRespInvalidChecksum { expected, actual } => { + write!( + f, + "Mailbox response had invalid checksum: expected {expected}, was {actual}" + ) + } + ModelError::MailboxRespInvalidFipsStatus(status) => { + write!( + f, + "Mailbox response had non-success FIPS status: 0x{status:x}" + ) + } } } } @@ -732,6 +768,56 @@ pub trait HwModel { fn set_apb_pauser(&mut self, pauser: u32); + /// Executes a typed request and (if success), returns the typed response. + /// The checksum field of the request is calculated, and the checksum of the + /// response is validated. + fn mailbox_execute_req( + &mut self, + mut req: R, + ) -> std::result::Result { + if mem::size_of::() < mem::size_of::() { + return Err(ModelError::MailboxReqTypeTooSmall); + } + if mem::size_of::() < mem::size_of::() { + return Err(ModelError::MailboxRespTypeTooSmall); + } + let (header_bytes, payload_bytes) = req + .as_bytes_mut() + .split_at_mut(mem::size_of::()); + + let mut header = MailboxReqHeader::read_from(header_bytes as &[u8]).unwrap(); + header.chksum = api::calc_checksum(R::ID.into(), payload_bytes); + header_bytes.copy_from_slice(header.as_bytes()); + + let Some(response_bytes) = self.mailbox_execute(R::ID.into(), req.as_bytes())? else { + return Err(ModelError::MailboxNoResponseData); + }; + let response = match R::Resp::read_from(response_bytes.as_slice()) { + Some(response) => response, + None => { + return Err(ModelError::MailboxUnexpectedResponseLen { + expected: mem::size_of::() as u32, + actual: response_bytes.len() as u32, + }) + } + }; + let response_header = + MailboxRespHeader::read_from_prefix(response_bytes.as_slice()).unwrap(); + let actual_checksum = calc_checksum(0, &response_bytes[4..]); + if actual_checksum != response_header.chksum { + return Err(ModelError::MailboxRespInvalidChecksum { + expected: response_header.chksum, + actual: actual_checksum, + }); + } + if response_header.fips_status != MailboxRespHeader::FIPS_STATUS_APPROVED { + return Err(ModelError::MailboxRespInvalidFipsStatus( + response_header.fips_status, + )); + } + Ok(response) + } + /// Executes `cmd` with request data `buf`. Returns `Ok(Some(_))` if /// the uC responded with data, `Ok(None)` if the uC indicated success /// without data, Err(ModelError::MailboxCmdFailed) if the microcontroller @@ -962,11 +1048,15 @@ pub trait HwModel { #[cfg(test)] mod tests { - use crate::{mmio::Rv32GenMmio, BootParams, HwModel, InitParams, ModelError, ShaAccMode}; + use crate::{ + mmio::Rv32GenMmio, BootParams, DefaultHwModel, HwModel, InitParams, ModelError, ShaAccMode, + }; + use caliptra_api::mailbox::{self, CommandId, MailboxReqHeader, MailboxRespHeader}; use caliptra_builder::firmware; use caliptra_emu_bus::Bus; use caliptra_emu_types::RvSize; use caliptra_registers::{mbox::enums::MboxStatusE, soc_ifc}; + use zerocopy::{AsBytes, FromBytes}; use crate as caliptra_hw_model; @@ -1377,4 +1467,140 @@ mod tests { ); } } + + #[test] + pub fn test_mailbox_execute_req() { + const NO_DATA_CMD: u32 = 0x2000_0000; + const SET_RESPONSE_CMD: u32 = 0x3000_0000; + const GET_RESPONSE_CMD: u32 = 0x3000_0001; + + #[repr(C)] + #[derive(AsBytes, FromBytes, Default)] + struct TestReq { + hdr: MailboxReqHeader, + data: [u8; 4], + } + impl mailbox::Request for TestReq { + const ID: CommandId = CommandId(GET_RESPONSE_CMD); + type Resp = TestResp; + } + #[repr(C)] + #[derive(AsBytes, Debug, FromBytes, PartialEq, Eq)] + struct TestResp { + hdr: MailboxRespHeader, + data: [u8; 4], + } + + #[repr(C)] + #[derive(AsBytes, FromBytes, Default)] + struct TestReqNoData { + hdr: MailboxReqHeader, + data: [u8; 4], + } + impl mailbox::Request for TestReqNoData { + const ID: CommandId = CommandId(NO_DATA_CMD); + type Resp = TestResp; + } + + fn set_response(model: &mut DefaultHwModel, data: &[u8]) { + model.mailbox_execute(SET_RESPONSE_CMD, data).unwrap(); + } + + let rom = + caliptra_builder::build_firmware_rom(&firmware::hw_model_tests::MAILBOX_RESPONDER) + .unwrap(); + let mut model = caliptra_hw_model::new(BootParams { + init_params: InitParams { + rom: &rom, + ..Default::default() + }, + ..Default::default() + }) + .unwrap(); + + // Success + set_response( + &mut model, + &[ + 0x2d, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!', b'!', + ], + ); + let resp = model + .mailbox_execute_req(TestReq { + data: *b"Hi!!", + ..Default::default() + }) + .unwrap(); + model + .step_until_output_and_take("|dcfeffff48692121|") + .unwrap(); + assert_eq!( + resp, + TestResp { + hdr: MailboxRespHeader { + chksum: -211, + fips_status: 0 + }, + data: *b"HI!!", + }, + ); + + // Set wrong length in response + set_response( + &mut model, + &[ + 0x2d, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!', + ], + ); + let resp = model.mailbox_execute_req(TestReq { + data: *b"Hi!!", + ..Default::default() + }); + assert_eq!( + resp, + Err(ModelError::MailboxUnexpectedResponseLen { + expected: 12, + actual: 11 + }) + ); + + // Set bad checksum in response + set_response( + &mut model, + &[ + 0x2e, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, b'H', b'I', b'!', b'!', + ], + ); + let resp = model.mailbox_execute_req(TestReq { + data: *b"Hi!!", + ..Default::default() + }); + assert_eq!( + resp, + Err(ModelError::MailboxRespInvalidChecksum { + expected: -210, + actual: -211 + }) + ); + + // Set bad FIPS status in response + set_response( + &mut model, + &[ + 0x0c, 0xff, 0xff, 0xff, 0x01, 0x20, 0x00, 0x00, b'H', b'I', b'!', b'!', + ], + ); + let resp = model.mailbox_execute_req(TestReq { + data: *b"Hi!!", + ..Default::default() + }); + assert_eq!(resp, Err(ModelError::MailboxRespInvalidFipsStatus(0x2001))); + + // Set no data in response + let resp = model.mailbox_execute_req(TestReqNoData { + data: *b"Hi!!", + ..Default::default() + }); + assert_eq!(resp, Err(ModelError::MailboxNoResponseData)); + } } diff --git a/hw-model/test-fw/Cargo.toml b/hw-model/test-fw/Cargo.toml index 9078119b4c..62b70b0b90 100644 --- a/hw-model/test-fw/Cargo.toml +++ b/hw-model/test-fw/Cargo.toml @@ -15,6 +15,7 @@ caliptra-drivers.workspace = true caliptra-registers.workspace = true caliptra-test-harness.workspace = true caliptra-cfi-lib = { workspace = true, default-features = false, features = ["cfi", "cfi-counter" ] } +ufmt.workspace = true [[bin]] name = "test_iccm_unaligned_write" diff --git a/hw-model/test-fw/mailbox_responder.rs b/hw-model/test-fw/mailbox_responder.rs index fcaa4f2afc..5a26921fe7 100644 --- a/hw-model/test-fw/mailbox_responder.rs +++ b/hw-model/test-fw/mailbox_responder.rs @@ -9,6 +9,7 @@ #[allow(unused)] use caliptra_test_harness; +use caliptra_drivers::cprint; use caliptra_registers::{self, mbox::MboxCsr, sha512_acc::Sha512AccCsr, soc_ifc::SocIfcReg}; #[panic_handler] @@ -30,6 +31,8 @@ extern "C" fn main() { .cptra_flow_status() .write(|w| w.ready_for_fw(true)); + let mut replay_buf_len = 0; + let mut replay_buf = [0u32; 2048]; loop { while !mbox.status().read().mbox_fsm_ps().mbox_execute_uc() { // Wait for a request from the SoC. @@ -70,6 +73,38 @@ extern "C" fn main() { 0x2000_0000 => { mbox.status().write(|w| w.status(|w| w.cmd_complete())); } + // Store a buf to be returned by 0x3000_0001 + 0x3000_0000 => { + let dlen = mbox.dlen().read(); + let dlen_words = usize::try_from((dlen + 3) / 4).unwrap(); + for i in 0..usize::min(dlen_words, replay_buf.len()) { + replay_buf[i] = mbox.dataout().read(); + } + replay_buf_len = u32::min(dlen, u32::try_from(replay_buf.len()).unwrap()); + mbox.status().write(|w| w.status(|w| w.cmd_complete())); + } + 0x3000_0001 => { + cprint!("|"); + let dlen = mbox.dlen().read(); + let dlen_words = usize::try_from((dlen + 3) / 4).unwrap(); + for i in 0..(dlen_words) { + if i == dlen_words - 1 && dlen % 4 != 0 { + let word_bytes = mbox.dataout().read().to_le_bytes(); + for byte in &word_bytes[..dlen as usize % 4] { + cprint!("{:02x}", *byte); + } + } else { + cprint!("{:08x}", mbox.dataout().read().to_be()); + } + } + cprint!("|"); + mbox.dlen().write(|_| replay_buf_len); + let dlen_words = usize::try_from((replay_buf_len + 3) / 4).unwrap(); + for i in 0..dlen_words { + mbox.datain().write(|_| replay_buf[i]); + } + mbox.status().write(|w| w.status(|w| w.data_ready())); + } 0x5000_0000 => { // Unlock sha512acc peripheral by writing 1 sha512acc.lock().write(|w| w.lock(true)); diff --git a/test/tests/caliptra_integration_tests/smoke_test.rs b/test/tests/caliptra_integration_tests/smoke_test.rs index 46c5bd1a15..ac838b2501 100644 --- a/test/tests/caliptra_integration_tests/smoke_test.rs +++ b/test/tests/caliptra_integration_tests/smoke_test.rs @@ -3,8 +3,8 @@ use caliptra_builder::{firmware, ImageOptions}; use caliptra_common::fips::FipsVersionCmd; use caliptra_common::mailbox_api::{ - CommandId, FipsVersionResp, GetLdevCertResp, MailboxReqHeader, MailboxRespHeader, - TestGetFmcAliasCertResp, + CommandId, FipsVersionResp, MailboxReqHeader, MailboxRespHeader, TestOnlyGetFmcAliasCertReq, + TestOnlyGetLdevCertReq, }; use caliptra_hw_model::{BootParams, HwModel, InitParams, ModelError, SecurityState}; use caliptra_hw_model_types::{DeviceLifecycle, Fuses}; @@ -191,37 +191,12 @@ fn smoke_test() { \____\__,_|_|_| .__/ \__|_| \__,_| |_| \_\|_|"#, ); - let payload = MailboxReqHeader { - chksum: caliptra_common::checksum::calc_checksum( - u32::from(CommandId::TEST_ONLY_GET_LDEV_CERT), - &[], - ), - }; - - // Execute the command let ldev_cert_resp = hw - .mailbox_execute( - u32::from(CommandId::TEST_ONLY_GET_LDEV_CERT), - payload.as_bytes(), - ) - .unwrap() + .mailbox_execute_req(TestOnlyGetLdevCertReq::default()) .unwrap(); - let ldev_cert_resp = GetLdevCertResp::read_from(ldev_cert_resp.as_bytes()).unwrap(); - - // Verify checksum and FIPS approval - assert!(caliptra_common::checksum::verify_checksum( - ldev_cert_resp.hdr.chksum, - 0x0, - &ldev_cert_resp.as_bytes()[core::mem::size_of_val(&ldev_cert_resp.hdr.chksum)..], - )); - assert_eq!( - ldev_cert_resp.hdr.fips_status, - MailboxRespHeader::FIPS_STATUS_APPROVED - ); - // Extract the certificate from the response - let ldev_cert_der = &ldev_cert_resp.data[..(ldev_cert_resp.data_size as usize)]; + let ldev_cert_der = ldev_cert_resp.data().unwrap(); let ldev_cert = openssl::x509::X509::from_der(ldev_cert_der).unwrap(); let ldev_cert_txt = String::from_utf8(ldev_cert.to_text().unwrap()).unwrap(); @@ -258,38 +233,13 @@ fn smoke_test() { println!("ldev-cert: {}", ldev_cert_txt); - let payload = MailboxReqHeader { - chksum: caliptra_common::checksum::calc_checksum( - u32::from(CommandId::TEST_ONLY_GET_FMC_ALIAS_CERT), - &[], - ), - }; - // Execute command let fmc_alias_cert_resp = hw - .mailbox_execute( - u32::from(CommandId::TEST_ONLY_GET_FMC_ALIAS_CERT), - payload.as_bytes(), - ) - .unwrap() + .mailbox_execute_req(TestOnlyGetFmcAliasCertReq::default()) .unwrap(); - let fmc_alias_cert_resp = - TestGetFmcAliasCertResp::read_from(fmc_alias_cert_resp.as_bytes()).unwrap(); - - // Verify checksum and FIPS approval - assert!(caliptra_common::checksum::verify_checksum( - fmc_alias_cert_resp.hdr.chksum, - 0x0, - &fmc_alias_cert_resp.as_bytes()[core::mem::size_of_val(&fmc_alias_cert_resp.hdr.chksum)..], - )); - assert_eq!( - fmc_alias_cert_resp.hdr.fips_status, - MailboxRespHeader::FIPS_STATUS_APPROVED - ); - // Extract the certificate from the response - let fmc_alias_cert_der = &fmc_alias_cert_resp.data[..(fmc_alias_cert_resp.data_size as usize)]; + let fmc_alias_cert_der = fmc_alias_cert_resp.data().unwrap(); let fmc_alias_cert = openssl::x509::X509::from_der(fmc_alias_cert_der).unwrap(); println!(