diff --git a/common/src/keyids.rs b/common/src/keyids.rs index a2b5c4b8c5..10a592dba5 100644 --- a/common/src/keyids.rs +++ b/common/src/keyids.rs @@ -29,6 +29,8 @@ pub const KEY_ID_FMC_PRIV_KEY: KeyId = KeyId::KeyId7; pub const KEY_ID_RT_CDI: KeyId = KeyId::KeyId4; #[cfg(feature = "fmc")] pub const KEY_ID_RT_PRIV_KEY: KeyId = KeyId::KeyId5; +#[cfg(feature = "fmc")] +pub const KEY_ID_RT_HASH_CHAIN: KeyId = KeyId::KeyId2; #[cfg(feature = "runtime")] pub const KEY_ID_DPE_CDI: KeyId = KeyId::KeyId8; #[cfg(feature = "runtime")] diff --git a/common/src/lib.rs b/common/src/lib.rs index 432f3bf9a8..2c3e02c62d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -38,9 +38,9 @@ pub use fuse::{FuseLogEntry, FuseLogEntryId}; pub use pcr::{PcrLogEntry, PcrLogEntryId, RT_FW_CURRENT_PCR, RT_FW_JOURNEY_PCR}; pub const FMC_ORG: u32 = 0x40000000; -pub const FMC_SIZE: u32 = 20 * 1024; +pub const FMC_SIZE: u32 = 22 * 1024; pub const RUNTIME_ORG: u32 = FMC_ORG + FMC_SIZE; -pub const RUNTIME_SIZE: u32 = 92 * 1024; +pub const RUNTIME_SIZE: u32 = 90 * 1024; pub use memory_layout::{DATA_ORG, FHT_ORG, FHT_SIZE, MAN1_ORG}; pub use wdt::{restart_wdt, start_wdt, stop_wdt, WdtTimeout}; diff --git a/error/src/lib.rs b/error/src/lib.rs index a0dd1480d1..dcdaa463a7 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -429,6 +429,7 @@ impl CaliptraError { pub const FMC_HANDOFF_NOT_READY_FOR_RT: CaliptraError = CaliptraError::new_const(0x000F000C); pub const FMC_GLOBAL_WDT_EXPIRED: CaliptraError = CaliptraError::new_const(0x000F000D); pub const FMC_UNKNOWN_RESET: CaliptraError = CaliptraError::new_const(0x000F000E); + pub const RT_SVN_EXCEEDS_MAX: CaliptraError = CaliptraError::new_const(0x000F000F); /// TRNG_EXT Errors pub const DRIVER_TRNG_EXT_TIMEOUT: CaliptraError = CaliptraError::new_const(0x00100001); diff --git a/fmc/src/flow/crypto.rs b/fmc/src/flow/crypto.rs index 23e83edae9..ae90c1fbf7 100644 --- a/fmc/src/flow/crypto.rs +++ b/fmc/src/flow/crypto.rs @@ -10,8 +10,8 @@ use caliptra_cfi_derive::cfi_impl_fn; use caliptra_common::{crypto::Ecc384KeyPair, keyids::KEY_ID_TMP}; use caliptra_drivers::{ hmac384_kdf, okref, Array4x12, Array4x5, Array4x8, CaliptraResult, Ecc384PrivKeyIn, - Ecc384PrivKeyOut, Ecc384PubKey, Ecc384Result, Ecc384Signature, KeyId, KeyReadArgs, KeyUsage, - KeyWriteArgs, Sha256Alg, + Ecc384PrivKeyOut, Ecc384PubKey, Ecc384Result, Ecc384Signature, Hmac384Data, KeyId, KeyReadArgs, + KeyUsage, KeyWriteArgs, Sha256Alg, }; pub enum Crypto {} @@ -95,6 +95,35 @@ impl Crypto { ) } + /// Calculate HMAC-348 + /// + /// # Arguments + /// + /// * `env` - ROM Environment + /// * `key` - HMAC384 key slot + /// * `data` - Input data to hash + /// * `tag` - Key slot to store the tag + #[inline(always)] + pub fn hmac384_mac( + env: &mut FmcEnv, + key: KeyId, + data: &Hmac384Data, + tag: KeyId, + ) -> CaliptraResult<()> { + env.hmac384.hmac( + &KeyReadArgs::new(key).into(), + data, + &mut env.trng, + KeyWriteArgs::new( + tag, + KeyUsage::default() + .set_hmac_key_en() + .set_ecc_key_gen_seed_en(), + ) + .into(), + ) + } + /// Generate ECC Key Pair /// /// # Arguments diff --git a/fmc/src/flow/hash_chain.rs b/fmc/src/flow/hash_chain.rs new file mode 100644 index 0000000000..0ecb1ba606 --- /dev/null +++ b/fmc/src/flow/hash_chain.rs @@ -0,0 +1,90 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + hash_chain.rs + +Abstract: + + File contains execution routines for deriving a hash chain based on RT FW's SVN. + +Environment: + + FMC + +--*/ + +use caliptra_cfi_derive::cfi_impl_fn; +use caliptra_cfi_lib::{cfi_assert_eq, cfi_assert_le}; + +use caliptra_common::keyids::KEY_ID_RT_HASH_CHAIN; +use caliptra_drivers::{cprintln, report_boot_status, Hmac384Data, KeyId}; +use caliptra_error::{CaliptraError, CaliptraResult}; + +use crate::{flow::crypto::Crypto, fmc_env::FmcEnv, hand_off::HandOff, FmcBootStatus}; + +pub const MAX_RT_SVN: u32 = 128; + +pub struct HashChain {} + +impl HashChain { + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + pub fn derive(env: &mut FmcEnv) -> CaliptraResult<()> { + HandOff::set_rt_hash_chain_max_svn(env, MAX_RT_SVN as u16); + + cprintln!("[hash chain rt] Deriving hash chain"); + + let fw_epoch = env.persistent_data.get().manifest1.header.owner_data.epoch; + Self::compute_chain(env, &fw_epoch)?; + + cprintln!("[hash chain rt] Derivation complete"); + report_boot_status(FmcBootStatus::RtHashChainComplete as u32); + Ok(()) + } + + fn compute_chain(env: &mut FmcEnv, fw_epoch: &[u8]) -> CaliptraResult<()> { + let rt_svn = HandOff::rt_svn(env); + if rt_svn > MAX_RT_SVN { + return Err(CaliptraError::RT_SVN_EXCEEDS_MAX); + } else { + cfi_assert_le(rt_svn, MAX_RT_SVN); + } + + let chain_len: u32 = MAX_RT_SVN - rt_svn; + cprintln!( + "[hash chain rt] SVN = {}, max = {}, chain_len = {}", + rt_svn, + MAX_RT_SVN, + chain_len + ); + + let fmc_cdi = HandOff::fmc_cdi(env); + + Crypto::hmac384_kdf( + env, + fmc_cdi, + b"rt_hash_chain", + Some(fw_epoch), + KEY_ID_RT_HASH_CHAIN, + )?; + + let mut num_iters: u32 = 0; + + for _ in 0..chain_len { + num_iters += 1; + Self::hmac_hash(env, KEY_ID_RT_HASH_CHAIN)?; + } + + cfi_assert_eq(num_iters, chain_len); + + HandOff::set_rt_hash_chain_kv_hdl(env, KEY_ID_RT_HASH_CHAIN); + Ok(()) + } + + // "Hashes" a value in-place in KV by HMACing it with empty data. + fn hmac_hash(env: &mut FmcEnv, key_id: KeyId) -> CaliptraResult<()> { + Crypto::hmac384_mac(env, key_id, &Hmac384Data::Slice(&[]), key_id) + } +} diff --git a/fmc/src/flow/mod.rs b/fmc/src/flow/mod.rs index 5e5d44712d..72f2237dee 100644 --- a/fmc/src/flow/mod.rs +++ b/fmc/src/flow/mod.rs @@ -14,14 +14,17 @@ Abstract: mod crypto; pub mod dice; +mod hash_chain; mod pcr; mod rt_alias; mod tci; mod x509; +use crate::flow::hash_chain::HashChain; use crate::flow::rt_alias::RtAliasLayer; use crate::fmc_env::FmcEnv; +use crate::hand_off::HandOff; use caliptra_drivers::CaliptraResult; /// Execute FMC Flows based on reset resason @@ -30,5 +33,10 @@ use caliptra_drivers::CaliptraResult; /// /// * `env` - FMC Environment pub fn run(env: &mut FmcEnv) -> CaliptraResult<()> { - RtAliasLayer::run(env) + RtAliasLayer::run(env)?; + HashChain::derive(env)?; + + env.key_vault.set_key_use_lock(HandOff::fmc_cdi(env)); + + Ok(()) } diff --git a/fmc/src/flow/rt_alias.rs b/fmc/src/flow/rt_alias.rs index 952c3ab32e..064934d6be 100644 --- a/fmc/src/flow/rt_alias.rs +++ b/fmc/src/flow/rt_alias.rs @@ -345,9 +345,9 @@ impl RtAliasLayer { "[alias rt] Erasing AUTHORITY.KEYID = {}", auth_priv_key as u8 ); - // FMC ensures that CDIFMC and PrivateKeyFMC are locked to block further usage until the next boot. + // FMC ensures that PrivateKeyFMC is locked to block further usage until the next boot. + // CDI is locked later, after it is used in hash chain derivation. env.key_vault.set_key_use_lock(auth_priv_key); - env.key_vault.set_key_use_lock(input.cdi); let _pub_x: [u8; 48] = (&pub_key.x).into(); let _pub_y: [u8; 48] = (&pub_key.y).into(); diff --git a/fmc/src/main.rs b/fmc/src/main.rs index 5f4a0023a8..68c1495ec0 100644 --- a/fmc/src/main.rs +++ b/fmc/src/main.rs @@ -18,7 +18,7 @@ use core::hint::black_box; use caliptra_cfi_lib::{cfi_assert_eq, CfiCounter}; use caliptra_common::{ cprintln, handle_fatal_error, - keyids::{KEY_ID_RT_CDI, KEY_ID_RT_PRIV_KEY}, + keyids::{KEY_ID_RT_CDI, KEY_ID_RT_HASH_CHAIN, KEY_ID_RT_PRIV_KEY}, }; use caliptra_cpu::{log_trap_record, TrapRecord}; @@ -80,6 +80,8 @@ pub extern "C" fn entry_point() -> ! { HandOffDataHandle::from(DataStore::KeyVaultSlot(KEY_ID_RT_CDI)); env.persistent_data.get_mut().fht.rt_priv_key_kv_hdl = HandOffDataHandle::from(DataStore::KeyVaultSlot(KEY_ID_RT_PRIV_KEY)); + env.persistent_data.get_mut().fht.rt_hash_chain_kv_hdl = + HandOffDataHandle::from(DataStore::KeyVaultSlot(KEY_ID_RT_HASH_CHAIN)); HandOff::to_rt(&env); } match flow::run(&mut env) { diff --git a/fmc/tests/fmc_integration_tests/test_rtalias.rs b/fmc/tests/fmc_integration_tests/test_rtalias.rs index e82e1ebe15..e278e7d8a2 100644 --- a/fmc/tests/fmc_integration_tests/test_rtalias.rs +++ b/fmc/tests/fmc_integration_tests/test_rtalias.rs @@ -28,6 +28,7 @@ const RT_ALIAS_SUBJ_ID_SN_GENERATION_COMPLETE: u32 = 0x403; const RT_ALIAS_SUBJ_KEY_ID_GENERATION_COMPLETE: u32 = 0x404; const RT_ALIAS_CERT_SIG_GENERATION_COMPLETE: u32 = 0x405; const RT_ALIAS_DERIVATION_COMPLETE: u32 = 0x406; +const RT_HASH_CHAIN_COMPLETE: u32 = 0x407; const PCR_COUNT: usize = 32; const PCR_ENTRY_SIZE: usize = core::mem::size_of::(); @@ -62,6 +63,7 @@ fn test_boot_status_reporting() { hw.step_until_boot_status(RT_ALIAS_SUBJ_KEY_ID_GENERATION_COMPLETE, true); hw.step_until_boot_status(RT_ALIAS_CERT_SIG_GENERATION_COMPLETE, true); hw.step_until_boot_status(RT_ALIAS_DERIVATION_COMPLETE, true); + hw.step_until_boot_status(RT_HASH_CHAIN_COMPLETE, true); } #[test] diff --git a/runtime/test-fw/src/mbox_responder.rs b/runtime/test-fw/src/mbox_responder.rs index 2fcd64c9d0..1f6f580e77 100644 --- a/runtime/test-fw/src/mbox_responder.rs +++ b/runtime/test-fw/src/mbox_responder.rs @@ -5,7 +5,7 @@ use core::mem::size_of; -use caliptra_common::{handle_fatal_error, mailbox_api::CommandId}; +use caliptra_common::{handle_fatal_error, keyids::KEY_ID_TMP, mailbox_api::CommandId}; use caliptra_drivers::{ cprintln, pcr_log::{PCR_ID_STASH_MEASUREMENT, RT_FW_JOURNEY_PCR}, @@ -13,8 +13,8 @@ use caliptra_drivers::{ }; use caliptra_registers::{mbox::enums::MboxStatusE, soc_ifc::SocIfcReg}; use caliptra_runtime::{ - mailbox::Mailbox, ContextState, DpeInstance, Drivers, RtBootStatus, TciMeasurement, U8Bool, - MAX_HANDLES, + handoff::RtHandoff, mailbox::Mailbox, ContextState, DpeInstance, Drivers, Hmac, RtBootStatus, + TciMeasurement, U8Bool, MAX_HANDLES, }; use caliptra_test_harness::{runtime_handlers, test_suite}; use zerocopy::{AsBytes, FromBytes}; @@ -31,6 +31,8 @@ const OPCODE_READ_DPE_INSTANCE: u32 = 0xA000_0000; const OPCODE_CORRUPT_DPE_INSTANCE: u32 = 0xB000_0000; const OPCODE_READ_PCR_RESET_COUNTER: u32 = 0xC000_0000; const OPCODE_CORRUPT_DPE_ROOT_TCI: u32 = 0xD000_0000; +const OPCODE_READ_HASH_CHAIN_MAX_SVN: u32 = 0xE000_0000; +const OPCODE_READ_HASH_CHAIN_DIGEST: u32 = 0xF000_0000; const OPCODE_FW_LOAD: u32 = CommandId::FIRMWARE_LOAD.0; fn read_request(mbox: &Mailbox) -> &[u8] { @@ -210,6 +212,44 @@ pub fn handle_command(drivers: &mut Drivers) -> CaliptraResult { .tci_current = TciMeasurement(input_bytes.try_into().unwrap()); write_response(&mut drivers.mbox, &[]); } + CommandId(OPCODE_READ_HASH_CHAIN_MAX_SVN) => { + write_response( + &mut drivers.mbox, + &drivers + .persistent_data + .get() + .fht + .rt_hash_chain_max_svn + .to_le_bytes(), + ); + } + // Ratchet the chain forwards a given number of times, then + // compute a digest from the chain. + CommandId(OPCODE_READ_HASH_CHAIN_DIGEST) => { + let handoff = RtHandoff { + data_vault: &drivers.data_vault, + fht: &drivers.persistent_data.get().fht, + }; + + let hash_chain_kv = handoff.rt_hash_chain().unwrap(); + let num_hashes = u32::read_from(read_request(&drivers.mbox)).unwrap(); + + let hmac_input = if num_hashes == 0 { + hash_chain_kv + } else { + Hmac::hmac384_hash(drivers, hash_chain_kv, KEY_ID_TMP).unwrap(); + for _ in 1..num_hashes { + Hmac::hmac384_hash(drivers, KEY_ID_TMP, KEY_ID_TMP).unwrap(); + } + KEY_ID_TMP + }; + + let hmac_output = + Hmac::ecc384_hmac(drivers, hmac_input, b"hmac_label", b"hmac_data").unwrap(); + + drivers.key_vault.erase_key(KEY_ID_TMP).unwrap(); + write_response(&mut drivers.mbox, hmac_output.as_bytes()); + } CommandId(OPCODE_FW_LOAD) => { unsafe { SocIfcReg::new() } .regs_mut() diff --git a/runtime/tests/runtime_integration_tests/test_update_reset.rs b/runtime/tests/runtime_integration_tests/test_update_reset.rs index 8164d35d0b..ec01a0dd44 100644 --- a/runtime/tests/runtime_integration_tests/test_update_reset.rs +++ b/runtime/tests/runtime_integration_tests/test_update_reset.rs @@ -11,7 +11,7 @@ use caliptra_common::mailbox_api::{ }; use caliptra_drivers::PcrResetCounter; use caliptra_error::CaliptraError; -use caliptra_hw_model::{DefaultHwModel, HwModel}; +use caliptra_hw_model::{DefaultHwModel, HwModel, ModelError}; use caliptra_runtime::{ContextState, RtBootStatus, PL0_DPE_ACTIVE_CONTEXT_THRESHOLD}; use dpe::{ context::{Context, ContextHandle, ContextType}, @@ -319,3 +319,103 @@ fn test_pcr_reset_counter_persistence() { // check that the pcr reset counters are not default assert_ne!(pcr_reset_counter_1, [0u8; size_of::()]); } + +fn get_image_opts(svn: u32, epoch: &[u8; 2]) -> ImageOptions { + let mut options = ImageOptions { + app_svn: svn, + ..Default::default() + }; + + options.owner_config.as_mut().unwrap().epoch = *epoch; + options +} + +fn get_chain_digest(model: &mut DefaultHwModel, num_hashes: u32) -> Vec { + let digest = model + .mailbox_execute(0xF000_0000, num_hashes.as_bytes()) + .unwrap() + .unwrap(); + + assert!(!digest.is_empty()); + digest +} + +const MAX_SVN: u32 = 128; + +#[test] +fn test_hash_chain() { + let epoch = [0x00, 0x01]; + + let update_to_svn = |model: &mut DefaultHwModel, svn: u32| { + update_fw(model, &MBOX, get_image_opts(svn, &epoch)); + }; + + let mut model = run_rt_test(Some(&MBOX), Some(get_image_opts(0, &epoch)), None); + + let chain_0 = get_chain_digest(&mut model, 0); + + update_to_svn(&mut model, 1); + + // FW should now have a different hash chain. + let chain_1 = get_chain_digest(&mut model, 0); + + // Ask FW to hash the chain once before returning a digest. + let chain_1_hashed = get_chain_digest(&mut model, 1); + + assert_ne!(chain_1_hashed, chain_1); + assert_eq!(chain_1_hashed, chain_0); + + // Update to the max SVN supported by FMC. + update_to_svn(&mut model, MAX_SVN); + + let chain_max = get_chain_digest(&mut model, 0); + + // Ask FW to hash the chain enough times to get back to 1. + let chain_max_hashed = get_chain_digest(&mut model, MAX_SVN - 1); + + assert_ne!(chain_max_hashed, chain_max); + assert_eq!(chain_max_hashed, chain_1); + + // Update past the max supported by FMC. + update_to_svn(&mut model, MAX_SVN + 1); + + model.step_until(|m| { + m.soc_ifc().cptra_fw_error_non_fatal().read() + == u32::from(CaliptraError::RT_SVN_EXCEEDS_MAX) + && m.soc_ifc().cptra_fw_error_fatal().read() + == u32::from(CaliptraError::RT_SVN_EXCEEDS_MAX) + }); + + assert_eq!( + model + .mailbox_execute(0xF000_0000, 0_u32.as_bytes()) + .unwrap_err(), + ModelError::MailboxCmdFailed(CaliptraError::RT_SVN_EXCEEDS_MAX.into()) + ); +} + +#[test] +fn test_hash_chain_different_epochs() { + // Same SVN, different epochs. + let options_0 = get_image_opts(0, &[0x00, 0x00]); + let options_1 = get_image_opts(0, &[0x00, 0x01]); + + let mut model = run_rt_test(Some(&MBOX), Some(options_0), None); + + let chain_0 = get_chain_digest(&mut model, 0); + + update_fw(&mut model, &MBOX, options_1); + + let chain_1 = get_chain_digest(&mut model, 0); + + assert_ne!(chain_0, chain_1); +} + +#[test] +fn test_hash_chain_max_svn() { + let mut model = run_rt_test(Some(&MBOX), None, None); + let resp = model.mailbox_execute(0xE000_0000, &[]).unwrap().unwrap(); + + let max = u16::from_le_bytes(resp.try_into().unwrap()); + assert_eq!(max as u32, MAX_SVN); +} diff --git a/test/tests/caliptra_integration_tests/smoke_test.rs b/test/tests/caliptra_integration_tests/smoke_test.rs index 9d8aa99e82..0410e10cd2 100644 --- a/test/tests/caliptra_integration_tests/smoke_test.rs +++ b/test/tests/caliptra_integration_tests/smoke_test.rs @@ -667,11 +667,11 @@ fn test_rt_wdt_timeout() { // TODO: Don't hard-code these; maybe measure from a previous boot? let rt_wdt_timeout_cycles = if cfg!(any(feature = "verilator", feature = "fpga_realtime")) { - 27_300_000 + 27_600_000 } else if firmware::rom_from_env() == &firmware::ROM_WITH_UART { - 3_300_000 + 3_600_000 } else { - 3_100_000 + 3_400_000 }; let security_state = *caliptra_hw_model::SecurityState::default().set_debug_locked(true);