From 76e43d8d991e9e497430c9ff775dcf28873995de Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 13 Jul 2026 21:14:16 +0000 Subject: [PATCH 01/13] Add IDK_S PTA --- litebox_common_optee/src/lib.rs | 5 + litebox_shim_optee/Cargo.toml | 3 + litebox_shim_optee/src/idk.rs | 204 ++++++++++++++++++++++--- litebox_shim_optee/src/lib.rs | 5 + litebox_shim_optee/src/syscalls/pta.rs | 42 ++--- 5 files changed, 223 insertions(+), 36 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 976e5ab26..7d0066483 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -739,6 +739,11 @@ bitflags::bitflags! { } } +/// Default flags for a single-instance, multi-session, keep-alive pseudo-TA. +pub const PTA_DEFAULT_FLAGS: TaFlags = TaFlags::SINGLE_INSTANCE + .union(TaFlags::MULTI_SESSION) + .union(TaFlags::INSTANCE_KEEP_ALIVE); + impl TaFlags { /// Returns true if this TA should only have one instance. pub fn is_single_instance(&self) -> bool { diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 75a74e903..2704876b9 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -23,6 +23,9 @@ zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false, features = ["alloc"] } p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa"] } +[features] +idks-production = [] + [lints] workspace = true diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 745b1e51f..9390a3e24 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -1,11 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use crate::NormalWorldMutPtr; -use litebox::{mm::linux::PAGE_SIZE, utils::TruncateExt}; +use crate::{NormalWorldMutPtr, Task, UserConstPtr, UserMutPtr, syscalls::Cleanup}; +use alloc::vec::Vec; +use litebox::{ + mm::linux::PAGE_SIZE, + platform::{RawConstPointer as _, RawMutPointer as _}, + utils::TruncateExt, +}; use litebox_common_linux::errno::Errno; +use litebox_common_optee::{ + PTA_DEFAULT_FLAGS, TaFlags, TeeParamType, TeeResult, TeeUuid, UteeParams, +}; use num_enum::TryFromPrimitive; -use p384::{NonZeroScalar, elliptic_curve::sec1::ToEncodedPoint}; +use p384::{ + NonZeroScalar, + ecdsa::{Signature, SigningKey, signature::Signer}, + elliptic_curve::sec1::ToEncodedPoint, +}; use spin::Once; use zeroize::Zeroizing; @@ -15,11 +27,155 @@ const KEY_ALGORITHM_MASK: u64 = 0xff00; const KEY_VARIANT_MASK: u64 = 0xff; const KEY_ALGORITHM_VALUE_MASK: u64 = KEY_ALGORITHM_MASK | KEY_VARIANT_MASK; const MAX_KEYGEN_ATTEMPT: usize = 256; +const IDKS_ENDORSEMENT_DATA_MAX_SIZE: usize = 8 * 1024 * 1024; +const IDKS_ENDORSEMENT_MAGIC: &[u8; 4] = b"IDKS"; +const IDKS_ENDORSEMENT_VERSION: u32 = 1; +#[cfg(not(feature = "idks-production"))] +const IDKS_DEBUG_FLAG: u8 = 1; +#[cfg(feature = "idks-production")] +const IDKS_DEBUG_FLAG: u8 = 0; +const ISOLATION_SOLUTION: &[u8] = b"LVBS"; +pub(crate) const IDKS_ENDORSEMENT_SIGNATURE_LEN: usize = 96; +const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + + size_of::() + + size_of::() + + size_of::() + + size_of::() + + ISOLATION_SOLUTION.len(); +pub(crate) struct IdksPta; + +#[derive(Clone, Copy, TryFromPrimitive)] +#[repr(u32)] +pub(crate) enum IdksCommandId { + EndorseData = 0, +} + +impl IdksPta { + pub(crate) const FLAGS: TaFlags = PTA_DEFAULT_FLAGS.union(TaFlags::CONCURRENT); + pub(crate) const UUID: TeeUuid = TeeUuid { + time_low: 0xfd79_8211, + time_mid: 0x38a3, + time_hi_and_version: 0x474a, + clock_seq_and_node: [0xab, 0x6c, 0x75, 0x61, 0x0d, 0x45, 0x35, 0x93], + }; + + pub(crate) fn open_session(params: &UteeParams) -> Result { + crate::syscalls::pta::open_default_pta_session(params) + } + + pub(crate) fn close_session( + _task: &Task, + _session_id: u32, + ) { + } + + pub(crate) fn invoke_command( + task: &Task, + cmd_id: u32, + params: &mut UteeParams, + ) -> Result { + match IdksCommandId::try_from(cmd_id).map_err(|_| TeeResult::BadParameters)? { + IdksCommandId::EndorseData => Self::endorse_data(task, params).map(|()| Cleanup::None), + } + } + + fn endorse_data( + task: &Task, + params: &mut UteeParams, + ) -> Result<(), TeeResult> { + use TeeParamType::{MemrefInput, MemrefOutput, None}; + + if !params.has_types([MemrefInput, MemrefOutput, None, None]) { + return Err(TeeResult::BadParameters); + } + + let (ta_data_addr, ta_data_size) = params + .get_values(0) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let ta_data_size = usize::try_from(ta_data_size).map_err(|_| TeeResult::BadParameters)?; + if ta_data_size > IDKS_ENDORSEMENT_DATA_MAX_SIZE { + return Err(TeeResult::BadParameters); + } + if ta_data_size > 0 && ta_data_addr == 0 { + return Err(TeeResult::BadParameters); + } + + let (endorsement_addr, endorsement_size) = params + .get_values(1) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let required_endorsement_size = ta_data_size + .checked_add(IDKS_ENDORSEMENT_METADATA_LEN) + .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) + .ok_or(TeeResult::BadParameters)?; + let required_endorsement_size_u64 = + u64::try_from(required_endorsement_size).map_err(|_| TeeResult::BadParameters)?; + if endorsement_size < required_endorsement_size_u64 { + params + .set_values(1, endorsement_addr, required_endorsement_size_u64) + .map_err(|_| TeeResult::BadParameters)?; + return Err(TeeResult::ShortBuffer); + } + if endorsement_addr == 0 { + return Err(TeeResult::BadParameters); + } + + let ta_data = if ta_data_size == 0 { + Vec::new().into_boxed_slice() + } else { + UserConstPtr::::from_usize( + usize::try_from(ta_data_addr).map_err(|_| TeeResult::BadParameters)?, + ) + .to_owned_slice(ta_data_size) + .ok_or(TeeResult::BadParameters)? + }; + let mut endorsement = build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn) + .ok_or(TeeResult::BadParameters)?; + let key_pair = get_identity_signing_key_pair(task.global.platform).map_err(|_| TeeResult::GenericError)?; + let signature = + endorse_data_with(&endorsement, &key_pair.private_key) + .map_err(|_| TeeResult::GenericError)?; + endorsement.extend_from_slice(&signature); + UserMutPtr::::from_usize( + usize::try_from(endorsement_addr).map_err(|_| TeeResult::BadParameters)?, + ) + .copy_from_slice(0, &endorsement) + .ok_or(TeeResult::AccessDenied)?; + params + .set_values(1, endorsement_addr, required_endorsement_size_u64) + .map_err(|_| TeeResult::BadParameters) + } +} + +fn build_endorsement_data(ta_data: &[u8], ta_uuid: &TeeUuid, ta_svn: u32) -> Option> { + // MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || DEBUG || ISOLATION_SOLUTION + let capacity = ta_data.len().checked_add(IDKS_ENDORSEMENT_METADATA_LEN)?; + let mut endorsement = Vec::with_capacity(capacity); + endorsement.extend_from_slice(IDKS_ENDORSEMENT_MAGIC); + endorsement.extend_from_slice(&IDKS_ENDORSEMENT_VERSION.to_le_bytes()); + endorsement.extend_from_slice(ta_data); + endorsement.extend_from_slice(&ta_uuid.to_le_bytes()); + endorsement.extend_from_slice(&ta_svn.to_le_bytes()); + endorsement.push(IDKS_DEBUG_FLAG); + endorsement.extend_from_slice(ISOLATION_SOLUTION); + Some(endorsement) +} + +fn endorse_data_with( + endorsement_data: &[u8], + private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], +) -> Result<[u8; IDKS_ENDORSEMENT_SIGNATURE_LEN], Errno> { + let signing_key = SigningKey::from_slice(private_key).map_err(|_| Errno::EINVAL)?; + let signature: Signature = signing_key.sign(endorsement_data); + let mut signature_bytes = [0u8; IDKS_ENDORSEMENT_SIGNATURE_LEN]; + signature_bytes.copy_from_slice(&signature.to_bytes()); + Ok(signature_bytes) +} static IDENTITY_SIGNING_KEY_PAIR: Once = Once::new(); struct IdentitySigningKeyPair { - #[allow(dead_code, reason = "retained for future IDK_S signing operations")] private_key: Zeroizing<[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]>, public_key: [u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], } @@ -162,25 +318,39 @@ mod tests { use super::*; #[test] - fn identity_signing_private_key_signs_and_verifies_message() { - use crate::syscalls::tests::init_platform; - use p384::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, + fn endorsement_signature_covers_plaintext_layout() { + use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; + + let mut private_key = [0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]; + private_key[IDENTITY_SIGNING_PRIVATE_KEY_LEN - 1] = 1; + let ta_data = b"TA public key"; + let ta_uuid = TeeUuid { + time_low: 0x1122_3344, + time_mid: 0x5566, + time_hi_and_version: 0x7788, + clock_seq_and_node: [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], }; - let message = b"IDK_S signing test message"; + let ta_svn = 7u32; + let expected_plaintext = build_endorsement_data(ta_data, &ta_uuid, ta_svn).unwrap(); - let _task = init_platform(); - let shim = crate::syscalls::tests::shim_builder().build(); - let private_key = generate_identity_signing_private_key(shim.platform()).unwrap(); - assert!(is_valid_identity_signing_private_key(&private_key)); - let signing_key = SigningKey::from_slice(&private_key[..]).unwrap(); + let signature = endorse_data_with(&expected_plaintext, &private_key).unwrap(); let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); + let signature = Signature::from_slice(&signature).unwrap(); - let signature: Signature = signing_key.sign(message); + verifying_key + .verify(&expected_plaintext, &signature) + .unwrap(); + let other_uuid = TeeUuid { + time_low: ta_uuid.time_low.wrapping_add(1), + ..ta_uuid + }; + let mut other_plaintext = Vec::from(ta_data.as_slice()); + other_plaintext.extend_from_slice(&other_uuid.to_le_bytes()); + other_plaintext.extend_from_slice(&ta_svn.to_le_bytes()); + other_plaintext.extend_from_slice(ISOLATION_SOLUTION); - verifying_key.verify(message, &signature).unwrap(); + assert!(verifying_key.verify(&other_plaintext, &signature).is_err()); } } diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index 4d96ccd24..5a2d722f3 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -304,6 +304,8 @@ impl OpteeShim { global: self.0.clone(), thread: ThreadState::new(), ta_app_id: ta_uuid, + // TODO: Populate this from trusted TA version metadata when available. + ta_svn: 0, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), @@ -1471,6 +1473,8 @@ struct Task { thread: ThreadState, /// TA UUID ta_app_id: TeeUuid, + /// TA security version number + ta_svn: u32, /// TEE cryptography state map tee_cryp_state_map: TeeCrypStateMap, /// TEE object map @@ -1638,6 +1642,7 @@ mod test_utils { global: self.clone(), thread: ThreadState::new(), ta_app_id: TeeUuid::default(), + ta_svn: 0, 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 a92bda6a2..a4bde5c64 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -4,8 +4,7 @@ //! Implementation of pseudo TAs (PTAs) which export system services as //! the functions of built-in TAs. -use crate::syscalls::Cleanup; -use crate::{Task, UserConstPtr, UserMutPtr}; +use crate::{Task, UserConstPtr, UserMutPtr, idk::IdksPta, syscalls::Cleanup}; use alloc::vec; use alloc::vec::Vec; use hmac::{Hmac, Mac}; @@ -13,27 +12,28 @@ use litebox::mm::linux::PAGE_SIZE; use litebox::platform::{DerivedKeyError, KDFParams, RawConstPointer as _, RawMutPointer as _}; use litebox::utils::TruncateExt; use litebox_common_optee::{ - HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, LdelfMapFlags, TaFlags, TeeParamType, TeeResult, TeeUuid, - UteeParams, + HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, LdelfMapFlags, PTA_DEFAULT_FLAGS, TaFlags, TeeParamType, + TeeResult, TeeUuid, UteeParams, }; use num_enum::TryFromPrimitive; use sha2::Sha256; use zeroize::{Zeroize, Zeroizing}; struct SystemPta; - /// A common interface to interact with various PTAs including the system PTA. /// /// Add new PTAs here as needed. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub(crate) enum PseudoTa { System, + Idks, } impl PseudoTa { pub(crate) fn from_uuid(uuid: &TeeUuid) -> Option { match *uuid { SystemPta::UUID => Some(Self::System), + IdksPta::UUID => Some(Self::Idks), _ => None, } } @@ -42,6 +42,7 @@ impl PseudoTa { fn open_session(self, params: &UteeParams) -> Result { match self { Self::System => SystemPta::open_session(params), + Self::Idks => IdksPta::open_session(params), } } @@ -54,6 +55,7 @@ impl PseudoTa { let _busy = task.try_set_busy(self)?; match self { Self::System => SystemPta::invoke_command(task, cmd_id, params), + Self::Idks => IdksPta::invoke_command(task, cmd_id, params), } } @@ -64,22 +66,33 @@ impl PseudoTa { ) { match self { Self::System => SystemPta::close_session(task, session_id), + Self::Idks => IdksPta::close_session(task, session_id), } } fn flags(self) -> TaFlags { match self { Self::System => SystemPta::FLAGS, + Self::Idks => IdksPta::FLAGS, } } } -const PTA_DEFAULT_FLAGS: TaFlags = TaFlags::SINGLE_INSTANCE - .union(TaFlags::MULTI_SESSION) - .union(TaFlags::INSTANCE_KEEP_ALIVE); - const MAX_PTA_SESSIONS_PER_TASK: usize = 100; +pub(crate) fn open_default_pta_session(params: &UteeParams) -> Result { + if !params.has_types([ + TeeParamType::None, + TeeParamType::None, + TeeParamType::None, + TeeParamType::None, + ]) { + return Err(TeeResult::BadParameters); + } + + crate::SessionIdPool::allocate().ok_or(TeeResult::Busy) +} + struct PtaBusyGuard<'a, Platform: crate::OpteeShimPlatform> { task: &'a Task, pta: PseudoTa, @@ -223,16 +236,7 @@ impl SystemPta { }; fn open_session(params: &UteeParams) -> Result { - if !params.has_types([ - TeeParamType::None, - TeeParamType::None, - TeeParamType::None, - TeeParamType::None, - ]) { - return Err(TeeResult::BadParameters); - } - - crate::SessionIdPool::allocate().ok_or(TeeResult::Busy) + open_default_pta_session(params) } fn close_session(_task: &Task, _session_id: u32) { From 194dce17b41184eefa098b0b2993ad7110a54e35 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Wed, 22 Jul 2026 19:56:42 +0000 Subject: [PATCH 02/13] Bind TA digest into IDK endorsement --- litebox_common_optee/src/lib.rs | 5 --- litebox_shim_optee/src/idk.rs | 51 +++++++++++++----------- litebox_shim_optee/src/lib.rs | 33 ++++++++++----- litebox_shim_optee/src/syscalls/ldelf.rs | 2 +- litebox_shim_optee/src/syscalls/pta.rs | 8 +++- 5 files changed, 57 insertions(+), 42 deletions(-) diff --git a/litebox_common_optee/src/lib.rs b/litebox_common_optee/src/lib.rs index 7d0066483..976e5ab26 100644 --- a/litebox_common_optee/src/lib.rs +++ b/litebox_common_optee/src/lib.rs @@ -739,11 +739,6 @@ bitflags::bitflags! { } } -/// Default flags for a single-instance, multi-session, keep-alive pseudo-TA. -pub const PTA_DEFAULT_FLAGS: TaFlags = TaFlags::SINGLE_INSTANCE - .union(TaFlags::MULTI_SESSION) - .union(TaFlags::INSTANCE_KEEP_ALIVE); - impl TaFlags { /// Returns true if this TA should only have one instance. pub fn is_single_instance(&self) -> bool { diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 9390a3e24..aa3d3be3d 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use crate::{NormalWorldMutPtr, Task, UserConstPtr, UserMutPtr, syscalls::Cleanup}; +use crate::syscalls::pta::PTA_DEFAULT_FLAGS; +use crate::{ + NormalWorldMutPtr, TA_DIGEST_LEN, TaDigest, Task, UserConstPtr, UserMutPtr, syscalls::Cleanup, +}; use alloc::vec::Vec; use litebox::{ mm::linux::PAGE_SIZE, @@ -9,9 +12,7 @@ use litebox::{ utils::TruncateExt, }; use litebox_common_linux::errno::Errno; -use litebox_common_optee::{ - PTA_DEFAULT_FLAGS, TaFlags, TeeParamType, TeeResult, TeeUuid, UteeParams, -}; +use litebox_common_optee::{TaFlags, TeeParamType, TeeResult, TeeUuid, UteeParams}; use num_enum::TryFromPrimitive; use p384::{ NonZeroScalar, @@ -40,6 +41,7 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() + size_of::() + size_of::() + + TA_DIGEST_LEN + size_of::() + ISOLATION_SOLUTION.len(); pub(crate) struct IdksPta; @@ -130,12 +132,13 @@ impl IdksPta { .to_owned_slice(ta_data_size) .ok_or(TeeResult::BadParameters)? }; - let mut endorsement = build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn) - .ok_or(TeeResult::BadParameters)?; - let key_pair = get_identity_signing_key_pair(task.global.platform).map_err(|_| TeeResult::GenericError)?; - let signature = - endorse_data_with(&endorsement, &key_pair.private_key) - .map_err(|_| TeeResult::GenericError)?; + let mut endorsement = + build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn, &task.ta_digest) + .ok_or(TeeResult::BadParameters)?; + let key_pair = get_identity_signing_key_pair(task.global.platform) + .map_err(|_| TeeResult::GenericError)?; + let signature = endorse_data_with(&endorsement, &key_pair.private_key) + .map_err(|_| TeeResult::GenericError)?; endorsement.extend_from_slice(&signature); UserMutPtr::::from_usize( usize::try_from(endorsement_addr).map_err(|_| TeeResult::BadParameters)?, @@ -148,8 +151,13 @@ impl IdksPta { } } -fn build_endorsement_data(ta_data: &[u8], ta_uuid: &TeeUuid, ta_svn: u32) -> Option> { - // MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || DEBUG || ISOLATION_SOLUTION +fn build_endorsement_data( + ta_data: &[u8], + ta_uuid: &TeeUuid, + ta_svn: u32, + ta_digest: &TaDigest, +) -> Option> { + // MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || DEBUG || ISOLATION_SOLUTION let capacity = ta_data.len().checked_add(IDKS_ENDORSEMENT_METADATA_LEN)?; let mut endorsement = Vec::with_capacity(capacity); endorsement.extend_from_slice(IDKS_ENDORSEMENT_MAGIC); @@ -157,6 +165,7 @@ fn build_endorsement_data(ta_data: &[u8], ta_uuid: &TeeUuid, ta_svn: u32) -> Opt endorsement.extend_from_slice(ta_data); endorsement.extend_from_slice(&ta_uuid.to_le_bytes()); endorsement.extend_from_slice(&ta_svn.to_le_bytes()); + endorsement.extend_from_slice(ta_digest); endorsement.push(IDKS_DEBUG_FLAG); endorsement.extend_from_slice(ISOLATION_SOLUTION); Some(endorsement) @@ -332,7 +341,13 @@ mod tests { }; let ta_svn = 7u32; - let expected_plaintext = build_endorsement_data(ta_data, &ta_uuid, ta_svn).unwrap(); + let ta_digest = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, + 0x3c, 0x2d, 0x1e, 0x0f, + ]; + let expected_plaintext = + build_endorsement_data(ta_data, &ta_uuid, ta_svn, &ta_digest).unwrap(); let signature = endorse_data_with(&expected_plaintext, &private_key).unwrap(); let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); @@ -342,15 +357,5 @@ mod tests { verifying_key .verify(&expected_plaintext, &signature) .unwrap(); - let other_uuid = TeeUuid { - time_low: ta_uuid.time_low.wrapping_add(1), - ..ta_uuid - }; - let mut other_plaintext = Vec::from(ta_data.as_slice()); - other_plaintext.extend_from_slice(&other_uuid.to_le_bytes()); - other_plaintext.extend_from_slice(&ta_svn.to_le_bytes()); - other_plaintext.extend_from_slice(ISOLATION_SOLUTION); - - assert!(verifying_key.verify(&other_plaintext, &signature).is_err()); } } diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index 5a2d722f3..60d7b44c6 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -28,6 +28,7 @@ use litebox_common_optee::{ TeeAttributeType, TeeCrypStateHandle, TeeHandleFlag, TeeIdentity, TeeLogin, TeeObjHandle, TeeObjectInfo, TeeObjectType, TeeOperationMode, TeeResult, TeeUuid, UteeAttribute, }; +use sha2::{Digest, Sha256}; pub mod loader; pub mod session; @@ -41,6 +42,8 @@ pub mod idk; pub use session::{OpenSessionTarget, SessionManager, SessionToken, TaInstance}; const MAX_KERNEL_BUF_SIZE: usize = 0x80_000; +pub(crate) const TA_DIGEST_LEN: usize = 32; +pub(crate) type TaDigest = [u8; TA_DIGEST_LEN]; /// Platform capabilities required by the OP-TEE shim. pub trait OpteeShimPlatform: @@ -237,11 +240,6 @@ impl GlobalState { } } - /// Get the TA flags associated with the given TA UUID. - pub(crate) fn get_ta_flags(&self, ta_uuid: &TeeUuid) -> TaFlags { - self.ta_uuid_map.get_flags(ta_uuid).unwrap_or_default() - } - /// Monotonic time elapsed since this instance was created, used as GP /// "system time" (`TEE_GetSystemTime`). /// @@ -286,7 +284,7 @@ impl Clone for OpteeShim { impl OpteeShim { /// Load the given `ldelf` binary into memory while making it ready to load the TA binary specified - /// by `ta_uuid` (and optionally `ta_bin`). + /// by `ta_uuid`. /// /// The loaded program is an *instance*: a single instance can serve many /// sessions. The active session id is supplied per entry via @@ -298,6 +296,11 @@ impl OpteeShim { ldelf_bin: &[u8], ta_uuid: TeeUuid, ) -> Result, loader::elf::ElfLoaderError> { + let (ta_flags, ta_digest) = self + .0 + .ta_uuid_map + .get_metadata(&ta_uuid) + .ok_or(loader::elf::ElfLoaderError::OpenError(Errno::ENOENT))?; let entrypoints = crate::OpteeShimEntrypoints { _not_send: core::marker::PhantomData, task: Task { @@ -306,6 +309,7 @@ impl OpteeShim { ta_app_id: ta_uuid, // TODO: Populate this from trusted TA version metadata when available. ta_svn: 0, + ta_digest, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), @@ -332,8 +336,6 @@ impl OpteeShim { } else { None }; - // Get TA flags from the stored binary - let ta_flags = entrypoints.task.global.get_ta_flags(&ta_uuid); Ok(LoadedProgram { entrypoints: Some(entrypoints), params_address, @@ -1409,6 +1411,8 @@ struct TaInfo { binary: Arc<[u8]>, /// Parsed TA flags from .ta_head section flags: TaFlags, + /// SHA-256 digest of the raw TA binary + digest: TaDigest, } /// Data structure to maintain a mapping from TA UUIDs to their binary data and flags. @@ -1434,11 +1438,13 @@ impl TaUuidMap { return false; } + let digest = Sha256::digest(&ta_bin).into(); let _replaced = self.inner.write().insert( uuid, TaInfo { binary: ta_bin, flags: ta_head.flags, + digest, }, ); true @@ -1448,9 +1454,11 @@ impl TaUuidMap { self.inner.read().get(uuid).map(|info| info.binary.clone()) } - /// Get the TA flags for a given UUID. - pub(crate) fn get_flags(&self, uuid: &TeeUuid) -> Option { - self.inner.read().get(uuid).map(|info| info.flags) + fn get_metadata(&self, uuid: &TeeUuid) -> Option<(TaFlags, TaDigest)> { + self.inner + .read() + .get(uuid) + .map(|info| (info.flags, info.digest)) } // Lazy removal of TA binaries when they are no longer needed. @@ -1475,6 +1483,8 @@ struct Task { ta_app_id: TeeUuid, /// TA security version number ta_svn: u32, + /// SHA-256 digest of the raw TA binary. + ta_digest: TaDigest, /// TEE cryptography state map tee_cryp_state_map: TeeCrypStateMap, /// TEE object map @@ -1643,6 +1653,7 @@ mod test_utils { thread: ThreadState::new(), ta_app_id: TeeUuid::default(), ta_svn: 0, + ta_digest: [0; TA_DIGEST_LEN], tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), diff --git a/litebox_shim_optee/src/syscalls/ldelf.rs b/litebox_shim_optee/src/syscalls/ldelf.rs index e79ceba61..5eb27e64d 100644 --- a/litebox_shim_optee/src/syscalls/ldelf.rs +++ b/litebox_shim_optee/src/syscalls/ldelf.rs @@ -256,7 +256,7 @@ impl Task { "sys_open_bin" ); - if self.global.get_ta_bin(&ta_uuid).is_none() { + if ta_uuid != self.ta_app_id && self.global.get_ta_bin(&ta_uuid).is_none() { return Err(TeeResult::ItemNotFound); } let new_handle = self.ta_handle_map.insert(ta_uuid); diff --git a/litebox_shim_optee/src/syscalls/pta.rs b/litebox_shim_optee/src/syscalls/pta.rs index a4bde5c64..f55f44bb7 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -12,8 +12,8 @@ use litebox::mm::linux::PAGE_SIZE; use litebox::platform::{DerivedKeyError, KDFParams, RawConstPointer as _, RawMutPointer as _}; use litebox::utils::TruncateExt; use litebox_common_optee::{ - HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, LdelfMapFlags, PTA_DEFAULT_FLAGS, TaFlags, TeeParamType, - TeeResult, TeeUuid, UteeParams, + HUK_SUBKEY_MAX_LEN, HukSubkeyUsage, LdelfMapFlags, TaFlags, TeeParamType, TeeResult, TeeUuid, + UteeParams, }; use num_enum::TryFromPrimitive; use sha2::Sha256; @@ -78,6 +78,10 @@ impl PseudoTa { } } +pub(crate) const PTA_DEFAULT_FLAGS: TaFlags = TaFlags::SINGLE_INSTANCE + .union(TaFlags::MULTI_SESSION) + .union(TaFlags::INSTANCE_KEEP_ALIVE); + const MAX_PTA_SESSIONS_PER_TASK: usize = 100; pub(crate) fn open_default_pta_session(params: &UteeParams) -> Result { From 93962382a3cfdc6bc90b41ad46d1ca47a0a00617 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 14 Aug 2026 00:49:09 +0000 Subject: [PATCH 03/13] Add KeyIso claim structure --- litebox_shim_optee/src/idk.rs | 469 +++++++++++++++++++++++++++++++++- 1 file changed, 460 insertions(+), 9 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index aa3d3be3d..4934a5019 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -20,6 +20,10 @@ use p384::{ elliptic_curve::sec1::ToEncodedPoint, }; use spin::Once; +use zerocopy::{ + Immutable, IntoBytes, + byteorder::{LittleEndian, U32}, +}; use zeroize::Zeroizing; const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; @@ -29,6 +33,7 @@ const KEY_VARIANT_MASK: u64 = 0xff; const KEY_ALGORITHM_VALUE_MASK: u64 = KEY_ALGORITHM_MASK | KEY_VARIANT_MASK; const MAX_KEYGEN_ATTEMPT: usize = 256; const IDKS_ENDORSEMENT_DATA_MAX_SIZE: usize = 8 * 1024 * 1024; +const IDKS_NONCE_MAX_SIZE: usize = 64; const IDKS_ENDORSEMENT_MAGIC: &[u8; 4] = b"IDKS"; const IDKS_ENDORSEMENT_VERSION: u32 = 1; #[cfg(not(feature = "idks-production"))] @@ -36,6 +41,13 @@ const IDKS_DEBUG_FLAG: u8 = 1; #[cfg(feature = "idks-production")] const IDKS_DEBUG_FLAG: u8 = 0; const ISOLATION_SOLUTION: &[u8] = b"LVBS"; +const TRUSTLET_PROPERTY_UUID: &[u8] = b"TRUSTLET_PROPERTY_UUID"; +const TRUSTLET_PROPERTY_SVN: &[u8] = b"TRUSTLET_PROPERTY_SVN"; +const TRUSTLET_PROPERTY_TA_DIGEST: &[u8] = b"TRUSTLET_PROPERTY_TA_DIGEST"; +const TRUSTLET_PROPERTY_DEBUGGED: &[u8] = b"TRUSTLET_PROPERTY_DEBUGGED"; +const TRUSTLET_PROPERTY_ISOLATION_SOLUTION: &[u8] = b"TRUSTLET_PROPERTY_ISOLATION_SOLUTION"; +const KEYISO_SIGNATURE_ALGORITHM_ID: &[u8] = b"ECDSA_P384"; +const KEYISO_SIGNATURE_HASH_ALGORITHM: &[u8] = b"SHA384"; pub(crate) const IDKS_ENDORSEMENT_SIGNATURE_LEN: usize = 96; const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() @@ -46,6 +58,107 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + ISOLATION_SOLUTION.len(); pub(crate) struct IdksPta; +type LeU32 = U32; + +const _: () = assert!(size_of::() == 16); + +#[derive(Clone, Copy)] +#[repr(u32)] +enum KeyIsoMagic { + AttestationStatement = 0x4d53_414b, + KeyAttestationHeader = 0x4841_4b4b, + TrustletReport = 0x4d52_544b, + TrustletInformation = 0x4954_414b, + TrustletProperty = 0x5054_414b, + SignatureParams = 0x5053_414b, + EccSignatureParams = 0x5045_414b, + Signature = 0x5353_414b, +} + +#[derive(Clone, Copy)] +#[repr(u32)] +enum KeyIsoVersion { + V1 = 1, +} + +#[derive(Clone, Copy)] +#[repr(u32)] +enum KeyIsoClaimType { + KeyAttestation = 0x8000_0001, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationStatement { + magic: LeU32, + version: LeU32, + claim_type: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoKeyAttestationHeader { + magic: LeU32, + version: LeU32, + cb_ta_data: LeU32, + cb_nonce: LeU32, + cb_report: LeU32, + c_signature_parameters: LeU32, + c_signatures: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoTrustletReport { + magic: LeU32, + report_size: LeU32, + offset_to_var_data: LeU32, + version: LeU32, + cb_trustlet_information: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationTrustletInformation { + magic: LeU32, + version: LeU32, + c_properties: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationTrustletProperty { + magic: LeU32, + version: LeU32, + cb_property_name: LeU32, + cb_property: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationSignatureParams { + magic: LeU32, + version: LeU32, + cb_alg_id: LeU32, + cb_alg_params: LeU32, + cb_hash_alg: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationEccSignatureParams { + magic: LeU32, + version: LeU32, +} + +#[derive(Immutable, IntoBytes)] +#[repr(C)] +struct KeyIsoAttestationSignature { + magic: LeU32, + version: LeU32, + cb_signature: LeU32, +} + #[derive(Clone, Copy, TryFromPrimitive)] #[repr(u32)] pub(crate) enum IdksCommandId { @@ -87,7 +200,7 @@ impl IdksPta { ) -> Result<(), TeeResult> { use TeeParamType::{MemrefInput, MemrefOutput, None}; - if !params.has_types([MemrefInput, MemrefOutput, None, None]) { + if !params.has_types([MemrefInput, MemrefInput, MemrefOutput, None]) { return Err(TeeResult::BadParameters); } @@ -103,19 +216,31 @@ impl IdksPta { return Err(TeeResult::BadParameters); } - let (endorsement_addr, endorsement_size) = params + let (nonce_addr, nonce_size) = params .get_values(1) .map_err(|_| TeeResult::BadParameters)? .ok_or(TeeResult::BadParameters)?; - let required_endorsement_size = ta_data_size - .checked_add(IDKS_ENDORSEMENT_METADATA_LEN) + let nonce_size = usize::try_from(nonce_size).map_err(|_| TeeResult::BadParameters)?; + if nonce_size > IDKS_NONCE_MAX_SIZE { + return Err(TeeResult::BadParameters); + } + if nonce_size > 0 && nonce_addr == 0 { + return Err(TeeResult::BadParameters); + } + + let (endorsement_addr, endorsement_size) = params + .get_values(2) + .map_err(|_| TeeResult::BadParameters)? + .ok_or(TeeResult::BadParameters)?; + let required_endorsement_size = KeyIsoClaimLayout::new(ta_data_size, nonce_size) + .map(|layout| layout.claim) .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) .ok_or(TeeResult::BadParameters)?; let required_endorsement_size_u64 = u64::try_from(required_endorsement_size).map_err(|_| TeeResult::BadParameters)?; if endorsement_size < required_endorsement_size_u64 { params - .set_values(1, endorsement_addr, required_endorsement_size_u64) + .set_values(2, endorsement_addr, required_endorsement_size_u64) .map_err(|_| TeeResult::BadParameters)?; return Err(TeeResult::ShortBuffer); } @@ -132,25 +257,44 @@ impl IdksPta { .to_owned_slice(ta_data_size) .ok_or(TeeResult::BadParameters)? }; - let mut endorsement = - build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn, &task.ta_digest) - .ok_or(TeeResult::BadParameters)?; + let nonce = if nonce_size == 0 { + Vec::new().into_boxed_slice() + } else { + UserConstPtr::::from_usize( + usize::try_from(nonce_addr).map_err(|_| TeeResult::BadParameters)?, + ) + .to_owned_slice(nonce_size) + .ok_or(TeeResult::BadParameters)? + }; + let mut endorsement = build_keyiso_claim( + &ta_data, + &nonce, + &task.ta_app_id, + task.ta_svn, + &task.ta_digest, + ) + .ok_or(TeeResult::GenericError)?; let key_pair = get_identity_signing_key_pair(task.global.platform) .map_err(|_| TeeResult::GenericError)?; let signature = endorse_data_with(&endorsement, &key_pair.private_key) .map_err(|_| TeeResult::GenericError)?; endorsement.extend_from_slice(&signature); + if endorsement.len() != required_endorsement_size { + return Err(TeeResult::GenericError); + } UserMutPtr::::from_usize( usize::try_from(endorsement_addr).map_err(|_| TeeResult::BadParameters)?, ) .copy_from_slice(0, &endorsement) .ok_or(TeeResult::AccessDenied)?; params - .set_values(1, endorsement_addr, required_endorsement_size_u64) + .set_values(2, endorsement_addr, required_endorsement_size_u64) .map_err(|_| TeeResult::BadParameters) } } +// TODO: drop this if we decide to use the KeyIso claim structure +#[allow(dead_code)] fn build_endorsement_data( ta_data: &[u8], ta_uuid: &TeeUuid, @@ -171,6 +315,167 @@ fn build_endorsement_data( Some(endorsement) } +struct KeyIsoClaimLayout { + trustlet_information: usize, + report: usize, + claim: usize, +} + +impl KeyIsoClaimLayout { + fn new(ta_data_len: usize, nonce_len: usize) -> Option { + let property_names_size = TRUSTLET_PROPERTY_UUID + .len() + .checked_add(TRUSTLET_PROPERTY_SVN.len())? + .checked_add(TRUSTLET_PROPERTY_TA_DIGEST.len())? + .checked_add(TRUSTLET_PROPERTY_DEBUGGED.len())? + .checked_add(TRUSTLET_PROPERTY_ISOLATION_SOLUTION.len())?; + let property_values_size = size_of::() + .checked_add(size_of::())? + .checked_add(TA_DIGEST_LEN)? + .checked_add(size_of::())? + .checked_add(ISOLATION_SOLUTION.len())?; + let properties_size = 5usize + .checked_mul(size_of::())? + .checked_add(property_names_size)? + .checked_add(property_values_size)?; + let trustlet_information_size = + size_of::().checked_add(properties_size)?; + let report_size = + size_of::().checked_add(trustlet_information_size)?; + let claim_size = size_of::() + .checked_add(size_of::())? + .checked_add(ta_data_len)? + .checked_add(nonce_len)? + .checked_add(report_size)? + .checked_add(size_of::())? + .checked_add(KEYISO_SIGNATURE_ALGORITHM_ID.len())? + .checked_add(size_of::())? + .checked_add(KEYISO_SIGNATURE_HASH_ALGORITHM.len())? + .checked_add(size_of::())?; + Some(Self { + trustlet_information: trustlet_information_size, + report: report_size, + claim: claim_size, + }) + } +} + +/// Serializes the KeyIso claim prefix covered by the identity-key signature. +/// +/// Variable data immediately follows its C-compatible zerocopy header. The signature header is +/// included in this returned prefix; only the signature bytes are appended after signing. +fn build_keyiso_claim( + ta_data: &[u8], + nonce: &[u8], + ta_uuid: &TeeUuid, + ta_svn: u32, + ta_digest: &TaDigest, +) -> Option> { + let uuid = ta_uuid.to_le_bytes(); + let svn = ta_svn.to_le_bytes(); + let debugged = [IDKS_DEBUG_FLAG]; + let properties: [(&[u8], &[u8]); 5] = [ + (TRUSTLET_PROPERTY_UUID, &uuid), + (TRUSTLET_PROPERTY_SVN, &svn), + (TRUSTLET_PROPERTY_TA_DIGEST, ta_digest), + (TRUSTLET_PROPERTY_DEBUGGED, &debugged), + (TRUSTLET_PROPERTY_ISOLATION_SOLUTION, ISOLATION_SOLUTION), + ]; + let layout = KeyIsoClaimLayout::new(ta_data.len(), nonce.len())?; + + let mut endorsement = Vec::with_capacity(layout.claim); + endorsement.extend_from_slice( + KeyIsoAttestationStatement { + magic: LeU32::new(KeyIsoMagic::AttestationStatement as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + claim_type: LeU32::new(KeyIsoClaimType::KeyAttestation as u32), + } + .as_bytes(), + ); + endorsement.extend_from_slice( + KeyIsoKeyAttestationHeader { + magic: LeU32::new(KeyIsoMagic::KeyAttestationHeader as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + cb_ta_data: LeU32::new(ta_data.len().try_into().ok()?), + cb_nonce: LeU32::new(nonce.len().try_into().ok()?), + cb_report: LeU32::new(layout.report.try_into().ok()?), + c_signature_parameters: LeU32::new(1), + c_signatures: LeU32::new(1), + } + .as_bytes(), + ); + endorsement.extend_from_slice(ta_data); + endorsement.extend_from_slice(nonce); + endorsement.extend_from_slice( + KeyIsoTrustletReport { + magic: LeU32::new(KeyIsoMagic::TrustletReport as u32), + report_size: LeU32::new(layout.report.try_into().ok()?), + offset_to_var_data: LeU32::new(size_of::().try_into().ok()?), + version: LeU32::new(KeyIsoVersion::V1 as u32), + cb_trustlet_information: LeU32::new(layout.trustlet_information.try_into().ok()?), + } + .as_bytes(), + ); + endorsement.extend_from_slice( + KeyIsoAttestationTrustletInformation { + magic: LeU32::new(KeyIsoMagic::TrustletInformation as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + c_properties: LeU32::new(properties.len().try_into().ok()?), + } + .as_bytes(), + ); + + for (name, value) in properties { + endorsement.extend_from_slice( + KeyIsoAttestationTrustletProperty { + magic: LeU32::new(KeyIsoMagic::TrustletProperty as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + cb_property_name: LeU32::new(name.len().try_into().ok()?), + cb_property: LeU32::new(value.len().try_into().ok()?), + } + .as_bytes(), + ); + endorsement.extend_from_slice(name); + endorsement.extend_from_slice(value); + } + + endorsement.extend_from_slice( + KeyIsoAttestationSignatureParams { + magic: LeU32::new(KeyIsoMagic::SignatureParams as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + cb_alg_id: LeU32::new(KEYISO_SIGNATURE_ALGORITHM_ID.len().try_into().ok()?), + cb_alg_params: LeU32::new( + size_of::() + .try_into() + .ok()?, + ), + cb_hash_alg: LeU32::new(KEYISO_SIGNATURE_HASH_ALGORITHM.len().try_into().ok()?), + } + .as_bytes(), + ); + endorsement.extend_from_slice(KEYISO_SIGNATURE_ALGORITHM_ID); + endorsement.extend_from_slice( + KeyIsoAttestationEccSignatureParams { + magic: LeU32::new(KeyIsoMagic::EccSignatureParams as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + } + .as_bytes(), + ); + endorsement.extend_from_slice(KEYISO_SIGNATURE_HASH_ALGORITHM); + endorsement.extend_from_slice( + KeyIsoAttestationSignature { + magic: LeU32::new(KeyIsoMagic::Signature as u32), + version: LeU32::new(KeyIsoVersion::V1 as u32), + cb_signature: LeU32::new(IDKS_ENDORSEMENT_SIGNATURE_LEN.try_into().ok()?), + } + .as_bytes(), + ); + if endorsement.len() != layout.claim { + return None; + } + Some(endorsement) +} + fn endorse_data_with( endorsement_data: &[u8], private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], @@ -326,6 +631,152 @@ fn identity_signing_public_key_from_private_key( mod tests { use super::*; + fn read_u32(bytes: &[u8], offset: &mut usize) -> u32 { + let value = u32::from_le_bytes(bytes[*offset..*offset + 4].try_into().unwrap()); + *offset += 4; + value + } + + fn assert_property(bytes: &[u8], offset: &mut usize, name: &[u8], value: &[u8]) { + assert_eq!(read_u32(bytes, offset), 0x5054_414b); + assert_eq!(read_u32(bytes, offset), 1); + assert_eq!(read_u32(bytes, offset), u32::try_from(name.len()).unwrap()); + assert_eq!(read_u32(bytes, offset), u32::try_from(value.len()).unwrap()); + assert_eq!(&bytes[*offset..*offset + name.len()], name); + *offset += name.len(); + assert_eq!(&bytes[*offset..*offset + value.len()], value); + *offset += value.len(); + } + + #[test] + fn keyiso_claim_has_expected_layout() { + let ta_data = b"TA data"; + let nonce = [0x5a; 32]; + let ta_uuid = TeeUuid { + time_low: 0x1122_3344, + time_mid: 0x5566, + time_hi_and_version: 0x7788, + clock_seq_and_node: [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], + }; + let ta_svn = 7u32; + let ta_digest = [0xa5; TA_DIGEST_LEN]; + + let endorsement = + build_keyiso_claim(ta_data, &nonce, &ta_uuid, ta_svn, &ta_digest).unwrap(); + let mut offset = 0; + + assert_eq!(read_u32(&endorsement, &mut offset), 0x4d53_414b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(read_u32(&endorsement, &mut offset), 0x8000_0001); + + assert_eq!(read_u32(&endorsement, &mut offset), 0x4841_4b4b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!( + read_u32(&endorsement, &mut offset), + u32::try_from(ta_data.len()).unwrap() + ); + assert_eq!( + read_u32(&endorsement, &mut offset), + u32::try_from(nonce.len()).unwrap() + ); + let report_size = read_u32(&endorsement, &mut offset) as usize; + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(&endorsement[offset..offset + ta_data.len()], ta_data); + offset += ta_data.len(); + assert_eq!(&endorsement[offset..offset + nonce.len()], nonce); + offset += nonce.len(); + + let report_start = offset; + assert_eq!(read_u32(&endorsement, &mut offset), 0x4d52_544b); + assert_eq!(read_u32(&endorsement, &mut offset) as usize, report_size); + assert_eq!(read_u32(&endorsement, &mut offset), 20); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + let trustlet_information_size = read_u32(&endorsement, &mut offset) as usize; + + let trustlet_information_start = offset; + assert_eq!(read_u32(&endorsement, &mut offset), 0x4954_414b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(read_u32(&endorsement, &mut offset), 5); + assert_property( + &endorsement, + &mut offset, + b"TRUSTLET_PROPERTY_UUID", + &ta_uuid.to_le_bytes(), + ); + assert_property( + &endorsement, + &mut offset, + b"TRUSTLET_PROPERTY_SVN", + &ta_svn.to_le_bytes(), + ); + assert_property( + &endorsement, + &mut offset, + b"TRUSTLET_PROPERTY_TA_DIGEST", + &ta_digest, + ); + assert_property( + &endorsement, + &mut offset, + b"TRUSTLET_PROPERTY_DEBUGGED", + &[IDKS_DEBUG_FLAG], + ); + assert_property( + &endorsement, + &mut offset, + b"TRUSTLET_PROPERTY_ISOLATION_SOLUTION", + b"LVBS", + ); + assert_eq!( + offset - trustlet_information_start, + trustlet_information_size + ); + assert_eq!(offset - report_start, report_size); + + assert_eq!(read_u32(&endorsement, &mut offset), 0x5053_414b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(read_u32(&endorsement, &mut offset), 10); + assert_eq!(read_u32(&endorsement, &mut offset), 8); + assert_eq!(read_u32(&endorsement, &mut offset), 6); + assert_eq!(&endorsement[offset..offset + 10], b"ECDSA_P384"); + offset += 10; + assert_eq!(read_u32(&endorsement, &mut offset), 0x5045_414b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(&endorsement[offset..offset + 6], b"SHA384"); + offset += 6; + + assert_eq!(read_u32(&endorsement, &mut offset), 0x5353_414b); + assert_eq!(read_u32(&endorsement, &mut offset), 1); + assert_eq!(read_u32(&endorsement, &mut offset), 96); + + assert_eq!(offset, endorsement.len()); + } + + #[test] + fn keyiso_signature_covers_entire_claim_prefix() { + use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; + + let mut private_key = [0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]; + private_key[IDENTITY_SIGNING_PRIVATE_KEY_LEN - 1] = 1; + let nonce = [0x5a; 32]; + let ta_uuid = TeeUuid::NIL; + let ta_digest = [0xa5; TA_DIGEST_LEN]; + let mut endorsement = + build_keyiso_claim(b"TA data", &nonce, &ta_uuid, 7, &ta_digest).unwrap(); + let signed_len = endorsement.len(); + let signature = endorse_data_with(&endorsement, &private_key).unwrap(); + + endorsement.extend_from_slice(&signature); + + let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); + let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); + let parsed_signature = Signature::from_slice(&endorsement[signed_len..]).unwrap(); + verifying_key + .verify(&endorsement[..signed_len], &parsed_signature) + .unwrap(); + } + #[test] fn endorsement_signature_covers_plaintext_layout() { use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; From 51b7afa1c73f8cdaf9398357ea78fd5a857dd32f Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 14 Aug 2026 15:03:51 +0000 Subject: [PATCH 04/13] optional TaDigest --- litebox_shim_optee/src/idk.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 4934a5019..b8ea83b5a 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -232,10 +232,11 @@ impl IdksPta { .get_values(2) .map_err(|_| TeeResult::BadParameters)? .ok_or(TeeResult::BadParameters)?; - let required_endorsement_size = KeyIsoClaimLayout::new(ta_data_size, nonce_size) - .map(|layout| layout.claim) - .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) - .ok_or(TeeResult::BadParameters)?; + let required_endorsement_size = + KeyIsoClaimLayout::new(ta_data_size, nonce_size, TA_DIGEST_LEN) + .map(|layout| layout.claim) + .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) + .ok_or(TeeResult::BadParameters)?; let required_endorsement_size_u64 = u64::try_from(required_endorsement_size).map_err(|_| TeeResult::BadParameters)?; if endorsement_size < required_endorsement_size_u64 { @@ -271,7 +272,7 @@ impl IdksPta { &nonce, &task.ta_app_id, task.ta_svn, - &task.ta_digest, + Some(&task.ta_digest), ) .ok_or(TeeResult::GenericError)?; let key_pair = get_identity_signing_key_pair(task.global.platform) @@ -322,7 +323,7 @@ struct KeyIsoClaimLayout { } impl KeyIsoClaimLayout { - fn new(ta_data_len: usize, nonce_len: usize) -> Option { + fn new(ta_data_len: usize, nonce_len: usize, ta_digest_len: usize) -> Option { let property_names_size = TRUSTLET_PROPERTY_UUID .len() .checked_add(TRUSTLET_PROPERTY_SVN.len())? @@ -331,7 +332,7 @@ impl KeyIsoClaimLayout { .checked_add(TRUSTLET_PROPERTY_ISOLATION_SOLUTION.len())?; let property_values_size = size_of::() .checked_add(size_of::())? - .checked_add(TA_DIGEST_LEN)? + .checked_add(ta_digest_len)? .checked_add(size_of::())? .checked_add(ISOLATION_SOLUTION.len())?; let properties_size = 5usize @@ -369,7 +370,7 @@ fn build_keyiso_claim( nonce: &[u8], ta_uuid: &TeeUuid, ta_svn: u32, - ta_digest: &TaDigest, + ta_digest: Option<&TaDigest>, ) -> Option> { let uuid = ta_uuid.to_le_bytes(); let svn = ta_svn.to_le_bytes(); @@ -377,11 +378,18 @@ fn build_keyiso_claim( let properties: [(&[u8], &[u8]); 5] = [ (TRUSTLET_PROPERTY_UUID, &uuid), (TRUSTLET_PROPERTY_SVN, &svn), - (TRUSTLET_PROPERTY_TA_DIGEST, ta_digest), + ( + TRUSTLET_PROPERTY_TA_DIGEST, + ta_digest.map_or(&[][..], TaDigest::as_slice), + ), (TRUSTLET_PROPERTY_DEBUGGED, &debugged), (TRUSTLET_PROPERTY_ISOLATION_SOLUTION, ISOLATION_SOLUTION), ]; - let layout = KeyIsoClaimLayout::new(ta_data.len(), nonce.len())?; + let layout = KeyIsoClaimLayout::new( + ta_data.len(), + nonce.len(), + ta_digest.map_or(0, |_| TA_DIGEST_LEN), + )?; let mut endorsement = Vec::with_capacity(layout.claim); endorsement.extend_from_slice( @@ -662,7 +670,7 @@ mod tests { let ta_digest = [0xa5; TA_DIGEST_LEN]; let endorsement = - build_keyiso_claim(ta_data, &nonce, &ta_uuid, ta_svn, &ta_digest).unwrap(); + build_keyiso_claim(ta_data, &nonce, &ta_uuid, ta_svn, Some(&ta_digest)).unwrap(); let mut offset = 0; assert_eq!(read_u32(&endorsement, &mut offset), 0x4d53_414b); @@ -763,7 +771,7 @@ mod tests { let ta_uuid = TeeUuid::NIL; let ta_digest = [0xa5; TA_DIGEST_LEN]; let mut endorsement = - build_keyiso_claim(b"TA data", &nonce, &ta_uuid, 7, &ta_digest).unwrap(); + build_keyiso_claim(b"TA data", &nonce, &ta_uuid, 7, Some(&ta_digest)).unwrap(); let signed_len = endorsement.len(); let signature = endorse_data_with(&endorsement, &private_key).unwrap(); From 4443bb6554ae5213fa305a8419016f45b8cd980c Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 14 Aug 2026 15:43:58 +0000 Subject: [PATCH 05/13] simplification --- litebox_shim_optee/src/idk.rs | 55 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index b8ea83b5a..e45a8418a 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -323,36 +323,37 @@ struct KeyIsoClaimLayout { } impl KeyIsoClaimLayout { + const PROPERTY_NAMES_SIZE: usize = TRUSTLET_PROPERTY_UUID.len() + + TRUSTLET_PROPERTY_SVN.len() + + TRUSTLET_PROPERTY_TA_DIGEST.len() + + TRUSTLET_PROPERTY_DEBUGGED.len() + + TRUSTLET_PROPERTY_ISOLATION_SOLUTION.len(); + const PROPERTIES_FIXED_SIZE: usize = 5 * size_of::() + + Self::PROPERTY_NAMES_SIZE + + size_of::() + + size_of::() + + size_of::() + + ISOLATION_SOLUTION.len(); + const TRUSTLET_INFORMATION_FIXED_SIZE: usize = + size_of::() + Self::PROPERTIES_FIXED_SIZE; + const REPORT_FIXED_SIZE: usize = + size_of::() + Self::TRUSTLET_INFORMATION_FIXED_SIZE; + const CLAIM_FIXED_SIZE: usize = size_of::() + + size_of::() + + Self::REPORT_FIXED_SIZE + + size_of::() + + KEYISO_SIGNATURE_ALGORITHM_ID.len() + + size_of::() + + KEYISO_SIGNATURE_HASH_ALGORITHM.len() + + size_of::(); + fn new(ta_data_len: usize, nonce_len: usize, ta_digest_len: usize) -> Option { - let property_names_size = TRUSTLET_PROPERTY_UUID - .len() - .checked_add(TRUSTLET_PROPERTY_SVN.len())? - .checked_add(TRUSTLET_PROPERTY_TA_DIGEST.len())? - .checked_add(TRUSTLET_PROPERTY_DEBUGGED.len())? - .checked_add(TRUSTLET_PROPERTY_ISOLATION_SOLUTION.len())?; - let property_values_size = size_of::() - .checked_add(size_of::())? - .checked_add(ta_digest_len)? - .checked_add(size_of::())? - .checked_add(ISOLATION_SOLUTION.len())?; - let properties_size = 5usize - .checked_mul(size_of::())? - .checked_add(property_names_size)? - .checked_add(property_values_size)?; - let trustlet_information_size = - size_of::().checked_add(properties_size)?; - let report_size = - size_of::().checked_add(trustlet_information_size)?; - let claim_size = size_of::() - .checked_add(size_of::())? + let trustlet_information_size = Self::TRUSTLET_INFORMATION_FIXED_SIZE + ta_digest_len; + let report_size = Self::REPORT_FIXED_SIZE + ta_digest_len; + let claim_size = Self::CLAIM_FIXED_SIZE .checked_add(ta_data_len)? .checked_add(nonce_len)? - .checked_add(report_size)? - .checked_add(size_of::())? - .checked_add(KEYISO_SIGNATURE_ALGORITHM_ID.len())? - .checked_add(size_of::())? - .checked_add(KEYISO_SIGNATURE_HASH_ALGORITHM.len())? - .checked_add(size_of::())?; + .checked_add(ta_digest_len)?; Some(Self { trustlet_information: trustlet_information_size, report: report_size, From f857d05f9a2e469352c493803d087d1495c245e6 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 17:26:07 +0000 Subject: [PATCH 06/13] Drop KeyIso claim structure in favor of flat endorsements --- litebox_shim_optee/src/idk.rs | 480 +--------------------------------- 1 file changed, 10 insertions(+), 470 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index e45a8418a..aa3d3be3d 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -20,10 +20,6 @@ use p384::{ elliptic_curve::sec1::ToEncodedPoint, }; use spin::Once; -use zerocopy::{ - Immutable, IntoBytes, - byteorder::{LittleEndian, U32}, -}; use zeroize::Zeroizing; const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; @@ -33,7 +29,6 @@ const KEY_VARIANT_MASK: u64 = 0xff; const KEY_ALGORITHM_VALUE_MASK: u64 = KEY_ALGORITHM_MASK | KEY_VARIANT_MASK; const MAX_KEYGEN_ATTEMPT: usize = 256; const IDKS_ENDORSEMENT_DATA_MAX_SIZE: usize = 8 * 1024 * 1024; -const IDKS_NONCE_MAX_SIZE: usize = 64; const IDKS_ENDORSEMENT_MAGIC: &[u8; 4] = b"IDKS"; const IDKS_ENDORSEMENT_VERSION: u32 = 1; #[cfg(not(feature = "idks-production"))] @@ -41,13 +36,6 @@ const IDKS_DEBUG_FLAG: u8 = 1; #[cfg(feature = "idks-production")] const IDKS_DEBUG_FLAG: u8 = 0; const ISOLATION_SOLUTION: &[u8] = b"LVBS"; -const TRUSTLET_PROPERTY_UUID: &[u8] = b"TRUSTLET_PROPERTY_UUID"; -const TRUSTLET_PROPERTY_SVN: &[u8] = b"TRUSTLET_PROPERTY_SVN"; -const TRUSTLET_PROPERTY_TA_DIGEST: &[u8] = b"TRUSTLET_PROPERTY_TA_DIGEST"; -const TRUSTLET_PROPERTY_DEBUGGED: &[u8] = b"TRUSTLET_PROPERTY_DEBUGGED"; -const TRUSTLET_PROPERTY_ISOLATION_SOLUTION: &[u8] = b"TRUSTLET_PROPERTY_ISOLATION_SOLUTION"; -const KEYISO_SIGNATURE_ALGORITHM_ID: &[u8] = b"ECDSA_P384"; -const KEYISO_SIGNATURE_HASH_ALGORITHM: &[u8] = b"SHA384"; pub(crate) const IDKS_ENDORSEMENT_SIGNATURE_LEN: usize = 96; const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() @@ -58,107 +46,6 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + ISOLATION_SOLUTION.len(); pub(crate) struct IdksPta; -type LeU32 = U32; - -const _: () = assert!(size_of::() == 16); - -#[derive(Clone, Copy)] -#[repr(u32)] -enum KeyIsoMagic { - AttestationStatement = 0x4d53_414b, - KeyAttestationHeader = 0x4841_4b4b, - TrustletReport = 0x4d52_544b, - TrustletInformation = 0x4954_414b, - TrustletProperty = 0x5054_414b, - SignatureParams = 0x5053_414b, - EccSignatureParams = 0x5045_414b, - Signature = 0x5353_414b, -} - -#[derive(Clone, Copy)] -#[repr(u32)] -enum KeyIsoVersion { - V1 = 1, -} - -#[derive(Clone, Copy)] -#[repr(u32)] -enum KeyIsoClaimType { - KeyAttestation = 0x8000_0001, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationStatement { - magic: LeU32, - version: LeU32, - claim_type: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoKeyAttestationHeader { - magic: LeU32, - version: LeU32, - cb_ta_data: LeU32, - cb_nonce: LeU32, - cb_report: LeU32, - c_signature_parameters: LeU32, - c_signatures: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoTrustletReport { - magic: LeU32, - report_size: LeU32, - offset_to_var_data: LeU32, - version: LeU32, - cb_trustlet_information: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationTrustletInformation { - magic: LeU32, - version: LeU32, - c_properties: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationTrustletProperty { - magic: LeU32, - version: LeU32, - cb_property_name: LeU32, - cb_property: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationSignatureParams { - magic: LeU32, - version: LeU32, - cb_alg_id: LeU32, - cb_alg_params: LeU32, - cb_hash_alg: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationEccSignatureParams { - magic: LeU32, - version: LeU32, -} - -#[derive(Immutable, IntoBytes)] -#[repr(C)] -struct KeyIsoAttestationSignature { - magic: LeU32, - version: LeU32, - cb_signature: LeU32, -} - #[derive(Clone, Copy, TryFromPrimitive)] #[repr(u32)] pub(crate) enum IdksCommandId { @@ -200,7 +87,7 @@ impl IdksPta { ) -> Result<(), TeeResult> { use TeeParamType::{MemrefInput, MemrefOutput, None}; - if !params.has_types([MemrefInput, MemrefInput, MemrefOutput, None]) { + if !params.has_types([MemrefInput, MemrefOutput, None, None]) { return Err(TeeResult::BadParameters); } @@ -216,32 +103,19 @@ impl IdksPta { return Err(TeeResult::BadParameters); } - let (nonce_addr, nonce_size) = params + let (endorsement_addr, endorsement_size) = params .get_values(1) .map_err(|_| TeeResult::BadParameters)? .ok_or(TeeResult::BadParameters)?; - let nonce_size = usize::try_from(nonce_size).map_err(|_| TeeResult::BadParameters)?; - if nonce_size > IDKS_NONCE_MAX_SIZE { - return Err(TeeResult::BadParameters); - } - if nonce_size > 0 && nonce_addr == 0 { - return Err(TeeResult::BadParameters); - } - - let (endorsement_addr, endorsement_size) = params - .get_values(2) - .map_err(|_| TeeResult::BadParameters)? + let required_endorsement_size = ta_data_size + .checked_add(IDKS_ENDORSEMENT_METADATA_LEN) + .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) .ok_or(TeeResult::BadParameters)?; - let required_endorsement_size = - KeyIsoClaimLayout::new(ta_data_size, nonce_size, TA_DIGEST_LEN) - .map(|layout| layout.claim) - .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) - .ok_or(TeeResult::BadParameters)?; let required_endorsement_size_u64 = u64::try_from(required_endorsement_size).map_err(|_| TeeResult::BadParameters)?; if endorsement_size < required_endorsement_size_u64 { params - .set_values(2, endorsement_addr, required_endorsement_size_u64) + .set_values(1, endorsement_addr, required_endorsement_size_u64) .map_err(|_| TeeResult::BadParameters)?; return Err(TeeResult::ShortBuffer); } @@ -258,44 +132,25 @@ impl IdksPta { .to_owned_slice(ta_data_size) .ok_or(TeeResult::BadParameters)? }; - let nonce = if nonce_size == 0 { - Vec::new().into_boxed_slice() - } else { - UserConstPtr::::from_usize( - usize::try_from(nonce_addr).map_err(|_| TeeResult::BadParameters)?, - ) - .to_owned_slice(nonce_size) - .ok_or(TeeResult::BadParameters)? - }; - let mut endorsement = build_keyiso_claim( - &ta_data, - &nonce, - &task.ta_app_id, - task.ta_svn, - Some(&task.ta_digest), - ) - .ok_or(TeeResult::GenericError)?; + let mut endorsement = + build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn, &task.ta_digest) + .ok_or(TeeResult::BadParameters)?; let key_pair = get_identity_signing_key_pair(task.global.platform) .map_err(|_| TeeResult::GenericError)?; let signature = endorse_data_with(&endorsement, &key_pair.private_key) .map_err(|_| TeeResult::GenericError)?; endorsement.extend_from_slice(&signature); - if endorsement.len() != required_endorsement_size { - return Err(TeeResult::GenericError); - } UserMutPtr::::from_usize( usize::try_from(endorsement_addr).map_err(|_| TeeResult::BadParameters)?, ) .copy_from_slice(0, &endorsement) .ok_or(TeeResult::AccessDenied)?; params - .set_values(2, endorsement_addr, required_endorsement_size_u64) + .set_values(1, endorsement_addr, required_endorsement_size_u64) .map_err(|_| TeeResult::BadParameters) } } -// TODO: drop this if we decide to use the KeyIso claim structure -#[allow(dead_code)] fn build_endorsement_data( ta_data: &[u8], ta_uuid: &TeeUuid, @@ -316,175 +171,6 @@ fn build_endorsement_data( Some(endorsement) } -struct KeyIsoClaimLayout { - trustlet_information: usize, - report: usize, - claim: usize, -} - -impl KeyIsoClaimLayout { - const PROPERTY_NAMES_SIZE: usize = TRUSTLET_PROPERTY_UUID.len() - + TRUSTLET_PROPERTY_SVN.len() - + TRUSTLET_PROPERTY_TA_DIGEST.len() - + TRUSTLET_PROPERTY_DEBUGGED.len() - + TRUSTLET_PROPERTY_ISOLATION_SOLUTION.len(); - const PROPERTIES_FIXED_SIZE: usize = 5 * size_of::() - + Self::PROPERTY_NAMES_SIZE - + size_of::() - + size_of::() - + size_of::() - + ISOLATION_SOLUTION.len(); - const TRUSTLET_INFORMATION_FIXED_SIZE: usize = - size_of::() + Self::PROPERTIES_FIXED_SIZE; - const REPORT_FIXED_SIZE: usize = - size_of::() + Self::TRUSTLET_INFORMATION_FIXED_SIZE; - const CLAIM_FIXED_SIZE: usize = size_of::() - + size_of::() - + Self::REPORT_FIXED_SIZE - + size_of::() - + KEYISO_SIGNATURE_ALGORITHM_ID.len() - + size_of::() - + KEYISO_SIGNATURE_HASH_ALGORITHM.len() - + size_of::(); - - fn new(ta_data_len: usize, nonce_len: usize, ta_digest_len: usize) -> Option { - let trustlet_information_size = Self::TRUSTLET_INFORMATION_FIXED_SIZE + ta_digest_len; - let report_size = Self::REPORT_FIXED_SIZE + ta_digest_len; - let claim_size = Self::CLAIM_FIXED_SIZE - .checked_add(ta_data_len)? - .checked_add(nonce_len)? - .checked_add(ta_digest_len)?; - Some(Self { - trustlet_information: trustlet_information_size, - report: report_size, - claim: claim_size, - }) - } -} - -/// Serializes the KeyIso claim prefix covered by the identity-key signature. -/// -/// Variable data immediately follows its C-compatible zerocopy header. The signature header is -/// included in this returned prefix; only the signature bytes are appended after signing. -fn build_keyiso_claim( - ta_data: &[u8], - nonce: &[u8], - ta_uuid: &TeeUuid, - ta_svn: u32, - ta_digest: Option<&TaDigest>, -) -> Option> { - let uuid = ta_uuid.to_le_bytes(); - let svn = ta_svn.to_le_bytes(); - let debugged = [IDKS_DEBUG_FLAG]; - let properties: [(&[u8], &[u8]); 5] = [ - (TRUSTLET_PROPERTY_UUID, &uuid), - (TRUSTLET_PROPERTY_SVN, &svn), - ( - TRUSTLET_PROPERTY_TA_DIGEST, - ta_digest.map_or(&[][..], TaDigest::as_slice), - ), - (TRUSTLET_PROPERTY_DEBUGGED, &debugged), - (TRUSTLET_PROPERTY_ISOLATION_SOLUTION, ISOLATION_SOLUTION), - ]; - let layout = KeyIsoClaimLayout::new( - ta_data.len(), - nonce.len(), - ta_digest.map_or(0, |_| TA_DIGEST_LEN), - )?; - - let mut endorsement = Vec::with_capacity(layout.claim); - endorsement.extend_from_slice( - KeyIsoAttestationStatement { - magic: LeU32::new(KeyIsoMagic::AttestationStatement as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - claim_type: LeU32::new(KeyIsoClaimType::KeyAttestation as u32), - } - .as_bytes(), - ); - endorsement.extend_from_slice( - KeyIsoKeyAttestationHeader { - magic: LeU32::new(KeyIsoMagic::KeyAttestationHeader as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - cb_ta_data: LeU32::new(ta_data.len().try_into().ok()?), - cb_nonce: LeU32::new(nonce.len().try_into().ok()?), - cb_report: LeU32::new(layout.report.try_into().ok()?), - c_signature_parameters: LeU32::new(1), - c_signatures: LeU32::new(1), - } - .as_bytes(), - ); - endorsement.extend_from_slice(ta_data); - endorsement.extend_from_slice(nonce); - endorsement.extend_from_slice( - KeyIsoTrustletReport { - magic: LeU32::new(KeyIsoMagic::TrustletReport as u32), - report_size: LeU32::new(layout.report.try_into().ok()?), - offset_to_var_data: LeU32::new(size_of::().try_into().ok()?), - version: LeU32::new(KeyIsoVersion::V1 as u32), - cb_trustlet_information: LeU32::new(layout.trustlet_information.try_into().ok()?), - } - .as_bytes(), - ); - endorsement.extend_from_slice( - KeyIsoAttestationTrustletInformation { - magic: LeU32::new(KeyIsoMagic::TrustletInformation as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - c_properties: LeU32::new(properties.len().try_into().ok()?), - } - .as_bytes(), - ); - - for (name, value) in properties { - endorsement.extend_from_slice( - KeyIsoAttestationTrustletProperty { - magic: LeU32::new(KeyIsoMagic::TrustletProperty as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - cb_property_name: LeU32::new(name.len().try_into().ok()?), - cb_property: LeU32::new(value.len().try_into().ok()?), - } - .as_bytes(), - ); - endorsement.extend_from_slice(name); - endorsement.extend_from_slice(value); - } - - endorsement.extend_from_slice( - KeyIsoAttestationSignatureParams { - magic: LeU32::new(KeyIsoMagic::SignatureParams as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - cb_alg_id: LeU32::new(KEYISO_SIGNATURE_ALGORITHM_ID.len().try_into().ok()?), - cb_alg_params: LeU32::new( - size_of::() - .try_into() - .ok()?, - ), - cb_hash_alg: LeU32::new(KEYISO_SIGNATURE_HASH_ALGORITHM.len().try_into().ok()?), - } - .as_bytes(), - ); - endorsement.extend_from_slice(KEYISO_SIGNATURE_ALGORITHM_ID); - endorsement.extend_from_slice( - KeyIsoAttestationEccSignatureParams { - magic: LeU32::new(KeyIsoMagic::EccSignatureParams as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - } - .as_bytes(), - ); - endorsement.extend_from_slice(KEYISO_SIGNATURE_HASH_ALGORITHM); - endorsement.extend_from_slice( - KeyIsoAttestationSignature { - magic: LeU32::new(KeyIsoMagic::Signature as u32), - version: LeU32::new(KeyIsoVersion::V1 as u32), - cb_signature: LeU32::new(IDKS_ENDORSEMENT_SIGNATURE_LEN.try_into().ok()?), - } - .as_bytes(), - ); - if endorsement.len() != layout.claim { - return None; - } - Some(endorsement) -} - fn endorse_data_with( endorsement_data: &[u8], private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], @@ -640,152 +326,6 @@ fn identity_signing_public_key_from_private_key( mod tests { use super::*; - fn read_u32(bytes: &[u8], offset: &mut usize) -> u32 { - let value = u32::from_le_bytes(bytes[*offset..*offset + 4].try_into().unwrap()); - *offset += 4; - value - } - - fn assert_property(bytes: &[u8], offset: &mut usize, name: &[u8], value: &[u8]) { - assert_eq!(read_u32(bytes, offset), 0x5054_414b); - assert_eq!(read_u32(bytes, offset), 1); - assert_eq!(read_u32(bytes, offset), u32::try_from(name.len()).unwrap()); - assert_eq!(read_u32(bytes, offset), u32::try_from(value.len()).unwrap()); - assert_eq!(&bytes[*offset..*offset + name.len()], name); - *offset += name.len(); - assert_eq!(&bytes[*offset..*offset + value.len()], value); - *offset += value.len(); - } - - #[test] - fn keyiso_claim_has_expected_layout() { - let ta_data = b"TA data"; - let nonce = [0x5a; 32]; - let ta_uuid = TeeUuid { - time_low: 0x1122_3344, - time_mid: 0x5566, - time_hi_and_version: 0x7788, - clock_seq_and_node: [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], - }; - let ta_svn = 7u32; - let ta_digest = [0xa5; TA_DIGEST_LEN]; - - let endorsement = - build_keyiso_claim(ta_data, &nonce, &ta_uuid, ta_svn, Some(&ta_digest)).unwrap(); - let mut offset = 0; - - assert_eq!(read_u32(&endorsement, &mut offset), 0x4d53_414b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(read_u32(&endorsement, &mut offset), 0x8000_0001); - - assert_eq!(read_u32(&endorsement, &mut offset), 0x4841_4b4b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!( - read_u32(&endorsement, &mut offset), - u32::try_from(ta_data.len()).unwrap() - ); - assert_eq!( - read_u32(&endorsement, &mut offset), - u32::try_from(nonce.len()).unwrap() - ); - let report_size = read_u32(&endorsement, &mut offset) as usize; - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(&endorsement[offset..offset + ta_data.len()], ta_data); - offset += ta_data.len(); - assert_eq!(&endorsement[offset..offset + nonce.len()], nonce); - offset += nonce.len(); - - let report_start = offset; - assert_eq!(read_u32(&endorsement, &mut offset), 0x4d52_544b); - assert_eq!(read_u32(&endorsement, &mut offset) as usize, report_size); - assert_eq!(read_u32(&endorsement, &mut offset), 20); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - let trustlet_information_size = read_u32(&endorsement, &mut offset) as usize; - - let trustlet_information_start = offset; - assert_eq!(read_u32(&endorsement, &mut offset), 0x4954_414b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(read_u32(&endorsement, &mut offset), 5); - assert_property( - &endorsement, - &mut offset, - b"TRUSTLET_PROPERTY_UUID", - &ta_uuid.to_le_bytes(), - ); - assert_property( - &endorsement, - &mut offset, - b"TRUSTLET_PROPERTY_SVN", - &ta_svn.to_le_bytes(), - ); - assert_property( - &endorsement, - &mut offset, - b"TRUSTLET_PROPERTY_TA_DIGEST", - &ta_digest, - ); - assert_property( - &endorsement, - &mut offset, - b"TRUSTLET_PROPERTY_DEBUGGED", - &[IDKS_DEBUG_FLAG], - ); - assert_property( - &endorsement, - &mut offset, - b"TRUSTLET_PROPERTY_ISOLATION_SOLUTION", - b"LVBS", - ); - assert_eq!( - offset - trustlet_information_start, - trustlet_information_size - ); - assert_eq!(offset - report_start, report_size); - - assert_eq!(read_u32(&endorsement, &mut offset), 0x5053_414b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(read_u32(&endorsement, &mut offset), 10); - assert_eq!(read_u32(&endorsement, &mut offset), 8); - assert_eq!(read_u32(&endorsement, &mut offset), 6); - assert_eq!(&endorsement[offset..offset + 10], b"ECDSA_P384"); - offset += 10; - assert_eq!(read_u32(&endorsement, &mut offset), 0x5045_414b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(&endorsement[offset..offset + 6], b"SHA384"); - offset += 6; - - assert_eq!(read_u32(&endorsement, &mut offset), 0x5353_414b); - assert_eq!(read_u32(&endorsement, &mut offset), 1); - assert_eq!(read_u32(&endorsement, &mut offset), 96); - - assert_eq!(offset, endorsement.len()); - } - - #[test] - fn keyiso_signature_covers_entire_claim_prefix() { - use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; - - let mut private_key = [0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]; - private_key[IDENTITY_SIGNING_PRIVATE_KEY_LEN - 1] = 1; - let nonce = [0x5a; 32]; - let ta_uuid = TeeUuid::NIL; - let ta_digest = [0xa5; TA_DIGEST_LEN]; - let mut endorsement = - build_keyiso_claim(b"TA data", &nonce, &ta_uuid, 7, Some(&ta_digest)).unwrap(); - let signed_len = endorsement.len(); - let signature = endorse_data_with(&endorsement, &private_key).unwrap(); - - endorsement.extend_from_slice(&signature); - - let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); - let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); - let parsed_signature = Signature::from_slice(&endorsement[signed_len..]).unwrap(); - verifying_key - .verify(&endorsement[..signed_len], &parsed_signature) - .unwrap(); - } - #[test] fn endorsement_signature_covers_plaintext_layout() { use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; From fa796408e471092ac8c1c86ca6a78b8722d412f3 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 17:26:08 +0000 Subject: [PATCH 07/13] Embed TA signing leaf certificate in IDK_S endorsements --- litebox_runner_lvbs/src/lib.rs | 10 +- litebox_shim_optee/src/idk.rs | 222 ++++++++++++++++++++++++++++----- litebox_shim_optee/src/lib.rs | 15 +++ 3 files changed, 217 insertions(+), 30 deletions(-) diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 442eba6eb..cbcc33e64 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -241,7 +241,9 @@ pub fn init(is_bsp: bool) -> &'static Platform { .get() .expect("init must publish the platform before any core uses it"); if is_bsp { - let shim = litebox_shim_optee::OpteeShimBuilder::new(platform, session_manager()).build(); + let shim = litebox_shim_optee::OpteeShimBuilder::new(platform, session_manager()) + .with_ta_signing_cert(TA_SIGNING_CERT_DER) + .build(); register_embedded_tas(&shim); } platform @@ -823,7 +825,9 @@ fn open_session_new_instance( client_identity: Option, ta_req_info: &litebox_shim_optee::msg_handler::TaRequestInfo, ) -> Result<(), OpteeSmcReturnCode> { - let shim = litebox_shim_optee::OpteeShimBuilder::new(platform, session_manager()).build(); + let shim = litebox_shim_optee::OpteeShimBuilder::new(platform, session_manager()) + .with_ta_signing_cert(TA_SIGNING_CERT_DER) + .build(); if shim.get_ta_bin(&ta_uuid).is_none() { msg_args.session = 0; msg_args.ret = TeeResult::ItemNotFound; @@ -1412,6 +1416,8 @@ fn write_rpc_args_to_normal_world( // use include_bytes! to include ldelf const LDELF_BINARY: &[u8] = &[0u8; 0]; const TA_BINARY: &[u8] = &[0u8; 0]; +// This is the TA signing leaf certificate, not an IDK_S certificate. +const TA_SIGNING_CERT_DER: &[u8] = &[0u8; 0]; const TA_BINARIES: &[&[u8]] = &[TA_BINARY]; /// Register a TA binary embedded in the runner image. diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index aa3d3be3d..61c8e8335 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -43,7 +43,8 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() + TA_DIGEST_LEN + size_of::() - + ISOLATION_SOLUTION.len(); + + ISOLATION_SOLUTION.len() + + size_of::(); // DER leaf certificate length pub(crate) struct IdksPta; #[derive(Clone, Copy, TryFromPrimitive)] @@ -81,6 +82,7 @@ impl IdksPta { } } + /// Parameter 0 is the TA data; parameter 1 receives the signed endorsement. fn endorse_data( task: &Task, params: &mut UteeParams, @@ -107,8 +109,8 @@ impl IdksPta { .get_values(1) .map_err(|_| TeeResult::BadParameters)? .ok_or(TeeResult::BadParameters)?; - let required_endorsement_size = ta_data_size - .checked_add(IDKS_ENDORSEMENT_METADATA_LEN) + let ta_signing_cert = task.global.ta_signing_cert; + let required_endorsement_size = endorsement_data_len(ta_data_size, ta_signing_cert.len()) .and_then(|size| size.checked_add(IDKS_ENDORSEMENT_SIGNATURE_LEN)) .ok_or(TeeResult::BadParameters)?; let required_endorsement_size_u64 = @@ -132,9 +134,14 @@ impl IdksPta { .to_owned_slice(ta_data_size) .ok_or(TeeResult::BadParameters)? }; - let mut endorsement = - build_endorsement_data(&ta_data, &task.ta_app_id, task.ta_svn, &task.ta_digest) - .ok_or(TeeResult::BadParameters)?; + let mut endorsement = build_endorsement_data( + &ta_data, + &task.ta_app_id, + task.ta_svn, + &task.ta_digest, + ta_signing_cert, + ) + .ok_or(TeeResult::BadParameters)?; let key_pair = get_identity_signing_key_pair(task.global.platform) .map_err(|_| TeeResult::GenericError)?; let signature = endorse_data_with(&endorsement, &key_pair.private_key) @@ -151,14 +158,31 @@ impl IdksPta { } } +fn endorsement_data_len(ta_data_len: usize, ta_signing_cert_len: usize) -> Option { + u32::try_from(ta_signing_cert_len).ok()?; + ta_data_len + .checked_add(IDKS_ENDORSEMENT_METADATA_LEN)? + .checked_add(ta_signing_cert_len) +} + +/// Serializes the flat prefix covered by the IDK_S signature: +/// MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || DEBUG || +/// ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. +/// +/// Integers (including the u32 certificate length) and the UUID use little endian. +/// TA_DATA retains its caller-known length. The certificate is the embedded TA +/// signing leaf certificate, not the IDK_S certificate; its bytes are copied +/// verbatim for the verifier to use when verifying the TA signature. An empty +/// certificate placeholder is encoded with length zero. fn build_endorsement_data( ta_data: &[u8], ta_uuid: &TeeUuid, ta_svn: u32, ta_digest: &TaDigest, + ta_signing_cert: &[u8], ) -> Option> { - // MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || DEBUG || ISOLATION_SOLUTION - let capacity = ta_data.len().checked_add(IDKS_ENDORSEMENT_METADATA_LEN)?; + let capacity = endorsement_data_len(ta_data.len(), ta_signing_cert.len())?; + let cert_len = u32::try_from(ta_signing_cert.len()).ok()?; let mut endorsement = Vec::with_capacity(capacity); endorsement.extend_from_slice(IDKS_ENDORSEMENT_MAGIC); endorsement.extend_from_slice(&IDKS_ENDORSEMENT_VERSION.to_le_bytes()); @@ -168,6 +192,8 @@ fn build_endorsement_data( endorsement.extend_from_slice(ta_digest); endorsement.push(IDKS_DEBUG_FLAG); endorsement.extend_from_slice(ISOLATION_SOLUTION); + endorsement.extend_from_slice(&cert_len.to_le_bytes()); + endorsement.extend_from_slice(ta_signing_cert); Some(endorsement) } @@ -326,36 +352,176 @@ fn identity_signing_public_key_from_private_key( mod tests { use super::*; - #[test] - fn endorsement_signature_covers_plaintext_layout() { - use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; + // Opaque test bytes: the shim transports the certificate without parsing it. + const TEST_CERT: &[u8] = &[0x30, 0x03, 0x02, 0x01, 0x01]; - let mut private_key = [0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]; - private_key[IDENTITY_SIGNING_PRIVATE_KEY_LEN - 1] = 1; - let ta_data = b"TA public key"; - let ta_uuid = TeeUuid { + #[test] + fn endorsement_has_expected_flat_layout() { + let uuid = TeeUuid { time_low: 0x1122_3344, time_mid: 0x5566, time_hi_and_version: 0x7788, clock_seq_and_node: [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], }; + let digest = [0xa5; TA_DIGEST_LEN]; + for cert in [TEST_CERT, &[]] { + for data in [b"TA data".as_slice(), &[]] { + let endorsement = build_endorsement_data(data, &uuid, 7, &digest, cert).unwrap(); + let mut expected = Vec::from(b"IDKS\x01\x00\x00\x00".as_slice()); + expected.extend_from_slice(data); + expected.extend_from_slice(&[ + 0x44, 0x33, 0x22, 0x11, 0x66, 0x55, 0x88, 0x77, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0x00, + ]); + expected.extend_from_slice(&[7, 0, 0, 0]); + expected.extend_from_slice(&digest); + expected.push(IDKS_DEBUG_FLAG); + expected.extend_from_slice(b"LVBS"); + expected.extend_from_slice(&u32::try_from(cert.len()).unwrap().to_le_bytes()); + expected.extend_from_slice(cert); + assert_eq!(endorsement, expected); + assert_eq!( + endorsement.len(), + endorsement_data_len(data.len(), cert.len()).unwrap() + ); + } + } + } + + #[test] + fn pta_endorsement_uses_global_certificate_and_reports_output_size() { + use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; - let ta_svn = 7u32; - let ta_digest = [ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, - 0xee, 0xff, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b, - 0x3c, 0x2d, 0x1e, 0x0f, - ]; - let expected_plaintext = - build_endorsement_data(ta_data, &ta_uuid, ta_svn, &ta_digest).unwrap(); + for cert in [TEST_CERT, &[]] { + let shim = crate::syscalls::tests::shim_builder() + .with_ta_signing_cert(cert) + .build(); + let task = shim.0.new_test_task(); + assert!(core::ptr::eq(task.global.ta_signing_cert, cert)); + + for data in [b"TA data".as_slice(), &[]] { + let mut params = UteeParams::new(); + params.set_type(0, TeeParamType::MemrefInput).unwrap(); + params.set_type(1, TeeParamType::MemrefOutput).unwrap(); + let data_addr = if data.is_empty() { + 0 + } else { + data.as_ptr() as u64 + }; + params.set_values(0, data_addr, data.len() as u64).unwrap(); + let expected = build_endorsement_data( + data, + &task.ta_app_id, + task.ta_svn, + &task.ta_digest, + cert, + ) + .unwrap(); + let required_size = expected.len() + IDKS_ENDORSEMENT_SIGNATURE_LEN; + + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::ShortBuffer) + ); + assert_eq!( + params.get_values(1).unwrap(), + Some((0, required_size as u64)) + ); + + let mut output = alloc::vec![0xcc; required_size + 1]; + let output_addr = output.as_mut_ptr() as u64; + params + .set_values(1, output_addr, (required_size - 1) as u64) + .unwrap(); + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::ShortBuffer) + ); + assert!(output.iter().all(|byte| *byte == 0xcc)); + assert_eq!( + params.get_values(1).unwrap(), + Some((output_addr, required_size as u64)) + ); + + IdksPta::endorse_data(&task, &mut params).unwrap(); + assert_eq!(&output[..expected.len()], expected); + assert_eq!(output[required_size], 0xcc); + let key_pair = get_identity_signing_key_pair(task.global.platform).unwrap(); + let verifying_key = VerifyingKey::from_sec1_bytes(&key_pair.public_key).unwrap(); + let signature = + Signature::from_slice(&output[expected.len()..required_size]).unwrap(); + verifying_key.verify(&expected, &signature).unwrap(); + } + } + } - let signature = endorse_data_with(&expected_plaintext, &private_key).unwrap(); + #[test] + fn pta_rejects_keyiso_parameters_and_invalid_buffers() { + let task = crate::syscalls::tests::init_platform(); + let mut params = UteeParams::new(); + params.set_type(0, TeeParamType::MemrefInput).unwrap(); + params.set_type(1, TeeParamType::MemrefInput).unwrap(); + params.set_type(2, TeeParamType::MemrefOutput).unwrap(); + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::BadParameters) + ); + + params.set_type(1, TeeParamType::MemrefOutput).unwrap(); + params.set_type(2, TeeParamType::None).unwrap(); + params.set_values(0, 0, 1).unwrap(); + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::BadParameters) + ); + params + .set_values(0, 1, (IDKS_ENDORSEMENT_DATA_MAX_SIZE + 1) as u64) + .unwrap(); + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::BadParameters) + ); + params.set_values(0, 0, 0).unwrap(); + params.set_values(1, 0, u64::MAX).unwrap(); + assert_eq!( + IdksPta::endorse_data(&task, &mut params), + Err(TeeResult::BadParameters) + ); + } + + #[test] + fn endorsement_length_rejects_overflow() { + assert!(endorsement_data_len(usize::MAX, 0).is_none()); + assert!(endorsement_data_len(0, usize::MAX).is_none()); + assert!(endorsement_data_len(0, u32::MAX as usize + 1).is_none()); + } + + #[test] + fn endorsement_signature_covers_plaintext_and_certificate() { + use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; + + let mut private_key = [0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]; + private_key[IDENTITY_SIGNING_PRIVATE_KEY_LEN - 1] = 1; + let mut endorsement = build_endorsement_data( + b"TA data", + &TeeUuid::NIL, + 7, + &[0xa5; TA_DIGEST_LEN], + TEST_CERT, + ) + .unwrap(); + let signature = endorse_data_with(&endorsement, &private_key).unwrap(); let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); let signature = Signature::from_slice(&signature).unwrap(); - - verifying_key - .verify(&expected_plaintext, &signature) - .unwrap(); + verifying_key.verify(&endorsement, &signature).unwrap(); + + // Tamper with the TA data, digest, certificate length, and certificate bytes. + let cert_start = endorsement.len() - TEST_CERT.len(); + for offset in [8, 8 + b"TA data".len() + 16 + 4, cert_start - 4, cert_start] { + endorsement[offset] ^= 1; + assert!(verifying_key.verify(&endorsement, &signature).is_err()); + endorsement[offset] ^= 1; + } } } diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index 60d7b44c6..79cf53cf2 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -155,6 +155,7 @@ pub struct OpteeShimBuilder { platform: &'static Platform, session_manager: &'static session::SessionManager, litebox: LiteBox, + ta_signing_cert: &'static [u8], } impl OpteeShimBuilder { @@ -169,9 +170,20 @@ impl OpteeShimBuilder { platform, session_manager, litebox: LiteBox::new(platform), + ta_signing_cert: &[], } } + /// Set the embedded DER leaf certificate used by verifiers to verify TA signatures. + /// + /// The certificate is shared by reference and included verbatim in IDK_S endorsements. + /// It defaults to an empty placeholder when no certificate is embedded. + #[must_use] + pub fn with_ta_signing_cert(mut self, ta_signing_cert: &'static [u8]) -> Self { + self.ta_signing_cert = ta_signing_cert; + self + } + /// Returns the litebox object for the shim. pub fn litebox(&self) -> &LiteBox { &self.litebox @@ -186,6 +198,7 @@ impl OpteeShimBuilder { pm: PageManager::new(&self.litebox), _litebox: self.litebox, ta_uuid_map: ta_uuid_map(), + ta_signing_cert: self.ta_signing_cert, pta_busy: spin::mutex::SpinMutex::new(HashSet::new()), }); OpteeShim(global) @@ -208,6 +221,8 @@ struct GlobalState { _litebox: litebox::LiteBox, /// The TA UUID to binary map for TA loading. ta_uuid_map: &'static TaUuidMap, + /// Embedded DER TA signing leaf certificate, shared without copying. + ta_signing_cert: &'static [u8], /// Tracks which non-concurrent PTAs (i.e., PTAs w/o `TaFlags::CONCURRENT`) /// are currently busy. A busy PTA is *rejected* with `TeeResult::Busy` /// rather than queued. From 0c2a669ecf26a21d268558112224d618b8b30025 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 17:45:31 +0000 Subject: [PATCH 08/13] add TA_DATA_LEN --- litebox_shim_optee/src/idk.rs | 46 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 61c8e8335..3209c7b60 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -38,7 +38,8 @@ const IDKS_DEBUG_FLAG: u8 = 0; const ISOLATION_SOLUTION: &[u8] = b"LVBS"; pub(crate) const IDKS_ENDORSEMENT_SIGNATURE_LEN: usize = 96; const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() - + size_of::() + + size_of::() // version + + size_of::() // TA data length + size_of::() + size_of::() + TA_DIGEST_LEN @@ -159,6 +160,7 @@ impl IdksPta { } fn endorsement_data_len(ta_data_len: usize, ta_signing_cert_len: usize) -> Option { + u32::try_from(ta_data_len).ok()?; u32::try_from(ta_signing_cert_len).ok()?; ta_data_len .checked_add(IDKS_ENDORSEMENT_METADATA_LEN)? @@ -166,12 +168,12 @@ fn endorsement_data_len(ta_data_len: usize, ta_signing_cert_len: usize) -> Optio } /// Serializes the flat prefix covered by the IDK_S signature: -/// MAGIC || VERSION || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || DEBUG || -/// ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. +/// MAGIC || VERSION || TA_DATA_LEN || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || +/// DEBUG || ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. /// -/// Integers (including the u32 certificate length) and the UUID use little endian. -/// TA_DATA retains its caller-known length. The certificate is the embedded TA -/// signing leaf certificate, not the IDK_S certificate; its bytes are copied +/// Integers (including both u32 lengths) and the UUID use little endian. +/// The certificate is the embedded TA signing leaf certificate, not the IDK_S +/// certificate; its bytes are copied /// verbatim for the verifier to use when verifying the TA signature. An empty /// certificate placeholder is encoded with length zero. fn build_endorsement_data( @@ -182,10 +184,12 @@ fn build_endorsement_data( ta_signing_cert: &[u8], ) -> Option> { let capacity = endorsement_data_len(ta_data.len(), ta_signing_cert.len())?; + let ta_data_len = u32::try_from(ta_data.len()).ok()?; let cert_len = u32::try_from(ta_signing_cert.len()).ok()?; let mut endorsement = Vec::with_capacity(capacity); endorsement.extend_from_slice(IDKS_ENDORSEMENT_MAGIC); endorsement.extend_from_slice(&IDKS_ENDORSEMENT_VERSION.to_le_bytes()); + endorsement.extend_from_slice(&ta_data_len.to_le_bytes()); endorsement.extend_from_slice(ta_data); endorsement.extend_from_slice(&ta_uuid.to_le_bytes()); endorsement.extend_from_slice(&ta_svn.to_le_bytes()); @@ -364,10 +368,12 @@ mod tests { clock_seq_and_node: [0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00], }; let digest = [0xa5; TA_DIGEST_LEN]; + let long_data = [0x5a; 256]; for cert in [TEST_CERT, &[]] { - for data in [b"TA data".as_slice(), &[]] { + for data in [b"TA data".as_slice(), &[], &long_data] { let endorsement = build_endorsement_data(data, &uuid, 7, &digest, cert).unwrap(); let mut expected = Vec::from(b"IDKS\x01\x00\x00\x00".as_slice()); + expected.extend_from_slice(&u32::try_from(data.len()).unwrap().to_le_bytes()); expected.extend_from_slice(data); expected.extend_from_slice(&[ 0x44, 0x33, 0x22, 0x11, 0x66, 0x55, 0x88, 0x77, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, @@ -384,6 +390,21 @@ mod tests { endorsement.len(), endorsement_data_len(data.len(), cert.len()).unwrap() ); + + // Locate the variable fields using only the encoded lengths. + let data_len = u32::from_le_bytes(endorsement[8..12].try_into().unwrap()) as usize; + let (parsed_data, metadata) = endorsement[12..].split_at(data_len); + assert_eq!(parsed_data, data); + assert_eq!(&metadata[..16], uuid.to_le_bytes()); + let cert_len_offset = 16 + 4 + TA_DIGEST_LEN + 1 + 4; + let cert_len = u32::from_le_bytes( + metadata[cert_len_offset..cert_len_offset + 4] + .try_into() + .unwrap(), + ) as usize; + let (parsed_cert, remaining) = metadata[cert_len_offset + 4..].split_at(cert_len); + assert_eq!(parsed_cert, cert); + assert!(remaining.is_empty()); } } } @@ -494,6 +515,7 @@ mod tests { assert!(endorsement_data_len(usize::MAX, 0).is_none()); assert!(endorsement_data_len(0, usize::MAX).is_none()); assert!(endorsement_data_len(0, u32::MAX as usize + 1).is_none()); + assert!(endorsement_data_len(u32::MAX as usize + 1, 0).is_none()); } #[test] @@ -516,9 +538,15 @@ mod tests { let signature = Signature::from_slice(&signature).unwrap(); verifying_key.verify(&endorsement, &signature).unwrap(); - // Tamper with the TA data, digest, certificate length, and certificate bytes. + // Tamper with the TA data length, data, digest, certificate length, and certificate bytes. let cert_start = endorsement.len() - TEST_CERT.len(); - for offset in [8, 8 + b"TA data".len() + 16 + 4, cert_start - 4, cert_start] { + for offset in [ + 8, + 12, + 12 + b"TA data".len() + 16 + 4, + cert_start - 4, + cert_start, + ] { endorsement[offset] ^= 1; assert!(verifying_key.verify(&endorsement, &signature).is_err()); endorsement[offset] ^= 1; From 019ad24bc38924bf220eae603506dd9272d0bdcd Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 18:02:33 +0000 Subject: [PATCH 09/13] revise comments --- litebox_runner_lvbs/src/lib.rs | 1 - litebox_shim_optee/src/idk.rs | 20 +++++++++----------- litebox_shim_optee/src/lib.rs | 7 ++----- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index cbcc33e64..7f4f3b754 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -1416,7 +1416,6 @@ fn write_rpc_args_to_normal_world( // use include_bytes! to include ldelf const LDELF_BINARY: &[u8] = &[0u8; 0]; const TA_BINARY: &[u8] = &[0u8; 0]; -// This is the TA signing leaf certificate, not an IDK_S certificate. const TA_SIGNING_CERT_DER: &[u8] = &[0u8; 0]; const TA_BINARIES: &[&[u8]] = &[TA_BINARY]; diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 3209c7b60..cfd0a4e79 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -83,13 +83,15 @@ impl IdksPta { } } - /// Parameter 0 is the TA data; parameter 1 receives the signed endorsement. fn endorse_data( task: &Task, params: &mut UteeParams, ) -> Result<(), TeeResult> { use TeeParamType::{MemrefInput, MemrefOutput, None}; - + // [in] params[0].memref.buffer Opaque TA data + // [in] params[0].memref.size TA data size + // [out] params[1].memref.buffer Output buffer for signed endorsement + // [out] params[1].memref.size Buffer size if !params.has_types([MemrefInput, MemrefOutput, None, None]) { return Err(TeeResult::BadParameters); } @@ -167,15 +169,12 @@ fn endorsement_data_len(ta_data_len: usize, ta_signing_cert_len: usize) -> Optio .checked_add(ta_signing_cert_len) } -/// Serializes the flat prefix covered by the IDK_S signature: +/// IDK_S-signed wire format: /// MAGIC || VERSION || TA_DATA_LEN || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || /// DEBUG || ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. /// -/// Integers (including both u32 lengths) and the UUID use little endian. -/// The certificate is the embedded TA signing leaf certificate, not the IDK_S -/// certificate; its bytes are copied -/// verbatim for the verifier to use when verifying the TA signature. An empty -/// certificate placeholder is encoded with length zero. +/// Integers and UUID are little endian; both lengths are u32 byte counts. +/// A zero certificate length means absent. fn build_endorsement_data( ta_data: &[u8], ta_uuid: &TeeUuid, @@ -356,7 +355,7 @@ fn identity_signing_public_key_from_private_key( mod tests { use super::*; - // Opaque test bytes: the shim transports the certificate without parsing it. + // Opaque fixture; certificate validation is the verifier's responsibility. const TEST_CERT: &[u8] = &[0x30, 0x03, 0x02, 0x01, 0x01]; #[test] @@ -391,7 +390,7 @@ mod tests { endorsement_data_len(data.len(), cert.len()).unwrap() ); - // Locate the variable fields using only the encoded lengths. + // Parsing must not require caller-supplied lengths. let data_len = u32::from_le_bytes(endorsement[8..12].try_into().unwrap()) as usize; let (parsed_data, metadata) = endorsement[12..].split_at(data_len); assert_eq!(parsed_data, data); @@ -538,7 +537,6 @@ mod tests { let signature = Signature::from_slice(&signature).unwrap(); verifying_key.verify(&endorsement, &signature).unwrap(); - // Tamper with the TA data length, data, digest, certificate length, and certificate bytes. let cert_start = endorsement.len() - TEST_CERT.len(); for offset in [ 8, diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index 79cf53cf2..12ca6733d 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -174,10 +174,8 @@ impl OpteeShimBuilder { } } - /// Set the embedded DER leaf certificate used by verifiers to verify TA signatures. - /// - /// The certificate is shared by reference and included verbatim in IDK_S endorsements. - /// It defaults to an empty placeholder when no certificate is embedded. + /// DER leaf certificate for TA signature verification, included in IDK_S endorsements. + /// Defaults to empty (no certificate). #[must_use] pub fn with_ta_signing_cert(mut self, ta_signing_cert: &'static [u8]) -> Self { self.ta_signing_cert = ta_signing_cert; @@ -221,7 +219,6 @@ struct GlobalState { _litebox: litebox::LiteBox, /// The TA UUID to binary map for TA loading. ta_uuid_map: &'static TaUuidMap, - /// Embedded DER TA signing leaf certificate, shared without copying. ta_signing_cert: &'static [u8], /// Tracks which non-concurrent PTAs (i.e., PTAs w/o `TaFlags::CONCURRENT`) /// are currently busy. A busy PTA is *rejected* with `TeeResult::Busy` From 6cac1121bbbb9ec3b9e545d239604091a216f134 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 18:56:07 +0000 Subject: [PATCH 10/13] lazy TA digest calculation for TOCTOU robustness --- litebox_shim_optee/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index 12ca6733d..b2dab71ac 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -308,11 +308,12 @@ impl OpteeShim { ldelf_bin: &[u8], ta_uuid: TeeUuid, ) -> Result, loader::elf::ElfLoaderError> { - let (ta_flags, ta_digest) = self + let (ta_binary, ta_flags) = self .0 .ta_uuid_map - .get_metadata(&ta_uuid) + .get_with_flags(&ta_uuid) .ok_or(loader::elf::ElfLoaderError::OpenError(Errno::ENOENT))?; + let ta_digest = Sha256::digest(&ta_binary).into(); let entrypoints = crate::OpteeShimEntrypoints { _not_send: core::marker::PhantomData, task: Task { @@ -1423,8 +1424,6 @@ struct TaInfo { binary: Arc<[u8]>, /// Parsed TA flags from .ta_head section flags: TaFlags, - /// SHA-256 digest of the raw TA binary - digest: TaDigest, } /// Data structure to maintain a mapping from TA UUIDs to their binary data and flags. @@ -1450,13 +1449,11 @@ impl TaUuidMap { return false; } - let digest = Sha256::digest(&ta_bin).into(); let _replaced = self.inner.write().insert( uuid, TaInfo { binary: ta_bin, flags: ta_head.flags, - digest, }, ); true @@ -1466,11 +1463,11 @@ impl TaUuidMap { self.inner.read().get(uuid).map(|info| info.binary.clone()) } - fn get_metadata(&self, uuid: &TeeUuid) -> Option<(TaFlags, TaDigest)> { + fn get_with_flags(&self, uuid: &TeeUuid) -> Option<(Arc<[u8]>, TaFlags)> { self.inner .read() .get(uuid) - .map(|info| (info.flags, info.digest)) + .map(|info| (info.binary.clone(), info.flags)) } // Lazy removal of TA binaries when they are no longer needed. From a0f8f034508d549dbf3f8032767e0e755687c219 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 19:02:54 +0000 Subject: [PATCH 11/13] nits --- litebox_runner_lvbs/src/lib.rs | 2 +- litebox_shim_optee/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 7f4f3b754..80bdb7513 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -1416,8 +1416,8 @@ fn write_rpc_args_to_normal_world( // use include_bytes! to include ldelf const LDELF_BINARY: &[u8] = &[0u8; 0]; const TA_BINARY: &[u8] = &[0u8; 0]; -const TA_SIGNING_CERT_DER: &[u8] = &[0u8; 0]; const TA_BINARIES: &[&[u8]] = &[TA_BINARY]; +const TA_SIGNING_CERT_DER: &[u8] = &[0u8; 0]; /// Register a TA binary embedded in the runner image. fn register_embedded_ta( diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index b2dab71ac..a1e005c63 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -174,8 +174,7 @@ impl OpteeShimBuilder { } } - /// DER leaf certificate for TA signature verification, included in IDK_S endorsements. - /// Defaults to empty (no certificate). + /// A certificate for TA signature verification. Defaults to empty (no certificate). #[must_use] pub fn with_ta_signing_cert(mut self, ta_signing_cert: &'static [u8]) -> Self { self.ta_signing_cert = ta_signing_cert; From 6ba804977d847e6cdcccfd65bb9fe953db8c7d84 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 20:01:30 +0000 Subject: [PATCH 12/13] add TA_DYNAMIC --- litebox_shim_optee/src/idk.rs | 33 +++++++++++++++++++++++++++------ litebox_shim_optee/src/lib.rs | 4 ++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index cfd0a4e79..8a5fad645 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -43,6 +43,7 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() + size_of::() + TA_DIGEST_LEN + + size_of::() // TA dynamic flag + size_of::() + ISOLATION_SOLUTION.len() + size_of::(); // DER leaf certificate length @@ -142,6 +143,7 @@ impl IdksPta { &task.ta_app_id, task.ta_svn, &task.ta_digest, + task.ta_dynamic, ta_signing_cert, ) .ok_or(TeeResult::BadParameters)?; @@ -171,7 +173,7 @@ fn endorsement_data_len(ta_data_len: usize, ta_signing_cert_len: usize) -> Optio /// IDK_S-signed wire format: /// MAGIC || VERSION || TA_DATA_LEN || TA_DATA || TA_UUID || TA_SVN || TA_DIGEST || -/// DEBUG || ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. +/// TA_DYNAMIC || DEBUG || ISOLATION_SOLUTION || TA_SIGNING_CERT_LEN || TA_SIGNING_CERT_DER. /// /// Integers and UUID are little endian; both lengths are u32 byte counts. /// A zero certificate length means absent. @@ -180,6 +182,7 @@ fn build_endorsement_data( ta_uuid: &TeeUuid, ta_svn: u32, ta_digest: &TaDigest, + ta_dynamic: bool, ta_signing_cert: &[u8], ) -> Option> { let capacity = endorsement_data_len(ta_data.len(), ta_signing_cert.len())?; @@ -193,6 +196,7 @@ fn build_endorsement_data( endorsement.extend_from_slice(&ta_uuid.to_le_bytes()); endorsement.extend_from_slice(&ta_svn.to_le_bytes()); endorsement.extend_from_slice(ta_digest); + endorsement.push(u8::from(ta_dynamic)); endorsement.push(IDKS_DEBUG_FLAG); endorsement.extend_from_slice(ISOLATION_SOLUTION); endorsement.extend_from_slice(&cert_len.to_le_bytes()); @@ -368,9 +372,15 @@ mod tests { }; let digest = [0xa5; TA_DIGEST_LEN]; let long_data = [0x5a; 256]; - for cert in [TEST_CERT, &[]] { + for (cert, ta_dynamic) in [ + (TEST_CERT, false), + (TEST_CERT, true), + (&[][..], false), + (&[][..], true), + ] { for data in [b"TA data".as_slice(), &[], &long_data] { - let endorsement = build_endorsement_data(data, &uuid, 7, &digest, cert).unwrap(); + let endorsement = + build_endorsement_data(data, &uuid, 7, &digest, ta_dynamic, cert).unwrap(); let mut expected = Vec::from(b"IDKS\x01\x00\x00\x00".as_slice()); expected.extend_from_slice(&u32::try_from(data.len()).unwrap().to_le_bytes()); expected.extend_from_slice(data); @@ -380,6 +390,7 @@ mod tests { ]); expected.extend_from_slice(&[7, 0, 0, 0]); expected.extend_from_slice(&digest); + expected.push(u8::from(ta_dynamic)); expected.push(IDKS_DEBUG_FLAG); expected.extend_from_slice(b"LVBS"); expected.extend_from_slice(&u32::try_from(cert.len()).unwrap().to_le_bytes()); @@ -395,7 +406,8 @@ mod tests { let (parsed_data, metadata) = endorsement[12..].split_at(data_len); assert_eq!(parsed_data, data); assert_eq!(&metadata[..16], uuid.to_le_bytes()); - let cert_len_offset = 16 + 4 + TA_DIGEST_LEN + 1 + 4; + assert_eq!(metadata[16 + 4 + TA_DIGEST_LEN], u8::from(ta_dynamic)); + let cert_len_offset = 16 + 4 + TA_DIGEST_LEN + 1 + 1 + 4; let cert_len = u32::from_le_bytes( metadata[cert_len_offset..cert_len_offset + 4] .try_into() @@ -412,11 +424,17 @@ mod tests { fn pta_endorsement_uses_global_certificate_and_reports_output_size() { use p384::ecdsa::{Signature, VerifyingKey, signature::Verifier}; - for cert in [TEST_CERT, &[]] { + for (cert, ta_dynamic) in [ + (TEST_CERT, false), + (TEST_CERT, true), + (&[][..], false), + (&[][..], true), + ] { let shim = crate::syscalls::tests::shim_builder() .with_ta_signing_cert(cert) .build(); - let task = shim.0.new_test_task(); + let mut task = shim.0.new_test_task(); + task.ta_dynamic = ta_dynamic; assert!(core::ptr::eq(task.global.ta_signing_cert, cert)); for data in [b"TA data".as_slice(), &[]] { @@ -434,6 +452,7 @@ mod tests { &task.ta_app_id, task.ta_svn, &task.ta_digest, + task.ta_dynamic, cert, ) .unwrap(); @@ -528,6 +547,7 @@ mod tests { &TeeUuid::NIL, 7, &[0xa5; TA_DIGEST_LEN], + false, TEST_CERT, ) .unwrap(); @@ -542,6 +562,7 @@ mod tests { 8, 12, 12 + b"TA data".len() + 16 + 4, + 12 + b"TA data".len() + 16 + 4 + TA_DIGEST_LEN, cert_start - 4, cert_start, ] { diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index a1e005c63..f39614f46 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -322,6 +322,8 @@ impl OpteeShim { // TODO: Populate this from trusted TA version metadata when available. ta_svn: 0, ta_digest, + // TODO: Set from the TA source when dynamic loading is supported. + ta_dynamic: false, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), @@ -1493,6 +1495,7 @@ struct Task { ta_svn: u32, /// SHA-256 digest of the raw TA binary. ta_digest: TaDigest, + ta_dynamic: bool, /// TEE cryptography state map tee_cryp_state_map: TeeCrypStateMap, /// TEE object map @@ -1662,6 +1665,7 @@ mod test_utils { ta_app_id: TeeUuid::default(), ta_svn: 0, ta_digest: [0; TA_DIGEST_LEN], + ta_dynamic: false, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), From 0de727025fa6f82e0cdb3a98a42d5d83c0eb4732 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 14 Sep 2026 20:40:11 +0000 Subject: [PATCH 13/13] nits --- litebox_shim_optee/src/idk.rs | 4 ++-- litebox_shim_optee/src/syscalls/ldelf.rs | 2 +- litebox_shim_optee/src/syscalls/pta.rs | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 8a5fad645..9530f040a 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -46,7 +46,7 @@ const IDKS_ENDORSEMENT_METADATA_LEN: usize = IDKS_ENDORSEMENT_MAGIC.len() + size_of::() // TA dynamic flag + size_of::() + ISOLATION_SOLUTION.len() - + size_of::(); // DER leaf certificate length + + size_of::(); // certificate length pub(crate) struct IdksPta; #[derive(Clone, Copy, TryFromPrimitive)] @@ -495,7 +495,7 @@ mod tests { } #[test] - fn pta_rejects_keyiso_parameters_and_invalid_buffers() { + fn pta_rejects_invalid_parameter_types_and_buffers() { let task = crate::syscalls::tests::init_platform(); let mut params = UteeParams::new(); params.set_type(0, TeeParamType::MemrefInput).unwrap(); diff --git a/litebox_shim_optee/src/syscalls/ldelf.rs b/litebox_shim_optee/src/syscalls/ldelf.rs index 5eb27e64d..e79ceba61 100644 --- a/litebox_shim_optee/src/syscalls/ldelf.rs +++ b/litebox_shim_optee/src/syscalls/ldelf.rs @@ -256,7 +256,7 @@ impl Task { "sys_open_bin" ); - if ta_uuid != self.ta_app_id && self.global.get_ta_bin(&ta_uuid).is_none() { + if self.global.get_ta_bin(&ta_uuid).is_none() { return Err(TeeResult::ItemNotFound); } let new_handle = self.ta_handle_map.insert(ta_uuid); diff --git a/litebox_shim_optee/src/syscalls/pta.rs b/litebox_shim_optee/src/syscalls/pta.rs index f55f44bb7..ae959ed6f 100644 --- a/litebox_shim_optee/src/syscalls/pta.rs +++ b/litebox_shim_optee/src/syscalls/pta.rs @@ -20,6 +20,7 @@ use sha2::Sha256; use zeroize::{Zeroize, Zeroizing}; struct SystemPta; + /// A common interface to interact with various PTAs including the system PTA. /// /// Add new PTAs here as needed.