From a379436f1f9b118bff8053ccd729a7ca9d7a36e7 Mon Sep 17 00:00:00 2001 From: Michal Tarnacki Date: Thu, 2 Apr 2026 10:18:46 +0200 Subject: [PATCH] refactor(attestation): verify policy against TDINFO_STRUCT Adapt rebinding handshake to use init_tdinfo instead of init_policy: - rebinding.rs: rename params in pre_session_data_exchange functions - server_client.rs: rename init_td_report -> init_tdinfo in RATLS cert creation/verification, update pre_session_data parsing with init_tdinfo naming, compare mrowner at TDINFO offset 112..160 directly instead of digest_sha384(init_policy) - spdm_rsp.rs: rename pre_session_data parsing, compare mrowner directly Per GHCI 1.5, policy and SERVTD_EXT verification operates on TDINFO_STRUCT: - verify_servtd_hash(): accepts TDINFO bytes, returns TdInfo (not TdxReport), parses via MaybeUninit + copy_nonoverlapping - verify_init_tdreport() -> verify_init_tdinfo(): renamed, returns TdInfo - Add get_rtmrs_from_tdinfo() and setup_evaluation_data_with_tdinfo() - authenticate_rebinding_old(): 6 params instead of 7 (removed init_policy and init_td_report, replaced with init_tdinfo); calls verify_event_log() directly against RTMRs from init_tdinfo; uses local policy for TCB eval - Remove get_init_tcb_evaluation_info() and TD_INFO_OFFSET constant - Update call sites in server_client.rs and spdm_rsp.rs Co-authored-by: Grams, Stanislaw --- src/migtd/src/mig_policy.rs | 135 ++++++++++++++++++--------- src/migtd/src/migration/rebinding.rs | 12 +-- src/migtd/src/ratls/server_client.rs | 53 +++++------ src/migtd/src/spdm/spdm_rsp.rs | 26 +++--- 4 files changed, 135 insertions(+), 91 deletions(-) diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index 7fd8eccc..f36a22f3 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -87,7 +87,6 @@ mod v2 { const SERVTD_ATTR_IGNORE_RTMR3: u64 = 0x200_0000_0000; const SERVTD_TYPE_MIGTD: u16 = 0; - const TD_INFO_OFFSET: usize = 512; lazy_static! { pub static ref LOCAL_TCB_INFO: Once = Once::new(); @@ -136,13 +135,6 @@ mod v2 { .ok_or(PolicyError::InvalidParameter) } - pub fn get_init_tcb_evaluation_info( - init_report: &TdxReport, - init_policy: &VerifiedPolicy, - ) -> Result { - setup_evaluation_data_with_tdreport(init_report, init_policy) - } - /// Get reference to the global verified policy /// Returns None if the policy hasn't been initialized yet pub fn get_verified_policy() -> Option<&'static VerifiedPolicy<'static>> { @@ -273,13 +265,14 @@ mod v2 { } // Authenticate the migtd-old from migtd-new side + // Per GHCI 1.5: init_tdinfo is a TDINFO_STRUCT (not full TDREPORT), + // and there is no separate init_policy JSON blob. pub fn authenticate_rebinding_old( tdreport_src: &[u8], event_log_src: &[u8], mig_policy_src: &[u8], - init_policy: &[u8], + init_tdinfo: &[u8], init_event_log: &[u8], - init_td_report: &[u8], servtd_ext_src: &[u8], ) -> Result, PolicyError> { let policy_issuer_chain = get_policy_issuer_chain().ok_or(PolicyError::InvalidParameter)?; @@ -294,29 +287,27 @@ mod v2 { )?; let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; - // Verify the td report init / event log init / policy init + // Verify the init tdinfo against servtd_ext hash let servtd_ext_src_obj = ServtdExt::read_from_bytes(servtd_ext_src).ok_or(PolicyError::InvalidParameter)?; - let init_tdreport = verify_init_tdreport(init_td_report, &servtd_ext_src_obj)?; + let init_td_info = verify_init_tdinfo(init_tdinfo, &servtd_ext_src_obj)?; let _engine_svn = policy .servtd_tcb_mapping .get_engine_svn_by_measurements(&Measurements::new_from_bytes( - &init_tdreport.td_info.mrtd, - &init_tdreport.td_info.rtmr0, - &init_tdreport.td_info.rtmr1, + &init_td_info.mrtd, + &init_td_info.rtmr0, + &init_td_info.rtmr1, None, None, )) .ok_or(PolicyError::SvnMismatch)?; - let verified_policy_init = verify_policy_and_event_log( - init_event_log, - init_policy, - policy_issuer_chain, - &get_rtmrs_from_tdreport(&init_tdreport)?, - )?; - let relative_reference = - get_init_tcb_evaluation_info(&init_tdreport, &verified_policy_init)?; + // Verify init event log integrity against RTMRs from init tdinfo + verify_event_log(init_event_log, &get_rtmrs_from_tdinfo(&init_td_info)?) + .map_err(|_| PolicyError::InvalidEventLog)?; + + // Use local policy's tcb_mapping with init tdinfo measurements + let relative_reference = setup_evaluation_data_with_tdinfo(&init_td_info, policy)?; policy.policy_data.evaluate_policy_common( &evaluation_data_src, &relative_reference, @@ -466,52 +457,62 @@ mod v2 { Ok(tdx_report) } + /// Per GHCI 1.5: accepts TDINFO_STRUCT bytes directly (not full TDREPORT) fn verify_servtd_hash( - servtd_report: &[u8], + tdinfo_bytes: &[u8], servtd_attr: u64, init_servtd_hash: &[u8], - ) -> Result { - if servtd_report.len() < TD_INFO_OFFSET + size_of::() { + ) -> Result { + if tdinfo_bytes.len() < size_of::() { return Err(PolicyError::InvalidParameter); } - // Extract TdInfo from the report - let mut td_report = - TdxReport::read_from_bytes(servtd_report).ok_or(PolicyError::InvalidTdReport)?; + // Parse TdInfo directly from bytes + let mut td_info = { + let mut uninit = core::mem::MaybeUninit::::uninit(); + unsafe { + core::ptr::copy_nonoverlapping( + tdinfo_bytes.as_ptr(), + uninit.as_mut_ptr() as *mut u8, + size_of::(), + ); + uninit.assume_init() + } + }; if (servtd_attr & SERVTD_ATTR_IGNORE_ATTRIBUTES) != 0 { - td_report.td_info.attributes.fill(0); + td_info.attributes.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_XFAM) != 0 { - td_report.td_info.xfam.fill(0); + td_info.xfam.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_MRTD) != 0 { - td_report.td_info.mrtd.fill(0); + td_info.mrtd.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_MRCONFIGID) != 0 { - td_report.td_info.mrconfig_id.fill(0); + td_info.mrconfig_id.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_MROWNER) != 0 { - td_report.td_info.mrowner.fill(0); + td_info.mrowner.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_MROWNERCONFIG) != 0 { - td_report.td_info.mrownerconfig.fill(0); + td_info.mrownerconfig.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR0) != 0 { - td_report.td_info.rtmr0.fill(0); + td_info.rtmr0.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR1) != 0 { - td_report.td_info.rtmr1.fill(0); + td_info.rtmr1.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR2) != 0 { - td_report.td_info.rtmr2.fill(0); + td_info.rtmr2.fill(0); } if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR3) != 0 { - td_report.td_info.rtmr3.fill(0); + td_info.rtmr3.fill(0); } - let info_hash = digest_sha384(td_report.td_info.as_bytes()) - .map_err(|_| PolicyError::HashCalculation)?; + let info_hash = + digest_sha384(td_info.as_bytes()).map_err(|_| PolicyError::HashCalculation)?; // Calculate ServTD hash: SHA384(info_hash || type || attr) let mut buffer = [0u8; SHA384_DIGEST_SIZE + size_of::() + size_of::()]; @@ -531,20 +532,62 @@ mod v2 { return Err(PolicyError::InvalidTdReport); } - Ok(td_report) + Ok(td_info) } - fn verify_init_tdreport( - init_report: &[u8], + /// Per GHCI 1.5: verifies TDINFO_STRUCT against servtd_ext hash + fn verify_init_tdinfo( + init_tdinfo: &[u8], servtd_ext: &ServtdExt, - ) -> Result { + ) -> Result { verify_servtd_hash( - init_report, + init_tdinfo, u64::from_le_bytes(servtd_ext.init_attr), &servtd_ext.init_servtd_info_hash, ) } + fn get_rtmrs_from_tdinfo( + td_info: &TdInfo, + ) -> Result<[[u8; SHA384_DIGEST_SIZE]; 4], PolicyError> { + let mut rtmrs = [[0u8; SHA384_DIGEST_SIZE]; 4]; + rtmrs[0].copy_from_slice(&td_info.rtmr0); + rtmrs[1].copy_from_slice(&td_info.rtmr1); + rtmrs[2].copy_from_slice(&td_info.rtmr2); + rtmrs[3].copy_from_slice(&td_info.rtmr3); + Ok(rtmrs) + } + + fn setup_evaluation_data_with_tdinfo( + td_info: &TdInfo, + policy: &VerifiedPolicy, + ) -> Result { + let migtd_svn = policy.servtd_tcb_mapping.get_engine_svn_by_measurements( + &Measurements::new_from_bytes( + &td_info.mrtd, + &td_info.rtmr0, + &td_info.rtmr1, + None, + None, + ), + ); + + let migtd_tcb = migtd_svn.and_then(|svn| policy.servtd_identity.get_tcb_level_by_svn(svn)); + + Ok(PolicyEvaluationInfo { + tee_tcb_svn: None, + tcb_date: None, + tcb_status: None, + tcb_evaluation_number: None, + fmspc: None, + migtd_isvsvn: migtd_svn, + migtd_tcb_date: migtd_tcb.map(|tcb| tcb.tcb_date.clone()), + migtd_tcb_status: migtd_tcb.map(|tcb| tcb.tcb_status.clone()), + pck_crl_num: None, + root_ca_crl_num: None, + }) + } + fn setup_evaluation_data( fmspc: [u8; 6], suppl_data: &[u8], diff --git a/src/migtd/src/migration/rebinding.rs b/src/migtd/src/migration/rebinding.rs index a1be8c7e..b2a7fcaf 100644 --- a/src/migtd/src/migration/rebinding.rs +++ b/src/migtd/src/migration/rebinding.rs @@ -235,7 +235,7 @@ impl<'a> MigtdDataEntry<'a> { pub(super) async fn rebinding_old_pre_session_data_exchange( transport: &mut TransportType, - init_policy: &[u8], + init_tdinfo: &[u8], ) -> Result, MigrationResult> { let version = exchange_hello_packet(transport).await.map_err(|e| { log::error!( @@ -271,7 +271,7 @@ pub(super) async fn rebinding_old_pre_session_data_exchange( e })?; - send_pre_session_data_packet(init_policy, transport) + send_pre_session_data_packet(init_tdinfo, transport) .await .map_err(|e| { log::error!( @@ -336,11 +336,11 @@ pub(super) async fn rebinding_new_pre_session_data_exchange( e })?; - let init_policy = receive_pre_session_data_packet(transport) + let init_tdinfo = receive_pre_session_data_packet(transport) .await .map_err(|e| { log::error!( - "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + "pre_session_data_exchange: receive init_tdinfo error: {:?}\n", e ); e @@ -365,8 +365,8 @@ pub(super) async fn rebinding_new_pre_session_data_exchange( let mut policy_buffer = Vec::new(); policy_buffer.extend_from_slice(&(remote_policy.len() as u32).to_le_bytes()); policy_buffer.extend_from_slice(&remote_policy); - policy_buffer.extend_from_slice(&(init_policy.len() as u32).to_le_bytes()); - policy_buffer.extend_from_slice(&init_policy); + policy_buffer.extend_from_slice(&(init_tdinfo.len() as u32).to_le_bytes()); + policy_buffer.extend_from_slice(&init_tdinfo); Ok(policy_buffer) } diff --git a/src/migtd/src/ratls/server_client.rs b/src/migtd/src/ratls/server_client.rs index b805c167..dfcf4861 100644 --- a/src/migtd/src/ratls/server_client.rs +++ b/src/migtd/src/ratls/server_client.rs @@ -184,7 +184,7 @@ pub fn client_rebinding( stream: T, remote_policy: Vec, init_policy_hash: &[u8], - init_td_report: &[u8], + init_tdinfo: &[u8], init_event_log: &[u8], servtd_ext: &ServtdExt, ) -> Result> { @@ -198,7 +198,7 @@ pub fn client_rebinding( let certs = create_certificate_for_rebinding_old( &signing_key, init_policy_hash, - init_td_report, + init_tdinfo, init_event_log, servtd_ext, ) @@ -409,7 +409,7 @@ fn create_certificate_for_client(signing_key: &EcdsaPk) -> Result<(Vec, Vec< fn create_certificate_for_rebinding_old( signing_key: &EcdsaPk, init_policy_hash: &[u8], - init_tdreport: &[u8], + init_tdinfo: &[u8], init_event_log: &[u8], servtd_ext: &ServtdExt, ) -> Result> { @@ -505,18 +505,15 @@ fn create_certificate_for_rebinding_old( e })? .add_extension( - Extension::new( - EXTNID_MIGTD_TDREPORT_INIT, - Some(false), - Some(&init_tdreport), - ) - .map_err(|e| { - log::error!( - "gen_cert policy_v2 add_extension failed with error {:?}.\n", + Extension::new(EXTNID_MIGTD_TDREPORT_INIT, Some(false), Some(&init_tdinfo)).map_err( + |e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); e - ); - e - })?, + }, + )?, ) .map_err(|e| { log::error!( @@ -992,9 +989,10 @@ mod verify { log::error!("Failed to find expected policy hash extension.\n"); CryptoError::ParseCertificate })?; - let init_td_report = + // Per GHCI 1.5: init extension now carries TDINFO_STRUCT instead of full TDREPORT + let init_tdinfo = find_extension(extensions, &EXTNID_MIGTD_TDREPORT_INIT).ok_or_else(|| { - log::error!("Failed to find init tdreport extension.\n"); + log::error!("Failed to find init tdinfo extension.\n"); CryptoError::ParseCertificate })?; let init_event_log = @@ -1002,6 +1000,7 @@ mod verify { log::error!("Failed to find init event log extension.\n"); CryptoError::ParseCertificate })?; + // Per GHCI 1.5: init_policy_hash is now mrowner from the initial TDINFO_STRUCT let init_policy_hash = find_extension(extensions, &EXTNID_MIGTD_INIT_POLICY_HASH) .ok_or_else(|| { log::error!("Failed to find init policy hash extension.\n"); @@ -1012,6 +1011,7 @@ mod verify { CryptoError::ParseCertificate })?; + // Parse pre_session_data: [remote_policy_size(4) | remote_policy | init_tdinfo_size(4) | init_tdinfo] let remote_policy_size = u32::from_le_bytes( pre_session_data .get(..4) @@ -1024,18 +1024,19 @@ mod verify { let remote_policy = pre_session_data.get(4..4 + remote_policy_size).ok_or( CryptoError::TlsVerifyPeerCert(INVALID_MIG_POLICY_ERROR.to_string()), )?; - let init_policy_offset = 4 + remote_policy_size; - let init_policy_size = u32::from_le_bytes( + // Per GHCI 1.5: second item is init_tdinfo (was init_policy) + let init_tdinfo_offset = 4 + remote_policy_size; + let init_tdinfo_size = u32::from_le_bytes( pre_session_data - .get(init_policy_offset..4 + init_policy_offset) + .get(init_tdinfo_offset..4 + init_tdinfo_offset) .ok_or(CryptoError::TlsVerifyPeerCert( INVALID_MIG_POLICY_ERROR.to_string(), ))? .try_into() .unwrap(), ) as usize; - let init_policy = pre_session_data - .get(init_policy_offset + 4..init_policy_offset + 4 + init_policy_size) + let _init_tdinfo_from_pre_session = pre_session_data + .get(init_tdinfo_offset + 4..init_tdinfo_offset + 4 + init_tdinfo_size) .ok_or(CryptoError::TlsVerifyPeerCert( INVALID_MIG_POLICY_ERROR.to_string(), ))?; @@ -1046,9 +1047,10 @@ mod verify { INVALID_MIG_POLICY_ERROR.to_string(), )); } - let exact_init_policy_hash = digest_sha384(init_policy)?; - if init_policy_hash != exact_init_policy_hash.as_slice() { - log::error!("Invalid init rebinding policy.\n"); + // Per GHCI 1.5: init_policy_hash is mrowner from TDINFO — compare directly + // (no longer a hash of a policy blob) + if init_policy_hash != init_tdinfo.get(112..160).unwrap_or(&[]) { + log::error!("Invalid init policy hash (mrowner mismatch).\n"); return Err(CryptoError::TlsVerifyPeerCert( INVALID_MIG_POLICY_ERROR.to_string(), )); @@ -1058,9 +1060,8 @@ mod verify { td_report, event_log, remote_policy, - init_policy, + init_tdinfo, init_event_log, - init_td_report, servtd_ext, ); diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 4bed1d68..64817040 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -1052,16 +1052,17 @@ pub fn handle_exchange_rebind_attest_info_req( let remote_policy = pre_session_data .get(4..4 + remote_policy_size) .ok_or(MigrationResult::InvalidPolicyError)?; - let init_policy_offset = 4 + remote_policy_size; - let init_policy_size = u32::from_le_bytes( + // Per GHCI 1.5: second item in pre_session_data is init_tdinfo (was init_policy) + let init_tdinfo_offset = 4 + remote_policy_size; + let init_tdinfo_size = u32::from_le_bytes( pre_session_data - .get(init_policy_offset..4 + init_policy_offset) + .get(init_tdinfo_offset..4 + init_tdinfo_offset) .ok_or(MigrationResult::InvalidPolicyError)? .try_into() .unwrap(), ) as usize; - let init_policy = pre_session_data - .get(init_policy_offset + 4..init_policy_offset + 4 + init_policy_size) + let _init_tdinfo_from_pre_session = pre_session_data + .get(init_tdinfo_offset + 4..init_tdinfo_offset + 4 + init_tdinfo_size) .ok_or(MigrationResult::InvalidPolicyError)?; let remote_policy_hash = @@ -1073,12 +1074,12 @@ pub fn handle_exchange_rebind_attest_info_req( return Err(SPDM_STATUS_INVALID_MSG_FIELD); } - let mig_policy_init_hash = - digest_sha384(init_policy).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; - if mig_policy_init_hash_src != mig_policy_init_hash.as_slice() { - error!( - "The received mig policy init hash does not match the expected init policy hash!\n" - ); + // Per GHCI 1.5: init_policy_hash is mrowner from TDINFO — compare directly + let mrowner = td_report_init_vec + .get(112..160) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if mig_policy_init_hash_src != mrowner { + error!("The received mig policy init hash does not match mrowner from init tdinfo!\n"); return Err(SPDM_STATUS_INVALID_MSG_FIELD); } @@ -1086,9 +1087,8 @@ pub fn handle_exchange_rebind_attest_info_req( &td_report_src_vec, &event_log_src_vec, remote_policy, - init_policy, - &event_log_init_vec, &td_report_init_vec, + &event_log_init_vec, &servtd_ext_vec, ); if let Err(e) = &policy_check_result {