diff --git a/Cargo.lock b/Cargo.lock index 5fb72e6d1..1e335833b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,7 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -721,6 +722,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "iced-x86" version = "1.21.0" @@ -1095,14 +1105,18 @@ dependencies = [ "elf 0.8.0", "elf_loader", "hashbrown 0.15.5", + "hmac", "litebox", "litebox_common_linux", "litebox_common_optee", + "litebox_platform_linux_userland", "litebox_platform_multiplex", "num_enum", "once_cell", + "sha2", "spin 0.10.0", "thiserror", + "zeroize", ] [[package]] diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index 58382feee..5698056ae 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -36,8 +36,8 @@ fn ratchet_globals() -> Result<()> { ("dev_bench/", 1), ("litebox/", 9), ("litebox_platform_linux_kernel/", 5), - ("litebox_platform_linux_userland/", 5), - ("litebox_platform_lvbs/", 20), + ("litebox_platform_linux_userland/", 6), + ("litebox_platform_lvbs/", 21), ("litebox_platform_multiplex/", 1), ("litebox_platform_windows_userland/", 7), ("litebox_runner_linux_userland/", 1), diff --git a/litebox/src/platform/mod.rs b/litebox/src/platform/mod.rs index 3e81b0dfc..1c1f5e1ec 100644 --- a/litebox/src/platform/mod.rs +++ b/litebox/src/platform/mod.rs @@ -670,3 +670,38 @@ pub trait CrngProvider { /// failures. fn fill_bytes_crng(&self, buf: &mut [u8]); } + +/// A provider of the Unique Platform Key (UPK). +/// +/// The UPK is a platform-wide secret used to derive security-critical keys +/// (e.g., TA unique keys, secure storage keys). It serves as the root of trust +/// for cryptographic operations within the trusted execution environment. +/// +/// Trusted hardware devices provide similar funtionalities under various names: +/// - OP-TEE's Hardware Unique Key (HUK) +/// - DICE's Unique Device Secret (UDS) +/// - TPM 2.0's Primary Seeds (e.g., Storage Primary Seed) +/// +/// Ideally, the platform features a persistent, hardware-backed key (e.g., fused +/// OTP, PUF-derived). The key should be unique per device, inaccessible outside +/// the trusted environment, and never directly exposed—only used to derive other +/// keys. +/// +/// If hardware support is unavailable, the platform may generate an ephemeral key +/// from a boot nonce and CRNG, valid only for the current boot session. +pub trait UniquePlatformKeyProvider { + /// Returns a reference to the Unique Platform Key. + /// + /// # Errors + /// + /// Returns [`UniquePlatformKeyError`] if the UPK is not supported or not + /// initialized. + fn unique_platform_key(&self) -> Result<&[u8], UniquePlatformKeyError> { + Err(UniquePlatformKeyError) + } +} + +/// Error returned when unique platform key is not supported on the platform. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] +#[error("unique platform key is not supported on this platform")] +pub struct UniquePlatformKeyError; diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 9e637d620..b84b9ae17 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -447,6 +447,11 @@ pub enum TeeParamType { impl UteeParams { pub const TEE_NUM_PARAMS: usize = TEE_NUM_PARAMS; + /// Returns `true` if every parameter slot matches the expected type. + pub fn has_types(&self, expected: [TeeParamType; 4]) -> bool { + (0..TEE_NUM_PARAMS).all(|i| self.get_type(i).is_ok_and(|t| t == expected[i])) + } + pub fn get_type(&self, index: usize) -> Result { let type_byte = match index { 0 => self.types.type_0(), @@ -596,6 +601,22 @@ impl TeeUuid { bytes[8..16].copy_from_slice(&data[1].to_be_bytes()); Self::from_bytes(bytes) } + + /// Converts the UUID to a 16-byte array with little-endian encoding for numeric fields. + /// + /// The byte layout is: + /// - bytes[0..4]: `time_low` (little-endian u32) + /// - bytes[4..6]: `time_mid` (little-endian u16) + /// - bytes[6..8]: `time_hi_and_version` (little-endian u16) + /// - bytes[8..16]: `clock_seq_and_node` (8 bytes, direct copy) + pub fn to_le_bytes(self) -> [u8; 16] { + let mut bytes = [0u8; 16]; + bytes[0..4].copy_from_slice(&self.time_low.to_le_bytes()); + bytes[4..6].copy_from_slice(&self.time_mid.to_le_bytes()); + bytes[6..8].copy_from_slice(&self.time_hi_and_version.to_le_bytes()); + bytes[8..16].copy_from_slice(&self.clock_seq_and_node); + bytes + } } /// `TEE_Identity` from `optee_os/lib/libutee/include/tee_api_types.h`. @@ -1701,6 +1722,26 @@ impl From for litebox_common_linux::errno::Errno { } } +/// HUK subkey usage identifiers, matching OP-TEE's `enum huk_subkey_usage`. +#[derive(Clone, Copy)] +#[repr(u32)] +pub enum HukSubkeyUsage { + /// Secure Storage Key + Ssk = 0, + /// RPMB key + Rpmb = 1, + /// TA unique key + UniqueTa = 2, + /// Die ID + DieId = 3, +} + +/// Hardware Unique Key length in bytes. +pub const HUK_LEN: usize = 32; + +/// Maximum length of an HUK subkey in bytes. +pub const HUK_SUBKEY_MAX_LEN: usize = 32; + #[cfg(test)] mod tests { use super::*; diff --git a/litebox_platform_linux_userland/src/lib.rs b/litebox_platform_linux_userland/src/lib.rs index 0db320b39..ebedd3d55 100644 --- a/litebox_platform_linux_userland/src/lib.rs +++ b/litebox_platform_linux_userland/src/lib.rs @@ -2206,12 +2206,49 @@ unsafe fn interrupt_signal_handler( set_signal_return(context, interrupt_callback, 0, 0, 0, 0); } +#[cfg(all(target_arch = "x86_64", feature = "optee_syscall"))] +/// Length of the Unique Platform Key in bytes. +pub const UPK_LEN: usize = 32; + +#[cfg(all(target_arch = "x86_64", feature = "optee_syscall"))] +static UPK_ONCE: std::sync::OnceLock<[u8; UPK_LEN]> = std::sync::OnceLock::new(); + +/// Sets the Unique Platform Key (UPK) for this platform. +/// +/// This should be called once during platform initialization with a key derived +/// from hardware or a boot nonce. +/// +/// # Panics +/// Panics if `key` length does not match `UPK_LEN`. +#[cfg(all(target_arch = "x86_64", feature = "optee_syscall"))] +pub fn set_unique_platform_key(key: &[u8]) { + assert_eq!(key.len(), UPK_LEN, "Unique Platform Key length mismatch"); + UPK_ONCE.get_or_init(|| { + let mut upk = [0u8; UPK_LEN]; + upk.copy_from_slice(key); + upk + }); +} + impl litebox::platform::CrngProvider for LinuxUserland { fn fill_bytes_crng(&self, buf: &mut [u8]) { getrandom::fill(buf).expect("getrandom failed"); } } +#[cfg(all(target_arch = "x86_64", feature = "optee_syscall"))] +impl litebox::platform::UniquePlatformKeyProvider for LinuxUserland { + fn unique_platform_key(&self) -> Result<&[u8], litebox::platform::UniquePlatformKeyError> { + UPK_ONCE + .get() + .map(<[u8; UPK_LEN]>::as_slice) + .ok_or(litebox::platform::UniquePlatformKeyError) + } +} + +#[cfg(not(all(target_arch = "x86_64", feature = "optee_syscall")))] +impl litebox::platform::UniquePlatformKeyProvider for LinuxUserland {} + /// Dummy `VmapManager`. /// /// In general, userland platforms do not support `vmap` and `vunmap` (which are kernel functions). diff --git a/litebox_platform_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index 0215e8024..d684253e4 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -96,6 +96,42 @@ impl litebox::platform::CrngProvider for LvbsLinuxKernel { } } +#[cfg(feature = "optee_syscall")] +/// Length of the Unique Platform Key in bytes. +pub const UPK_LEN: usize = 32; +#[cfg(feature = "optee_syscall")] +static UPK_ONCE: spin::Once<[u8; UPK_LEN]> = spin::Once::new(); + +/// Sets the Unique Platform Key (UPK) for this platform. +/// +/// This should be called once during platform initialization with a key derived +/// from hardware or a boot nonce. +/// +/// # Panics +/// Panics if `key` length does not match `UPK_LEN`. +#[cfg(feature = "optee_syscall")] +pub fn set_unique_platform_key(key: &[u8]) { + assert_eq!(key.len(), UPK_LEN, "Unique Platform Key length mismatch"); + UPK_ONCE.call_once(|| { + let mut upk = [0u8; UPK_LEN]; + upk.copy_from_slice(key); + upk + }); +} + +#[cfg(feature = "optee_syscall")] +impl litebox::platform::UniquePlatformKeyProvider for LvbsLinuxKernel { + fn unique_platform_key(&self) -> Result<&[u8], litebox::platform::UniquePlatformKeyError> { + UPK_ONCE + .get() + .map(<[u8; UPK_LEN]>::as_slice) + .ok_or(litebox::platform::UniquePlatformKeyError) + } +} + +#[cfg(not(feature = "optee_syscall"))] +impl litebox::platform::UniquePlatformKeyProvider for LvbsLinuxKernel {} + pub struct HostLvbsInterface; impl HostLvbsInterface {} diff --git a/litebox_platform_lvbs/src/host/mod.rs b/litebox_platform_lvbs/src/host/mod.rs index ec4de9f9b..abbccf72f 100644 --- a/litebox_platform_lvbs/src/host/mod.rs +++ b/litebox_platform_lvbs/src/host/mod.rs @@ -8,6 +8,8 @@ pub mod lvbs_impl; pub mod per_cpu_variables; pub use lvbs_impl::LvbsLinuxKernel; +#[cfg(feature = "optee_syscall")] +pub use lvbs_impl::{UPK_LEN, set_unique_platform_key}; #[cfg(test)] pub mod mock; diff --git a/litebox_platform_lvbs/src/lib.rs b/litebox_platform_lvbs/src/lib.rs index bc446a81d..0cba8fad5 100644 --- a/litebox_platform_lvbs/src/lib.rs +++ b/litebox_platform_lvbs/src/lib.rs @@ -1296,6 +1296,8 @@ unsafe extern "C" fn switch_to_user(_ctx: &litebox_common_linux::PtRegs) -> ! { ); } +pub use crate::host::{UPK_LEN, set_unique_platform_key}; + // Note on user page table management: // The legacy platform code creates a new page table to load a program in a separate // address space and destroys it when the program terminates. This is why the old syscall diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 268f0b063..9355134ec 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -8,6 +8,7 @@ extern crate alloc; use core::{ops::Neg, panic::PanicInfo}; use litebox::{ mm::linux::PAGE_SIZE, + platform::CrngProvider, utils::{ReinterpretSignedExt, TruncateExt}, }; use litebox_common_linux::errno::Errno; @@ -100,6 +101,13 @@ pub fn init() -> Option<&'static Platform> { ); allocate_per_cpu_variables(); + + // Set the Unique Platform Key + // + // *** This is PoC. Once an HVCI/Heki call is ready, we SHALL set UPK via that function *** + let mut upk = [0u8; litebox_platform_lvbs::UPK_LEN]; + platform.fill_bytes_crng(&mut upk); + litebox_platform_lvbs::set_unique_platform_key(&upk); } else { panic!("Failed to get memory info"); } diff --git a/litebox_runner_optee_on_linux_userland/src/lib.rs b/litebox_runner_optee_on_linux_userland/src/lib.rs index 7c8722717..528a2341a 100644 --- a/litebox_runner_optee_on_linux_userland/src/lib.rs +++ b/litebox_runner_optee_on_linux_userland/src/lib.rs @@ -3,6 +3,7 @@ use anyhow::Result; use clap::Parser; +use litebox::platform::CrngProvider; use litebox_common_optee::{TeeUuid, UteeEntryFunc, UteeParamOwned}; use litebox_platform_multiplex::Platform; use std::path::PathBuf; @@ -97,6 +98,11 @@ pub fn run(cli_args: CliArgs) -> Result<()> { InterceptionBackend::Rewriter => {} } + // For now, we use a random UPK for this runner. We can get one via command line if needed. + let mut upk = [0u8; litebox_platform_linux_userland::UPK_LEN]; + platform.fill_bytes_crng(&mut upk); + litebox_platform_linux_userland::set_unique_platform_key(&upk); + if cli_args.command_sequence.is_empty() { run_ta_with_default_commands(&shim, ldelf_data.as_slice(), prog_data.as_slice()); } else { diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 224f18e57..ace250bd5 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -6,6 +6,9 @@ edition = "2024" [dependencies] aes = { version = "0.7", default-features = false } arrayvec = { version = "0.7.6", default-features = false } +hmac = { version = "0.12", default-features = false } +sha2 = { version = "0.10", default-features = false } +zeroize = { version = "1.8", default-features = false, features = ["alloc"] } bitflags = "2.9.0" cfg-if = "1.0.0" ctr = { version = "0.8", default-features = false } @@ -37,4 +40,5 @@ platform_lvbs = ["litebox_platform_multiplex/platform_lvbs_with_optee_syscall"] workspace = true [dev-dependencies] +litebox_platform_linux_userland = { path = "../litebox_platform_linux_userland/", version = "0.1.0", features = ["optee_syscall"] } litebox_platform_multiplex = { path = "../litebox_platform_multiplex/", version = "0.1.0", default-features = false, features = ["platform_linux_userland_with_optee_syscall"] } diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index e9409067e..b89c69b4d 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -205,6 +205,7 @@ impl OpteeShim { thread: ThreadState::new(), session_id: self.0.session_id_pool.allocate(), ta_app_id: ta_uuid, + ta_svn: 0, // TODO: Initialize from TA binary or OP-TEE Secure Storage client_identity: client.unwrap_or(TeeIdentity { login: TeeLogin::User, uuid: TeeUuid::default(), @@ -1141,6 +1142,8 @@ struct Task { session_id: u32, /// TA UUID ta_app_id: TeeUuid, + /// TA Secure Version Number (SVN) + ta_svn: u32, /// Client identity (VTL0 process or another TA) client_identity: TeeIdentity, /// TEE cryptography state map @@ -1258,6 +1261,7 @@ mod test_utils { thread: ThreadState::new(), session_id: self.session_id_pool.allocate(), ta_app_id: TeeUuid::default(), + ta_svn: 0, client_identity: TeeIdentity { login: TeeLogin::User, uuid: TeeUuid::default(), diff --git a/litebox_shim_optee/src/syscalls/pta.rs b/litebox_shim_optee/src/syscalls/pta.rs index 2be433ad0..cecb33442 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -4,9 +4,17 @@ //! Implementation of pseudo TAs (PTAs) which export system services as //! the functions of built-in TAs. -use crate::Task; -use litebox_common_optee::{TeeParamType, TeeResult, TeeUuid, UteeParams}; +use crate::{Task, UserConstPtr, UserMutPtr}; +use alloc::vec; +use hmac::{Hmac, Mac}; +use litebox::platform::{RawConstPointer, RawMutPointer, UniquePlatformKeyProvider}; +use litebox::utils::TruncateExt; +use litebox_common_optee::{ + HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, TeeParamType, TeeResult, TeeUuid, UteeParams, +}; use num_enum::TryFromPrimitive; +use sha2::Sha256; +use zeroize::Zeroizing; pub const PTA_SYSTEM_UUID: TeeUuid = TeeUuid { time_low: 0x3a2f_8978, @@ -29,9 +37,20 @@ const PTA_SYSTEM_DLOPEN: u32 = 10; const PTA_SYSTEM_DLSYM: u32 = 11; const PTA_SYSTEM_GET_TPM_EVENT_LOG: u32 = 12; const PTA_SYSTEM_SUPP_PLUGIN_INVOKE: u32 = 13; +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)] +#[non_exhaustive] #[repr(u32)] pub enum PtaSystemCommandId { AddRngEntropy = PTA_SYSTEM_ADD_RNG_ENTROPY, @@ -48,16 +67,14 @@ pub 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, } /// Checks whether a given TA is a (system) PTA and its parameter is valid. pub fn is_pta(ta_uuid: &TeeUuid, params: &UteeParams) -> bool { // TODO: consider other PTAs - *ta_uuid == PTA_SYSTEM_UUID - && params.get_type(0).is_ok_and(|t| t == TeeParamType::None) - && params.get_type(1).is_ok_and(|t| t == TeeParamType::None) - && params.get_type(2).is_ok_and(|t| t == TeeParamType::None) - && params.get_type(3).is_ok_and(|t| t == TeeParamType::None) + use TeeParamType::None; + *ta_uuid == PTA_SYSTEM_UUID && params.has_types([None, None, None, None]) } // TODO: replace it with a proper implementation. @@ -68,6 +85,8 @@ pub fn is_pta_session(ta_sess_id: u32) -> bool { ta_sess_id == crate::SessionIdPool::get_pta_session_id() } +type HmacSha256 = Hmac; + impl Task { /// Handle a command of the system PTA. pub fn handle_system_pta_command( @@ -77,51 +96,228 @@ impl Task { ) -> Result<(), TeeResult> { #[allow(clippy::single_match_else)] match PtaSystemCommandId::try_from(cmd_id).map_err(|_| TeeResult::BadParameters)? { - PtaSystemCommandId::DeriveTaUniqueKey => { - if params - .get_type(0) - .is_ok_and(|t| t == TeeParamType::MemrefInput) - && params - .get_type(1) - .is_ok_and(|t| t == TeeParamType::MemrefOutput) - && params.get_type(2).is_ok_and(|t| t == TeeParamType::None) - && params.get_type(3).is_ok_and(|t| t == TeeParamType::None) - && let Ok(Some(input)) = - params.get_values(0).map_err(|_| TeeResult::BadParameters) - && let Ok(Some(output)) = - params.get_values(1).map_err(|_| TeeResult::BadParameters) - { - let _extra_data = unsafe { - &*core::ptr::slice_from_raw_parts( - input.0 as *const u8, - usize::try_from(input.1).map_err(|_| TeeResult::BadParameters)?, - ) - }; - let key_slice = unsafe { - &mut *core::ptr::slice_from_raw_parts_mut( - output.0 as *mut u8, - usize::try_from(output.1).map_err(|_| TeeResult::BadParameters)?, - ) - }; - - // TODO: checks whether `key_slice` is within the secure memory - - // TODO: derive a TA unique key using the hardware unique key (HUK), TA's UUID, and `extra_data` - litebox::log_println!( - self.global.platform, - "derive a key and store it in the secure memory (ptr: {:#x}, size: {})", - key_slice.as_ptr() as usize, - key_slice.len() - ); - // TODO: replace below with a secure key derivation function - self.sys_cryp_random_number_generate(key_slice)?; - - Ok(()) - } else { - Err(TeeResult::BadParameters) - } - } + PtaSystemCommandId::DeriveTaUniqueKey => self.derive_ta_unique_key(params), + PtaSystemCommandId::DeriveTaSvnKeyStack => self.derive_ta_svn_key_stack(params), _ => todo!("support other system PTA commands {cmd_id}"), } } + + /// Derive a subkey using HUK and constant data + /// + /// This follows the OP-TEE `huk_subkey_derive` interface from `core/kernel/huk_subkey.c`. + /// + /// - `usage` - The intended usage for the subkey + /// - `const_data` - Constant data chunks to include in derivation + /// - `subkey` - Output buffer to store the derived key + fn huk_subkey_derive( + &self, + usage: HukSubkeyUsage, + const_data: &[&[u8]], + subkey: &mut [u8], + ) -> Result<(), TeeResult> { + let subkey_len = subkey.len(); + if subkey_len > HUK_SUBKEY_MAX_LEN { + return Err(TeeResult::BadParameters); + } + + // We use the unique platform key as the HUK + let huk = self + .global + .platform + .unique_platform_key() + .map_err(|_| TeeResult::NotSupported)?; + + // subkey = HMAC(huk, usage || const_data) + let mut hmac = HmacSha256::new_from_slice(huk).map_err(|_| TeeResult::BadParameters)?; + + hmac.update(&(usage as u32).to_le_bytes()); + + for chunk in const_data { + hmac.update(chunk); + } + + let hmac_bytes = hmac.finalize().into_bytes(); + subkey.copy_from_slice(&hmac_bytes[..subkey_len]); + Ok(()) + } + + /// Derives a unique key for a TA using HUK + /// + /// This follows the OP-TEE `system_derive_ta_unique_key` implementation from + /// `core/pta/system.c`. + fn derive_ta_unique_key(&self, params: &UteeParams) -> Result<(), TeeResult> { + use TeeParamType::{MemrefInput, MemrefOutput, None}; + // Validate parameter types: + // [in] params[0].memref.buffer Extra data for key derivation + // [in] params[0].memref.size Extra data size + // [out] params[1].memref.buffer Output buffer for derived key + // [out] params[1].memref.size Buffer size + if !params.has_types([MemrefInput, MemrefOutput, None, None]) { + return Err(TeeResult::BadParameters); + } + + let (extra_data_addr, extra_data_size_u64) = params + .get_values(0) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let extra_data_size: usize = extra_data_size_u64.truncate(); + + let (subkey_addr, subkey_size_u64) = params + .get_values(1) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let subkey_size: usize = subkey_size_u64.truncate(); + + if extra_data_size > TA_DERIVED_EXTRA_DATA_MAX_SIZE + || !(TA_DERIVED_KEY_MIN_SIZE..=TA_DERIVED_KEY_MAX_SIZE).contains(&subkey_size) + || subkey_addr == 0 + { + return Err(TeeResult::BadParameters); + } + + let extra_data_ptr = UserConstPtr::::from_usize( + usize::try_from(extra_data_addr).map_err(|_| TeeResult::BadParameters)?, + ); + let extra_data = extra_data_ptr + .to_owned_slice(extra_data_size) + .ok_or(TeeResult::BadParameters)?; + + let subkey_ptr = UserMutPtr::::from_usize( + usize::try_from(subkey_addr).map_err(|_| TeeResult::BadParameters)?, + ); + + // subkey = KDF(huk, usage || ta_uuid || extra_data) + let ta_uuid_bytes = self.ta_app_id.to_le_bytes(); + let mut subkey_buf = Zeroizing::new(vec![0u8; subkey_size]); + self.huk_subkey_derive( + HukSubkeyUsage::UniqueTa, + &[&ta_uuid_bytes, &extra_data], + &mut subkey_buf, + ) + .and_then(|()| { + subkey_ptr + .copy_from_slice(0, &subkey_buf) + .ok_or(TeeResult::AccessDenied) + }) + } + + /// 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. + /// + /// NOTE: This function requires `unique_platform_key()` to return a stable + /// value across calls. If the HUK changes between invocations (specifically, + /// between reboots), the derived key stack will be inconsistent. + fn derive_ta_svn_key_stack(&self, 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.truncate(); + 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.truncate(); + + 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.truncate(); + + 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 + || key_stack_addr == 0 + { + return Err(TeeResult::BadParameters); + } + + // Validate TA version is within the key stack bounds + let ta_version = self.ta_svn; + if ta_version >= svn_key_stack_size { + return Err(TeeResult::BadParameters); + } + + let required_stack_buffer_size = key_size + .checked_mul(ta_version as usize + 1) + .ok_or(TeeResult::BadParameters)?; + if key_stack_buffer_size < required_stack_buffer_size { + return Err(TeeResult::BadParameters); + } + + let extra_data_ptr = UserConstPtr::::from_usize( + usize::try_from(extra_data_addr).map_err(|_| TeeResult::BadParameters)?, + ); + let extra_data = extra_data_ptr + .to_owned_slice(extra_data_size) + .ok_or(TeeResult::BadParameters)?; + + let key_stack_ptr = UserMutPtr::::from_usize( + usize::try_from(key_stack_addr).map_err(|_| TeeResult::BadParameters)?, + ); + + let uuid_bytes = self.ta_app_id.to_le_bytes(); + let mut stage_key = Zeroizing::new(vec![0u8; key_size]); + + // Derive keys from max SVN down to 0 + for svn_idx in (0..svn_key_stack_size).rev() { + if svn_idx == svn_key_stack_size - 1 { + // First iteration: derive base key = KDF(huk, usage || ta_uuid || extra data) + self.huk_subkey_derive( + HukSubkeyUsage::UniqueTa, + &[&uuid_bytes, &extra_data], + &mut stage_key, + )?; + } + + // 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 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_version { + 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); + } + + Ok(()) + } } diff --git a/litebox_shim_optee/src/syscalls/tests.rs b/litebox_shim_optee/src/syscalls/tests.rs index 9eaf5f4b1..883ab320a 100644 --- a/litebox_shim_optee/src/syscalls/tests.rs +++ b/litebox_shim_optee/src/syscalls/tests.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +use litebox::platform::CrngProvider; use litebox_platform_multiplex::{Platform, set_platform}; // Ensure we only init the platform once @@ -19,6 +20,11 @@ pub(crate) fn init_platform() -> crate::Task { #[cfg(not(target_os = "linux"))] let platform = Platform::new(); + // Initialize the unique platform key with a random boot nonce + let mut boot_nonce = [0u8; litebox_platform_linux_userland::UPK_LEN]; + platform.fill_bytes_crng(&mut boot_nonce); + litebox_platform_linux_userland::set_unique_platform_key(&boot_nonce); + set_platform(platform); }); @@ -41,3 +47,155 @@ fn test_cryp_random_number_generate() { let result = task.sys_cryp_random_number_generate(&mut buf); assert!(result.is_ok() && buf != [0u8; 16]); } + +#[test] +fn test_derive_ta_svn_key_stack_success() { + use litebox_common_optee::{TeeParamType, UteeParams, UteeParamsTypes}; + + let task = init_platform(); + + let key_size: u32 = 32; + let svn_key_stack_size: u32 = 10; + let extra_data = [0u8; 64]; + let mut key_stack = [0u8; 256]; + + let mut params = UteeParams { + types: UteeParamsTypes::new() + .with_type_0(TeeParamType::ValueInput as u8) + .with_type_1(TeeParamType::MemrefInput as u8) + .with_type_2(TeeParamType::MemrefOutput as u8) + .with_type_3(TeeParamType::None as u8), + vals: [0u64; 8], + }; + + // params[0]: key_size and svn_key_stack_size + params.vals[0] = u64::from(key_size); + params.vals[1] = u64::from(svn_key_stack_size); + + // params[1]: extra_data buffer and size + params.vals[2] = extra_data.as_ptr() as u64; + params.vals[3] = extra_data.len() as u64; + + // params[2]: key_stack buffer and size + params.vals[4] = key_stack.as_mut_ptr() as u64; + params.vals[5] = key_stack.len() as u64; + + let result = task.handle_system_pta_command( + crate::syscalls::pta::PtaSystemCommandId::DeriveTaSvnKeyStack as u32, + ¶ms, + ); + + assert!(result.is_ok()); + // With SVN=0, only key[0] should be populated (first 32 bytes) + assert_ne!(&key_stack[..32], &[0u8; 32]); +} + +#[test] +fn test_derive_ta_svn_key_stack_bad_key_size() { + use litebox_common_optee::{TeeParamType, TeeResult, UteeParams, UteeParamsTypes}; + + let task = init_platform(); + + let extra_data = [0u8; 64]; + let mut key_stack = [0u8; 256]; + + let mut params = UteeParams { + types: UteeParamsTypes::new() + .with_type_0(TeeParamType::ValueInput as u8) + .with_type_1(TeeParamType::MemrefInput as u8) + .with_type_2(TeeParamType::MemrefOutput as u8) + .with_type_3(TeeParamType::None as u8), + vals: [0u64; 8], + }; + + // key_size too small (< 16) + params.vals[0] = 8; + params.vals[1] = 10; + params.vals[2] = extra_data.as_ptr() as u64; + params.vals[3] = extra_data.len() as u64; + params.vals[4] = key_stack.as_mut_ptr() as u64; + params.vals[5] = key_stack.len() as u64; + + let result = task.handle_system_pta_command( + crate::syscalls::pta::PtaSystemCommandId::DeriveTaSvnKeyStack as u32, + ¶ms, + ); + + assert!(matches!(result, Err(TeeResult::BadParameters))); + + // key_size too large (> 32) + params.vals[0] = 64; + let result = task.handle_system_pta_command( + crate::syscalls::pta::PtaSystemCommandId::DeriveTaSvnKeyStack as u32, + ¶ms, + ); + + assert!(matches!(result, Err(TeeResult::BadParameters))); +} + +#[test] +fn test_derive_ta_svn_key_stack_bad_param_types() { + use litebox_common_optee::{TeeParamType, TeeResult, UteeParams, UteeParamsTypes}; + + let task = init_platform(); + + let extra_data = [0u8; 64]; + let mut key_stack = [0u8; 256]; + + // Wrong type for params[0] - should be ValueInput + let mut params = UteeParams { + types: UteeParamsTypes::new() + .with_type_0(TeeParamType::MemrefInput as u8) // Wrong! + .with_type_1(TeeParamType::MemrefInput as u8) + .with_type_2(TeeParamType::MemrefOutput as u8) + .with_type_3(TeeParamType::None as u8), + vals: [0u64; 8], + }; + + params.vals[0] = 32; + params.vals[1] = 10; + params.vals[2] = extra_data.as_ptr() as u64; + params.vals[3] = extra_data.len() as u64; + params.vals[4] = key_stack.as_mut_ptr() as u64; + params.vals[5] = key_stack.len() as u64; + + let result = task.handle_system_pta_command( + crate::syscalls::pta::PtaSystemCommandId::DeriveTaSvnKeyStack as u32, + ¶ms, + ); + + assert!(matches!(result, Err(TeeResult::BadParameters))); +} + +#[test] +fn test_derive_ta_svn_key_stack_zero_stack_size() { + use litebox_common_optee::{TeeParamType, TeeResult, UteeParams, UteeParamsTypes}; + + let task = init_platform(); + + let extra_data = [0u8; 64]; + let mut key_stack = [0u8; 256]; + + let mut params = UteeParams { + types: UteeParamsTypes::new() + .with_type_0(TeeParamType::ValueInput as u8) + .with_type_1(TeeParamType::MemrefInput as u8) + .with_type_2(TeeParamType::MemrefOutput as u8) + .with_type_3(TeeParamType::None as u8), + vals: [0u64; 8], + }; + + params.vals[0] = 32; + params.vals[1] = 0; // Zero stack size - invalid + params.vals[2] = extra_data.as_ptr() as u64; + params.vals[3] = extra_data.len() as u64; + params.vals[4] = key_stack.as_mut_ptr() as u64; + params.vals[5] = key_stack.len() as u64; + + let result = task.handle_system_pta_command( + crate::syscalls::pta::PtaSystemCommandId::DeriveTaSvnKeyStack as u32, + ¶ms, + ); + + assert!(matches!(result, Err(TeeResult::BadParameters))); +}