diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 459c3d729..25ca9730f 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -27,6 +27,7 @@ p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", [features] default = ["platform_lvbs"] +idks-production = [] platform_linux_userland = ["litebox_platform_multiplex/platform_linux_userland_with_optee_syscall"] platform_lvbs = ["litebox_platform_multiplex/platform_lvbs_with_optee_syscall"] diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index 282aea63b..c9cecc0af 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -1,12 +1,29 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -use crate::NormalWorldMutPtr; -use litebox::{mm::linux::PAGE_SIZE, platform::CrngProvider, utils::TruncateExt}; +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, + platform::{CrngProvider, RawConstPointer as _, RawMutPointer as _}, + utils::TruncateExt, +}; use litebox_common_linux::errno::Errno; +use litebox_common_optee::{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 zerocopy::{ + Immutable, IntoBytes, + byteorder::{LittleEndian, U32}, +}; use zeroize::Zeroizing; const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; @@ -15,11 +32,465 @@ 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_NONCE_MAX_SIZE: usize = 64; +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"; +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::() + + size_of::() + + size_of::() + + TA_DIGEST_LEN + + size_of::() + + 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 { + 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, MemrefInput, MemrefOutput, 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 (nonce_addr, nonce_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)? + .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) + .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 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 key_pair = get_identity_signing_key_pair().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) + .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, + 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); + 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.extend_from_slice(ta_digest); + endorsement.push(IDKS_DEBUG_FLAG); + endorsement.extend_from_slice(ISOLATION_SOLUTION); + 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], +) -> 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], } @@ -149,25 +620,182 @@ 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 identity_signing_private_key_signs_and_verifies_message() { - use crate::syscalls::tests::init_platform; - use p384::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, + 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); - let message = b"IDK_S signing test message"; + 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 _task = init_platform(); - let private_key = generate_identity_signing_private_key().unwrap(); - assert!(is_valid_identity_signing_private_key(&private_key)); - let signing_key = SigningKey::from_slice(&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 parsed_signature = Signature::from_slice(&endorsement[signed_len..]).unwrap(); + verifying_key + .verify(&endorsement[..signed_len], &parsed_signature) + .unwrap(); + } - let signature: Signature = signing_key.sign(message); + #[test] + 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 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(); + + 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(); - verifying_key.verify(message, &signature).unwrap(); + verifying_key + .verify(&expected_plaintext, &signature) + .unwrap(); } } diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index cd316fed8..dadd32e94 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -29,6 +29,7 @@ use litebox_common_optee::{ TeeObjectInfo, TeeObjectType, TeeOperationMode, TeeResult, TeeUuid, UteeAttribute, }; use litebox_platform_multiplex::Platform; +use sha2::{Digest, Sha256}; pub mod loader; pub mod session; @@ -36,13 +37,14 @@ pub(crate) mod syscalls; pub mod msg_handler; -#[cfg(feature = "platform_lvbs")] pub mod idk; // Re-export session management types for convenience 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]; pub struct OpteeShimEntrypoints { task: Task, @@ -203,11 +205,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`). /// @@ -244,7 +241,7 @@ pub struct OpteeShim(Arc); 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 @@ -256,12 +253,20 @@ impl OpteeShim { ldelf_bin: &[u8], ta_uuid: TeeUuid, ) -> Result { + 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 { 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, + ta_digest, tee_cryp_state_map: TeeCrypStateMap::new(), tee_obj_map: TeeObjMap::new(), ta_handle_map: TaHandleMap::new(), @@ -288,8 +293,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, @@ -1323,6 +1326,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. @@ -1348,11 +1353,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 @@ -1362,9 +1369,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. @@ -1387,6 +1396,10 @@ struct Task { thread: ThreadState, /// TA UUID 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 @@ -1563,6 +1576,8 @@ mod test_utils { global: self.clone(), 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 5f46d67c9..45a698ce6 100644 --- a/litebox_shim_optee/src/syscalls/ldelf.rs +++ b/litebox_shim_optee/src/syscalls/ldelf.rs @@ -252,7 +252,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 ab0c426ba..fafbabcdb 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}; @@ -23,19 +22,20 @@ 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, } } @@ -44,6 +44,7 @@ impl PseudoTa { fn open_session(self, params: &UteeParams) -> Result { match self { Self::System => SystemPta::open_session(params), + Self::Idks => IdksPta::open_session(params), } } @@ -56,28 +57,44 @@ 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), } } fn close_session(self, task: &Task, session_id: u32) { 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 +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 { + 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> { task: &'a Task, pta: PseudoTa, @@ -221,16 +238,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) {