diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index c956074e6..0c1fb009b 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -262,6 +262,7 @@ impl OpteeShim { global: self.0.clone(), thread: ThreadState::new(), ta_app_id: ta_uuid, + ta_svn: 0, // TODO: Initialize from TA binary tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), @@ -1349,6 +1350,8 @@ struct Task { thread: ThreadState, /// TA UUID ta_app_id: TeeUuid, + /// TA Secure Version Number (SVN) + ta_svn: u32, /// TEE cryptography state map tee_cryp_state_map: TeeCrypStateMap, /// TEE object map @@ -1519,10 +1522,16 @@ mod test_utils { impl GlobalState { /// Make a new task with default values for testing. pub(crate) fn new_test_task(self: Arc) -> Task { + self.new_test_task_with_svn(0) + } + + /// Make a new task with the provided TA SVN for testing. + pub(crate) fn new_test_task_with_svn(self: Arc, ta_svn: u32) -> Task { Task { global: self.clone(), thread: ThreadState::new(), ta_app_id: TeeUuid::default(), + ta_svn, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), diff --git a/litebox_shim_optee/src/syscalls/pta.rs b/litebox_shim_optee/src/syscalls/pta.rs index ab0c426ba..e692a8a7c 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -104,17 +104,22 @@ const PTA_SYSTEM_DLSYM: u32 = 11; const PTA_SYSTEM_GET_TPM_EVENT_LOG: u32 = 12; const PTA_SYSTEM_SUPP_PLUGIN_INVOKE: u32 = 13; +// subject to change. This is not an official system PTA command ID. +const PTA_SYSTEM_DERIVE_TA_SVN_KEY_STACK: u32 = 14; + /// Minimum size of a derived key in bytes. const TA_DERIVED_KEY_MIN_SIZE: usize = 16; /// Maximum size of a derived key in bytes. const TA_DERIVED_KEY_MAX_SIZE: usize = 32; /// Maximum size of extra data for key derivation in bytes. const TA_DERIVED_EXTRA_DATA_MAX_SIZE: usize = 1024; +/// Maximum number of keys in SVN key stack. +const SVN_KEY_STACK_MAX_SIZE: u32 = 4096; /// `PTA_SYSTEM_*` command ID from `optee_os/lib/libutee/include/pta_system.h` #[derive(Clone, Copy, TryFromPrimitive)] #[repr(u32)] -enum PtaSystemCommandId { +pub(crate) enum PtaSystemCommandId { AddRngEntropy = PTA_SYSTEM_ADD_RNG_ENTROPY, DeriveTaUniqueKey = PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY, MapZi = PTA_SYSTEM_MAP_ZI, @@ -129,6 +134,7 @@ enum PtaSystemCommandId { Dlsym = PTA_SYSTEM_DLSYM, GetTpmEventLog = PTA_SYSTEM_GET_TPM_EVENT_LOG, SuppPluginInvoke = PTA_SYSTEM_SUPP_PLUGIN_INVOKE, + DeriveTaSvnKeyStack = PTA_SYSTEM_DERIVE_TA_SVN_KEY_STACK, } type HmacSha256 = Hmac; @@ -249,6 +255,9 @@ impl SystemPta { PtaSystemCommandId::DeriveTaUniqueKey => { Self::derive_ta_unique_key(task, params).map(|()| Cleanup::None) } + PtaSystemCommandId::DeriveTaSvnKeyStack => { + Self::derive_ta_svn_key_stack(task, params).map(|()| Cleanup::None) + } PtaSystemCommandId::MapZi => Self::map_zi(task, params), PtaSystemCommandId::Unmap => Self::unmap(task, params).map(|()| Cleanup::None), _ => { @@ -321,6 +330,120 @@ impl SystemPta { }) } + /// Derives a stack of unique keys for a TA, one for each possible + /// Secure Version Number (SVN) value up to a maximum. + /// + /// The key derivation follows a two-stage process: + /// 1. First stage: KDF(huk, uuid || extra_data) -> base key + /// 2. Second stage: Iterate from max SVN down to 0, chaining keys: + /// - Key\[max\] = HMAC(base_key, max) + /// - Key\[n\] = HMAC(Key\[n+1\], n) + /// + /// Only keys for SVN values <= current TA version are copied to output. + fn derive_ta_svn_key_stack(task: &Task, params: &UteeParams) -> Result<(), TeeResult> { + use TeeParamType::{MemrefInput, MemrefOutput, None, ValueInput}; + // Validate parameter types: + // [in] params[0].value.a Size of each key + // [in] params[0].value.b Number of keys to derive + // [in] params[1].memref.buffer Extra data for key derivation + // [in] params[1].memref.size Extra data size + // [out] params[2].memref.buffer Output buffer for key stack + // [out] params[2].memref.size Buffer size + if !params.has_types([ValueInput, MemrefInput, MemrefOutput, None]) { + return Err(TeeResult::BadParameters); + } + + let (key_size_u64, svn_key_stack_size_u64) = params + .get_values(0) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let key_size: usize = key_size_u64.trunc(); + let svn_key_stack_size = + u32::try_from(svn_key_stack_size_u64).map_err(|_| TeeResult::BadParameters)?; + + let (extra_data_addr, extra_data_size_u64) = params + .get_values(1) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let extra_data_size: usize = extra_data_size_u64.trunc(); + + let (key_stack_addr, key_stack_buffer_size_u64) = params + .get_values(2) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let key_stack_buffer_size: usize = key_stack_buffer_size_u64.trunc(); + + if !(TA_DERIVED_KEY_MIN_SIZE..=TA_DERIVED_KEY_MAX_SIZE).contains(&key_size) + || extra_data_size > TA_DERIVED_EXTRA_DATA_MAX_SIZE + || svn_key_stack_size > SVN_KEY_STACK_MAX_SIZE + || svn_key_stack_size == 0 + || (extra_data_size > 0 && extra_data_addr == 0) + || key_stack_addr == 0 + { + return Err(TeeResult::BadParameters); + } + + let ta_svn = task.ta_svn; + let required_stack_buffer_size = key_size + .checked_mul(ta_svn as usize + 1) + .ok_or(TeeResult::BadParameters)?; + if key_stack_buffer_size < required_stack_buffer_size { + return Err(TeeResult::BadParameters); + } + + let extra_data = if extra_data_size == 0 { + Vec::new().into_boxed_slice() + } else { + let extra_data_ptr = UserConstPtr::::from_usize(extra_data_addr.trunc()); + extra_data_ptr + .to_owned_slice(extra_data_size) + .ok_or(TeeResult::BadParameters)? + }; + + // Unlike OP-TEE OS, `UserMutPtr` (and `UserConstPtr`) in LiteBox ensure this + // pointer can never be used to access normal-world memory. That is, we don't + // need extra security check for detecting key leakage here. + let key_stack_ptr = UserMutPtr::::from_usize(key_stack_addr.trunc()); + + // First stage: derive base key = KDF(huk, usage || ta_uuid || extra data) + let uuid_bytes = task.ta_app_id.to_le_bytes(); + let mut stage_key = Zeroizing::new(vec![0u8; key_size]); + Self::huk_subkey_derive( + task, + HukSubkeyUsage::UniqueTa, + &[&uuid_bytes, &extra_data], + &mut stage_key, + )?; + + // Derive keys from max SVN down to 0 + for svn_idx in (0..svn_key_stack_size).rev() { + // Second stage KDF: HMAC(current_key, SVN_index) + // Key_v2047 = KDF(KDF(HUK, UUID), 2047) + // Key_v2046 = KDF(Key_v2047, 2046) + // ... + // Key_v001 = KDF(Key_v002, 001) + // Key_v000 = KDF(Key_v001, 000) + let mut hmac = + HmacSha256::new_from_slice(&stage_key).map_err(|_| TeeResult::BadParameters)?; + hmac.update(&svn_idx.to_le_bytes()); + + let mut hmac_bytes = hmac.finalize().into_bytes(); + let derived_key = &hmac_bytes[..key_size]; + + // Only copy keys for SVN values <= current TA version to userspace + if svn_idx <= ta_svn { + let offset = svn_idx as usize * key_size; + key_stack_ptr + .copy_from_slice(offset, derived_key) + .ok_or(TeeResult::AccessDenied)?; + } + stage_key.copy_from_slice(derived_key); + hmac_bytes.zeroize(); + } + + Ok(()) + } + /// Derive a subkey using HUK and constant data. /// /// This follows the OP-TEE `huk_subkey_derive` interface from `core/kernel/huk_subkey.c`. diff --git a/litebox_shim_optee/src/syscalls/tests.rs b/litebox_shim_optee/src/syscalls/tests.rs index 441228918..36beeb8ea 100644 --- a/litebox_shim_optee/src/syscalls/tests.rs +++ b/litebox_shim_optee/src/syscalls/tests.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +use crate::syscalls::pta::{PseudoTa, PtaSystemCommandId}; +use litebox_common_optee::{TeeParamType, UteeParams}; use litebox_platform_multiplex::{Platform, set_platform}; // Ensure we only init the platform once @@ -19,6 +21,9 @@ pub(crate) fn init_platform() -> crate::Task { #[cfg(not(target_os = "linux"))] let platform = Platform::new(); + #[cfg(target_os = "linux")] + platform.initialize_boot_specific_kdf_support(); + set_platform(platform); }); @@ -27,6 +32,26 @@ pub(crate) fn init_platform() -> crate::Task { shim_builder.build().0.new_test_task() } +#[must_use] +pub(crate) fn init_platform_with_svn(ta_svn: u32) -> crate::Task { + INIT_FUNC.call_once(|| { + #[cfg(target_os = "linux")] + let platform = Platform::new(None); + + #[cfg(not(target_os = "linux"))] + let platform = Platform::new(); + + #[cfg(target_os = "linux")] + platform.initialize_boot_specific_kdf_support(); + + set_platform(platform); + }); + + let shim_builder = crate::OpteeShimBuilder::new(); + let _litebox = shim_builder.litebox(); + shim_builder.build().0.new_test_task_with_svn(ta_svn) +} + #[test] fn test_sys_log() { let task = init_platform(); @@ -66,3 +91,93 @@ fn test_sys_get_time_system_is_monotonic() { let second_ms = u64::from(second.seconds) * 1000 + u64::from(second.millis); assert!(second_ms >= first_ms, "system time went backwards"); } + +#[test] +fn test_derive_ta_svn_key_stack() { + const KEY_SIZE: usize = 32; + const STACK_SIZE: u32 = 8; + + let task = init_platform(); + let extra_data = [0xAAu8; 16]; + let mut key_buf = [0u8; KEY_SIZE]; + + let mut params = UteeParams::new(); + params.set_type(0, TeeParamType::ValueInput).unwrap(); + params + .set_values(0, KEY_SIZE as u64, u64::from(STACK_SIZE)) + .unwrap(); + params.set_type(1, TeeParamType::MemrefInput).unwrap(); + params + .set_values(1, extra_data.as_ptr() as u64, extra_data.len() as u64) + .unwrap(); + params.set_type(2, TeeParamType::MemrefOutput).unwrap(); + params + .set_values(2, key_buf.as_mut_ptr() as u64, key_buf.len() as u64) + .unwrap(); + params.set_type(3, TeeParamType::None).unwrap(); + + let cmd_id = PtaSystemCommandId::DeriveTaSvnKeyStack as u32; + + // First call: actual derivation produces a non-trivial key for SVN 0. + let _ = PseudoTa::System + .invoke_command(&task, cmd_id, &mut params) + .unwrap(); + let first = key_buf; + assert_ne!(first, [0u8; KEY_SIZE], "derived key must not be all zeros"); + + // Same inputs reproduce the same Key[0] (determinism). + key_buf.fill(0); + let _ = PseudoTa::System + .invoke_command(&task, cmd_id, &mut params) + .unwrap(); + assert_eq!(key_buf, first, "derivation must be deterministic"); + + // Shorter chain must yield a different Key[0], proving the stack derivation + // actually chains through all SVN levels rather than collapsing. + params + .set_values(0, KEY_SIZE as u64, u64::from(STACK_SIZE - 1)) + .unwrap(); + key_buf.fill(0); + let _ = PseudoTa::System + .invoke_command(&task, cmd_id, &mut params) + .unwrap(); + assert_ne!( + key_buf, first, + "different chain depth must produce a different Key[0]" + ); +} + +#[test] +fn test_derive_ta_svn_key_stack_allows_ta_svn_above_stack_size() { + const KEY_SIZE: usize = 32; + const STACK_SIZE: u32 = 2; + const TA_SVN: u32 = 3; + + let task = init_platform_with_svn(TA_SVN); + let extra_data = [0xAAu8; 16]; + let mut key_buf = [0u8; KEY_SIZE * (TA_SVN as usize + 1)]; + + let mut params = UteeParams::new(); + params.set_type(0, TeeParamType::ValueInput).unwrap(); + params + .set_values(0, KEY_SIZE as u64, u64::from(STACK_SIZE)) + .unwrap(); + params.set_type(1, TeeParamType::MemrefInput).unwrap(); + params + .set_values(1, extra_data.as_ptr() as u64, extra_data.len() as u64) + .unwrap(); + params.set_type(2, TeeParamType::MemrefOutput).unwrap(); + params + .set_values(2, key_buf.as_mut_ptr() as u64, key_buf.len() as u64) + .unwrap(); + params.set_type(3, TeeParamType::None).unwrap(); + + let cmd_id = PtaSystemCommandId::DeriveTaSvnKeyStack as u32; + let _ = PseudoTa::System + .invoke_command(&task, cmd_id, &mut params) + .unwrap(); + + assert_ne!(&key_buf[..KEY_SIZE], &[0u8; KEY_SIZE]); + assert_ne!(&key_buf[KEY_SIZE..KEY_SIZE * 2], &[0u8; KEY_SIZE]); + assert_eq!(&key_buf[KEY_SIZE * 2..], &[0u8; KEY_SIZE * 2]); +}