From 6f6f4a0b7826acbe01ef4303d053e8a7bd39dbfa Mon Sep 17 00:00:00 2001 From: Jeff Andersen Date: Mon, 25 Nov 2024 13:37:21 -0800 Subject: [PATCH] Implement key ladder in ROM and RT FW. The key ladder is initialized in cold-boot from LDevID CDI. It is lengthened during update-reset flows to match min-SVN-since-cold-boot. This PR does not include a smoke test, because the production application firmware doesn't yet include any APIs for exercising the key ladder. The additional cycles from this feature depends on the firmware's SVN, and is roughly 2.7k cycles * (128 - fw_svn). In the worst case, when fw_svn = 0, the cycle count is ~350k. --- builder/src/firmware.rs | 8 + common/src/boot_status.rs | 12 +- common/src/keyids.rs | 2 + drivers/src/hand_off.rs | 10 +- error/src/lib.rs | 5 +- fmc/README.md | 8 +- fmc/src/boot_status.rs | 3 - rom/dev/src/fht.rs | 12 +- rom/dev/src/flow/cold_reset/fw_processor.rs | 36 ++- rom/dev/src/flow/fake.rs | 2 +- rom/dev/src/flow/update_reset.rs | 27 +- rom/dev/src/key_ladder.rs | 83 +++++++ rom/dev/src/main.rs | 1 + .../test_dice_derivations.rs | 1 + .../test_update_reset.rs | 1 + runtime/src/handoff.rs | 8 +- runtime/src/key_ladder.rs | 99 ++++++++ runtime/src/lib.rs | 2 + runtime/test-fw/src/mbox_responder.rs | 31 ++- .../tests/runtime_integration_tests/common.rs | 11 +- .../test_update_reset.rs | 235 +++++++++++++++++- 21 files changed, 560 insertions(+), 37 deletions(-) create mode 100644 rom/dev/src/key_ladder.rs create mode 100644 runtime/src/key_ladder.rs diff --git a/builder/src/firmware.rs b/builder/src/firmware.rs index 95265f42a8..dd057830ae 100644 --- a/builder/src/firmware.rs +++ b/builder/src/firmware.rs @@ -375,6 +375,13 @@ pub mod runtime_tests { ..RUNTIME_TEST_FWID_BASE }; + // Used to test updates between RT FW images. + pub const MBOX_WITHOUT_UART: FwId = FwId { + bin_name: "mbox", + features: &["riscv", "runtime"], + ..RUNTIME_TEST_FWID_BASE + }; + pub const PERSISTENT_RT: FwId = FwId { bin_name: "persistent_rt", ..RUNTIME_TEST_FWID_BASE @@ -447,6 +454,7 @@ pub const REGISTERED_FW: &[&FwId] = &[ &rom_tests::TEST_PMP_TESTS, &runtime_tests::BOOT, &runtime_tests::MBOX, + &runtime_tests::MBOX_WITHOUT_UART, &runtime_tests::PERSISTENT_RT, &runtime_tests::MOCK_RT_INTERACTIVE, ]; diff --git a/common/src/boot_status.rs b/common/src/boot_status.rs index f63b643510..a3fea3ce07 100644 --- a/common/src/boot_status.rs +++ b/common/src/boot_status.rs @@ -52,7 +52,8 @@ pub enum RomBootStatus { FwProcessorExtendPcrComplete = FWPROCESSOR_BOOT_STATUS_BASE + 4, FwProcessorLoadImageComplete = FWPROCESSOR_BOOT_STATUS_BASE + 5, FwProcessorFirmwareDownloadTxComplete = FWPROCESSOR_BOOT_STATUS_BASE + 6, - FwProcessorComplete = FWPROCESSOR_BOOT_STATUS_BASE + 7, + FwProcessorCalculateKeyLadderComplete = FWPROCESSOR_BOOT_STATUS_BASE + 7, + FwProcessorComplete = FWPROCESSOR_BOOT_STATUS_BASE + 8, // FmcAlias Statuses FmcAliasDeriveCdiComplete = FMCALIAS_BOOT_STATUS_BASE, @@ -71,10 +72,11 @@ pub enum RomBootStatus { UpdateResetLoadManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 1, UpdateResetImageVerificationComplete = UPDATE_RESET_BOOT_STATUS_BASE + 2, UpdateResetPopulateDataVaultComplete = UPDATE_RESET_BOOT_STATUS_BASE + 3, - UpdateResetExtendPcrComplete = UPDATE_RESET_BOOT_STATUS_BASE + 4, - UpdateResetLoadImageComplete = UPDATE_RESET_BOOT_STATUS_BASE + 5, - UpdateResetOverwriteManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 6, - UpdateResetComplete = UPDATE_RESET_BOOT_STATUS_BASE + 7, + UpdateResetExtendKeyLadderComplete = UPDATE_RESET_BOOT_STATUS_BASE + 4, + UpdateResetExtendPcrComplete = UPDATE_RESET_BOOT_STATUS_BASE + 5, + UpdateResetLoadImageComplete = UPDATE_RESET_BOOT_STATUS_BASE + 6, + UpdateResetOverwriteManifestComplete = UPDATE_RESET_BOOT_STATUS_BASE + 7, + UpdateResetComplete = UPDATE_RESET_BOOT_STATUS_BASE + 8, // ROM Global Boot Statues CfiInitialized = ROM_GLOBAL_BOOT_STATUS_BASE, diff --git a/common/src/keyids.rs b/common/src/keyids.rs index 3c4c693d49..1689f550d7 100644 --- a/common/src/keyids.rs +++ b/common/src/keyids.rs @@ -31,6 +31,8 @@ pub const KEY_ID_ROM_FMC_CDI: KeyId = KeyId::KeyId6; pub const KEY_ID_FMC_ECDSA_PRIV_KEY: KeyId = KeyId::KeyId7; #[cfg(feature = "rom")] pub const KEY_ID_FMC_MLDSA_KEYPAIR_SEED: KeyId = KeyId::KeyId8; +#[cfg(any(feature = "rom"))] +pub const KEY_ID_FW_KEY_LADDER: KeyId = KeyId::KeyId2; #[cfg(feature = "fmc")] pub const KEY_ID_RT_CDI: KeyId = KeyId::KeyId4; #[cfg(feature = "fmc")] diff --git a/drivers/src/hand_off.rs b/drivers/src/hand_off.rs index 960f1e3f3a..54bfd6b9d9 100644 --- a/drivers/src/hand_off.rs +++ b/drivers/src/hand_off.rs @@ -216,10 +216,10 @@ pub struct FirmwareHandoffTable { pub rtalias_tbs_size: u16, /// Maximum value RT FW SVN can take. - pub rt_hash_chain_max_svn: u16, + pub rt_key_ladder_max_svn: u16, - /// Index of RT hash chain value in the Key Vault. - pub rt_hash_chain_kv_hdl: HandOffDataHandle, + /// Index of RT key ladder value in the Key Vault. + pub rt_key_ladder_kv_hdl: HandOffDataHandle, /// Reserved for future use. pub reserved: [u8; FHT_RESERVED_SIZE], @@ -257,8 +257,8 @@ impl Default for FirmwareHandoffTable { idev_dice_mldsa_pub_key_load_addr: 0, rom_info_addr: RomAddr::new(FHT_INVALID_ADDRESS), rtalias_tbs_size: 0, - rt_hash_chain_max_svn: 0, - rt_hash_chain_kv_hdl: HandOffDataHandle(0), + rt_key_ladder_max_svn: 0, + rt_key_ladder_kv_hdl: HandOffDataHandle(0), reserved: [0u8; FHT_RESERVED_SIZE], } } diff --git a/error/src/lib.rs b/error/src/lib.rs index 68c99fd738..451c1d774c 100644 --- a/error/src/lib.rs +++ b/error/src/lib.rs @@ -468,7 +468,7 @@ impl CaliptraError { CaliptraError::new_const(0x000E0039); pub const RUNTIME_PRIV_KEY_KV_HDL_HANDOFF_FAILED: CaliptraError = CaliptraError::new_const(0x000E003A); - pub const RUNTIME_HASH_CHAIN_HANDOFF_FAILED: CaliptraError = + pub const RUNTIME_KEY_LADDER_HANDOFF_FAILED: CaliptraError = CaliptraError::new_const(0x000E003B); /// PCR Runtime Errors pub const RUNTIME_PCR_RESERVED: CaliptraError = CaliptraError::new_const(0x000E003C); @@ -519,6 +519,8 @@ impl CaliptraError { CaliptraError::new_const(0x000E0054); pub const RUNTIME_AUTH_MANIFEST_LMS_OWNER_PUB_KEY_INVALID: CaliptraError = CaliptraError::new_const(0x000E0055); + pub const RUNTIME_KEY_LADDER_TARGET_SVN_TOO_LARGE: CaliptraError = + CaliptraError::new_const(0x000E0056); /// FMC Errors pub const FMC_GLOBAL_NMI: CaliptraError = CaliptraError::new_const(0x000F0001); @@ -579,6 +581,7 @@ impl CaliptraError { CaliptraError::new_const(0x0102000A); pub const FW_PROC_MAILBOX_FW_LOAD_CMD_IN_ACTIVE_MODE: CaliptraError = CaliptraError::new_const(0x0102000B); + pub const FW_PROC_SVN_TOO_LARGE: CaliptraError = CaliptraError::new_const(0x0102000C); /// FMC Alias Layer : Certificate Verification Failure. pub const FMC_ALIAS_CERT_VERIFY: CaliptraError = CaliptraError::new_const(0x01030001); diff --git a/fmc/README.md b/fmc/README.md index 1b98c1fd17..95fdd599cd 100644 --- a/fmc/README.md +++ b/fmc/README.md @@ -306,14 +306,14 @@ This field provides the size of the *To Be Signed* portion of the Runtime Alias This field provides the size of the *To Be Signed* portion of the Runtime Alias MLDSA certificate. -### rt_hash_chain_max_svn +### rt_key_ladder_max_svn This field informs firmware of the maximum RT SVN, which value was used -to determine the length of RT FW's hash chain. +to determine the length of RT FW's key ladder. -### rt_hash_chain_kv_hdl +### rt_key_ladder_kv_hdl -This field provides the Handle into the Key Vault where RT's hash chain is stored. +This field provides the Handle into the Key Vault where RT's key ladder is stored. ### reserved diff --git a/fmc/src/boot_status.rs b/fmc/src/boot_status.rs index 11ea857a0d..1e671ec8ab 100644 --- a/fmc/src/boot_status.rs +++ b/fmc/src/boot_status.rs @@ -14,9 +14,6 @@ pub enum FmcBootStatus { RtAliasSubjKeyIdGenerationComplete = RTALIAS_BOOT_STATUS_BASE + 4, RtAliasCertSigGenerationComplete = RTALIAS_BOOT_STATUS_BASE + 5, RtAliasDerivationComplete = RTALIAS_BOOT_STATUS_BASE + 6, - - // Hash chain statuses - RtHashChainComplete = RTALIAS_BOOT_STATUS_BASE + 7, } impl From for u32 { diff --git a/rom/dev/src/fht.rs b/rom/dev/src/fht.rs index 73adb5b3aa..4a5e4b8d9a 100644 --- a/rom/dev/src/fht.rs +++ b/rom/dev/src/fht.rs @@ -16,10 +16,14 @@ use crate::{rom_env::RomEnv, CALIPTRA_ROM_INFO}; #[cfg(not(feature = "no-cfi"))] use caliptra_cfi_derive::cfi_mod_fn; use caliptra_common::{ - keyids::{KEY_ID_FMC_ECDSA_PRIV_KEY, KEY_ID_FMC_MLDSA_KEYPAIR_SEED, KEY_ID_ROM_FMC_CDI}, + keyids::{ + KEY_ID_FMC_ECDSA_PRIV_KEY, KEY_ID_FMC_MLDSA_KEYPAIR_SEED, KEY_ID_FW_KEY_LADDER, + KEY_ID_ROM_FMC_CDI, + }, FirmwareHandoffTable, HandOffDataHandle, Vault, FHT_INVALID_HANDLE, FHT_MARKER, }; use caliptra_drivers::{cprintln, RomAddr}; +use caliptra_image_verify::MAX_RUNTIME_SVN; const FHT_MAJOR_VERSION: u16 = 1; const FHT_MINOR_VERSION: u16 = 0; @@ -40,6 +44,10 @@ impl FhtDataStore { pub const fn fmc_mldsa_keypair_seed_store() -> HandOffDataHandle { HandOffDataHandle(((Vault::KeyVault as u32) << 12) | KEY_ID_FMC_MLDSA_KEYPAIR_SEED as u32) } + /// The firmware key ladder is stored in a KeyVault slot. + pub const fn fw_key_ladder_store() -> HandOffDataHandle { + HandOffDataHandle(((Vault::KeyVault as u32) << 12) | KEY_ID_FW_KEY_LADDER as u32) + } } #[cfg_attr(not(feature = "no-cfi"), cfi_mod_fn)] @@ -67,6 +75,8 @@ pub fn initialize_fht(env: &mut RomEnv) { pcr_log_addr: &pdata.pcr_log as *const _ as u32, meas_log_addr: &pdata.measurement_log as *const _ as u32, fuse_log_addr: &pdata.fuse_log as *const _ as u32, + rt_key_ladder_max_svn: MAX_RUNTIME_SVN as u16, + rt_key_ladder_kv_hdl: FhtDataStore::fw_key_ladder_store(), ..Default::default() }; } diff --git a/rom/dev/src/flow/cold_reset/fw_processor.rs b/rom/dev/src/flow/cold_reset/fw_processor.rs index d5dd837419..4ef8ee09b4 100644 --- a/rom/dev/src/flow/cold_reset/fw_processor.rs +++ b/rom/dev/src/flow/cold_reset/fw_processor.rs @@ -11,9 +11,11 @@ Abstract: File contains the code to download and validate the firmware. --*/ + #[cfg(feature = "fake-rom")] use crate::flow::fake::FakeRomImageVerificationEnv; use crate::fuse::log_fuse_data; +use crate::key_ladder; use crate::pcr; use crate::rom_env::RomEnv; use crate::run_fips_tests; @@ -32,6 +34,7 @@ use caliptra_common::{ }; use caliptra_drivers::{pcr_log::MeasurementLogEntry, *}; use caliptra_image_types::{FwVerificationPqcKeyType, ImageManifest, IMAGE_BYTE_SIZE}; +use caliptra_image_verify::MAX_RUNTIME_SVN; use caliptra_image_verify::{ImageVerificationInfo, ImageVerificationLogInfo, ImageVerifier}; use caliptra_kat::KatsEnv; use caliptra_x509::{NotAfter, NotBefore}; @@ -156,6 +159,8 @@ impl FirmwareProcessor { // Get the certificate validity info let (nb, nf) = Self::get_cert_validity_info(manifest); + Self::populate_fw_key_ladder(env)?; + report_boot_status(FwProcessorComplete.into()); Ok(FwProcInfo { fmc_cert_valid_not_before: nb, @@ -647,6 +652,32 @@ impl FirmwareProcessor { report_boot_status(FwProcessorPopulateDataVaultComplete.into()); } + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + fn populate_fw_key_ladder(env: &mut RomEnv) -> CaliptraResult<()> { + let svn = env.persistent_data.get().data_vault.rt_svn(); + + if svn > MAX_RUNTIME_SVN { + // If this occurs it is an internal programming error. + Err(CaliptraError::FW_PROC_SVN_TOO_LARGE)?; + } + + let chain_len = MAX_RUNTIME_SVN - svn; + + cprintln!( + "[fwproc] Initializing chain, length {} (max {})", + chain_len, + MAX_RUNTIME_SVN + ); + + key_ladder::initialize_key_ladder(env, chain_len)?; + + cprintln!("[fwproc] Chain initialized"); + + report_boot_status(FwProcessorCalculateKeyLadderComplete.into()); + + Ok(()) + } + /// Process the certificate validity info /// /// # Arguments @@ -788,7 +819,10 @@ impl FirmwareProcessor { stash_measurement: &StashMeasurementReq, ) -> CaliptraResult<()> { let fht = &mut persistent_data.fht; - let Some(dst) = persistent_data.measurement_log.get_mut(fht.meas_log_index as usize) else { + let Some(dst) = persistent_data + .measurement_log + .get_mut(fht.meas_log_index as usize) + else { return Err(CaliptraError::ROM_GLOBAL_MEASUREMENT_LOG_EXHAUSTED); }; diff --git a/rom/dev/src/flow/fake.rs b/rom/dev/src/flow/fake.rs index 18f6df1356..c14a49c679 100644 --- a/rom/dev/src/flow/fake.rs +++ b/rom/dev/src/flow/fake.rs @@ -193,7 +193,7 @@ impl FakeRomFlow { } } -// Used to derive the firmware's hash chain. +// Used to derive the firmware's key ladder. fn initialize_fake_ldevid_cdi(env: &mut RomEnv) -> CaliptraResult<()> { env.hmac.hmac( &HmacKey::Array4x12(&Array4x12::default()), diff --git a/rom/dev/src/flow/update_reset.rs b/rom/dev/src/flow/update_reset.rs index d819f62163..edee3756f1 100644 --- a/rom/dev/src/flow/update_reset.rs +++ b/rom/dev/src/flow/update_reset.rs @@ -13,14 +13,15 @@ Abstract: --*/ #[cfg(feature = "fake-rom")] use crate::flow::fake::FakeRomImageVerificationEnv; +use crate::key_ladder; use crate::{cprintln, pcr, rom_env::RomEnv}; #[cfg(not(feature = "no-cfi"))] use caliptra_cfi_derive::cfi_impl_fn; use caliptra_common::mailbox_api::CommandId; use caliptra_common::verifier::FirmwareImageVerificationEnv; use caliptra_common::RomBootStatus::*; -use caliptra_drivers::report_fw_error_non_fatal; use caliptra_drivers::{okref, report_boot_status, MailboxRecvTxn, ResetReason}; +use caliptra_drivers::{report_fw_error_non_fatal, Hmac, Trng}; use caliptra_drivers::{DataVault, PersistentData}; use caliptra_error::{CaliptraError, CaliptraResult}; use caliptra_image_types::ImageManifest; @@ -82,7 +83,7 @@ impl UpdateResetFlow { // Populate data vault let data_vault = &mut env.persistent_data.get_mut().data_vault; - Self::populate_data_vault(data_vault, info); + Self::populate_data_vault(data_vault, info, &mut env.hmac, &mut env.trng)?; // Extend PCR0 and PCR1 pcr::extend_pcrs( @@ -216,16 +217,32 @@ impl UpdateResetFlow { /// /// * `env` - ROM Environment /// * `info` - Image Verification Info - fn populate_data_vault(data_vault: &mut DataVault, info: &ImageVerificationInfo) { + /// * `hmac` - HMAC helper + /// * `trng` - TRNG helper + fn populate_data_vault( + data_vault: &mut DataVault, + info: &ImageVerificationInfo, + hmac: &mut Hmac, + trng: &mut Trng, + ) -> CaliptraResult<()> { data_vault.set_rt_tci(&info.runtime.digest.into()); - let cur_min_svn = data_vault.rt_min_svn(); - let new_min_svn = core::cmp::min(cur_min_svn, info.fw_svn); + let old_min_svn = data_vault.rt_min_svn(); + let new_min_svn = core::cmp::min(old_min_svn, info.fw_svn); data_vault.set_rt_svn(info.fw_svn); data_vault.set_rt_min_svn(new_min_svn); data_vault.set_rt_entry_point(info.runtime.entry_point); report_boot_status(UpdateResetPopulateDataVaultComplete.into()); + + // Extend the key ladder if the min-SVN is being decremented. + let decrement_by = old_min_svn - new_min_svn; + cprintln!("[update-reset] Extending key ladder by {}", decrement_by); + + key_ladder::extend_key_ladder(hmac, trng, decrement_by)?; + report_boot_status(UpdateResetExtendKeyLadderComplete.into()); + + Ok(()) } } diff --git a/rom/dev/src/key_ladder.rs b/rom/dev/src/key_ladder.rs new file mode 100644 index 0000000000..a382c76bb9 --- /dev/null +++ b/rom/dev/src/key_ladder.rs @@ -0,0 +1,83 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + key_ladder.rs + +Abstract: + + File contains function to manage the firmware's key ladder. + +--*/ + +#[cfg(not(feature = "no-cfi"))] +use caliptra_cfi_derive::cfi_mod_fn; +use caliptra_cfi_lib::cfi_assert_eq; +use caliptra_common::keyids::{KEY_ID_FW_KEY_LADDER, KEY_ID_ROM_FMC_CDI}; +use caliptra_drivers::{Hmac, HmacMode, KeyId, Trng}; +use caliptra_error::CaliptraResult; + +use crate::{crypto::Crypto, rom_env::RomEnv}; + +// This KeyId only holds the LDevID CDI during a specific phase of cold-boot: after +// the LDevID has been derived, but before firmware has been verified and executed. +const LDEVID_CDI: KeyId = KEY_ID_ROM_FMC_CDI; +const LADDER_KEY: KeyId = KEY_ID_FW_KEY_LADDER; + +/// Initialize key ladder on cold reset, bound to the lifecycle and debug states. +/// +/// # Arguments +/// +/// * `env` - ROM Environment +/// * `ladder_len` - Length of ladder to initialize, based on firmware's SVN +#[cfg_attr(not(feature = "no-cfi"), cfi_mod_fn)] +pub(crate) fn initialize_key_ladder(env: &mut RomEnv, ladder_len: u32) -> CaliptraResult<()> { + Crypto::env_hmac_kdf( + env, + LDEVID_CDI, + b"si_init", + Some(&[ + env.soc_ifc.lifecycle() as u8, + env.soc_ifc.debug_locked() as u8, + ]), + LADDER_KEY, + HmacMode::Hmac512, + )?; + + extend_key_ladder(&mut env.hmac, &mut env.trng, ladder_len) +} + +/// Extend key ladder, on cold or update reset. +/// +/// # Arguments +/// +/// * `hmac` - HMAC helper +/// * `trng` - TRNG helper +/// * `num_iters` - Amount by which to extend the ladder +#[cfg_attr(not(feature = "no-cfi"), cfi_mod_fn)] +pub(crate) fn extend_key_ladder( + hmac: &mut Hmac, + trng: &mut Trng, + num_iters: u32, +) -> CaliptraResult<()> { + let mut i: u32 = 0; + + for _ in 0..num_iters { + i += 1; + Crypto::hmac_kdf( + hmac, + trng, + LADDER_KEY, + b"si_extend", + None, + LADDER_KEY, + HmacMode::Hmac512, + )?; + } + + cfi_assert_eq(num_iters, i); + + Ok(()) +} diff --git a/rom/dev/src/main.rs b/rom/dev/src/main.rs index fa121603a6..6167399d90 100644 --- a/rom/dev/src/main.rs +++ b/rom/dev/src/main.rs @@ -46,6 +46,7 @@ mod exception; mod fht; mod flow; mod fuse; +mod key_ladder; mod lock; mod pcr; mod rom_env; diff --git a/rom/dev/tests/rom_integration_tests/test_dice_derivations.rs b/rom/dev/tests/rom_integration_tests/test_dice_derivations.rs index 770d6a7438..8d897d50ed 100644 --- a/rom/dev/tests/rom_integration_tests/test_dice_derivations.rs +++ b/rom/dev/tests/rom_integration_tests/test_dice_derivations.rs @@ -101,6 +101,7 @@ fn test_cold_reset_status_reporting() { hw.step_until_boot_status(FwProcessorFirmwareDownloadTxComplete.into(), false); } + hw.step_until_boot_status(FwProcessorCalculateKeyLadderComplete.into(), false); hw.step_until_boot_status(FwProcessorComplete.into(), false); hw.step_until_boot_status(FmcAliasDeriveCdiComplete.into(), false); hw.step_until_boot_status(FmcAliasKeyPairDerivationComplete.into(), false); diff --git a/rom/dev/tests/rom_integration_tests/test_update_reset.rs b/rom/dev/tests/rom_integration_tests/test_update_reset.rs index 3a02e88d18..6d224d667f 100644 --- a/rom/dev/tests/rom_integration_tests/test_update_reset.rs +++ b/rom/dev/tests/rom_integration_tests/test_update_reset.rs @@ -308,6 +308,7 @@ fn test_update_reset_boot_status() { hw.step_until_boot_status(UpdateResetLoadManifestComplete.into(), false); hw.step_until_boot_status(UpdateResetImageVerificationComplete.into(), false); hw.step_until_boot_status(UpdateResetPopulateDataVaultComplete.into(), false); + hw.step_until_boot_status(UpdateResetExtendKeyLadderComplete.into(), false); hw.step_until_boot_status(UpdateResetExtendPcrComplete.into(), false); hw.step_until_boot_status(UpdateResetLoadImageComplete.into(), false); hw.step_until_boot_status(UpdateResetOverwriteManifestComplete.into(), false); diff --git a/runtime/src/handoff.rs b/runtime/src/handoff.rs index 465b7fc92c..d3553e0c4c 100644 --- a/runtime/src/handoff.rs +++ b/runtime/src/handoff.rs @@ -44,9 +44,9 @@ impl RtHandoff<'_> { self.data_vault.fmc_svn() } - /// Retrieve the RT FW hash chain. - pub fn rt_hash_chain(&self) -> CaliptraResult { - self.read_as_kv(self.fht.rt_hash_chain_kv_hdl.try_into()?) - .map_err(|_| CaliptraError::RUNTIME_HASH_CHAIN_HANDOFF_FAILED) + /// Retrieve the RT FW key ladder. + pub fn rt_key_ladder(&self) -> CaliptraResult { + self.read_as_kv(self.fht.rt_key_ladder_kv_hdl.try_into()?) + .map_err(|_| CaliptraError::RUNTIME_KEY_LADDER_HANDOFF_FAILED) } } diff --git a/runtime/src/key_ladder.rs b/runtime/src/key_ladder.rs new file mode 100644 index 0000000000..e3b25fcb23 --- /dev/null +++ b/runtime/src/key_ladder.rs @@ -0,0 +1,99 @@ +/*++ + +Licensed under the Apache-2.0 license. + +File Name: + + key_ladder.rs + +Abstract: + + File contains key ladder utilities. + +--*/ + +use crate::{handoff::RtHandoff, Drivers, Hmac}; +use caliptra_cfi_derive_git::cfi_impl_fn; +use caliptra_common::keyids::KEY_ID_TMP; +use caliptra_drivers::{CaliptraResult, HmacMode, KeyId}; +use caliptra_error::CaliptraError; + +pub struct KeyLadder; +impl KeyLadder { + /// Calculates a secret from the key ladder. + /// + /// Extends the key ladder the requisite number of times, based on + /// the given target SVN. Fails if the target SVN is too large. Runs + /// a final KDF to derive the resulting secret in the destination KV + /// slot. + /// + /// # Arguments + /// + /// * `drivers` - Drivers + /// * `target_svn` - SVN to which the derived secret should be bound. May not be larger than the current key ladder's SVN. + /// * `context` - Diversification value + /// * `dest` - Key Vault slot to whch the derived secret should be written. + #[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)] + #[inline(never)] + pub fn derive_secret( + drivers: &mut Drivers, + target_svn: u32, + context: &[u8], + dest: KeyId, + ) -> CaliptraResult<()> { + let handoff = RtHandoff { + data_vault: &drivers.persistent_data.get().data_vault, + fht: &drivers.persistent_data.get().fht, + }; + + let key_ladder_svn = handoff.rt_min_svn(); + let key_ladder_kv = handoff.rt_key_ladder()?; + + // Don't allow stomping over the key ladder secret. + if dest == key_ladder_kv { + // If this occurs it is an internal programming error within Caliptra firmware. + Err(CaliptraError::RUNTIME_INTERNAL)?; + } + + if target_svn > key_ladder_svn { + Err(CaliptraError::RUNTIME_KEY_LADDER_TARGET_SVN_TOO_LARGE)?; + } + + let num_iters = key_ladder_svn - target_svn; + + let secret_source = if num_iters == 0 { + key_ladder_kv + } else { + let mut src_slot = key_ladder_kv; + for _ in 0..num_iters { + // First time through, KDF from key_ladder_kv into KEY_ID_TMP; + // all other times through, stay in KEY_ID_TMP. + Hmac::hmac_kdf( + drivers, + src_slot, + b"si_extend", + None, + HmacMode::Hmac512, + KEY_ID_TMP, + )?; + src_slot = KEY_ID_TMP; + } + KEY_ID_TMP + }; + + Hmac::hmac_kdf( + drivers, + secret_source, + b"chain_output", + Some(context), + HmacMode::Hmac512, + dest, + )?; + + if secret_source == KEY_ID_TMP && dest != KEY_ID_TMP { + drivers.key_vault.erase_key(KEY_ID_TMP).unwrap(); + } + + Ok(()) + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 58627a9999..55bd15b0a2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -27,6 +27,7 @@ pub mod handoff; mod hmac; pub mod info; mod invoke_dpe; +pub mod key_ladder; mod pcr; mod populate_idev; mod set_auth_manifest; @@ -61,6 +62,7 @@ pub use populate_idev::PopulateIDevIdCertCmd; pub use get_idev_csr::GetIdevCsrCmd; pub use info::{FwInfoCmd, IDevIdInfoCmd}; pub use invoke_dpe::InvokeDpeCmd; +pub use key_ladder::KeyLadder; pub use pcr::IncrementPcrResetCounterCmd; pub use set_auth_manifest::SetAuthManifestCmd; pub use stash_measurement::StashMeasurementCmd; diff --git a/runtime/test-fw/src/mbox_responder.rs b/runtime/test-fw/src/mbox_responder.rs index 09b08fa391..b0153998db 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, + key_ladder::KeyLadder, 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_KEY_LADDER_MAX_SVN: u32 = 0xE000_0000; +const OPCODE_READ_KEY_LADDER_DIGEST: u32 = 0xF000_0000; const OPCODE_FW_LOAD: u32 = CommandId::FIRMWARE_LOAD.0; fn read_request(mbox: &Mailbox) -> &[u8] { @@ -208,6 +210,29 @@ pub fn handle_command(drivers: &mut Drivers) -> CaliptraResult { .tci_current = TciMeasurement(input_bytes.try_into().unwrap()); write_response(&mut drivers.mbox, &[]); } + CommandId(OPCODE_READ_KEY_LADDER_MAX_SVN) => { + write_response( + &mut drivers.mbox, + &drivers + .persistent_data + .get() + .fht + .rt_key_ladder_max_svn + .to_le_bytes(), + ); + } + // Computes a digest from the key ladder for a given target SVN. + CommandId(OPCODE_READ_KEY_LADDER_DIGEST) => { + let target_svn = u32::read_from(read_request(&drivers.mbox)).unwrap(); + + KeyLadder::derive_secret(drivers, target_svn, b"", KEY_ID_TMP)?; + + let digest = Hmac::ecc384_hmac(drivers, KEY_ID_TMP, b"label", b"data").unwrap(); + + drivers.key_vault.erase_key(KEY_ID_TMP).unwrap(); + + write_response(&mut drivers.mbox, digest.as_bytes()); + } CommandId(OPCODE_FW_LOAD) => { unsafe { SocIfcReg::new() } .regs_mut() diff --git a/runtime/tests/runtime_integration_tests/common.rs b/runtime/tests/runtime_integration_tests/common.rs index 1e439c531e..562db209bd 100644 --- a/runtime/tests/runtime_integration_tests/common.rs +++ b/runtime/tests/runtime_integration_tests/common.rs @@ -20,6 +20,7 @@ use caliptra_hw_model::{ StackInfo, StackRange, }; use caliptra_image_types::FwVerificationPqcKeyType; +use caliptra_test::image_pk_desc_hash; use dpe::{ commands::{Command, CommandHdr}, response::{ @@ -57,6 +58,7 @@ pub const PQC_KEY_TYPE: [FwVerificationPqcKeyType; 2] = [ #[derive(Default)] pub struct RuntimeTestArgs<'a> { pub test_fwid: Option<&'static FwId<'static>>, + pub test_fmc_fwid: Option<&'static FwId<'static>>, pub test_image_options: Option, pub init_params: Option>, pub test_mfg_flags: Option, @@ -69,6 +71,7 @@ pub fn run_rt_test_lms(args: RuntimeTestArgs) -> DefaultHwModel { &APP_WITH_UART }; let runtime_fwid = args.test_fwid.unwrap_or(default_rt_fwid); + let fmc_fwid = args.test_fmc_fwid.unwrap_or(&FMC_WITH_UART); let image_options = args.test_image_options.unwrap_or_else(|| { let mut opts = ImageOptions::default(); @@ -103,8 +106,10 @@ pub fn run_rt_test_lms(args: RuntimeTestArgs) -> DefaultHwModel { }, }; - let image = caliptra_builder::build_and_sign_image(&FMC_WITH_UART, runtime_fwid, image_options) - .unwrap(); + let image = + caliptra_builder::build_and_sign_image(fmc_fwid, runtime_fwid, image_options).unwrap(); + + let (vendor_pk_hash, owner_pk_hash) = image_pk_desc_hash(&image.manifest); let boot_flags = if let Some(flags) = args.test_mfg_flags { flags.bits() @@ -118,6 +123,8 @@ pub fn run_rt_test_lms(args: RuntimeTestArgs) -> DefaultHwModel { fw_image: Some(&image.to_bytes().unwrap()), fuses: Fuses { fuse_pqc_key_type: FwVerificationPqcKeyType::LMS as u32, + vendor_pk_hash, + owner_pk_hash, ..Default::default() }, initial_dbg_manuf_service_reg: boot_flags, diff --git a/runtime/tests/runtime_integration_tests/test_update_reset.rs b/runtime/tests/runtime_integration_tests/test_update_reset.rs index 2d7d39ef39..15b483fbd7 100644 --- a/runtime/tests/runtime_integration_tests/test_update_reset.rs +++ b/runtime/tests/runtime_integration_tests/test_update_reset.rs @@ -4,7 +4,10 @@ use std::mem::size_of; pub use caliptra_api::SocManager; use caliptra_builder::{ - firmware::{runtime_tests::MBOX, APP_WITH_UART, FMC_WITH_UART}, + firmware::{ + runtime_tests::{MBOX, MBOX_WITHOUT_UART}, + APP_WITH_UART, FMC_FAKE_WITH_UART, FMC_WITH_UART, + }, FwId, ImageOptions, }; use caliptra_common::mailbox_api::{ @@ -12,7 +15,9 @@ use caliptra_common::mailbox_api::{ }; use caliptra_drivers::PcrResetCounter; use caliptra_error::CaliptraError; -use caliptra_hw_model::{DefaultHwModel, HwModel}; +use caliptra_hw_model::{ + DefaultHwModel, DeviceLifecycle, HwModel, InitParams, ModelError, SecurityState, +}; use caliptra_image_types::FwVerificationPqcKeyType; use caliptra_runtime::{ContextState, RtBootStatus, PL0_DPE_ACTIVE_CONTEXT_THRESHOLD}; use dpe::{ @@ -390,3 +395,229 @@ 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) -> ImageOptions { + ImageOptions { + app_svn: svn, + pqc_key_type: FwVerificationPqcKeyType::LMS, + ..Default::default() + } +} + +fn cold_update_to_svn(model: DefaultHwModel, svn: u32) -> DefaultHwModel { + drop(model); + run_rt_test(RuntimeTestArgs { + test_fwid: Some(&MBOX), + test_image_options: Some(get_image_opts(svn)), + ..Default::default() + }) +} + +fn runtime_update_to_svn(model: &mut DefaultHwModel, svn: u32) { + update_fw(model, &MBOX, get_image_opts(svn)); +} + +fn get_ladder_digest(model: &mut DefaultHwModel, target_svn: u32) -> Vec { + let digest = model + .mailbox_execute(0xF000_0000, target_svn.as_bytes()) + .unwrap() + .unwrap(); + + assert!(!digest.is_empty()); + digest +} + +fn assert_target_svn_too_large(model: &mut DefaultHwModel, target_svn: u32) { + assert_eq!( + model.mailbox_execute(0xF000_0000, target_svn.as_bytes()), + Err(ModelError::MailboxCmdFailed(u32::from( + CaliptraError::RUNTIME_KEY_LADDER_TARGET_SVN_TOO_LARGE, + ))) + ); +} + +const MAX_SVN: u32 = 128; + +#[test] +fn test_key_ladder_cold_boot() { + let mut model = run_rt_test(RuntimeTestArgs { + test_fwid: Some(&MBOX), + test_image_options: Some(get_image_opts(0)), + ..Default::default() + }); + + let ladder_0 = get_ladder_digest(&mut model, 0); + + model = cold_update_to_svn(model, 1); + + // FW should now have a different key ladder. + let ladder_1 = get_ladder_digest(&mut model, 1); + + // Ask FW to extend the ladder once before returning a digest. + let ladder_0_from_1 = get_ladder_digest(&mut model, 0); + + assert_ne!(ladder_0_from_1, ladder_1); + assert_eq!(ladder_0_from_1, ladder_0); + + // Update to the max SVN supported by ROM. + model = cold_update_to_svn(model, MAX_SVN); + + let ladder_max = get_ladder_digest(&mut model, MAX_SVN); + + // Ask FW for a secret available to SVN 1. + let ladder_1_from_max = get_ladder_digest(&mut model, 1); + + assert_ne!(ladder_1_from_max, ladder_max); + assert_eq!(ladder_1_from_max, ladder_1); +} + +#[test] +fn test_key_ladder_runtime_update() { + let mut model = run_rt_test(RuntimeTestArgs { + test_fwid: Some(&MBOX), + test_image_options: Some(get_image_opts(5)), + ..Default::default() + }); + + // Start at SVN 5. (Min-SVN = 5) + let ladder_5 = get_ladder_digest(&mut model, 5); + + // Update to SVN 6. (Min-SVN = 5) + runtime_update_to_svn(&mut model, 6); + let ladder_6 = get_ladder_digest(&mut model, 5); + assert_eq!(ladder_5, ladder_6); + + // Try to get a secret for SVN 6 while the min-SVN is still 5. + assert_target_svn_too_large(&mut model, 6); + + // Downgrade to SVN 4. (Min-SVN = 4) + runtime_update_to_svn(&mut model, 4); + let ladder_4 = get_ladder_digest(&mut model, 4); + assert_ne!(ladder_4, ladder_5); + + assert_target_svn_too_large(&mut model, 5); + + // Upgrade to SVN 5. (Min-SVN = 4) + runtime_update_to_svn(&mut model, 5); + let ladder_5_after_4 = get_ladder_digest(&mut model, 4); + assert_eq!(ladder_5_after_4, ladder_4); + + assert_target_svn_too_large(&mut model, 5); + + // Upgrade to SVN 6. (Min-SVN = 4) + runtime_update_to_svn(&mut model, 6); + let ladder_6_after_4 = get_ladder_digest(&mut model, 4); + assert_eq!(ladder_6_after_4, ladder_4); + + assert_target_svn_too_large(&mut model, 5); + assert_target_svn_too_large(&mut model, 6); + + // Cold-boot to SVN 6 (Min-SVN = 6) + model = cold_update_to_svn(model, 6); + let ladder_6_after_boot = get_ladder_digest(&mut model, 6); + assert_ne!(ladder_6_after_boot, ladder_6_after_4); + + let ladder_5_from_6 = get_ladder_digest(&mut model, 5); + let ladder_4_from_6 = get_ladder_digest(&mut model, 4); + assert_eq!(ladder_5_from_6, ladder_5); + assert_eq!(ladder_4_from_6, ladder_4); + + // Can still get its own secret after deriving older ones. + let ladder_6_from_self = get_ladder_digest(&mut model, 6); + assert_eq!(ladder_6_from_self, ladder_6_after_boot); + + assert_target_svn_too_large(&mut model, 7); + + // Downgrade to SVN 5 (Min-SVN = 5) + runtime_update_to_svn(&mut model, 5); + let ladder_5_after_boot = get_ladder_digest(&mut model, 5); + assert_eq!(ladder_5_after_boot, ladder_5); + + let ladder_4_from_5 = get_ladder_digest(&mut model, 4); + assert_eq!(ladder_4_from_5, ladder_4); + + assert_target_svn_too_large(&mut model, 6); +} + +#[test] +fn test_key_ladder_max_svn() { + let mut model = run_rt_test(RuntimeTestArgs { + test_fwid: Some(&MBOX), + ..Default::default() + }); + + 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); +} + +fn make_model_with_security_state( + fmc: &'static FwId<'static>, + app: &'static FwId<'static>, + debug_locked: bool, + lifecycle: DeviceLifecycle, +) -> DefaultHwModel { + run_rt_test(RuntimeTestArgs { + test_fwid: Some(app), + test_fmc_fwid: Some(fmc), + init_params: Some(InitParams { + rom: &caliptra_builder::rom_for_fw_integration_tests().unwrap(), + security_state: *SecurityState::default() + .set_debug_locked(debug_locked) + .set_device_lifecycle(lifecycle), + ..Default::default() + }), + ..Default::default() + }) +} + +#[test] +fn test_key_ladder_changes_with_lifecycle() { + // Test with several combinations of security state. + + let mut model = + make_model_with_security_state(&FMC_WITH_UART, &MBOX, false, DeviceLifecycle::Production); + let ladder_a = get_ladder_digest(&mut model, 0); + + model = make_model_with_security_state( + &FMC_WITH_UART, + &MBOX, + false, + DeviceLifecycle::Manufacturing, + ); + let ladder_b = get_ladder_digest(&mut model, 0); + + model = + make_model_with_security_state(&FMC_WITH_UART, &MBOX, true, DeviceLifecycle::Production); + let ladder_c = get_ladder_digest(&mut model, 0); + + model = + make_model_with_security_state(&FMC_WITH_UART, &MBOX, true, DeviceLifecycle::Manufacturing); + let ladder_d = get_ladder_digest(&mut model, 0); + + assert_ne!(ladder_a, ladder_b); + assert_ne!(ladder_a, ladder_c); + assert_ne!(ladder_a, ladder_d); + + assert_ne!(ladder_b, ladder_c); + assert_ne!(ladder_b, ladder_d); + + assert_ne!(ladder_c, ladder_d); +} + +#[test] +fn test_key_ladder_stable_across_fw_updates() { + // Update both FMC and app FW, and ensure the key ladder is still identical. + + let (fmc_a, app_a) = (&FMC_WITH_UART, &MBOX); + let (fmc_b, app_b) = (&FMC_FAKE_WITH_UART, &MBOX_WITHOUT_UART); + + let mut model = make_model_with_security_state(fmc_a, app_a, true, DeviceLifecycle::Production); + let ladder_a = get_ladder_digest(&mut model, 0); + + model = make_model_with_security_state(fmc_b, app_b, true, DeviceLifecycle::Production); + let ladder_b = get_ladder_digest(&mut model, 0); + + assert_eq!(ladder_a, ladder_b); +}