diff --git a/builder/src/firmware.rs b/builder/src/firmware.rs index 8d194d825b..7de4f72a13 100644 --- a/builder/src/firmware.rs +++ b/builder/src/firmware.rs @@ -33,6 +33,12 @@ pub const ROM_FAKE_WITH_UART: FwId = FwId { features: &["emu", "fake-rom"], }; +pub const ROM_WITH_UART_FIPS_TEST_HOOKS: FwId = FwId { + crate_name: "caliptra-rom", + bin_name: "caliptra-rom", + features: &["emu", "fips-test-hooks"], +}; + pub const FMC_WITH_UART: FwId = FwId { crate_name: "caliptra-fmc", bin_name: "caliptra-fmc", @@ -368,6 +374,7 @@ pub const REGISTERED_FW: &[&FwId] = &[ &ROM, &ROM_WITH_UART, &ROM_FAKE_WITH_UART, + &ROM_WITH_UART_FIPS_TEST_HOOKS, &FMC_WITH_UART, &FMC_FAKE_WITH_UART, &APP, diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml index 8735d9fd44..0825f13e72 100644 --- a/drivers/Cargo.toml +++ b/drivers/Cargo.toml @@ -35,6 +35,7 @@ itrng = ["caliptra-hw-model/itrng"] verilator = ["caliptra-hw-model/verilator"] no-cfi = [] "hw-1.0" = ["caliptra-builder/hw-1.0", "caliptra-registers/hw-1.0"] +fips-test-hooks = [] [dev-dependencies] caliptra-builder.workspace = true diff --git a/drivers/src/fips_test_hooks.rs b/drivers/src/fips_test_hooks.rs new file mode 100755 index 0000000000..66f964fd7c --- /dev/null +++ b/drivers/src/fips_test_hooks.rs @@ -0,0 +1,82 @@ +// Licensed under the Apache-2.0 license + +use caliptra_registers::soc_ifc::SocIfcReg; + +pub struct FipsTestHook; + +impl FipsTestHook { + pub const RSVD: u8 = 0x0; + // Set by Caliptra + pub const COMPLETE: u8 = 0x1; + // Set by external test + pub const CONTINUE: u8 = 0x10; + pub const HALT_SELF_TESTS: u8 = 0x21; + pub const SHA384_ERROR: u8 = 0x22; + pub const LMS_ERROR: u8 = 0x23; + + /// # Safety + /// + /// This function interrupts normal flow and halts operation of the ROM or FW + /// (Only when the hook_cmd matches the value from get_fips_test_hook_code) + pub unsafe fn halt_if_hook_set(hook_cmd: u8) { + if get_fips_test_hook_code() == hook_cmd { + // Report that we've reached this point + set_fips_test_hook_code(FipsTestHook::COMPLETE); + + // Wait for the CONTINUE command + while get_fips_test_hook_code() != FipsTestHook::CONTINUE {} + + // Write COMPLETE + set_fips_test_hook_code(FipsTestHook::COMPLETE); + } + } + + /// # Safety + /// + /// This function returns an intentionally corrupted version of the data provided + /// (Only when the hook_cmd matches the value from get_fips_test_hook_code) + pub unsafe fn corrupt_data_if_hook_set(hook_cmd: u8, data: &T) -> T { + if get_fips_test_hook_code() == hook_cmd { + let mut mut_data = *data; + let ptr_t = &mut mut_data as *mut T; + let mut_u8 = ptr_t as *mut u8; + let byte_0 = unsafe { &mut *mut_u8 }; + + // Corrupt (invert) the first byte + *byte_0 = !*byte_0; + + return mut_data; + } + + *data + } +} + +/// # Safety +/// +/// Temporarily creates a new instance of SocIfcReg instead of following the +/// normal convention of sharing one instance +unsafe fn get_fips_test_hook_code() -> u8 { + // Bits 23:16 indicate the 8 bit code for the enabled FIPS test hook + const CODE_MASK: u32 = 0x00FF0000; + const CODE_OFFSET: u32 = 16; + let soc_ifc = unsafe { SocIfcReg::new() }; + let soc_ifc_regs = soc_ifc.regs(); + let val = soc_ifc_regs.cptra_dbg_manuf_service_reg().read(); + ((val & CODE_MASK) >> CODE_OFFSET) as u8 +} + +/// # Safety +/// +/// Temporarily creates a new instance of SocIfcReg instead of following the +/// normal convention of sharing one instance +unsafe fn set_fips_test_hook_code(code: u8) { + // Bits 23:16 indicate the 8 bit code for the enabled FIPS test hook + const CODE_MASK: u32 = 0x00FF0000; + const CODE_OFFSET: u32 = 16; + let mut soc_ifc = unsafe { SocIfcReg::new() }; + let soc_ifc_regs = soc_ifc.regs_mut(); + let val = (soc_ifc_regs.cptra_dbg_manuf_service_reg().read() & !(CODE_MASK)) + | ((code as u32) << CODE_OFFSET); + soc_ifc_regs.cptra_dbg_manuf_service_reg().write(|_| val); +} diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index ce8c140259..632567e00f 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -25,6 +25,8 @@ mod doe; mod ecc384; mod error_reporter; mod exit_ctrl; +#[cfg(feature = "fips-test-hooks")] +pub mod fips_test_hooks; mod fuse_bank; pub mod fuse_log; pub mod hand_off; @@ -65,6 +67,8 @@ pub use ecc384::{ }; pub use error_reporter::{report_fw_error_fatal, report_fw_error_non_fatal}; pub use exit_ctrl::ExitCtrl; +#[cfg(feature = "fips-test-hooks")] +pub use fips_test_hooks::FipsTestHook; pub use fuse_bank::{ FuseBank, IdevidCertAttr, RomVerifyConfig, VendorPubKeyRevocation, X509KeyIdAlgo, }; diff --git a/drivers/src/lms.rs b/drivers/src/lms.rs index 414f4f19cd..884374336b 100644 --- a/drivers/src/lms.rs +++ b/drivers/src/lms.rs @@ -444,8 +444,17 @@ impl Lms { lms_public_key: &LmsPublicKey<6>, lms_sig: &LmsSignature<6, 51, 15>, ) -> CaliptraResult { + #[cfg(feature = "fips-test-hooks")] + let input_string = unsafe { + crate::FipsTestHook::corrupt_data_if_hook_set( + crate::FipsTestHook::LMS_ERROR, + &input_string, + ) + }; + let mut candidate_key = self.verify_lms_signature_cfi(sha256_driver, input_string, lms_public_key, lms_sig)?; + let result = if candidate_key != HashValue::from(lms_public_key.digest) { Ok(LmsResult::SigVerifyFailed) } else { diff --git a/drivers/src/sha384.rs b/drivers/src/sha384.rs index 65798a1c4a..9f9eaed2e2 100644 --- a/drivers/src/sha384.rs +++ b/drivers/src/sha384.rs @@ -102,6 +102,14 @@ impl Sha384 { } let digest = self.read_digest(); + #[cfg(feature = "fips-test-hooks")] + let digest = unsafe { + crate::FipsTestHook::corrupt_data_if_hook_set( + crate::FipsTestHook::SHA384_ERROR, + &digest, + ) + }; + self.zeroize_internal(); Ok(digest) diff --git a/error/src/lib.rs b/error/src/lib.rs index 6d07cb9a39..c0179e1eca 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -542,7 +542,9 @@ impl CaliptraError { pub const ROM_GLOBAL_MEASUREMENT_LOG_EXHAUSTED: CaliptraError = CaliptraError::new_const(0x0105000D); - /// KAT Errors + pub const ROM_GLOBAL_FIPS_HOOKS_ROM_EXIT: CaliptraError = CaliptraError::new_const(0x0105000F); + + /// ROM KAT Errors pub const KAT_SHA256_DIGEST_FAILURE: CaliptraError = CaliptraError::new_const(0x90010001); pub const KAT_SHA256_DIGEST_MISMATCH: CaliptraError = CaliptraError::new_const(0x90010002); diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs index 2d58de5e49..31a8079533 100644 --- a/hw-model/src/lib.rs +++ b/hw-model/src/lib.rs @@ -299,6 +299,7 @@ pub enum ModelError { actual: u32, }, MailboxRespInvalidFipsStatus(u32), + MailboxTimeout, } impl Error for ModelError {} impl Display for ModelError { @@ -361,6 +362,9 @@ impl Display for ModelError { "Mailbox response had non-success FIPS status: 0x{status:x}" ) } + ModelError::MailboxTimeout => { + write!(f, "Mailbox timed out in busy state") + } } } } @@ -971,8 +975,13 @@ pub trait HwModel { /// Wait for the response to a previous call to `start_mailbox_execute()`. fn finish_mailbox_execute(&mut self) -> std::result::Result>, ModelError> { // Wait for the microcontroller to finish executing + let mut timeout_cycles = 40000000; // 100ms @400MHz while self.soc_mbox().status().read().status().cmd_busy() { self.step(); + timeout_cycles -= 1; + if timeout_cycles == 0 { + return Err(ModelError::MailboxTimeout); + } } let status = self.soc_mbox().status().read().status(); if status.cmd_failure() { diff --git a/rom/dev/Cargo.toml b/rom/dev/Cargo.toml index 822075319e..02ee960b3d 100644 --- a/rom/dev/Cargo.toml +++ b/rom/dev/Cargo.toml @@ -62,6 +62,7 @@ fake-rom = [] no-cfi = ["caliptra-image-verify/no-cfi", "caliptra-drivers/no-cfi"] slow_tests = [] "hw-1.0" = ["caliptra-builder/hw-1.0", "caliptra-drivers/hw-1.0", "caliptra-registers/hw-1.0", "caliptra-hw-model/hw-1.0"] +fips-test-hooks = ["caliptra-drivers/fips-test-hooks"] [[bin]] name = "asm_tests" diff --git a/rom/dev/src/main.rs b/rom/dev/src/main.rs index 9e1378fe34..807183661b 100644 --- a/rom/dev/src/main.rs +++ b/rom/dev/src/main.rs @@ -14,6 +14,7 @@ Abstract: #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(feature = "fake-rom", allow(unused_imports))] +#![cfg_attr(feature = "fips-test-hooks", allow(dead_code))] use crate::{lock::lock_registers, print::HexBytes}; use caliptra_cfi_lib::{cfi_assert_eq, CfiCounter}; @@ -187,7 +188,11 @@ pub extern "C" fn rom_entry() -> ! { CfiCounter::corrupt(); } - #[cfg(not(feature = "no-fmc"))] + // FIPS test hooks mode does not allow handoff to FMC to prevent incorrect/accidental usage + #[cfg(feature = "fips-test-hooks")] + handle_fatal_error(CaliptraError::ROM_GLOBAL_FIPS_HOOKS_ROM_EXIT.into()); + + #[cfg(not(any(feature = "no-fmc", feature = "fips-test-hooks")))] launch_fmc(&mut env); #[cfg(feature = "no-fmc")] @@ -200,6 +205,13 @@ fn run_fips_tests(env: &mut KatsEnv) -> CaliptraResult<()> { cprintln!("[kat] SHA2-256"); Sha256Kat::default().execute(env.sha256)?; + #[cfg(feature = "fips-test-hooks")] + unsafe { + caliptra_drivers::FipsTestHook::halt_if_hook_set( + caliptra_drivers::FipsTestHook::HALT_SELF_TESTS, + ) + }; + // ROM integrity check needs SHA2-256 KAT to be executed first per FIPS requirement AS10.20. let rom_info = unsafe { &CALIPTRA_ROM_INFO }; rom_integrity_test(env, &rom_info.sha256_digest)?; diff --git a/rom/dev/tests/rom_integration_tests/main.rs b/rom/dev/tests/rom_integration_tests/main.rs index 375cd6f7ee..a193ee2ca0 100644 --- a/rom/dev/tests/rom_integration_tests/main.rs +++ b/rom/dev/tests/rom_integration_tests/main.rs @@ -8,6 +8,7 @@ mod test_cfi; mod test_cpu_fault; mod test_dice_derivations; mod test_fake_rom; +mod test_fips_hooks; mod test_fmcalias_derivation; mod test_idevid_derivation; mod test_image_validation; diff --git a/rom/dev/tests/rom_integration_tests/test_fips_hooks.rs b/rom/dev/tests/rom_integration_tests/test_fips_hooks.rs new file mode 100644 index 0000000000..87fca4565d --- /dev/null +++ b/rom/dev/tests/rom_integration_tests/test_fips_hooks.rs @@ -0,0 +1,41 @@ +// Licensed under the Apache-2.0 license + +use caliptra_builder::firmware::{APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART_FIPS_TEST_HOOKS}; +use caliptra_builder::ImageOptions; +use caliptra_drivers::CaliptraError; +use caliptra_hw_model::{BootParams, HwModel, InitParams}; + +#[test] +fn test_fips_hook_exit() { + let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART_FIPS_TEST_HOOKS).unwrap(); + + let image_bundle = caliptra_builder::build_and_sign_image( + &FMC_WITH_UART, + &APP_WITH_UART, + ImageOptions::default(), + ) + .unwrap() + .to_bytes() + .unwrap(); + + let init_params = InitParams { + rom: &rom, + ..Default::default() + }; + + let boot_params = BootParams { + fw_image: Some(&image_bundle), + ..Default::default() + }; + + let mut hw = caliptra_hw_model::new(init_params, boot_params).unwrap(); + + // Wait for fatal error + hw.step_until(|m| m.soc_ifc().cptra_fw_error_fatal().read() != 0); + + // Verify fatal code is correct + assert_eq!( + hw.soc_ifc().cptra_fw_error_fatal().read(), + u32::from(CaliptraError::ROM_GLOBAL_FIPS_HOOKS_ROM_EXIT) + ); +} diff --git a/test/Cargo.toml b/test/Cargo.toml index 1d0f3738ff..31cff863c2 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -13,7 +13,7 @@ asn1.workspace = true caliptra-builder.workspace = true caliptra_common = { workspace = true, default-features = false } caliptra-coverage.workspace = true -caliptra-drivers.workspace = true +caliptra-drivers = { workspace = true, features = ["fips-test-hooks"] } caliptra-hw-model-types.workspace = true caliptra-image-types.workspace = true caliptra-runtime = { workspace = true, default-features = false } diff --git a/test/tests/fips_test_suite/README.md b/test/tests/fips_test_suite/README.md index 168b0d31a6..80a0170e3a 100755 --- a/test/tests/fips_test_suite/README.md +++ b/test/tests/fips_test_suite/README.md @@ -36,4 +36,19 @@ These can be enabled using the --features argument for rust like: ## Additional Environments -Support for additional environments can be done by creating new implementations/interfaces for the HW model at hw-model/src. See model_fpga_realtime.rs as an example. This implementation needs to be able to access the APB bus, control the input signals to caliptra, and, if possible, control ROM. \ No newline at end of file +Support for additional environments can be done by creating new implementations/interfaces for the HW model at hw-model/src. See model_fpga_realtime.rs as an example. This implementation needs to be able to access the APB bus, control the input signals to Caliptra, and, if possible, control ROM. + +## Test Hooks + +Certain tests require "hooks" into the ROM or FW to cause operation to deviate from the normal flow (ie. injecting errors or halting execution at specific points). This functionality is enabled using a build option called "fips-test-hooks". Then, the specific command codes are written and read from the DBG_MANUF_SERVICE_REG. The ROM/FW can respond back with a status code written to the same field if applicable. See command codes in drivers\src\fips_test_hooks.rs for more details. + +Test hooks are needed to meet the following FIPS 140-3 test requirements: + TE03.07.02 + TE03.07.04 + TE04.29.01 + TE10.07.03 + TE10.08.03 + TE10.09.03 + TE10.10.01 + TE10.10.02 + TE10.35.04 diff --git a/test/tests/fips_test_suite/common.rs b/test/tests/fips_test_suite/common.rs index bfecf27538..3e89d2b0a5 100755 --- a/test/tests/fips_test_suite/common.rs +++ b/test/tests/fips_test_suite/common.rs @@ -3,7 +3,8 @@ use caliptra_builder::firmware::{APP_WITH_UART, FMC_WITH_UART}; use caliptra_builder::ImageOptions; use caliptra_common::mailbox_api::*; -use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, ModelError}; +use caliptra_drivers::FipsTestHook; +use caliptra_hw_model::{BootParams, DefaultHwModel, HwModel, InitParams, ModelError, ShaAccMode}; use dpe::{ commands::*, response::{ @@ -13,6 +14,9 @@ use dpe::{ }; use zerocopy::{AsBytes, FromBytes}; +pub const HOOK_CODE_MASK: u32 = 0x00FF0000; +pub const HOOK_CODE_OFFSET: u32 = 16; + // ================================= // EXPECTED CONSTANTS // ================================= @@ -58,6 +62,7 @@ const RT_EXP_1_0_0: RtExpVals = RtExpVals { }; const RT_EXP_CURRENT: RtExpVals = RtExpVals { + // Update expected versions fmc_version: 0x0, fw_version: 0x0, }; @@ -167,6 +172,22 @@ fn fips_test_init_base( caliptra_hw_model::new(init_params, boot_params).unwrap() } +// Initializes Caliptra +// Builds and uses default ROM if not provided +pub fn fips_test_init_to_boot_start( + init_params: Option, + boot_params: Option, +) -> DefaultHwModel { + // Check that no fw_image is in boot params + if let Some(ref params) = boot_params { + if params.fw_image.is_some() { + panic!("No FW image should be provided when calling fips_test_init_to_boot_start") + } + } + + fips_test_init_base(init_params, boot_params, None) +} + // Initializes caliptra to "ready_for_fw" // Builds and uses default ROM if not provided pub fn fips_test_init_to_rom( @@ -346,3 +367,45 @@ pub fn contains_some_data(data: &[T]) -> bool { false } + +pub fn verify_output_inhibited(hw: &mut T) { + // Check mailbox output is inhibited + let payload = MailboxReqHeader { + chksum: caliptra_common::checksum::calc_checksum(u32::from(CommandId::VERSION), &[]), + }; + match hw.mailbox_execute(u32::from(CommandId::VERSION), payload.as_bytes()) { + Ok(_) => panic!("Mailbox output is not inhibited"), + Err(ModelError::MailboxTimeout) => (), + Err(ModelError::UnableToLockMailbox) => (), + Err(_) => panic!("Unexpected error from mailbox_execute"), + } + + // Check sha engine output is inhibited (ensure sha engine is locked) + let message: &[u8] = &[0x0, 0x1, 0x2, 0x3]; + match hw.compute_sha512_acc_digest(message, ShaAccMode::Sha384Stream) { + Ok(_) => panic!("SHA engine is not locked, output is not inhibited"), + Err(ModelError::UnableToLockSha512Acc) => (), + Err(_) => panic!("Unexpected error from compute_sha512_acc_digest"), + } +} + +pub fn hook_code_read(hw: &mut T) -> u8 { + ((hw.soc_ifc().cptra_dbg_manuf_service_reg().read() & HOOK_CODE_MASK) >> HOOK_CODE_OFFSET) as u8 +} + +pub fn hook_code_write(hw: &mut T, code: u8) { + let val = (hw.soc_ifc().cptra_dbg_manuf_service_reg().read() & !(HOOK_CODE_MASK)) + | ((code as u32) << HOOK_CODE_OFFSET); + hw.soc_ifc().cptra_dbg_manuf_service_reg().write(|_| val); +} + +pub fn hook_wait_for_complete(hw: &mut T) { + while hook_code_read(hw) != FipsTestHook::COMPLETE { + // Give FW time to run + let mut cycle_count = 1000; + hw.step_until(|_| -> bool { + cycle_count -= 1; + cycle_count == 0 + }); + } +} diff --git a/test/tests/fips_test_suite/main.rs b/test/tests/fips_test_suite/main.rs index 155afa80bc..2f6e2f759b 100644 --- a/test/tests/fips_test_suite/main.rs +++ b/test/tests/fips_test_suite/main.rs @@ -2,4 +2,5 @@ mod common; #[cfg(feature = "fpga_realtime")] mod jtag_locked; +mod self_tests; mod services; diff --git a/test/tests/fips_test_suite/self_tests.rs b/test/tests/fips_test_suite/self_tests.rs new file mode 100755 index 0000000000..8342e07227 --- /dev/null +++ b/test/tests/fips_test_suite/self_tests.rs @@ -0,0 +1,104 @@ +// Licensed under the Apache-2.0 license +use crate::common; + +use caliptra_builder::firmware::ROM_WITH_UART_FIPS_TEST_HOOKS; +use caliptra_drivers::CaliptraError; +use caliptra_drivers::FipsTestHook; +use caliptra_hw_model::{BootParams, HwModel, InitParams}; +use common::*; + +#[test] +#[cfg(not(feature = "test_env_immutable_rom"))] +pub fn kat_halt_check_no_output() { + let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART_FIPS_TEST_HOOKS).unwrap(); + + let mut hw = fips_test_init_to_boot_start( + Some(InitParams { + rom: &rom, + ..Default::default() + }), + Some(BootParams { + initial_dbg_manuf_service_reg: (FipsTestHook::HALT_SELF_TESTS as u32) + << HOOK_CODE_OFFSET, + ..Default::default() + }), + ); + + // Wait for ACK that ROM reached halt point + hook_wait_for_complete(&mut hw); + + // Check output is inhibited + verify_output_inhibited(&mut hw); + + // TODO: Remove continuing if it's not needed + // Tell ROM to continue + hook_code_write(&mut hw, FipsTestHook::CONTINUE); + + // Wait for ACK that ROM continued + hook_wait_for_complete(&mut hw); + + // Step to ready for FW in ROM + hw.step_until(|m| m.soc_ifc().cptra_flow_status().read().ready_for_fw()); +} + +#[test] +#[cfg(not(feature = "test_env_immutable_rom"))] +pub fn kat_sha384_error() { + let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART_FIPS_TEST_HOOKS).unwrap(); + + let mut hw = fips_test_init_to_boot_start( + Some(InitParams { + rom: &rom, + ..Default::default() + }), + Some(BootParams { + initial_dbg_manuf_service_reg: (FipsTestHook::SHA384_ERROR as u32) << HOOK_CODE_OFFSET, + ..Default::default() + }), + ); + + // Wait for fatal error + hw.step_until(|m| m.soc_ifc().cptra_fw_error_fatal().read() != 0); + + // Verify fatal code is correct + assert_eq!( + hw.soc_ifc().cptra_fw_error_fatal().read(), + u32::from(CaliptraError::KAT_SHA384_DIGEST_MISMATCH) + ); + + // TODO: Verify we cannot use the algorithm + // TODO: Attempt to clear the error in an undocumented way + // TODO: Restart Caliptra + // TODO: Verify crypto operations can be performed +} + +#[test] +#[cfg(not(feature = "test_env_immutable_rom"))] +pub fn kat_lms_error() { + let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART_FIPS_TEST_HOOKS).unwrap(); + + let mut hw = fips_test_init_to_boot_start( + Some(InitParams { + rom: &rom, + ..Default::default() + }), + Some(BootParams { + initial_dbg_manuf_service_reg: (FipsTestHook::LMS_ERROR as u32) << HOOK_CODE_OFFSET, + ..Default::default() + }), + ); + + // Wait for fatal error + hw.step_until(|m| m.soc_ifc().cptra_fw_error_fatal().read() != 0); + + // Verify fatal code is correct + assert_eq!( + hw.soc_ifc().cptra_fw_error_fatal().read(), + u32::from(CaliptraError::KAT_LMS_DIGEST_MISMATCH) + ); + + // TODO: Verify we cannot use the algorithm + // TODO: Attempt to clear the error in an undocumented way + // TODO: Restart Caliptra + // TODO: Verify crypto operations can be performed +}