diff --git a/Cargo.lock b/Cargo.lock index 184991228..f50859c7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,6 +668,7 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -1019,6 +1020,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 = "http" version = "1.4.0" @@ -1707,15 +1717,19 @@ dependencies = [ "ctr", "elf", "hashbrown", + "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", "zerocopy", + "zeroize", ] [[package]] diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index 276452d4d..f70bd54f6 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -37,7 +37,7 @@ fn ratchet_globals() -> Result<()> { ("litebox/", 9), ("litebox_platform_linux_kernel/", 6), ("litebox_platform_linux_userland/", 5), - ("litebox_platform_lvbs/", 23), + ("litebox_platform_lvbs/", 24), ("litebox_platform_multiplex/", 1), ("litebox_platform_windows_userland/", 8), ("litebox_runner_linux_userland/", 1), diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 31cc4dde4..383bcb318 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -482,6 +482,11 @@ pub enum TeeParamType { impl UteeParams { pub const TEE_NUM_PARAMS: usize = TEE_NUM_PARAMS; + /// Return `true` if every parameter matches the expected type. + pub fn has_types(&self, expected: [TeeParamType; Self::TEE_NUM_PARAMS]) -> bool { + (0..Self::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(), @@ -619,6 +624,16 @@ impl TeeUuid { bytes[8..16].copy_from_slice(&data[1].to_le_bytes()); Self::from_bytes(bytes) } + + /// Converts the UUID to a 16-byte array with little-endian encoding. + 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 + } } /// TA flags from `optee_os/lib/libutee/include/user_ta_header.h`. @@ -2306,6 +2321,30 @@ pub fn parse_ta_head(elf_data: &[u8]) -> Option { None } +/// Hardware Unique Key (HUK) subkey usage identifiers based on OP-TEE's `enum huk_subkey_usage`. +#[derive(Clone, Copy)] +#[repr(u32)] +pub enum HukSubkeyUsage { + /// RPMB key + Rpmb = 0, + /// Secure Storage Key + Ssk = 1, + /// Die ID + DieId = 2, + /// TA unique key + UniqueTa = 3, + /// TA encryption key + TaEnc = 4, + /// SCP03 set of encryption keys + Se050 = 5, +} + +/// HUK 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_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index 8bb1f415b..79dc16028 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -114,6 +114,46 @@ impl litebox::platform::CrngProvider for LvbsLinuxKernel { } } +/// Length of the Platform Root Key in bytes. +pub const PRK_LEN: usize = 32; + +static PRK_ONCE: spin::Once<[u8; PRK_LEN]> = spin::Once::new(); + +/// Sets the Platform Root Key (PRK) 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 `PRK_LEN`. +pub fn set_platform_root_key(key: &[u8]) { + assert_eq!(key.len(), PRK_LEN, "Platform Root Key length mismatch"); + PRK_ONCE.call_once(|| { + let mut prk = [0u8; PRK_LEN]; + prk.copy_from_slice(key); + prk + }); +} + +impl litebox::platform::DerivedKeyProvider for LvbsLinuxKernel { + fn derive_key( + &self, + shim_kdf: Option Result<(), E>>, + params: litebox::platform::KDFParams, + ) -> Result<(), litebox::platform::DerivedKeyError> { + let Some(prk) = PRK_ONCE.get() else { + return Err(litebox::platform::DerivedKeyError::UnsupportedRebootPersistentKey); + }; + match shim_kdf { + None => { + // LVBS platform doesn't have its own KDF implementation. + Err(litebox::platform::DerivedKeyError::ShimKDFRequired) + } + Some(shim_kdf) => Ok(shim_kdf(prk, params)?), + } + } +} + pub struct HostLvbsInterface; impl HostLvbsInterface {} diff --git a/litebox_platform_lvbs/src/host/mod.rs b/litebox_platform_lvbs/src/host/mod.rs index 48fa248e8..0ac8271fa 100644 --- a/litebox_platform_lvbs/src/host/mod.rs +++ b/litebox_platform_lvbs/src/host/mod.rs @@ -8,6 +8,7 @@ pub mod lvbs_impl; pub mod per_cpu_variables; pub use lvbs_impl::LvbsLinuxKernel; +pub use lvbs_impl::{PRK_LEN, set_platform_root_key}; #[cfg(test)] pub mod mock; diff --git a/litebox_platform_lvbs/src/mshv/error.rs b/litebox_platform_lvbs/src/mshv/error.rs index 5d6274be5..ebcd09368 100644 --- a/litebox_platform_lvbs/src/mshv/error.rs +++ b/litebox_platform_lvbs/src/mshv/error.rs @@ -106,7 +106,7 @@ pub enum VsmError { OperationNotSupported(&'static str), // VTL0 Memory Copy Errors - #[error("failed to copy data to VTL0")] + #[error("failed to copy data from/to VTL0")] Vtl0CopyFailed, // Hypercall Errors @@ -152,6 +152,9 @@ pub enum VsmError { #[error("symbol name contains invalid UTF-8")] SymbolNameInvalidUtf8, + + #[error("root key is invalid")] + PlatformRootKeyInvalid, } impl From for VsmError { @@ -217,7 +220,8 @@ impl From for Errno { | VsmError::SymbolNameInvalidUtf8 | VsmError::SymbolNameNoTerminator | VsmError::CertificateDerLengthInvalid { .. } - | VsmError::CertificateParseFailed => Errno::EINVAL, + | VsmError::CertificateParseFailed + | VsmError::PlatformRootKeyInvalid => Errno::EINVAL, // Signature verification failures delegate to VerificationError's Errno mapping VsmError::SignatureVerificationFailed(e) => Errno::from(e), diff --git a/litebox_platform_lvbs/src/mshv/mod.rs b/litebox_platform_lvbs/src/mshv/mod.rs index 0da18875d..c250fc748 100644 --- a/litebox_platform_lvbs/src/mshv/mod.rs +++ b/litebox_platform_lvbs/src/mshv/mod.rs @@ -130,6 +130,9 @@ pub const VSM_VTL_CALL_FUNC_ID_KEXEC_VALIDATE: u32 = 0x1_ffea; pub const VSM_VTL_CALL_FUNC_ID_PATCH_TEXT: u32 = 0x1_ffeb; pub const VSM_VTL_CALL_FUNC_ID_ALLOCATE_RINGBUFFER_MEMORY: u32 = 0x1_ffec; +// This VSM function ID for setting the platform root key is subject to change +pub const VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY: u32 = 0x1_ffed; + // This VSM function ID for OP-TEE messages is subject to change pub const VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE: u32 = 0x1_fff0; @@ -152,6 +155,7 @@ pub enum VsmFunction { PatchText = VSM_VTL_CALL_FUNC_ID_PATCH_TEXT, OpteeMessage = VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE, AllocateRingbufferMemory = VSM_VTL_CALL_FUNC_ID_ALLOCATE_RINGBUFFER_MEMORY, + SetPlatformRootKey = VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY, } pub const MSR_EFER: u32 = 0xc000_0080; diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index 483de17bb..326bbb834 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -9,9 +9,11 @@ use crate::mshv::ringbuffer::set_ringbuffer; use crate::{ debug_serial_println, host::{ + PRK_LEN, bootparam::get_vtl1_memory_info, linux::{CpuMask, KEXEC_SEGMENT_MAX, Kimage}, per_cpu_variables::with_per_cpu_variables, + set_platform_root_key, }, mshv::{ HV_REGISTER_CR_INTERCEPT_CONTROL, HV_REGISTER_CR_INTERCEPT_CR0_MASK, @@ -916,6 +918,34 @@ fn mshv_vsm_allocate_ringbuffer_memory(phys_addr: u64, size: usize) -> Result Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot("set platform root key")); + } + + let key_pa = PhysAddr::try_new(key_pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; + let key_size: usize = key_size.truncate(); + if key_size != PRK_LEN { + return Err(VsmError::PlatformRootKeyInvalid); + } + + let mut keybuf = [0u8; PRK_LEN]; + if unsafe { crate::platform_low().copy_slice_from_vtl0_phys(key_pa, &mut keybuf) } { + set_platform_root_key(&keybuf); + Ok(0) + } else { + Err(VsmError::Vtl0CopyFailed) + } +} + /// VSM function dispatcher pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { let result: Result = match func_id { @@ -939,6 +969,7 @@ pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { let size: usize = params[1].truncate(); mshv_vsm_allocate_ringbuffer_memory(params[0], size) } + VsmFunction::SetPlatformRootKey => mshv_vsm_set_platform_root_key(params[0], params[1]), VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")), }; match result { diff --git a/litebox_runner_optee_on_linux_userland/src/lib.rs b/litebox_runner_optee_on_linux_userland/src/lib.rs index 4cc021dea..0eaa33e2c 100644 --- a/litebox_runner_optee_on_linux_userland/src/lib.rs +++ b/litebox_runner_optee_on_linux_userland/src/lib.rs @@ -98,6 +98,8 @@ pub fn run(cli_args: CliArgs) -> Result<()> { InterceptionBackend::Rewriter => {} } + platform.initialize_boot_specific_kdf_support(); + 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 9bc43c2dd..65e09299a 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -18,6 +18,9 @@ once_cell = { version = "1.20.2", default-features = false, features = ["alloc", spin = { version = "0.10.0", default-features = false, features = ["spin_mutex", "once"] } thiserror = { version = "2.0.6", default-features = false } zerocopy = { version = "0.8", default-features = false, features = ["derive"] } +hmac = { version = "0.12", default-features = false } +sha2 = { version = "0.10", default-features = false } +zeroize = { version = "1.8", default-features = false, features = ["alloc"] } [features] default = ["platform_lvbs"] @@ -28,4 +31,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/syscalls/pta.rs b/litebox_shim_optee/src/syscalls/pta.rs index 8a20135d8..adaad3f96 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -5,12 +5,21 @@ //! the functions of built-in TAs. use crate::{Task, UserConstPtr, UserMutPtr}; +use alloc::vec; +use alloc::vec::Vec; +use hmac::{Hmac, Mac}; use litebox::{ - platform::{RawConstPointer as _, RawMutPointer as _}, + platform::{ + DerivedKeyError, DerivedKeyProvider, KDFParams, RawConstPointer as _, RawMutPointer as _, + }, utils::TruncateExt, }; -use litebox_common_optee::{TeeParamType, TeeResult, TeeUuid, UteeParams}; +use litebox_common_optee::{ + HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, TeeParamType, TeeResult, TeeUuid, UteeParams, +}; use num_enum::TryFromPrimitive; +use sha2::Sha256; +use zeroize::{Zeroize, Zeroizing}; pub const PTA_SYSTEM_UUID: TeeUuid = TeeUuid { time_low: 0x3a2f_8978, @@ -34,6 +43,13 @@ const PTA_SYSTEM_DLSYM: u32 = 11; const PTA_SYSTEM_GET_TPM_EVENT_LOG: u32 = 12; const PTA_SYSTEM_SUPP_PLUGIN_INVOKE: u32 = 13; +/// 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; + /// `PTA_SYSTEM_*` command ID from `optee_os/lib/libutee/include/pta_system.h` #[derive(Clone, Copy, TryFromPrimitive)] #[repr(u32)] @@ -72,6 +88,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( @@ -81,67 +99,135 @@ 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) - { - // TODO: revisit buffer size limits based on OP-TEE spec and deployment constraints - let input_len = - usize::try_from(input.1).map_err(|_| TeeResult::BadParameters)?; - if input_len > crate::MAX_KERNEL_BUF_SIZE { - return Err(TeeResult::BadParameters); - } - let input_addr: usize = input.0.truncate(); - let input_ptr = UserConstPtr::::from_usize(input_addr); - let _extra_data = input_ptr - .to_owned_slice(input_len) - .ok_or(TeeResult::BadParameters)?; - - let output_len = - usize::try_from(output.1).map_err(|_| TeeResult::BadParameters)?; - if output_len > crate::MAX_KERNEL_BUF_SIZE { - return Err(TeeResult::BadParameters); - } - let output_addr: usize = output.0.truncate(); - let output_ptr = UserMutPtr::::from_usize(output_addr); - - // TODO: checks whether output 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: {})", - output_addr, - output_len - ); - // TODO: replace below with a secure key derivation function - let mut key_buf = alloc::vec![0u8; output_len]; - self.sys_cryp_random_number_generate(&mut key_buf)?; - output_ptr - .copy_from_slice(0, &key_buf) - .ok_or(TeeResult::BadParameters)?; - - Ok(()) - } else { - Err(TeeResult::BadParameters) - } - } + PtaSystemCommandId::DeriveTaUniqueKey => self.derive_ta_unique_key(params), _ => { #[cfg(debug_assertions)] todo!("support other system PTA commands {cmd_id}"); #[cfg(not(debug_assertions))] - Err(TeeResult::NotSupported) + return Err(TeeResult::NotSupported); } } } + + /// 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)?; + + // In LiteBox for LVBS, TA's address space is always in the secure world. + 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) + }) + } + + /// 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); + } + + let kdf_context_len = + core::mem::size_of::() + const_data.iter().map(|chunk| chunk.len()).sum::(); + let mut kdf_context = Zeroizing::new(Vec::with_capacity(kdf_context_len)); + kdf_context.extend_from_slice(&(usage as u32).to_le_bytes()); + for chunk in const_data { + kdf_context.extend_from_slice(chunk); + } + let kdf_params = KDFParams { + context: kdf_context.as_slice(), + output: subkey, + }; + + self.global + .platform + .derive_key(Some(huk_subkey_derive_inner), kdf_params) + .map_err(|err| match err { + DerivedKeyError::ShimKDFRequired => { + unreachable!("we always provide a shim KDF callback") + } + DerivedKeyError::UnsupportedRebootPersistentKey => TeeResult::NotSupported, + DerivedKeyError::ShimKDFError(err) => err, + })?; + + Ok(()) + } +} + +/// A KDF callback that derives a subkey from `huk` and `params.context` to be passed to +/// the underlying platform implementation of `derive_key`. +fn huk_subkey_derive_inner(huk: &[u8], params: KDFParams<'_>) -> Result<(), TeeResult> { + let subkey_len = params.output.len(); + if subkey_len > HUK_SUBKEY_MAX_LEN { + return Err(TeeResult::BadParameters); + } + + let mut hmac = HmacSha256::new_from_slice(huk).map_err(|_| TeeResult::BadParameters)?; + hmac.update(params.context); + + let mut hmac_bytes = hmac.finalize().into_bytes(); + params.output.copy_from_slice(&hmac_bytes[..subkey_len]); + hmac_bytes.zeroize(); + Ok(()) }