From e8f9b528db3bfc85337143b25ba720485edb06e5 Mon Sep 17 00:00:00 2001 From: Jiaqi Gao Date: Tue, 6 Jan 2026 21:28:35 +0800 Subject: [PATCH 1/7] migtd: provide rebinding support Signed-off-by: Jiaqi Gao --- src/migtd/src/migration/mod.rs | 6 +- src/migtd/src/migration/rebinding.rs | 691 ++++++++++++++++++++++++++ src/migtd/src/migration/servtd_ext.rs | 147 ++++++ src/migtd/src/migration/session.rs | 24 +- src/migtd/src/migration/transport.rs | 31 +- 5 files changed, 877 insertions(+), 22 deletions(-) create mode 100644 src/migtd/src/migration/rebinding.rs create mode 100644 src/migtd/src/migration/servtd_ext.rs diff --git a/src/migtd/src/migration/mod.rs b/src/migtd/src/migration/mod.rs index 59c5670f..52ba0e3e 100644 --- a/src/migtd/src/migration/mod.rs +++ b/src/migtd/src/migration/mod.rs @@ -7,6 +7,9 @@ pub mod event; pub mod logging; #[cfg(feature = "policy_v2")] pub mod pre_session_data; +#[cfg(all(feature = "main", feature = "policy_v2", feature = "vmcall-raw"))] +pub mod rebinding; +pub mod servtd_ext; #[cfg(feature = "main")] pub mod session; #[cfg(feature = "main")] @@ -220,7 +223,8 @@ impl From for MigrationResult { RatlsError::Crypto(_) | RatlsError::X509(_) | RatlsError::InvalidEventlog - | RatlsError::InvalidPolicy => MigrationResult::SecureSessionError, + | RatlsError::InvalidPolicy + | RatlsError::GenerateCertificate => MigrationResult::SecureSessionError, RatlsError::TdxModule(_) => MigrationResult::TdxModuleError, RatlsError::GetQuote | RatlsError::VerifyQuote => { MigrationResult::MutualAttestationError diff --git a/src/migtd/src/migration/rebinding.rs b/src/migtd/src/migration/rebinding.rs new file mode 100644 index 00000000..c56015ad --- /dev/null +++ b/src/migtd/src/migration/rebinding.rs @@ -0,0 +1,691 @@ +// Copyright (c) 2025 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +use alloc::{boxed::Box, vec::Vec}; +use core::mem::MaybeUninit; +use core::time::Duration; +use crypto::{ + tls::SecureChannel, + x509::{Certificate, Decode}, + SHA384_DIGEST_SIZE, +}; +use ring::rand::{SecureRandom, SystemRandom}; +use tdx_tdcall::tdx::{tdcall_servtd_rebind_approve, tdcall_vm_write}; + +use crate::migration::servtd_ext::read_servtd_ext; +use crate::{event_log, migration::transport::*}; +use crypto::hash::digest_sha384; + +use crate::{ + config, + migration::pre_session_data::{ + exchange_hello_packet, receive_pre_session_data_packet, receive_start_session_packet, + send_pre_session_data_packet, send_start_session_packet, + }, +}; + +use crate::{ + driver::ticks::with_timeout, + migration::{ + servtd_ext::{write_approved_servtd_ext_hash, ServtdExt}, + MigrationResult, + }, + ratls::{self, find_extension, EXTNID_MIGTD_SERVTD_EXT}, +}; +pub use tdx_tdcall::tdx::TargetTdUuid; + +/// Rebind session token held by the Service TD. This field is written by the ServiceTD +/// executing TDG.VM.WR. +pub const TDCS_FIELD_SERVTD_REBIND_ACCEPT_TOKEN: u64 = 0x191000030000021E; +/// The intended SERVTD_ATTR for the Service TD about to be bound to the TD. +pub const TDCS_FIELD_SERVTD_REBIND_ATTR: u64 = 0x1910000300000222; + +const TLS_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds + // FIXME: Need VMM provide socket information +const MIGTD_DATA_SIGNATURE: &[u8] = b"MIGTDATA"; +const MIGTD_DATA_TYPE_INIT_MIG_POLICY: u32 = 0; +const MIGTD_DATA_TYPE_INIT_TD_REPORT: u32 = 1; +const MIGTD_DATA_TYPE_INIT_EVENT_LOG: u32 = 2; + +const MIGTD_REBIND_OP_PREPARE: u8 = 0; +const MIGTD_REBIND_OP_FINALIZE: u8 = 1; + +#[repr(C)] +pub struct RebindingToken { + pub token: [u8; 32], + pub target_td_uuid: TargetTdUuid, +} + +impl RebindingToken { + pub fn read_from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < size_of::() { + return None; + } + + let mut uinit: MaybeUninit = MaybeUninit::uninit(); + // Safety: MaybeUninit has same layout with RebindingToken + Some(unsafe { + core::ptr::copy_nonoverlapping( + bytes.as_ptr(), + uinit.as_mut_ptr() as *mut u8, + size_of::(), + ); + uinit.assume_init() + }) + } + + pub fn as_bytes(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) } + } +} + +pub struct RebindingInfo<'a> { + pub mig_request_id: u64, + pub rebinding_src: u8, + pub has_init_data: u8, + pub operation: u8, + pub target_td_uuid: [u64; 4], + pub binding_handle: u64, + pub init_migtd_data: Option>, +} + +impl<'a> RebindingInfo<'a> { + pub fn read_from_bytes(b: &'a [u8]) -> Option { + // Check the length of input and the reserved fields + if b.len() < 56 || b[9..16] != [0; 7] || b[11..16] != [0; 4] { + return None; + } + let mig_request_id = u64::from_le_bytes(b[..8].try_into().unwrap()); + let rebinding_src = b[8]; + let has_init_data = b[9]; + let operation = b[10]; + + let target_td_uuid: [u64; 4] = core::array::from_fn(|i| { + let offset = 16 + i * 8; + u64::from_le_bytes(b[offset..offset + 8].try_into().unwrap()) + }); + let binding_handle = u64::from_le_bytes(b[48..56].try_into().unwrap()); + + let mut init_migtd_data = None; + if has_init_data == 1 { + // Returns None if `has_init_data` is set but reading initialization data from the input buffer fails. + init_migtd_data = Some(InitData::read_from_bytes(&b[56..])?); + } + + Some(Self { + mig_request_id, + rebinding_src, + has_init_data, + operation, + target_td_uuid, + binding_handle, + init_migtd_data, + }) + } +} + +pub struct InitData<'a> { + pub init_report: Vec, + pub init_policy: &'a [u8], + pub init_event_log: &'a [u8], +} + +impl<'a> InitData<'a> { + pub fn read_from_bytes(b: &'a [u8]) -> Option { + if b.len() < 20 || &b[..8] != MIGTD_DATA_SIGNATURE { + return None; + } + + let version = u32::from_le_bytes(b[8..12].try_into().unwrap()); + let length = u32::from_le_bytes(b[12..16].try_into().unwrap()); + let num_entries = u32::from_le_bytes(b[16..20].try_into().unwrap()); + + if version != 0x00010000 || b.len() < length as usize { + return None; + } + + let mut offset = 20; + let mut init_report = None; + let mut init_policy = None; + let mut init_event_log = None; + for _ in 0..num_entries { + let entry = MigtdDateEntry::read_from_bytes(&b[offset..])?; + match entry.r#type { + MIGTD_DATA_TYPE_INIT_MIG_POLICY => init_policy = Some(entry.value), + MIGTD_DATA_TYPE_INIT_TD_REPORT => { + if entry.value.len() > 1024 { + return None; + } + init_report = Some(entry.value.to_vec()) + } + MIGTD_DATA_TYPE_INIT_EVENT_LOG => init_event_log = Some(entry.value), + _ => return None, + } + offset += entry.length as usize + 8; + } + + Some(Self { + init_report: init_report?, + init_policy: init_policy?, + init_event_log: init_event_log?, + }) + } + + pub fn get_from_local() -> Option { + Some(Self { + init_report: tdx_tdcall::tdreport::tdcall_report(&[0u8; 64]) + .ok()? + .as_bytes() + .to_vec(), + init_policy: config::get_policy()?, + init_event_log: event_log::get_event_log()?, + }) + } +} + +pub struct MigtdDateEntry<'a> { + pub r#type: u32, + pub length: u32, + pub value: &'a [u8], +} + +impl<'a> MigtdDateEntry<'a> { + pub fn read_from_bytes(b: &'a [u8]) -> Option { + if b.len() < 8 { + return None; + } + + let r#type = u32::from_le_bytes(b[0..4].try_into().unwrap()); + let length = u32::from_le_bytes(b[4..8].try_into().unwrap()); + + if b.len() < length as usize + 8 { + return None; + } + + Some(Self { + r#type, + length, + value: &b[8..8 + length as usize], + }) + } +} + +pub(super) async fn rebinding_old_pre_session_data_exchange( + transport: &mut TransportType, + init_policy: &[u8], +) -> Result, MigrationResult> { + let version = exchange_hello_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: exchange_hello_packet error: {:?}\n", + e + ); + e + })?; + log::info!("Pre-Session-Message Version: 0x{:04x}\n", version); + + let policy = config::get_policy() + .ok_or(MigrationResult::InvalidParameter) + .map_err(|e| { + log::error!("pre_session_data_exchange: get_policy error: {:?}\n", e); + e + })?; + send_pre_session_data_packet(policy, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + let remote_policy = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + + send_pre_session_data_packet(init_policy, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + + send_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: send_start_session_packet error: {:?}\n", + e + ); + e + })?; + receive_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_start_session_packet error: {:?}\n", + e + ); + e + })?; + + Ok(remote_policy) +} + +pub(super) async fn rebinding_new_pre_session_data_exchange( + transport: &mut TransportType, +) -> Result, MigrationResult> { + let version = exchange_hello_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: exchange_hello_packet error: {:?}\n", + e + ); + e + })?; + log::info!("Pre-Session-Message Version: 0x{:04x}\n", version); + + let policy = config::get_policy() + .ok_or(MigrationResult::InvalidParameter) + .map_err(|e| { + log::error!("pre_session_data_exchange: get_policy error: {:?}\n", e); + e + })?; + send_pre_session_data_packet(policy, transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + let remote_policy = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + + let init_policy = receive_pre_session_data_packet(transport) + .await + .map_err(|e| { + log::error!( + "pre_session_data_exchange: send_pre_session_data_packet error: {:?}\n", + e + ); + e + })?; + + send_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: send_start_session_packet error: {:?}\n", + e + ); + e + })?; + receive_start_session_packet(transport).await.map_err(|e| { + log::error!( + "pre_session_data_exchange: receive_start_session_packet error: {:?}\n", + e + ); + e + })?; + + // FIXME: Refactor the TLS verification callback to enable easier access to pre-session data. + 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); + + Ok(policy_buffer) +} + +pub async fn start_rebinding( + info: &RebindingInfo<'_>, + data: &mut Vec, +) -> Result<(), MigrationResult> { + let mut transport = setup_transport(info.mig_request_id, data).await?; + + // Exchange policy firstly because of the message size limitation of TLS protocol + const PRE_SESSION_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds + if info.rebinding_src == 1 { + let local_data = InitData::get_from_local().ok_or(MigrationResult::InvalidParameter)?; + let init_migtd_data = info + .init_migtd_data + .as_ref() + .or(Some(&local_data)) + .ok_or(MigrationResult::InvalidParameter)?; + let remote_policy = Box::pin(with_timeout( + PRE_SESSION_TIMEOUT, + rebinding_old_pre_session_data_exchange(&mut transport, init_migtd_data.init_policy), + )) + .await + .map_err(|e| { + log::error!( + "start_rebinding: rebinding_old_pre_session_data_exchange timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!( + "start_rebinding: rebinding_old_pre_session_data_exchange error: {:?}\n", + e + ); + e + })?; + #[cfg(not(feature = "spdm_attestation"))] + match info.operation { + MIGTD_REBIND_OP_PREPARE => { + rebinding_old_prepare(transport, info, &init_migtd_data, data, remote_policy) + .await? + } + MIGTD_REBIND_OP_FINALIZE => rebinding_old_finalize(info, data).await?, + _ => return Err(MigrationResult::InvalidParameter), + } + } else { + let pre_session_data = Box::pin(with_timeout( + PRE_SESSION_TIMEOUT, + rebinding_new_pre_session_data_exchange(&mut transport), + )) + .await + .map_err(|e| { + log::error!( + "start_rebinding: rebinding_new_pre_session_data_exchange timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!( + "start_rebinding: rebinding_new_pre_session_data_exchange error: {:?}\n", + e + ); + e + })?; + + #[cfg(not(feature = "spdm_attestation"))] + match info.operation { + MIGTD_REBIND_OP_PREPARE => { + rebinding_new_prepare(transport, info, data, pre_session_data).await? + } + MIGTD_REBIND_OP_FINALIZE => rebinding_new_finalize(info, data).await?, + _ => return Err(MigrationResult::InvalidParameter), + } + } + + #[cfg(feature = "vmcall-raw")] + { + use crate::migration::logging::entrylog; + + entrylog( + &format!("Complete rebinding and report status\n").into_bytes(), + log::Level::Info, + info.mig_request_id, + ); + log::info!("Complete rebinding and report status\n"); + } + Ok(()) +} + +pub async fn rebinding_old_prepare( + transport: TransportType, + info: &RebindingInfo<'_>, + init_migtd_data: &InitData<'_>, + data: &mut Vec, + remote_policy: Vec, +) -> Result<(), MigrationResult> { + let servtd_ext = read_servtd_ext(info.binding_handle, &info.target_td_uuid)?; + let init_policy_hash = digest_sha384(init_migtd_data.init_policy)?; + + // TLS client + let mut ratls_client = ratls::client_rebinding( + transport, + remote_policy, + &init_policy_hash, + &init_migtd_data.init_report, + init_migtd_data.init_event_log, + &servtd_ext, + ) + .map_err(|_| { + #[cfg(feature = "vmcall-raw")] + data.extend_from_slice( + &format!( + "Error: rebinding_old(): Failed in ratls transport. Migration ID: {:x}\n", + info.mig_request_id, + ) + .into_bytes(), + ); + log::error!( + "rebinding_old(): Failed in ratls transport. Migration ID: {}\n", + info.mig_request_id + ); + MigrationResult::SecureSessionError + })?; + + let rebind_token = create_rebind_token(info)?; + tls_send_rebind_token(&mut ratls_client, &rebind_token).await?; + + approve_rebinding(info, &rebind_token)?; + + shutdown_transport(ratls_client.transport_mut(), info.mig_request_id, data).await?; + Ok(()) +} + +pub async fn rebinding_old_finalize( + _info: &RebindingInfo<'_>, + _data: &mut Vec, +) -> Result<(), MigrationResult> { + Ok(()) +} + +async fn rebinding_new_prepare( + transport: TransportType, + info: &RebindingInfo<'_>, + data: &mut Vec, + pre_session_data: Vec, +) -> Result<(), MigrationResult> { + // TLS server + let mut ratls_server = ratls::server_rebinding(transport, pre_session_data).map_err(|_| { + #[cfg(feature = "vmcall-raw")] + data.extend_from_slice( + &format!( + "Error: rebinding_new(): Failed in ratls transport. Migration ID: {:x}\n", + info.mig_request_id + ) + .into_bytes(), + ); + log::error!( + "rebinding_new(): Failed in ratls transport. Migration ID: {}\n", + info.mig_request_id + ); + MigrationResult::SecureSessionError + })?; + + let servtd_ext = get_servtd_ext_from_cert(&ratls_server.peer_certs())?; + let rebind_token = tls_receive_rebind_token(&mut ratls_server).await?; + if rebind_token.target_td_uuid != info.target_td_uuid { + return Err(MigrationResult::InvalidParameter); + } + + write_rebinding_session_token(&rebind_token.token)?; + write_servtd_rebind_attr(&servtd_ext.cur_servtd_attr)?; + write_approved_servtd_ext_hash(&servtd_ext.calculate_approved_servtd_ext_hash()?)?; + + shutdown_transport(ratls_server.transport_mut(), info.mig_request_id, data).await?; + Ok(()) +} + +async fn rebinding_new_finalize( + _info: &RebindingInfo<'_>, + _data: &mut Vec, +) -> Result<(), MigrationResult> { + write_rebinding_session_token(&[0u8; 32])?; + write_approved_servtd_ext_hash(&[0u8; SHA384_DIGEST_SIZE])?; + Ok(()) +} + +pub fn write_rebinding_session_token(rebind_token: &[u8]) -> Result<(), MigrationResult> { + if rebind_token.len() != 32 { + return Err(MigrationResult::InvalidParameter); + } + + for (idx, chunk) in rebind_token.chunks_exact(size_of::()).enumerate() { + let elem = u64::from_le_bytes(chunk.try_into().unwrap()); + tdcall_vm_write(TDCS_FIELD_SERVTD_REBIND_ACCEPT_TOKEN + idx as u64, elem, 0)?; + } + + Ok(()) +} + +pub fn write_servtd_rebind_attr(servtd_attr: &[u8]) -> Result<(), MigrationResult> { + if servtd_attr.len() != 8 { + return Err(MigrationResult::InvalidParameter); + } + + let elem = u64::from_le_bytes(servtd_attr.try_into().unwrap()); + tdcall_vm_write(TDCS_FIELD_SERVTD_REBIND_ATTR, elem, 0)?; + + Ok(()) +} + +pub fn approve_rebinding( + info: &RebindingInfo, + rebind_token: &RebindingToken, +) -> Result<(), MigrationResult> { + tdcall_servtd_rebind_approve( + info.binding_handle, + &rebind_token.token, + &info.target_td_uuid, + )?; + Ok(()) +} + +fn get_servtd_ext_from_cert(certs: &Option>) -> Result { + if let Some(cert_chain) = certs { + if cert_chain.is_empty() { + return Err(MigrationResult::SecureSessionError); + } + + let cert = Certificate::from_der(cert_chain[0]) + .map_err(|_| MigrationResult::SecureSessionError)?; + + let extensions = cert + .tbs_certificate + .extensions + .as_ref() + .ok_or(MigrationResult::SecureSessionError)?; + + let servtd_ext = find_extension(extensions, &EXTNID_MIGTD_SERVTD_EXT) + .ok_or(MigrationResult::SecureSessionError)?; + + ServtdExt::read_from_bytes(servtd_ext).ok_or(MigrationResult::InvalidParameter) + } else { + Err(MigrationResult::SecureSessionError) + } +} + +fn create_rebind_token(info: &RebindingInfo) -> Result { + let mut token = [0u8; 32]; + let rng = SystemRandom::new(); + rng.fill(&mut token) + .map_err(|_| MigrationResult::InvalidParameter)?; + + Ok(RebindingToken { + token, + target_td_uuid: info.target_td_uuid, + }) +} + +async fn tls_send_rebind_token( + tls_session: &mut SecureChannel, + rebind_token: &RebindingToken, +) -> Result<(), MigrationResult> { + // MigTD old send rebinding session token to peer + with_timeout( + TLS_TIMEOUT, + tls_session_write_all(tls_session, rebind_token.as_bytes()), + ) + .await + .map_err(|e| { + log::error!( + "tls_send_rebind_token: tls_session_write_all timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!( + "tls_send_rebind_token: tls_session_write_all error: {:?}\n", + e + ); + e + })?; + Ok(()) +} + +async fn tls_receive_rebind_token( + tls_session: &mut SecureChannel, +) -> Result { + let mut data = [0u8; size_of::()]; + // MigTD old send rebinding session token to peer + with_timeout(TLS_TIMEOUT, tls_session_read_exact(tls_session, &mut data)) + .await + .map_err(|e| { + log::error!( + "tls_receive_rebind_token: tls_session_read_exact timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!( + "tls_receive_rebind_token: tls_session_read_exact error: {:?}\n", + e + ); + e + })?; + + let rebind_token = + RebindingToken::read_from_bytes(&data).ok_or(MigrationResult::InvalidParameter)?; + Ok(rebind_token) +} + +async fn tls_session_write_all( + tls_session: &mut SecureChannel, + data: &[u8], +) -> Result<(), MigrationResult> { + let mut sent = 0; + while sent < data.len() { + let n = tls_session + .write(&data[sent..]) + .await + .map_err(|_| MigrationResult::SecureSessionError)?; + sent += n; + } + Ok(()) +} + +async fn tls_session_read_exact( + tls_session: &mut SecureChannel, + data: &mut [u8], +) -> Result<(), MigrationResult> { + let mut recvd = 0; + while recvd < data.len() { + let n = tls_session + .read(&mut data[recvd..]) + .await + .map_err(|_| MigrationResult::NetworkError)?; + recvd += n; + } + Ok(()) +} diff --git a/src/migtd/src/migration/servtd_ext.rs b/src/migtd/src/migration/servtd_ext.rs new file mode 100644 index 00000000..15e064a7 --- /dev/null +++ b/src/migtd/src/migration/servtd_ext.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2025 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +use alloc::vec::Vec; +use core::mem::MaybeUninit; +use crypto::{hash::digest_sha384, SHA384_DIGEST_SIZE}; +use tdx_tdcall::tdx::{tdcall_servtd_rd, tdcall_vm_write}; + +use crate::migration::MigrationResult; + +/// SERVTD_EXT_STRUCT fields in target TD’s TDCS +pub const TDCS_FIELD_SERVTD_INIT_SERVTD_INFO_HASH: u64 = 0x191000030000020E; +pub const TDCS_FIELD_SERVTD_INIT_ATTR: u64 = 0x191000030000020D; +pub const TDCS_FIELD_INIT_CPUSVN: u64 = 0x1110000300000060; +pub const TDCS_FIELD_INIT_TEE_TCB_SVN: u64 = 0x1110000300000062; +pub const TDCS_FIELD_INIT_TEE_MODEL: u64 = 0x1110000200000064; +/// SERVTD_EXT_STRUCT fields in Service TDs Binding Table Entry in target TD’s TDCS +pub const TDCS_FIELD_SERVTD_INFO_HASH: u64 = 0x1910000300000207; +pub const TDCS_FIELD_SERVTD_ATTR: u64 = 0x1910000300000202; + +/// Hash of SERVTD_EXT that the new Service TD 0 (i.e., rebound Service TD or MigTD on the +/// destination platform) believes is the SERVTD_EXT for this TD. +pub const TDCS_FIELD_SERVTD_ACCEPT_SERVTD_EXT_HASH: u64 = 0x1910000300000214; + +#[repr(C)] +pub struct ServtdExt { + pub init_servtd_info_hash: [u8; 48], + pub init_attr: [u8; 8], + reserved: [u8; 8], + pub init_cpusvn: [u8; 16], + pub init_tee_tcb_svn: [u8; 16], + pub init_tee_model: [u8; 12], + pub cur_servtd_info_hash: [u8; 48], + pub cur_servtd_attr: [u8; 8], + reserved2: [u8; 104], +} + +impl ServtdExt { + pub fn read_from_bytes(bytes: &[u8]) -> Option { + if bytes.len() < size_of::() { + return None; + } + + let mut uninit = MaybeUninit::::uninit(); + // SAFETY: `MaybeUninit` has same memory layout with T. + Some(unsafe { + core::ptr::copy_nonoverlapping( + bytes.as_ptr(), + uninit.as_mut_ptr() as *mut u8, + core::mem::size_of::(), + ); + uninit.assume_init() + }) + } + + pub fn as_bytes(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, size_of::()) } + } + + pub fn calculate_approved_servtd_ext_hash(mut self) -> Result, MigrationResult> { + self.cur_servtd_attr.fill(0); + self.cur_servtd_info_hash.fill(0); + digest_sha384(self.as_bytes()).map_err(|_| MigrationResult::InvalidParameter) + } +} + +#[repr(C)] +pub struct TeeModel { + custom: u16, + platform_id: u16, + fm: u32, + reservtd: [u8; 8], +} + +pub fn read_servtd_ext( + binding_handle: u64, + target_td_uuid: &[u64], +) -> Result { + let read_field = + |field_base: u64, elem_size: usize, buf: &mut [u8]| -> Result<(), MigrationResult> { + for (idx, chunk) in buf.chunks_mut(elem_size).enumerate() { + let result = + tdcall_servtd_rd(binding_handle, field_base + idx as u64, &target_td_uuid)?; + let bytes = result.content.to_le_bytes(); + chunk.copy_from_slice(&bytes[..chunk.len()]); + } + + Ok(()) + }; + + let mut init_servtd_info_hash = [0u8; 48]; + let mut init_attr = [0u8; 8]; + let mut init_cpusvn = [0u8; 16]; + let mut init_tee_tcb_svn = [0u8; 16]; + let mut init_tee_model = [0u8; 12]; + let mut cur_servtd_info_hash = [0u8; 48]; + let mut cur_servtd_attr = [0u8; 8]; + + read_field( + TDCS_FIELD_SERVTD_INIT_SERVTD_INFO_HASH, + 8, + &mut init_servtd_info_hash, + )?; + read_field(TDCS_FIELD_SERVTD_INIT_ATTR, 8, &mut init_attr)?; + read_field(TDCS_FIELD_INIT_CPUSVN, 8, &mut init_cpusvn)?; + read_field(TDCS_FIELD_INIT_TEE_TCB_SVN, 8, &mut init_tee_tcb_svn)?; + read_field(TDCS_FIELD_INIT_TEE_MODEL, 4, &mut init_tee_model)?; + read_field(TDCS_FIELD_SERVTD_INFO_HASH, 8, &mut cur_servtd_info_hash)?; + read_field(TDCS_FIELD_SERVTD_ATTR, 8, &mut cur_servtd_attr)?; + + Ok(ServtdExt { + init_servtd_info_hash, + init_attr, + init_cpusvn, + init_tee_tcb_svn, + init_tee_model, + cur_servtd_info_hash, + cur_servtd_attr, + reserved: [0u8; 8], + reserved2: [0u8; 104], + }) +} + +pub fn write_approved_servtd_ext_hash(servtd_ext_hash: &[u8]) -> Result<(), MigrationResult> { + if servtd_ext_hash.len() != SHA384_DIGEST_SIZE { + return Err(MigrationResult::InvalidParameter); + } + + for (idx, chunk) in servtd_ext_hash.chunks_exact(size_of::()).enumerate() { + let elem = u64::from_le_bytes(chunk.try_into().unwrap()); + tdcall_vm_write( + TDCS_FIELD_SERVTD_ACCEPT_SERVTD_EXT_HASH + idx as u64, + elem, + 0, + )?; + } + + Ok(()) +} + +mod test { + #[test] + fn test_structure_sizes() { + assert_eq!(size_of::(), 272) + } +} diff --git a/src/migtd/src/migration/session.rs b/src/migtd/src/migration/session.rs index 44d75a2f..6c43518d 100644 --- a/src/migtd/src/migration/session.rs +++ b/src/migtd/src/migration/session.rs @@ -799,7 +799,12 @@ async fn migration_src_exchange_msk( log::error!("exchange_msk(): Incorrect ExchangeInformation size Migration ID: {}. Size - Expected: {} Actual: {}\n", info.mig_info.mig_request_id, size_of::(), size); return Err(MigrationResult::NetworkError); } - shutdown_transport(ratls_client.transport_mut(), info, data).await?; + shutdown_transport( + ratls_client.transport_mut(), + info.mig_info.mig_request_id, + data, + ) + .await?; Ok(()) } @@ -868,7 +873,12 @@ async fn migration_dst_exchange_msk( log::error!("exchange_msk(): Incorrect ExchangeInformation size Migration ID: {}. Size - Expected: {} Actual: {}\n", info.mig_info.mig_request_id, size_of::(), size); return Err(MigrationResult::NetworkError); } - shutdown_transport(ratls_server.transport_mut(), info, data).await?; + shutdown_transport( + ratls_server.transport_mut(), + info.mig_info.mig_request_id, + data, + ) + .await?; Ok(()) } @@ -953,7 +963,15 @@ async fn migration_dst_exchange_msk( #[cfg(feature = "main")] pub async fn exchange_msk(info: &MigrationInformation, data: &mut Vec) -> Result<()> { - let mut transport = setup_transport(info, data).await?; + let mut transport = setup_transport( + info.mig_info.mig_request_id, + #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] + info.mig_socket_info.mig_td_cid, + #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] + info.mig_socket_info.mig_channel_port, + data, + ) + .await?; // Exchange policy firstly because of the message size limitation of TLS protocol #[cfg(feature = "policy_v2")] diff --git a/src/migtd/src/migration/transport.rs b/src/migtd/src/migration/transport.rs index 1e6ca3ee..3469f378 100644 --- a/src/migtd/src/migration/transport.rs +++ b/src/migtd/src/migration/transport.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent use super::MigrationResult; -use crate::migration::data::MigrationInformation; use alloc::vec::Vec; type Result = core::result::Result; @@ -18,7 +17,9 @@ pub(super) type TransportType = virtio_serial::VirtioSerialPort; pub(super) type TransportType = vsock::stream::VsockStream; pub(super) async fn setup_transport( - info: &MigrationInformation, + mig_request_id: u64, + #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] migtd_cid: u64, + #[cfg(any(feature = "vmcall-vsock", feature = "virtio-vsock"))] mig_channel_port: u32, data: &mut Vec, ) -> Result { #[cfg(not(feature = "vmcall-raw"))] @@ -27,10 +28,10 @@ pub(super) async fn setup_transport( #[cfg(feature = "vmcall-raw")] { use vmcall_raw::stream::VmcallRaw; - let mut vmcall_raw_instance = VmcallRaw::new_with_mid(info.mig_info.mig_request_id) + let mut vmcall_raw_instance = VmcallRaw::new_with_mid(mig_request_id) .map_err(|e| { - data.extend_from_slice(&format!("Error: exchange_msk(): Failed to create vmcall_raw_instance with Migration ID: {:x} errorcode: {}\n", info.mig_info.mig_request_id, e).into_bytes()); - log::error!("exchange_msk: Failed to create vmcall_raw_instance with Migration ID: {} errorcode: {:?}\n", info.mig_info.mig_request_id, e); + data.extend_from_slice(&format!("Error: exchange_msk(): Failed to create vmcall_raw_instance with Migration ID: {:x} errorcode: {}\n", mig_request_id, e).into_bytes()); + log::error!("exchange_msk: Failed to create vmcall_raw_instance with Migration ID: {} errorcode: {:?}\n", mig_request_id, e); MigrationResult::InvalidParameter })?; @@ -38,8 +39,8 @@ pub(super) async fn setup_transport( .connect() .await .map_err(|e| { - data.extend_from_slice(&format!("Error: exchange_msk(): Failed to connect vmcall_raw_instance with Migration ID: {:x} errorcode: {}\n", info.mig_info.mig_request_id, e).into_bytes()); - log::error!("exchange_msk: Failed to connect vmcall_raw_instance with Migration ID: {} errorcode: {:?}\n", info.mig_info.mig_request_id, e); + data.extend_from_slice(&format!("Error: exchange_msk(): Failed to connect vmcall_raw_instance with Migration ID: {:x} errorcode: {}\n", mig_request_id, e).into_bytes()); + log::error!("exchange_msk: Failed to connect vmcall_raw_instance with Migration ID: {} errorcode: {:?}\n", mig_request_id, e); MigrationResult::InvalidParameter })?; return Ok(vmcall_raw_instance); @@ -63,17 +64,11 @@ pub(super) async fn setup_transport( let mut vsock = VsockStream::new()?; #[cfg(feature = "vmcall-vsock")] - let mut vsock = VsockStream::new_with_cid( - info.mig_socket_info.mig_td_cid, - info.mig_info.mig_request_id, - )?; + let mut vsock = VsockStream::new_with_cid(migtd_cid, mig_request_id)?; // Establish the vsock connection with host vsock - .connect(&VsockAddr::new( - info.mig_socket_info.mig_td_cid as u32, - info.mig_socket_info.mig_channel_port, - )) + .connect(&VsockAddr::new(migtd_cid as u32, mig_channel_port)) .await?; return Ok(vsock); } @@ -81,7 +76,7 @@ pub(super) async fn setup_transport( pub(super) async fn shutdown_transport( transport: &mut TransportType, - info: &MigrationInformation, + mig_request_id: u64, data: &mut Vec, ) -> Result<()> { #[cfg(not(feature = "vmcall-raw"))] @@ -92,14 +87,14 @@ pub(super) async fn shutdown_transport( data.extend_from_slice( &format!( "Error: shutdown_transport(): Failed to transport in vmcall_raw_instance with Migration ID: {:x} errorcode: {}\n", - info.mig_info.mig_request_id, + mig_request_id, e ) .into_bytes(), ); log::error!( "shutdown_transport: Failed to transport in vmcall_raw_instance with Migration ID: {} errorcode: {}", - info.mig_info.mig_request_id, + mig_request_id, e ); MigrationResult::InvalidParameter From 049922d16724fe9275f624c5c8ce017a5b28249d Mon Sep 17 00:00:00 2001 From: Jiaqi Gao Date: Thu, 8 Jan 2026 23:45:44 +0800 Subject: [PATCH 2/7] policy: support rebinding old/new authentication Signed-off-by: Jiaqi Gao --- src/migtd/src/event_log.rs | 26 +- src/migtd/src/mig_policy.rs | 362 +++++++++++++++++++++++-- src/policy/src/lib.rs | 2 + src/policy/src/v2/policy.rs | 6 +- src/policy/src/v2/servtd_collateral.rs | 16 ++ 5 files changed, 377 insertions(+), 35 deletions(-) diff --git a/src/migtd/src/event_log.rs b/src/migtd/src/event_log.rs index 09b3fdeb..0d5eb50c 100644 --- a/src/migtd/src/event_log.rs +++ b/src/migtd/src/event_log.rs @@ -12,7 +12,7 @@ use cc_measurement::{ }; use core::mem::size_of; use crypto::hash::digest_sha384; -use policy::{CcEvent, EventName, Report, REPORT_DATA_SIZE}; +use policy::{CcEvent, EventName}; use spin::Once; use td_payload::acpi::get_acpi_tables; use td_shim::event_log::{ @@ -218,11 +218,17 @@ pub(crate) fn parse_events(event_log: &[u8]) -> Option Result<()> { - replay_event_log_with_report(event_log, report) +pub fn verify_event_log( + event_log: &[u8], + report_rtmrs: &[[u8; SHA384_DIGEST_SIZE]; 4], +) -> Result<()> { + replay_event_log_with_report(event_log, report_rtmrs) } -fn replay_event_log_with_report(event_log: &[u8], report: &[u8]) -> Result<()> { +fn replay_event_log_with_report( + event_log: &[u8], + report_rtmrs: &[[u8; SHA384_DIGEST_SIZE]; 4], +) -> Result<()> { let mut rtmrs: [[u8; 96]; 4] = [[0; 96]; 4]; let event_log = if let Some(event_log) = CcEventLogReader::new(event_log) { @@ -250,14 +256,10 @@ fn replay_event_log_with_report(event_log: &[u8], report: &[u8]) -> Result<()> { } } - if report.len() < REPORT_DATA_SIZE { - return Err(anyhow!("Invalid report")); - } - - if report[Report::R_MIGTD_RTMR0] == rtmrs[0][0..48] - && report[Report::R_MIGTD_RTMR1] == rtmrs[1][0..48] - && report[Report::R_MIGTD_RTMR2] == rtmrs[2][0..48] - && report[Report::R_MIGTD_RTMR3] == rtmrs[3][0..48] + if report_rtmrs[0] == rtmrs[0][0..48] + && report_rtmrs[1] == rtmrs[1][0..48] + && report_rtmrs[2] == rtmrs[2][0..48] + && report_rtmrs[3] == rtmrs[3][0..48] { Ok(()) } else { diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index 265e84ba..1bb0ebfd 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -2,14 +2,17 @@ // // SPDX-License-Identifier: BSD-2-Clause-Patent +use crypto::SHA384_DIGEST_SIZE; +pub use policy::{PolicyError, Report, REPORT_DATA_SIZE}; + #[cfg(not(feature = "policy_v2"))] pub use v1::*; #[cfg(not(feature = "policy_v2"))] mod v1 { use policy::verify_policy; - pub use policy::PolicyError; + use super::{get_rtmrs_from_suppl_data, PolicyError}; use crate::{ config::get_policy, event_log::{get_event_log, parse_events, verify_event_log}, @@ -33,8 +36,11 @@ mod v1 { return Err(PolicyError::InvalidParameter); }; - verify_event_log(event_log_peer, verified_report_peer) - .map_err(|_| PolicyError::InvalidEventLog)?; + verify_event_log( + event_log_peer, + &get_rtmrs_from_suppl_data(verified_report_peer)?, + ) + .map_err(|_| PolicyError::InvalidEventLog)?; let event_log = parse_events(event_log).ok_or(PolicyError::InvalidParameter)?; let event_log_peer = parse_events(event_log_peer).ok_or(PolicyError::InvalidParameter)?; @@ -58,13 +64,30 @@ mod v2 { use alloc::{string::String, string::ToString, vec::Vec}; use attestation::verify_quote_with_collaterals; use chrono::DateTime; - use crypto::{crl::get_crl_number, pem_cert_to_der}; + use crypto::{crl::get_crl_number, hash::digest_sha384, pem_cert_to_der, SHA384_DIGEST_SIZE}; use lazy_static::lazy_static; use policy::*; use spin::Once; + use tdx_tdcall::tdreport::{tdcall_verify_report, TdInfo, TdxReport}; use crate::config::get_policy_issuer_chain; use crate::event_log::{parse_events, verify_event_log}; + use crate::mig_policy::get_rtmrs_from_suppl_data; + use crate::migration::servtd_ext::ServtdExt; + + const SERVTD_ATTR_IGNORE_ATTRIBUTES: u64 = 0x1_0000_0000; + const SERVTD_ATTR_IGNORE_XFAM: u64 = 0x2_0000_0000; + const SERVTD_ATTR_IGNORE_MRTD: u64 = 0x4_0000_0000; + const SERVTD_ATTR_IGNORE_MRCONFIGID: u64 = 0x8_0000_0000; + const SERVTD_ATTR_IGNORE_MROWNER: u64 = 0x10_0000_0000; + const SERVTD_ATTR_IGNORE_MROWNERCONFIG: u64 = 0x20_0000_0000; + const SERVTD_ATTR_IGNORE_RTMR0: u64 = 0x40_0000_0000; + const SERVTD_ATTR_IGNORE_RTMR1: u64 = 0x80_0000_0000; + const SERVTD_ATTR_IGNORE_RTMR2: u64 = 0x100_0000_0000; + 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(); @@ -113,6 +136,13 @@ 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>> { @@ -195,6 +225,93 @@ mod v2 { Ok(suppl_data) } + // Authenticate the migtd-new from migtd-old side + pub fn authenticate_rebinding_new( + tdreport_dst: &[u8], + event_log_dst: &[u8], + mig_policy_dst: &[u8], + ) -> Result, PolicyError> { + let policy_issuer_chain = get_policy_issuer_chain().ok_or(PolicyError::InvalidParameter)?; + + let (evaluation_data_dst, verified_policy_dst, tdx_report) = authenticate_rebinding_common( + tdreport_dst, + event_log_dst, + mig_policy_dst, + policy_issuer_chain, + )?; + let relative_reference = get_local_tcb_evaluation_info()?; + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + + policy + .policy_data + .evaluate_policy_forward(&evaluation_data_dst, &relative_reference)?; + + // Verify the destination's policy against local policy + verified_policy_dst + .policy_data + .evaluate_against_policy(&policy.policy_data)?; + + Ok(tdx_report.as_bytes().to_vec()) + } + + // Authenticate the migtd-old from migtd-new side + pub fn authenticate_rebinding_old( + tdreport_src: &[u8], + event_log_src: &[u8], + mig_policy_src: &[u8], + init_policy: &[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)?; + + // Verify quote src / event log src / policy src + let (evaluation_data_src, _verified_policy_src, tdx_report) = + authenticate_rebinding_common( + tdreport_src, + event_log_src, + mig_policy_src, + policy_issuer_chain, + )?; + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + + // Verify the td report init / event log init / policy init + 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 _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, + 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)?; + policy + .policy_data + .evaluate_policy_common(&evaluation_data_src, &relative_reference)?; + + // If backward policy exists, evaluate the migration src based on it. + let relative_reference = get_local_tcb_evaluation_info()?; + policy + .policy_data + .evaluate_policy_backward(&evaluation_data_src, &relative_reference)?; + + Ok(tdx_report.as_bytes().to_vec()) + } + fn authenticate_remote_common<'p>( quote: &[u8], event_log: &[u8], @@ -202,33 +319,20 @@ mod v2 { policy_issuer_chain: &[u8], ) -> Result<(PolicyEvaluationInfo, VerifiedPolicy<'p>, Vec), PolicyError> { let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; - let unverified_policy = RawPolicyData::deserialize_from_json(mig_policy)?; // 1. Verify quote & get supplemental data let (fmspc, suppl_data) = verify_quote(quote, policy.get_collaterals()) .map_err(|_| PolicyError::QuoteVerification)?; - // 2. Verify the event log integrity - verify_event_log( + // 2. Verify the signature of the provided policy and the integrity of the event log + let verified_policy = verify_policy_and_event_log( event_log, - suppl_data - .get(..REPORT_DATA_SIZE) - .ok_or(PolicyError::QuoteVerification)?, - ) - .map_err(|_| PolicyError::InvalidEventLog)?; - - // 3. Verify the integrity of migration policy, with the issuer chains from local policy - let verified_policy = unverified_policy.verify( + mig_policy, policy_issuer_chain, - Some(policy.servtd_identity_issuer_chain.as_bytes()), - Some(policy.servtd_tcb_mapping_issuer_chain.as_bytes()), + &get_rtmrs_from_suppl_data(&suppl_data)?, )?; - // 4. Check the integrity of the policy with its event log - let events = parse_events(event_log).ok_or(PolicyError::InvalidEventLog)?; - check_policy_integrity(mig_policy, &events)?; - - // 5. Get TCB evaluation info from the collaterals + // 3. Get TCB evaluation info from the collaterals let evaluation_data = setup_evaluation_data( fmspc, &suppl_data, @@ -239,6 +343,69 @@ mod v2 { Ok((evaluation_data, verified_policy, suppl_data)) } + fn authenticate_rebinding_common<'p>( + tdreport: &[u8], + event_log: &[u8], + mig_policy: &'p [u8], + policy_issuer_chain: &[u8], + ) -> Result<(PolicyEvaluationInfo, VerifiedPolicy<'p>, TdxReport), PolicyError> { + // 1. Verify quote & get supplemental data + let tdreport_verified = + verify_tdreport(tdreport).map_err(|_| PolicyError::QuoteVerification)?; + + // 2. Verify the signature of the provided policy and the integrity of the event log + let verified_policy = verify_policy_and_event_log( + event_log, + mig_policy, + policy_issuer_chain, + &get_rtmrs_from_tdreport(&tdreport_verified)?, + )?; + + // 3. Get TCB evaluation info from the collaterals + let evaluation_data = + setup_evaluation_data_with_tdreport(&tdreport_verified, &verified_policy)?; + + Ok((evaluation_data, verified_policy, tdreport_verified)) + } + + fn get_rtmrs_from_tdreport( + td_report: &TdxReport, + ) -> Result<[[u8; SHA384_DIGEST_SIZE]; 4], PolicyError> { + let mut rtmrs = [[0u8; SHA384_DIGEST_SIZE]; 4]; + rtmrs[0].copy_from_slice(&td_report.td_info.rtmr0); + rtmrs[1].copy_from_slice(&td_report.td_info.rtmr1); + rtmrs[2].copy_from_slice(&td_report.td_info.rtmr2); + rtmrs[3].copy_from_slice(&td_report.td_info.rtmr3); + + Ok(rtmrs) + } + + fn verify_policy_and_event_log<'p>( + event_log: &[u8], + mig_policy: &'p [u8], + policy_issuer_chain: &[u8], + rtmrs: &[[u8; SHA384_DIGEST_SIZE]; 4], + ) -> Result, PolicyError> { + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + let unverified_policy = RawPolicyData::deserialize_from_json(mig_policy)?; + + // 1. Verify the event log integrity + verify_event_log(event_log, rtmrs).map_err(|_| PolicyError::InvalidEventLog)?; + + // 2. Verify the integrity of migration policy, with the issuer chains from local policy + let verified_policy = unverified_policy.verify( + policy_issuer_chain, + Some(policy.servtd_identity_issuer_chain.as_bytes()), + Some(policy.servtd_tcb_mapping_issuer_chain.as_bytes()), + )?; + + // 3. Check the integrity of the policy with its event log + let events = parse_events(event_log).ok_or(PolicyError::InvalidEventLog)?; + check_policy_integrity(mig_policy, &events)?; + + Ok(verified_policy) + } + fn verify_quote( quote: &[u8], collaterals: &Collaterals, @@ -252,6 +419,110 @@ mod v2 { Ok((fmspc, suppl_data)) } + fn verify_tdreport(tdreport: &[u8]) -> Result { + let tdx_report = + TdxReport::read_from_bytes(tdreport).ok_or(PolicyError::InvalidTdReport)?; + + // Verify the REPORTMACSTRUCT + tdcall_verify_report(tdx_report.report_mac.as_bytes()) + .map_err(|_| PolicyError::TdReportVerification)?; + + // Verify the TDINFO_STRUCT and TEE_TCB_INFO + let tdinfo_hash = digest_sha384(tdx_report.td_info.as_bytes()) + .map_err(|_| PolicyError::HashCalculation)?; + let tee_tcb_info_hash = digest_sha384(tdx_report.tee_tcb_info.as_bytes()) + .map_err(|_| PolicyError::HashCalculation)?; + + let mut validity = true; + validity &= &tdx_report.report_mac.tee_tcb_info_hash == tee_tcb_info_hash.as_slice(); + validity &= tdx_report.report_mac.tee_info_hash != [0; 48]; + validity &= &tdx_report.report_mac.tee_info_hash == tdinfo_hash.as_slice(); + + if !validity { + return Err(PolicyError::InvalidTdReport); + } + Ok(tdx_report) + } + + fn verify_servtd_hash( + servtd_report: &[u8], + servtd_attr: u64, + init_servtd_hash: &[u8], + ) -> Result { + if servtd_report.len() < TD_INFO_OFFSET + 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)?; + + if (servtd_attr & SERVTD_ATTR_IGNORE_ATTRIBUTES) != 0 { + td_report.td_info.attributes.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_XFAM) != 0 { + td_report.td_info.xfam.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_MRTD) != 0 { + td_report.td_info.mrtd.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_MRCONFIGID) != 0 { + td_report.td_info.mrconfig_id.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_MROWNER) != 0 { + td_report.td_info.mrowner.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_MROWNERCONFIG) != 0 { + td_report.td_info.mrownerconfig.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR0) != 0 { + td_report.td_info.rtmr0.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR1) != 0 { + td_report.td_info.rtmr1.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR2) != 0 { + td_report.td_info.rtmr2.fill(0); + } + if (servtd_attr & SERVTD_ATTR_IGNORE_RTMR3) != 0 { + td_report.td_info.rtmr3.fill(0); + } + + let info_hash = digest_sha384(td_report.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::()]; + let mut offset = 0; + + buffer[offset..offset + SHA384_DIGEST_SIZE].copy_from_slice(&info_hash); + offset += SHA384_DIGEST_SIZE; + + buffer[offset..offset + size_of::()].copy_from_slice(&SERVTD_TYPE_MIGTD.to_le_bytes()); + offset += size_of::(); + + buffer[offset..offset + size_of::()].copy_from_slice(&servtd_attr.to_le_bytes()); + + let calculated_hash = digest_sha384(&buffer).map_err(|_| PolicyError::HashCalculation)?; + + if calculated_hash.as_slice() != init_servtd_hash { + return Err(PolicyError::InvalidTdReport); + } + + Ok(td_report) + } + + fn verify_init_tdreport( + init_report: &[u8], + servtd_ext: &ServtdExt, + ) -> Result { + verify_servtd_hash( + init_report, + u64::from_le_bytes(servtd_ext.init_attr), + &servtd_ext.init_servtd_info_hash, + ) + } + fn setup_evaluation_data( fmspc: [u8; 6], suppl_data: &[u8], @@ -278,6 +549,7 @@ mod v2 { .map_err(|_| PolicyError::InvalidCollateral)?; Ok(PolicyEvaluationInfo { + tee_tcb_svn: None, tcb_date: Some(tcb_date.to_string()), tcb_status: Some(tcb_status.as_str().to_string()), tcb_evaluation_number: Some(tcb_evaluation_number), @@ -290,6 +562,36 @@ mod v2 { }) } + fn setup_evaluation_data_with_tdreport( + tdreport: &TdxReport, + policy: &VerifiedPolicy, + ) -> Result { + let migtd_svn = policy.servtd_tcb_mapping.get_engine_svn_by_measurements( + &Measurements::new_from_bytes( + &tdreport.td_info.mrtd, + &tdreport.td_info.rtmr0, + &tdreport.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: Some(tdreport.tee_tcb_info.tee_tcb_svn), + 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 get_tcb_date_and_status_from_suppl_data( suppl_data: &[u8], ) -> Result<(String, String), PolicyError> { @@ -350,3 +652,19 @@ mod v2 { assert_eq!(iso_date, "2024-01-01T00:00:00Z"); } } + +fn get_rtmrs_from_suppl_data( + suppl_data: &[u8], +) -> Result<[[u8; SHA384_DIGEST_SIZE]; 4], PolicyError> { + if suppl_data.len() < REPORT_DATA_SIZE { + return Err(PolicyError::InvalidParameter); + } + + let mut rtmrs = [[0u8; SHA384_DIGEST_SIZE]; 4]; + rtmrs[0].copy_from_slice(&suppl_data[Report::R_MIGTD_RTMR0]); + rtmrs[1].copy_from_slice(&suppl_data[Report::R_MIGTD_RTMR1]); + rtmrs[2].copy_from_slice(&suppl_data[Report::R_MIGTD_RTMR2]); + rtmrs[3].copy_from_slice(&suppl_data[Report::R_MIGTD_RTMR3]); + + Ok(rtmrs) +} diff --git a/src/policy/src/lib.rs b/src/policy/src/lib.rs index 51791f3d..a68c08cb 100644 --- a/src/policy/src/lib.rs +++ b/src/policy/src/lib.rs @@ -27,6 +27,7 @@ pub enum PolicyError { InvalidParameter, InvalidPolicy, InvalidEventLog, + InvalidTdReport, PlatformNotFound(String), PlatformNotMatch(String, String), UnqualifiedPlatformInfo, @@ -47,6 +48,7 @@ pub enum PolicyError { CrlEvaluation, HashCalculation, QuoteVerification, + TdReportVerification, QuoteGeneration, GetTdxReport, } diff --git a/src/policy/src/v2/policy.rs b/src/policy/src/v2/policy.rs index 40ca7e30..0928e6cc 100644 --- a/src/policy/src/v2/policy.rs +++ b/src/policy/src/v2/policy.rs @@ -133,9 +133,12 @@ impl PartialEq for ServtdTcbStatus { impl Eq for ServtdTcbStatus {} -/// Contains all required data to be evaluated against a policy +/// Contains all required data to be evaluated against a rebinding policy #[derive(Debug, Clone, Default)] pub struct PolicyEvaluationInfo { + /// The TEE_TCB_SVN of MigTD + pub tee_tcb_svn: Option<[u8; 16]>, + /// The date of the Trusted Computing Base (TCB) in ISO-8601 format, e.g. "2023-06-19T00:00:00Z" pub tcb_date: Option, @@ -846,6 +849,7 @@ mod test { let global = include_str!("../../test/policy_v2/global.json"); let global_policy = serde_json::from_str::(global).unwrap(); let mut value = PolicyEvaluationInfo { + tee_tcb_svn: None, tcb_date: Some("2025-09-01T00:00:00Z".to_string()), tcb_status: Some("UpToDate".to_string()), tcb_evaluation_number: Some(15), diff --git a/src/policy/src/v2/servtd_collateral.rs b/src/policy/src/v2/servtd_collateral.rs index 6434c3f8..e746ac3c 100644 --- a/src/policy/src/v2/servtd_collateral.rs +++ b/src/policy/src/v2/servtd_collateral.rs @@ -161,6 +161,22 @@ pub struct Measurements { } impl Measurements { + pub fn new_from_bytes( + mrtd: &[u8], + rtmr0: &[u8], + rtmr1: &[u8], + rtmr2: Option<&[u8]>, + rtmr3: Option<&[u8]>, + ) -> Self { + Measurements { + mrtd: bytes_to_hex_string(mrtd), + rtmr0: bytes_to_hex_string(rtmr0), + rtmr1: bytes_to_hex_string(rtmr1), + rtmr2: rtmr2.map(|b| bytes_to_hex_string(b)), + rtmr3: rtmr3.map(|b| bytes_to_hex_string(b)), + } + } + fn to_ascii_uppercase(&self) -> Self { Measurements { mrtd: self.mrtd.to_ascii_uppercase(), From 8d25a92b7123144763157b66abd4ca868e40ee92 Mon Sep 17 00:00:00 2001 From: Jiaqi Gao Date: Thu, 8 Jan 2026 23:46:45 +0800 Subject: [PATCH 3/7] tls: support rebinding certificate generation/verification Signed-off-by: Jiaqi Gao --- src/crypto/src/rustls_impl/tls.rs | 25 +- src/migtd/src/ratls/mod.rs | 11 + src/migtd/src/ratls/server_client.rs | 904 +++++++++++++++++++++++---- 3 files changed, 829 insertions(+), 111 deletions(-) diff --git a/src/crypto/src/rustls_impl/tls.rs b/src/crypto/src/rustls_impl/tls.rs index bc4ff16f..862bb666 100644 --- a/src/crypto/src/rustls_impl/tls.rs +++ b/src/crypto/src/rustls_impl/tls.rs @@ -55,6 +55,10 @@ where pub async fn read(&mut self, data: &mut [u8]) -> Result { self.conn.read(data).await } + + pub fn peer_certs(&self) -> Option> { + self.conn.peer_certs() + } } enum TlsConnection { @@ -94,6 +98,13 @@ impl TlsConnection { } } + fn peer_certs(&self) -> Option> { + match self { + Self::Server(conn) => conn.peer_certs(), + Self::Client(conn) => conn.peer_certs(), + } + } + fn transport_mut(&mut self) -> &mut T { match self { Self::Server(conn) => &mut conn.transport, @@ -498,7 +509,7 @@ pub(crate) mod connection { } pub struct TlsServerConnection { - conn: UnbufferedServerConnection, + pub(super) conn: UnbufferedServerConnection, input: TlsBuffer, output: TlsBuffer, pub transport: T, @@ -518,6 +529,12 @@ pub(crate) mod connection { }) } + pub fn peer_certs(&self) -> Option> { + self.conn + .peer_certificates() + .map(|certs| certs.iter().map(|der| der.as_ref()).collect()) + } + pub async fn read(&mut self, data: &mut [u8]) -> Result { if self.is_handshaking { self.process_tls_status().await?; @@ -776,6 +793,12 @@ pub(crate) mod connection { }) } + pub fn peer_certs(&self) -> Option> { + self.conn + .peer_certificates() + .map(|certs| certs.iter().map(|der| der.as_ref()).collect()) + } + pub async fn read(&mut self, data: &mut [u8]) -> Result { if self.is_handshaking { self.process_tls_status().await?; diff --git a/src/migtd/src/ratls/mod.rs b/src/migtd/src/ratls/mod.rs index 3117cad4..716739e9 100644 --- a/src/migtd/src/ratls/mod.rs +++ b/src/migtd/src/ratls/mod.rs @@ -22,6 +22,7 @@ pub enum RatlsError { X509(DerError), InvalidEventlog, InvalidPolicy, + GenerateCertificate, } impl From for RatlsError { @@ -54,6 +55,16 @@ pub const EXTNID_MIGTD_EVENT_LOG: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.3"); pub const EXTNID_MIGTD_POLICY_HASH: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.4"); +pub const EXTNID_MIGTD_TDREPORT: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.5"); +pub const EXTNID_MIGTD_SERVTD_EXT: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.6"); +pub const EXTNID_MIGTD_TDREPORT_INIT: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.7"); +pub const EXTNID_MIGTD_EVENT_LOG_INIT: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.8"); +pub const EXTNID_MIGTD_INIT_POLICY_HASH: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113741.1.5.5.1.9"); // As specified in https://datatracker.ietf.org/doc/html/rfc5480#appendix-A // id-ecPublicKey OBJECT IDENTIFIER ::= { diff --git a/src/migtd/src/ratls/server_client.rs b/src/migtd/src/ratls/server_client.rs index 4e6f8cc3..44751fd7 100644 --- a/src/migtd/src/ratls/server_client.rs +++ b/src/migtd/src/ratls/server_client.rs @@ -14,11 +14,12 @@ use crypto::{ }, Error as CryptoError, }; +use tdx_tdcall::tdreport::TdxReport; use super::*; -#[cfg(feature = "policy_v2")] -use crate::config::get_policy; use crate::event_log::get_event_log; +#[cfg(feature = "policy_v2")] +use crate::{config::get_policy, migration::servtd_ext::ServtdExt}; use verify::*; type Result = core::result::Result; @@ -29,7 +30,7 @@ pub fn server(stream: T) -> Result( ); e })?; - let (certs, _quote) = gen_cert(&signing_key).map_err(|e| { + let (certs, _quote) = create_certificate_for_server(&signing_key).map_err(|e| { log::error!("server policy_v2 gen_cert() failed with error {:?}\n", e); e })?; let certs = vec![certs]; // Server verifies certificate of client - let config = - TlsConfig::new(certs, signing_key, verify_client_cert, remote_policy).map_err(|e| { - log::error!( - "server policy_v2 TlsConfig::new() failed with error {:?}\n", - e - ); + let config = TlsConfig::new( + certs, + signing_key, + move |cert, quote| verify_client_cert(cert, quote), + remote_policy, + ) + .map_err(|e| { + log::error!( + "server policy_v2 TlsConfig::new() failed with error {:?}\n", e - })?; + ); + e + })?; config.tls_server(stream).map_err(|e| { log::error!("server policy_v2 tls_server() failed with error {:?}\n", e); e.into() @@ -89,7 +95,7 @@ pub fn client( log::error!("client EcdsaPk::new() failed with error {:?}\n", e); e })?; - let (certs, quote) = gen_cert(&signing_key).map_err(|e| { + let (certs, quote) = create_certificate_for_client(&signing_key).map_err(|e| { log::error!("client gen_cert() failed with error {:?}\n", e); e })?; @@ -130,7 +136,7 @@ pub fn client( ); e })?; - let (certs, _quote) = gen_cert(&signing_key).map_err(|e| { + let (certs, _quote) = create_certificate_for_client(&signing_key).map_err(|e| { log::error!("client policy_v2 gen_cert() failed with error {:?}\n", e); e })?; @@ -159,21 +165,109 @@ pub fn client( }) } -fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { - let algorithm = AlgorithmIdentifier { - algorithm: ID_EC_PUBKEY_OID, - parameters: Some(AnyRef::new( - Tag::ObjectIdentifier, - SECP384R1_OID.as_bytes(), - )?), - }; - let eku = vec![SERVER_AUTH, CLIENT_AUTH, MIGTD_EXTENDED_KEY_USAGE] - .to_der() +// TLS server for rebinding new +#[cfg(feature = "policy_v2")] +pub fn server_rebinding( + stream: T, + remote_policy: Vec, +) -> Result> { + let signing_key = EcdsaPk::new().map_err(|e| { + log::error!( + "server rebinding EcdsaPk::new() failed with error {:?}\n", + e + ); + e + })?; + let certs = create_certificate_for_rebinding_new(&signing_key).map_err(|e| { + log::error!("server rebinding gen_cert() failed with error {:?}\n", e); + e + })?; + let certs = vec![certs]; + + let config = TlsConfig::new(certs, signing_key, verify_rebinding_old_cert, remote_policy) .map_err(|e| { - log::error!("gen_cert to_der failed with error {:?}\n", e); + log::error!( + "server rebinding TlsConfig::new() failed with error {:?}\n", + e + ); e })?; + config.tls_server(stream).map_err(|e| { + log::error!("server rebinding tls_server() failed with error {:?}\n", e); + e.into() + }) +} +// TLS client for rebinding old +#[cfg(feature = "policy_v2")] +pub fn client_rebinding( + stream: T, + remote_policy: Vec, + init_policy_hash: &[u8], + init_td_report: &[u8], + init_event_log: &[u8], + servtd_ext: &ServtdExt, +) -> Result> { + let signing_key = EcdsaPk::new().map_err(|e| { + log::error!( + "server rebinding EcdsaPk::new() failed with error {:?}\n", + e + ); + e + })?; + let certs = create_certificate_for_rebinding_old( + &signing_key, + init_policy_hash, + init_td_report, + init_event_log, + servtd_ext, + ) + .map_err(|e| { + log::error!("server rebinding gen_cert() failed with error {:?}\n", e); + e + })?; + let certs = vec![certs]; + + let config = TlsConfig::new(certs, signing_key, verify_rebinding_new_cert, remote_policy) + .map_err(|e| { + log::error!( + "server rebinding TlsConfig::new() failed with error {:?}\n", + e + ); + e + })?; + config.tls_server(stream).map_err(|e| { + log::error!("server rebinding tls_server() failed with error {:?}\n", e); + e.into() + }) +} + +fn gen_quote(public_key: &[u8]) -> Result> { + let td_report = gen_tdreport(public_key)?; + + attestation::get_quote(td_report.as_bytes()).map_err(|e| { + log::error!("Failed to get quote from TD report. Error: {:?}\n", e); + RatlsError::GetQuote + }) +} + +fn gen_tdreport(public_key: &[u8]) -> Result { + let hash = digest_sha384(public_key).map_err(|e| { + log::error!("Failed to compute SHA384 digest: {:?}\n", e); + e + })?; + + // Generate the TD Report that contains the public key hash as nonce + let mut additional_data = [0u8; 64]; + additional_data[..hash.len()].copy_from_slice(hash.as_ref()); + + tdx_tdcall::tdreport::tdcall_report(&additional_data).map_err(|e| { + log::error!("Failed to get TD report via tdcall. Error: {:?}\n", e); + e.into() + }) +} + +fn create_certificate_for_server(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { let pub_key = signing_key.public_key().map_err(|e| { log::error!( "gen_cert signing_key.public_key() failed with error {:?}\n", @@ -181,34 +275,90 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { ); e })?; - let sig_alg = AlgorithmIdentifier { - algorithm: ID_EC_SIG_OID, - parameters: None, - }; - let key_usage = BitStringRef::from_bytes(&[0x80]) + let quote = gen_quote(&pub_key).map_err(|e| { + log::error!("gen_cert gen_quote() failed with error {:?}\n", e); + e + })?; + + #[cfg(feature = "policy_v2")] + let policy_hash = { + let policy = get_policy().ok_or_else(|| { + log::error!( + "gen_cert client policy_v2 Failed to get migration policy for policy hash.\n" + ); + RatlsError::InvalidPolicy + })?; + digest_sha384(policy) + } + .map_err(|e| { + log::error!("gen_cert digest_sha384() failed with error {:?}\n", e); + e + })?; + + let eku = create_eku()?; + let key_usage = create_key_usage()?; + + let x509_builder = create_tls_tbs_common(&pub_key, &key_usage, &eku)?.add_extension( + Extension::new( + EXTNID_MIGTD_QUOTE_REPORT, + Some(false), + Some(quote.as_slice()), + ) + .map_err(|e| { + log::error!( + "gen_cert Extension::new for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", + e + ); + e + })?, + ) .map_err(|e| { log::error!( - "gen_cert BitStringRef::from_bytes() failed with error {:?}\n", + "gen_cert add_extension for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", e ); e - })? - .to_der() + })?; + + // If policy_v2 feature is enabled, add policy extension + #[cfg(feature = "policy_v2")] + let x509_builder = x509_builder + .add_extension( + Extension::new(EXTNID_MIGTD_POLICY_HASH, Some(false), Some(&policy_hash)).map_err( + |e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + }, + )?, + ) .map_err(|e| { log::error!( - "gen_cert BitStringRef::to_der() failed with error {:?}\n", + "gen_cert policy_v2 add_extension for policy hash failed with error {:?}.\n", e ); e })?; + + let x509_cert_der = sign_tls_tbs(x509_builder, &signing_key)?; + Ok((x509_cert_der, quote)) +} + +fn create_certificate_for_client(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { + let pub_key = signing_key.public_key().map_err(|e| { + log::error!( + "gen_cert signing_key.public_key() failed with error {:?}\n", + e + ); + e + })?; let quote = gen_quote(&pub_key).map_err(|e| { log::error!("gen_cert gen_quote() failed with error {:?}\n", e); e })?; - let event_log = get_event_log().ok_or_else(|| { - log::error!("gen_cert get_event_log() failed with error RatlsError::InvalidEventlog.\n"); - RatlsError::InvalidEventlog - })?; + #[cfg(feature = "policy_v2")] let policy_hash = { let policy = get_policy().ok_or_else(|| { @@ -224,28 +374,284 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { e })?; - let x509_builder = CertificateBuilder::new(sig_alg, algorithm, &pub_key) + let eku = create_eku()?; + let key_usage = create_key_usage()?; + + let x509_builder = create_tls_tbs_common(&pub_key, &key_usage, &eku)?.add_extension( + Extension::new( + EXTNID_MIGTD_QUOTE_REPORT, + Some(false), + Some(quote.as_slice()), + ) + .map_err(|e| { + log::error!( + "gen_cert Extension::new for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", + e + ); + e + })?, + ) .map_err(|e| { - log::error!("gen_cert CertificateBuilder::new failed with error {:?}\n", e); + log::error!( + "gen_cert add_extension for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", + e + ); + e + })?; + + // If policy_v2 feature is enabled, add policy extension + #[cfg(feature = "policy_v2")] + let x509_builder = x509_builder + .add_extension( + Extension::new(EXTNID_MIGTD_POLICY_HASH, Some(false), Some(&policy_hash)).map_err( + |e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + }, + )?, + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension for policy hash failed with error {:?}.\n", + e + ); + e + })?; + + let x509_cert_der = sign_tls_tbs(x509_builder, &signing_key)?; + Ok((x509_cert_der, quote)) +} + +#[cfg(feature = "policy_v2")] +fn create_certificate_for_rebinding_old( + signing_key: &EcdsaPk, + init_policy_hash: &[u8], + init_tdreport: &[u8], + init_event_log: &[u8], + servtd_ext: &ServtdExt, +) -> Result> { + let pub_key = signing_key.public_key().map_err(|e| { + log::error!( + "gen_cert signing_key.public_key() failed with error {:?}\n", + e + ); + e + })?; + let tdreport = gen_tdreport(&pub_key).map_err(|e| { + log::error!("gen_cert gen_tdreport() failed with error {:?}\n", e); + e + })?; + + let policy = get_policy().ok_or_else(|| { + log::error!( + "gen_cert rebinding old policy_v2 Failed to get migration policy for policy hash.\n" + ); + RatlsError::InvalidPolicy + })?; + let policy_hash = digest_sha384(policy).map_err(|e| { + log::error!("gen_cert digest_sha384() failed with error {:?}\n", e); + e + })?; + + let eku = create_eku()?; + let key_usage = create_key_usage()?; + + let x509_builder = create_tls_tbs_common(&pub_key, &key_usage, &eku)? + .add_extension( + Extension::new( + EXTNID_MIGTD_TDREPORT, + Some(false), + Some(tdreport.as_bytes()), + ) + .map_err(|e| { + log::error!( + "gen_cert Extension::new for EXTNID_MIGTD_TDREPORT failed with error {:?}\n", + e + ); + e + })?, + ) + .map_err(|e| { + log::error!( + "gen_cert add_extension for EXTNID_MIGTD_TDREPORT failed with error {:?}\n", + e + ); + e + })?; + + // If policy_v2 feature is enabled, add policy extension + #[cfg(feature = "policy_v2")] + let x509_builder = x509_builder + .add_extension( + Extension::new(EXTNID_MIGTD_POLICY_HASH, Some(false), Some(&policy_hash)).map_err( + |e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + }, + )?, + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension for policy hash failed with error {:?}.\n", + e + ); e })? - // 1970-01-01T00:00:00Z - .set_not_before(core::time::Duration::new(0, 0)) + .add_extension( + Extension::new( + EXTNID_MIGTD_SERVTD_EXT, + Some(false), + Some(servtd_ext.as_bytes()), + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + })?, + ) .map_err(|e| { - log::error!("gen_cert set_not_before failed with error {:?}\n", e); + log::error!( + "gen_cert policy_v2 add_extension for servtd_ext failed with error {:?}.\n", + e + ); e })? - // 9999-12-31T23:59:59Z - .set_not_after(core::time::Duration::new(253402300799, 0)) + .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", + e + ); + e + })?, + ) .map_err(|e| { - log::error!("gen_cert set_not_after failed with error {:?}\n", e); + log::error!( + "gen_cert policy_v2 add_extension for tdreport init failed with error {:?}.\n", + e + ); + e + })? + .add_extension( + Extension::new( + EXTNID_MIGTD_EVENT_LOG_INIT, + Some(false), + Some(&init_event_log), + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + })?, + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension for event log init failed with error {:?}.\n", + e + ); e })? .add_extension( - Extension::new(KEY_USAGE_EXTENSION, Some(true), Some(key_usage.as_slice())).map_err( + Extension::new( + EXTNID_MIGTD_INIT_POLICY_HASH, + Some(false), + Some(&init_policy_hash), + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension failed with error {:?}.\n", + e + ); + e + })?, + ) + .map_err(|e| { + log::error!( + "gen_cert policy_v2 add_extension for init policy hash failed with error {:?}.\n", + e + ); + e + })?; + + let x509_cert_der = sign_tls_tbs(x509_builder, &signing_key)?; + Ok(x509_cert_der) +} + +#[cfg(feature = "policy_v2")] +fn create_certificate_for_rebinding_new(signing_key: &EcdsaPk) -> Result> { + let pub_key = signing_key.public_key().map_err(|e| { + log::error!( + "gen_cert signing_key.public_key() failed with error {:?}\n", + e + ); + e + })?; + let tdreport = gen_tdreport(&pub_key).map_err(|e| { + log::error!("gen_cert gen_quote() failed with error {:?}\n", e); + e + })?; + + let policy_hash = { + let policy = get_policy().ok_or_else(|| { + log::error!( + "gen_cert rebinding old policy_v2 Failed to get migration policy for policy hash.\n" + ); + RatlsError::InvalidPolicy + })?; + digest_sha384(policy) + } + .map_err(|e| { + log::error!("gen_cert digest_sha384() failed with error {:?}\n", e); + e + })?; + + let eku = create_eku()?; + let key_usage = create_key_usage()?; + + let x509_builder = create_tls_tbs_common(&pub_key, &key_usage, &eku)? + .add_extension( + Extension::new( + EXTNID_MIGTD_TDREPORT, + Some(false), + Some(tdreport.as_bytes()), + ) + .map_err(|e| { + log::error!( + "gen_cert Extension::new for EXTNID_MIGTD_TDREPORT failed with error {:?}\n", + e + ); + e + })?, + ) + .map_err(|e| { + log::error!( + "gen_cert add_extension for EXTNID_MIGTD_TDREPORT failed with error {:?}\n", + e + ); + e + })?; + + let x509_builder = x509_builder + .add_extension( + Extension::new(EXTNID_MIGTD_POLICY_HASH, Some(false), Some(&policy_hash)).map_err( |e| { log::error!( - "gen_cert Extension::new for KEY_USAGE_EXTENSION failed with error {:?}\n", + "gen_cert policy_v2 add_extension failed with error {:?}.\n", e ); e @@ -253,16 +659,63 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { )?, ) .map_err(|e| { - log::error!( - "gen_cert add_extension for KEY_USAGE_EXTENSION failed with error {:?}\n", - e - ); + log::error!( + "gen_cert policy_v2 add_extension for policy hash failed with error {:?}.\n", + e + ); + e + })?; + + let x509_cert_der = sign_tls_tbs(x509_builder, &signing_key)?; + Ok(x509_cert_der) +} + +fn create_tls_tbs_common<'a>( + public_key: &'a [u8], + key_usage: &'a [u8], + eku: &'a [u8], +) -> Result> { + let algorithm = AlgorithmIdentifier { + algorithm: ID_EC_PUBKEY_OID, + parameters: Some(AnyRef::new( + Tag::ObjectIdentifier, + SECP384R1_OID.as_bytes(), + )?), + }; + let sig_alg = AlgorithmIdentifier { + algorithm: ID_EC_SIG_OID, + parameters: None, + }; + + let event_log = get_event_log().ok_or_else(|| { + log::error!("gen_cert get_event_log() failed with error RatlsError::InvalidEventlog.\n"); + RatlsError::InvalidEventlog + })?; + + let x509_builder = CertificateBuilder::new(sig_alg, algorithm, public_key) + .map_err(|e| { + log::error!( + "gen_cert CertificateBuilder::new failed with error {:?}\n", + e + ); + e + })? + // 1970-01-01T00:00:00Z + .set_not_before(core::time::Duration::new(0, 0)) + .map_err(|e| { + log::error!("gen_cert set_not_before failed with error {:?}\n", e); + e + })? + // 9999-12-31T23:59:59Z + .set_not_after(core::time::Duration::new(253402300799, 0)) + .map_err(|e| { + log::error!("gen_cert set_not_after failed with error {:?}\n", e); e })? .add_extension( - Extension::new(EXTENDED_KEY_USAGE, Some(false), Some(eku.as_slice())).map_err(|e| { + Extension::new(KEY_USAGE_EXTENSION, Some(true), Some(key_usage)).map_err(|e| { log::error!( - "gen_cert Extension::new for EXTENDED_KEY_USAGE failed with error {:?}\n", + "gen_cert Extension::new for KEY_USAGE_EXTENSION failed with error {:?}\n", e ); e @@ -270,20 +723,15 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { ) .map_err(|e| { log::error!( - "gen_cert add_extension for EXTENDED_KEY_USAGE failed with error {:?}\n", + "gen_cert add_extension for KEY_USAGE_EXTENSION failed with error {:?}\n", e ); e })? .add_extension( - Extension::new( - EXTNID_MIGTD_QUOTE_REPORT, - Some(false), - Some(quote.as_slice()), - ) - .map_err(|e| { + Extension::new(EXTENDED_KEY_USAGE, Some(false), Some(eku)).map_err(|e| { log::error!( - "gen_cert Extension::new for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", + "gen_cert Extension::new for EXTENDED_KEY_USAGE failed with error {:?}\n", e ); e @@ -291,7 +739,7 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { ) .map_err(|e| { log::error!( - "gen_cert add_extension for EXTNID_MIGTD_QUOTE_REPORT failed with error {:?}\n", + "gen_cert add_extension for EXTENDED_KEY_USAGE failed with error {:?}\n", e ); e @@ -313,28 +761,10 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { e })?; - // If policy_v2 feature is enabled, add policy extension - #[cfg(feature = "policy_v2")] - let x509_builder = x509_builder - .add_extension( - Extension::new(EXTNID_MIGTD_POLICY_HASH, Some(false), Some(&policy_hash)).map_err( - |e| { - log::error!( - "gen_cert policy_v2 add_extension failed with error {:?}.\n", - e - ); - e - }, - )?, - ) - .map_err(|e| { - log::error!( - "gen_cert policy_v2 add_extension for policy hash failed with error {:?}.\n", - e - ); - e - })?; + Ok(x509_builder) +} +fn sign_tls_tbs(x509_builder: CertificateBuilder, signing_key: &EcdsaPk) -> Result> { let mut x509_certificate = x509_builder.build(); let tbs = x509_certificate.tbs_certificate.to_der().map_err(|e| { log::error!( @@ -355,36 +785,41 @@ fn gen_cert(signing_key: &EcdsaPk) -> Result<(Vec, Vec)> { e })?; - Ok(( - x509_certificate.to_der().map_err(|e| { - log::error!( - "gen_cert x509_certificate.to_der failed with error {:?}.\n", - e - ); + Ok(x509_certificate.to_der().map_err(|e| { + log::error!( + "gen_cert x509_certificate.to_der failed with error {:?}.\n", e - })?, - quote, - )) -} - -fn gen_quote(public_key: &[u8]) -> Result> { - let hash = digest_sha384(public_key).map_err(|e| { - log::error!("Failed to compute SHA384 digest: {:?}\n", e); + ); e - })?; + })?) +} - // Generate the TD Report that contains the public key hash as nonce - let mut additional_data = [0u8; 64]; - additional_data[..hash.len()].copy_from_slice(hash.as_ref()); - let td_report = tdx_tdcall::tdreport::tdcall_report(&additional_data).map_err(|e| { - log::error!("Failed to get TD report via tdcall. Error: {:?}\n", e); - e - })?; +fn create_eku() -> Result> { + Ok(vec![SERVER_AUTH, CLIENT_AUTH, MIGTD_EXTENDED_KEY_USAGE] + .to_der() + .map_err(|e| { + log::error!("gen_cert to_der failed with error {:?}\n", e); + e + })?) +} - attestation::get_quote(td_report.as_bytes()).map_err(|e| { - log::error!("Failed to get quote from TD report. Error: {:?}\n", e); - RatlsError::GetQuote - }) +fn create_key_usage() -> Result> { + Ok(BitStringRef::from_bytes(&[0x80]) + .map_err(|e| { + log::error!( + "gen_cert BitStringRef::from_bytes() failed with error {:?}\n", + e + ); + e + })? + .to_der() + .map_err(|e| { + log::error!( + "gen_cert BitStringRef::to_der() failed with error {:?}\n", + e + ); + e + })?) } fn verify_server_cert(cert: &[u8], quote: &[u8]) -> core::result::Result<(), CryptoError> { @@ -404,6 +839,7 @@ mod verify { use crypto::ecdsa::ecdsa_verify; use crypto::{Error as CryptoError, Result as CryptoResult}; use policy::PolicyError; + use tdx_tdcall::tdreport::TdxReport; #[cfg(not(feature = "policy_v2"))] pub fn verify_peer_cert( @@ -543,6 +979,194 @@ mod verify { verify_signature(&cert, suppl_data.as_slice()) } + #[cfg(feature = "policy_v2")] + pub fn verify_rebinding_old_cert( + cert: &[u8], + pre_session_data: &[u8], + ) -> core::result::Result<(), CryptoError> { + let cert = Certificate::from_der(cert).map_err(|_| { + log::error!("Failed to parse certificate from DER.\n"); + CryptoError::ParseCertificate + })?; + + let extensions = cert.tbs_certificate.extensions.as_ref().ok_or_else(|| { + log::error!("Failed to get certificate extensions.\n"); + CryptoError::ParseCertificate + })?; + // Check if extensions contain `MIGTD_EXTENDED_KEY_USAGE` + check_migtd_eku(extensions).map_err(|e| { + log::error!("Failed to check MIGTD EKU: {:?}\n", e); + e + })?; + + let td_report = find_extension(extensions, &EXTNID_MIGTD_TDREPORT).ok_or_else(|| { + log::error!("Failed to find tdreport extension.\n"); + CryptoError::ParseCertificate + })?; + let event_log = find_extension(extensions, &EXTNID_MIGTD_EVENT_LOG).ok_or_else(|| { + log::error!("Failed to find event log extension.\n"); + CryptoError::ParseCertificate + })?; + let expected_policy_hash = find_extension(extensions, &EXTNID_MIGTD_POLICY_HASH) + .ok_or_else(|| { + log::error!("Failed to find expected policy hash extension.\n"); + CryptoError::ParseCertificate + })?; + let init_td_report = + find_extension(extensions, &EXTNID_MIGTD_TDREPORT_INIT).ok_or_else(|| { + log::error!("Failed to find init tdreport extension.\n"); + CryptoError::ParseCertificate + })?; + let init_event_log = + find_extension(extensions, &EXTNID_MIGTD_EVENT_LOG_INIT).ok_or_else(|| { + log::error!("Failed to find init event log extension.\n"); + CryptoError::ParseCertificate + })?; + 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"); + CryptoError::ParseCertificate + })?; + let servtd_ext = find_extension(extensions, &EXTNID_MIGTD_SERVTD_EXT).ok_or_else(|| { + log::error!("Failed to find servtd ext extension.\n"); + CryptoError::ParseCertificate + })?; + + let remote_policy_size = u32::from_le_bytes( + pre_session_data + .get(..4) + .ok_or(CryptoError::TlsVerifyPeerCert( + INVALID_MIG_POLICY_ERROR.to_string(), + ))? + .try_into() + .unwrap(), + ) as usize; + 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( + pre_session_data + .get(init_policy_offset..4 + init_policy_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) + .ok_or(CryptoError::TlsVerifyPeerCert( + INVALID_MIG_POLICY_ERROR.to_string(), + ))?; + let exact_policy_hash = digest_sha384(remote_policy)?; + if expected_policy_hash != exact_policy_hash.as_slice() { + log::error!("Invalid rebinding policy.\n"); + return Err(CryptoError::TlsVerifyPeerCert( + 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"); + return Err(CryptoError::TlsVerifyPeerCert( + INVALID_MIG_POLICY_ERROR.to_string(), + )); + } + + let policy_check_result = mig_policy::authenticate_rebinding_old( + td_report, + event_log, + remote_policy, + init_policy, + init_event_log, + init_td_report, + servtd_ext, + ); + + if let Err(e) = &policy_check_result { + log::error!("Policy check failed, below is the detail information:\n"); + log::error!("{:x?}\n", e); + } + + let suppl_data = policy_check_result.map_err(|e| match e { + PolicyError::InvalidPolicy => { + log::error!("Invalid rebinding policy.\n"); + CryptoError::TlsVerifyPeerCert(INVALID_MIG_POLICY_ERROR.to_string()) + } + _ => { + log::error!("Rebinding policy unsatisfied.\n"); + CryptoError::TlsVerifyPeerCert(MIG_POLICY_UNSATISFIED_ERROR.to_string()) + } + })?; + + verify_signature_with_tdreport(&cert, suppl_data.as_slice()) + } + + #[cfg(feature = "policy_v2")] + pub fn verify_rebinding_new_cert( + cert: &[u8], + policy: &[u8], + ) -> core::result::Result<(), CryptoError> { + let cert = Certificate::from_der(cert).map_err(|_| { + log::error!("Failed to parse certificate from DER.\n"); + CryptoError::ParseCertificate + })?; + + let extensions = cert.tbs_certificate.extensions.as_ref().ok_or_else(|| { + log::error!("Failed to get certificate extensions.\n"); + CryptoError::ParseCertificate + })?; + // Check if extensions contain `MIGTD_EXTENDED_KEY_USAGE` + check_migtd_eku(extensions).map_err(|e| { + log::error!("Failed to check MIGTD EKU: {:?}\n", e); + e + })?; + + let td_report = find_extension(extensions, &EXTNID_MIGTD_TDREPORT).ok_or_else(|| { + log::error!("Failed to find quote report extension.\n"); + CryptoError::ParseCertificate + })?; + let event_log = find_extension(extensions, &EXTNID_MIGTD_EVENT_LOG).ok_or_else(|| { + log::error!("Failed to find event log extension.\n"); + CryptoError::ParseCertificate + })?; + let expected_policy_hash = find_extension(extensions, &EXTNID_MIGTD_POLICY_HASH) + .ok_or_else(|| { + log::error!("Failed to find expected policy hash extension.\n"); + CryptoError::ParseCertificate + })?; + + let exact_policy_hash = digest_sha384(policy)?; + if expected_policy_hash != exact_policy_hash.as_slice() { + log::error!("Invalid migration policy.\n"); + return Err(CryptoError::TlsVerifyPeerCert( + INVALID_MIG_POLICY_ERROR.to_string(), + )); + } + + let policy_check_result = + mig_policy::authenticate_rebinding_new(td_report, event_log, policy); + + if let Err(e) = &policy_check_result { + log::error!("Policy check failed, below is the detail information:\n"); + log::error!("{:x?}\n", e); + } + + let suppl_data = policy_check_result.map_err(|e| match e { + PolicyError::InvalidPolicy => { + log::error!("Invalid rebinding policy.\n"); + CryptoError::TlsVerifyPeerCert(INVALID_MIG_POLICY_ERROR.to_string()) + } + _ => { + log::error!("Rebinding policy unsatisfied.\n"); + CryptoError::TlsVerifyPeerCert(MIG_POLICY_UNSATISFIED_ERROR.to_string()) + } + })?; + + verify_signature_with_tdreport(&cert, suppl_data.as_slice()) + } + fn verify_signature(cert: &Certificate, verified_report: &[u8]) -> CryptoResult<()> { let public_key = cert .tbs_certificate @@ -568,6 +1192,32 @@ mod verify { ecdsa_verify(public_key, &tbs, signature) } + #[cfg(feature = "policy_v2")] + fn verify_signature_with_tdreport(cert: &Certificate, tdreport: &[u8]) -> CryptoResult<()> { + let public_key = cert + .tbs_certificate + .subject_public_key_info + .subject_public_key + .as_bytes() + .ok_or_else(|| { + log::error!("Failed to get public key bytes from certificate.\n"); + CryptoError::ParseCertificate + })?; + let tbs = cert.tbs_certificate.to_der().map_err(|e| { + log::error!("Failed to get tbs_certificate der: {:?}\n", e); + e + })?; + let signature = cert.signature_value.as_bytes().ok_or_else(|| { + log::error!("Failed to get signature bytes from certificate.\n"); + CryptoError::ParseCertificate + })?; + verify_public_key_with_tdreport(tdreport, public_key).map_err(|e| { + log::error!("Public key verification failed: {:?}\n", e); + e + })?; + ecdsa_verify(public_key, &tbs, signature) + } + fn verify_public_key(verified_report: &[u8], public_key: &[u8]) -> CryptoResult<()> { if cfg!(feature = "AzCVMEmu") { // In AzCVMEmu mode, REPORTDATA is constructed differently. @@ -594,6 +1244,37 @@ mod verify { )) } } + + #[cfg(feature = "policy_v2")] + fn verify_public_key_with_tdreport(tdreport: &[u8], public_key: &[u8]) -> CryptoResult<()> { + if cfg!(feature = "AzCVMEmu") { + // In AzCVMEmu mode, REPORTDATA is constructed differently. + // Bypass public key hash check in this development environment. + log::warn!( + "AzCVMEmu mode: Skipping public key verification in TD report. This is NOT secure for production use.\n" + ); + return Ok(()); + } + const PUBLIC_KEY_HASH_SIZE: usize = 48; + + let tdx_report = TdxReport::read_from_bytes(tdreport).ok_or( + CryptoError::TlsVerifyPeerCert(MISMATCH_PUBLIC_KEY.to_string()), + )?; + let report_data = &tdx_report.report_mac.report_data[..PUBLIC_KEY_HASH_SIZE]; + let digest = digest_sha384(public_key).map_err(|e| { + log::error!("Failed to compute SHA384 digest: {:?}\n", e); + e + })?; + + if report_data == digest.as_slice() { + Ok(()) + } else { + log::error!("Public key verification failed in TD report.\n"); + Err(CryptoError::TlsVerifyPeerCert( + MISMATCH_PUBLIC_KEY.to_string(), + )) + } + } } // Only for test to bypass the quote verification @@ -646,7 +1327,10 @@ fn check_migtd_eku(extensions: &Extensions) -> core::result::Result<(), CryptoEr Err(CryptoError::ParseCertificate) } -fn find_extension<'a>(extensions: &'a Extensions, id: &ObjectIdentifier) -> Option<&'a [u8]> { +pub(crate) fn find_extension<'a>( + extensions: &'a Extensions, + id: &ObjectIdentifier, +) -> Option<&'a [u8]> { extensions.get().iter().find_map(|extn| { if &extn.extn_id == id { extn.extn_value.map(|v| v.as_bytes()) From b0610e2209ddb61e875369516bf4febe5930b9da Mon Sep 17 00:00:00 2001 From: siweicai Date: Sun, 18 Jan 2026 18:39:54 +0800 Subject: [PATCH 4/7] Add spdm attestation flow for rebinding --- src/migtd/src/migration/rebinding.rs | 118 ++++++- src/migtd/src/migration/servtd_ext.rs | 1 + src/migtd/src/ratls/server_client.rs | 2 +- src/migtd/src/spdm/mod.rs | 6 + src/migtd/src/spdm/spdm_rebind.rs | 85 +++++ src/migtd/src/spdm/spdm_req.rs | 462 +++++++++++++++++++++++- src/migtd/src/spdm/spdm_rsp.rs | 486 +++++++++++++++++++++++++- src/migtd/src/spdm/spdm_vdm.rs | 49 ++- 8 files changed, 1179 insertions(+), 30 deletions(-) create mode 100644 src/migtd/src/spdm/spdm_rebind.rs diff --git a/src/migtd/src/migration/rebinding.rs b/src/migtd/src/migration/rebinding.rs index c56015ad..414cd80d 100644 --- a/src/migtd/src/migration/rebinding.rs +++ b/src/migtd/src/migration/rebinding.rs @@ -14,6 +14,8 @@ use ring::rand::{SecureRandom, SystemRandom}; use tdx_tdcall::tdx::{tdcall_servtd_rebind_approve, tdcall_vm_write}; use crate::migration::servtd_ext::read_servtd_ext; +#[cfg(feature = "spdm_attestation")] +use crate::spdm; use crate::{event_log, migration::transport::*}; use crypto::hash::digest_sha384; @@ -392,6 +394,22 @@ pub async fn start_rebinding( MIGTD_REBIND_OP_FINALIZE => rebinding_old_finalize(info, data).await?, _ => return Err(MigrationResult::InvalidParameter), } + + #[cfg(feature = "spdm_attestation")] + match info.operation { + MIGTD_REBIND_OP_PREPARE => { + rebinding_old_spdm( + transport, + info, + data, + #[cfg(feature = "policy_v2")] + remote_policy, + ) + .await? + } + MIGTD_REBIND_OP_FINALIZE => rebinding_old_finalize(info, data).await?, + _ => return Err(MigrationResult::InvalidParameter), + } } else { let pre_session_data = Box::pin(with_timeout( PRE_SESSION_TIMEOUT, @@ -421,8 +439,23 @@ pub async fn start_rebinding( MIGTD_REBIND_OP_FINALIZE => rebinding_new_finalize(info, data).await?, _ => return Err(MigrationResult::InvalidParameter), } - } + #[cfg(feature = "spdm_attestation")] + match info.operation { + MIGTD_REBIND_OP_PREPARE => { + rebinding_new_spdm( + transport, + info, + data, + #[cfg(feature = "policy_v2")] + pre_session_data, + ) + .await? + } + MIGTD_REBIND_OP_FINALIZE => rebinding_new_finalize(info, data).await?, + _ => return Err(MigrationResult::InvalidParameter), + } + } #[cfg(feature = "vmcall-raw")] { use crate::migration::logging::entrylog; @@ -437,6 +470,87 @@ pub async fn start_rebinding( Ok(()) } +#[cfg(feature = "spdm_attestation")] +pub async fn rebinding_old_spdm( + transport: TransportType, + info: &RebindingInfo<'_>, + _data: &mut Vec, + #[cfg(feature = "policy_v2")] remote_policy: Vec, +) -> Result<(), MigrationResult> { + const SPDM_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds + let mut spdm_requester = spdm::spdm_requester(transport).map_err(|_e| { + log::error!( + "rebinding: Failed in spdm_requester transport. Migration ID: {}\n", + info.mig_request_id + ); + MigrationResult::SecureSessionError + })?; + with_timeout( + SPDM_TIMEOUT, + spdm::spdm_requester_rebind_old( + &mut spdm_requester, + info, + #[cfg(feature = "policy_v2")] + remote_policy, + ), + ) + .await + .map_err(|e| { + log::error!( + "rebinding: spdm_requester_rebind_old timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!("rebinding: spdm_requester_rebind_old error: {:?}\n", e); + e + })?; + log::info!("Rebind completed\n"); + Ok(()) +} + +#[cfg(feature = "spdm_attestation")] +pub async fn rebinding_new_spdm( + transport: TransportType, + info: &RebindingInfo<'_>, + _data: &mut Vec, + #[cfg(feature = "policy_v2")] remote_policy: Vec, +) -> Result<(), MigrationResult> { + const SPDM_TIMEOUT: Duration = Duration::from_secs(60); // 60 seconds + let mut spdm_responder = spdm::spdm_responder(transport).map_err(|_e| { + log::error!( + "rebinding: Failed in spdm_responder transport. Migration ID: {}\n", + info.mig_request_id + ); + MigrationResult::SecureSessionError + })?; + + with_timeout( + SPDM_TIMEOUT, + spdm::spdm_responder_rebind_new( + &mut spdm_responder, + info, + #[cfg(feature = "policy_v2")] + remote_policy, + ), + ) + .await + .map_err(|e| { + log::error!( + "rebinding: spdm_responder_rebind_new timeout error: {:?}\n", + e + ); + e + })? + .map_err(|e| { + log::error!("rebinding: spdm_responder_rebind_new error: {:?}\n", e); + e + })?; + log::info!("Rebind completed\n"); + Ok(()) +} + pub async fn rebinding_old_prepare( transport: TransportType, info: &RebindingInfo<'_>, @@ -594,7 +708,7 @@ fn get_servtd_ext_from_cert(certs: &Option>) -> Result Result { +pub fn create_rebind_token(info: &RebindingInfo) -> Result { let mut token = [0u8; 32]; let rng = SystemRandom::new(); rng.fill(&mut token) diff --git a/src/migtd/src/migration/servtd_ext.rs b/src/migtd/src/migration/servtd_ext.rs index 15e064a7..e48bb85c 100644 --- a/src/migtd/src/migration/servtd_ext.rs +++ b/src/migtd/src/migration/servtd_ext.rs @@ -24,6 +24,7 @@ pub const TDCS_FIELD_SERVTD_ATTR: u64 = 0x1910000300000202; pub const TDCS_FIELD_SERVTD_ACCEPT_SERVTD_EXT_HASH: u64 = 0x1910000300000214; #[repr(C)] +#[derive(Clone, Copy)] pub struct ServtdExt { pub init_servtd_info_hash: [u8; 48], pub init_attr: [u8; 8], diff --git a/src/migtd/src/ratls/server_client.rs b/src/migtd/src/ratls/server_client.rs index 44751fd7..b29da972 100644 --- a/src/migtd/src/ratls/server_client.rs +++ b/src/migtd/src/ratls/server_client.rs @@ -251,7 +251,7 @@ fn gen_quote(public_key: &[u8]) -> Result> { }) } -fn gen_tdreport(public_key: &[u8]) -> Result { +pub fn gen_tdreport(public_key: &[u8]) -> Result { let hash = digest_sha384(public_key).map_err(|e| { log::error!("Failed to compute SHA384 digest: {:?}\n", e); e diff --git a/src/migtd/src/spdm/mod.rs b/src/migtd/src/spdm/mod.rs index 5c2f1924..6922af8a 100644 --- a/src/migtd/src/spdm/mod.rs +++ b/src/migtd/src/spdm/mod.rs @@ -4,6 +4,8 @@ #![cfg(feature = "spdm_attestation")] +#[cfg(feature = "policy_v2")] +mod spdm_rebind; mod spdm_req; mod spdm_rsp; mod spdm_vdm; @@ -25,6 +27,10 @@ use zeroize::ZeroizeOnDrop; use async_io::AsyncRead; use async_io::AsyncWrite; use crypto::hash::digest_sha384; +#[cfg(feature = "policy_v2")] +pub use spdm_rebind::spdm_requester_rebind_old; +#[cfg(feature = "policy_v2")] +pub use spdm_rebind::spdm_responder_rebind_new; pub use spdm_req::spdm_requester; pub use spdm_req::spdm_requester_transfer_msk; pub use spdm_rsp::spdm_responder; diff --git a/src/migtd/src/spdm/spdm_rebind.rs b/src/migtd/src/spdm/spdm_rebind.rs new file mode 100644 index 00000000..cfb3c2f1 --- /dev/null +++ b/src/migtd/src/spdm/spdm_rebind.rs @@ -0,0 +1,85 @@ +// Copyright (c) 2026 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent +use crate::{ + migration::{rebinding::RebindingInfo, MigtdMigrationInformation}, + spdm::{ + spdm_req::{ + send_and_receive_pub_key, send_and_receive_sdm_rebind_attest_info, + send_and_receive_sdm_rebind_info, + }, + spdm_rsp::{rsp_handle_message, ResponderContextEx, ResponderContextExInfo}, + PrivateKeyDer, SpdmAppContextData, + }, +}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use codec::{Codec, Writer}; +use spdmlib::{ + error::{SpdmStatus, SPDM_STATUS_BUFFER_FULL}, + protocol::SpdmMeasurementSummaryHashType, + requester::RequesterContext, +}; +use zeroize::Zeroize; + +pub async fn spdm_requester_rebind_old( + spdm_requester: &mut RequesterContext, + rebind_info: &RebindingInfo<'_>, + remote_policy: Vec, +) -> Result<(), SpdmStatus> { + Box::pin(spdm_requester.send_receive_spdm_version()).await?; + Box::pin(spdm_requester.send_receive_spdm_capability()).await?; + Box::pin(spdm_requester.send_receive_spdm_algorithm()).await?; + + Box::pin(send_and_receive_pub_key(spdm_requester)).await?; + let session_id = Box::pin(spdm_requester.send_receive_spdm_key_exchange( + 0xff, + SpdmMeasurementSummaryHashType::SpdmMeasurementSummaryHashTypeNone, + )) + .await?; + + Box::pin(send_and_receive_sdm_rebind_attest_info( + spdm_requester, + rebind_info, + session_id, + remote_policy, + )) + .await?; + + Box::pin(spdm_requester.send_receive_spdm_finish(Some(0xff), session_id)).await?; + + Box::pin(send_and_receive_sdm_rebind_info( + spdm_requester, + rebind_info, + Some(session_id), + )) + .await?; + + Box::pin(spdm_requester.send_receive_spdm_end_session(session_id)).await?; + Ok(()) +} + +pub async fn spdm_responder_rebind_new<'a>( + spdm_responder_ex: &mut ResponderContextEx<'a>, + rebind_info: &'a RebindingInfo<'a>, + remote_policy: Vec, +) -> Result<(), SpdmStatus> { + spdm_responder_ex.remote_policy = remote_policy; + spdm_responder_ex.info = ResponderContextExInfo::RebindInformation(rebind_info); + + let spdm_responder = &mut spdm_responder_ex.responder_context; + let mut writer = Writer::init(&mut spdm_responder.common.app_context_data_buffer); + + let responder_app_context = SpdmAppContextData { + migration_info: MigtdMigrationInformation::default(), + private_key: PrivateKeyDer::default(), + }; + responder_app_context + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + Box::pin(rsp_handle_message(spdm_responder)).await?; + spdm_responder.common.app_context_data_buffer.zeroize(); + + Ok(()) +} diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index fafcc06f..4e35ec01 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: BSD-2-Clause-Patent use crate::mig_policy; +#[cfg(feature = "policy_v2")] +use crate::migration::rebinding::RebindingInfo; use crate::{ migration::{ data::MigrationSessionKey, @@ -23,12 +25,11 @@ use spdmlib::{ }; use spin::Mutex; extern crate alloc; -use alloc::sync::Arc; -use log::error; - use crate::{ config::get_policy, event_log::get_event_log, migration::session::ExchangeInformation, }; +use alloc::sync::Arc; +use log::error; pub fn spdm_requester( stream: T, @@ -118,7 +119,7 @@ pub async fn spdm_requester_transfer_msk( Ok(()) } -async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> SpdmResult { +pub async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> SpdmResult { let signing_key = EcdsaPk::new().map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; let my_pub_key = signing_key.public_key_spki(); @@ -306,7 +307,7 @@ pub async fn send_and_receive_sdm_migration_attest_info( major_version: VDM_MESSAGE_MAJOR_VERSION, minor_version: VDM_MESSAGE_MINOR_VERSION, op_code: VdmMessageOpCode::ExchangeMigrationAttestInfoReq, - element_count: VDM_MESSAGE_EXCHANGE_MIG_ATTEST_INFO_ELEMENT_COUNT, + element_count: VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT, }; cnt += vdm_exchange_attest_info @@ -460,7 +461,7 @@ pub async fn send_and_receive_sdm_migration_attest_info( error!("Invalid VDM message op_code: {:x?}\n", vdm_message.op_code); return Err(SPDM_STATUS_INVALID_MSG_FIELD); } - if vdm_message.element_count != VDM_MESSAGE_EXCHANGE_MIG_ATTEST_INFO_ELEMENT_COUNT { + if vdm_message.element_count != VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT { error!( "Invalid VDM message element_count: {:x?}\n", vdm_message.element_count @@ -777,3 +778,452 @@ async fn send_and_receive_sdm_exchange_migration_info( Ok(()) } + +#[cfg(feature = "policy_v2")] +pub async fn send_and_receive_sdm_rebind_attest_info( + spdm_requester: &mut RequesterContext, + rebind_info: &RebindingInfo<'_>, + session_id: u32, + remote_policy: Vec, +) -> SpdmResult { + use crate::{migration::servtd_ext::read_servtd_ext, ratls::gen_tdreport}; + + if spdm_requester.common.provision_info.my_pub_key.is_none() + || spdm_requester.common.provision_info.peer_pub_key.is_none() + { + error!("Cannot transfer attestation info without provisioning my_pub_key.\n"); + return Err(SPDM_STATUS_UNSUPPORTED_CAP); + } + + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let mut payload = vec![0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_attest_info = VdmMessage { + major_version: VDM_MESSAGE_MAJOR_VERSION, + minor_version: VDM_MESSAGE_MINOR_VERSION, + op_code: VdmMessageOpCode::ExchangeRebindAttestInfoReq, + element_count: VDM_MESSAGE_EXCHANGE_ATTEST_INFO_WITH_HISTORY_INFO_ELEMENT_COUNT, + }; + + cnt += vdm_exchange_attest_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + let th1 = if let Some(s) = spdm_requester.common.get_session_via_id(session_id) { + s.get_th1() + } else { + error!("Cannot get session id. Attestation failed.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + }; + + let report_data_prefix = "MigTDReq".as_bytes(); + let report_data_prefix_len = report_data_prefix.len(); + // Build concatenated slice: "MigTDReq" || th1 + let th1_len = th1.data_size as usize; + // th1 for SHA-384 should be 48 bytes; 8 (prefix) + 48 digest = 56 bytes needed. + if th1_len > SPDM_MAX_HASH_SIZE { + error!("th1 length is too large: {}\n", th1_len); + return Err(SPDM_STATUS_BUFFER_FULL); + } + let mut report_data = [0u8; "MigTDReq".len() + SPDM_MAX_HASH_SIZE]; + // Copy prefix + report_data[..report_data_prefix_len].copy_from_slice(report_data_prefix); + report_data[report_data_prefix_len..report_data_prefix_len + th1_len] + .copy_from_slice(&th1.data[..th1_len]); + + //TD report src + let td_report_src = gen_tdreport(&report_data[..report_data_prefix_len + th1_len]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + let td_report_src_bytes = td_report_src.as_bytes(); + + if td_report_src_bytes.len() > u16::MAX as usize { + error!( + "Td report src size is too large: {}\n", + td_report_src_bytes.len() + ); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let tdreport_element = VdmMessageElement { + element_type: VdmMessageElementType::TdReportMy, + length: td_report_src_bytes.len() as u16, + }; + cnt += tdreport_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(td_report_src_bytes) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //event log src + let event_log_src = get_event_log().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if event_log_src.len() > u16::MAX as usize { + error!("Event log size is too large: {}\n", event_log_src.len()); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let event_log_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogMy, + length: event_log_src.len() as u16, + }; + cnt += event_log_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(event_log_src) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //mig policy src + let mig_policy_src = get_policy().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + let mig_policy_src_hash = + digest_sha384(mig_policy_src).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + + let mig_policy_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyMy, + length: mig_policy_src_hash.len() as u16, + }; + cnt += mig_policy_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&mig_policy_src_hash) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //SERVTD_EXT + let binding_handle = rebind_info.binding_handle; + let target_td_uuid = &rebind_info.target_td_uuid; + if rebind_info.init_migtd_data.is_none() { + error!("RebindingInfo.init_migtd_data is None!\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let init_migtd_data = rebind_info.init_migtd_data.as_ref().unwrap(); + + let servtd_ext = read_servtd_ext(binding_handle, target_td_uuid) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + + let servtd_ext_element = VdmMessageElement { + element_type: VdmMessageElementType::SerVtdExt, + length: servtd_ext.as_bytes().len() as u16, + }; + cnt += servtd_ext_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(servtd_ext.as_bytes()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //TD report init + let tdreport_init = &init_migtd_data.init_report; + let tdreport_init_element = VdmMessageElement { + element_type: VdmMessageElementType::TdReportInit, + length: tdreport_init.len() as u16, + }; + cnt += tdreport_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(tdreport_init) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //event log init + let event_log_init = init_migtd_data.init_event_log; + let event_log_init_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogInit, + length: event_log_init.len() as u16, + }; + cnt += event_log_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&event_log_init) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //mig policy init hash + let mig_policy_init = init_migtd_data.init_policy; + let mig_policy_init_hash = + digest_sha384(mig_policy_init).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + let mig_policy_init_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyInit, + length: mig_policy_init_hash.len() as u16, + }; + cnt += mig_policy_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&mig_policy_init_hash) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + spdm_requester.common.reset_buffer_via_request_code( + SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + None, + ); + + let mut send_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let mut writer = Writer::init(&mut send_buffer); + let request_header = SpdmMessageHeader { + version: spdm_requester.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + }; + let request_payload = SpdmVdmRequestPayload { + standard_id: RegistryOrStandardsBodyID::IANA, + vendor_id, + req_length: cnt as u32, + req_payload: payload, + }; + let mut send_used = 0; + send_used += request_header + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + send_used += request_payload.spdm_encode(&mut spdm_requester.common, &mut writer)?; + + let mut receive_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let response = spdm_requester + .send_spdm_vendor_defined_request_ex(None, &send_buffer[..send_used], &mut receive_buffer) + .await?; + + //Format checks + let mut reader = Reader::init(response); + let _response_header = + SpdmMessageHeader::read(&mut reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let response_payload = + SpdmVdmResponsePayload::spdm_read(&mut spdm_requester.common, &mut reader) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + let reader = + &mut Reader::init(&response_payload.rsp_payload[..response_payload.rsp_length as usize]); + let vdm_message = VdmMessage::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_message.major_version != VDM_MESSAGE_MAJOR_VERSION { + error!( + "Invalid VDM message major_version: {:x?}\n", + vdm_message.major_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.minor_version != VDM_MESSAGE_MINOR_VERSION { + error!( + "Invalid VDM message minor_version: {:x?}\n", + vdm_message.minor_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.op_code != VdmMessageOpCode::ExchangeRebindAttestInfoRsp { + error!("Invalid VDM message op_code: {:x?}\n", vdm_message.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.element_count != VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_message.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + //td report dst + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::TdReportMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let td_report_dst = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let td_report_dst_vec = td_report_dst.to_vec(); + + //event log dst + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::EventLogMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let event_log_dst = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let event_log_dst_vec = event_log_dst.to_vec(); + + //mig policy dst + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::MigPolicyMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let mig_policy_hash_dst = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + #[cfg(not(feature = "test_disable_ra_and_accept_all"))] + { + let remote_policy_hash = + digest_sha384(&remote_policy).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + if mig_policy_hash_dst != remote_policy_hash.as_slice() { + error!( + "The received mig policy hash does not match the expected remote policy hash!\n" + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let policy_check_result = mig_policy::authenticate_rebinding_new( + &td_report_dst_vec, + &event_log_dst_vec, + &remote_policy, + ); + + if let Err(e) = &policy_check_result { + error!("Policy v2 check failed, below is the detail information:\n"); + error!("{:x?}\n", e); + let session = spdm_requester + .common + .get_session_via_id(session_id) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + session.teardown(); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + } + + let vdm_attest_info_src_hash = + digest_sha384(&send_buffer[..send_used]).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + let vdm_attest_info_dst_hash = digest_sha384(response).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + let mut transcript_before_finish = ManagedVdmBuffer::default(); + transcript_before_finish + .append_message(vdm_attest_info_src_hash.as_slice()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + transcript_before_finish + .append_message(vdm_attest_info_dst_hash.as_slice()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + if let Some(s) = spdm_requester.common.get_session_via_id(session_id) { + s.runtime_info.vdm_message_transcript_before_finish = Some(transcript_before_finish); + } else { + error!("Cannot get session id. Attestation failed.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + Ok(()) +} + +#[cfg(feature = "policy_v2")] +pub async fn send_and_receive_sdm_rebind_info( + spdm_requester: &mut RequesterContext, + rebind_info: &RebindingInfo<'_>, + session_id: Option, +) -> SpdmResult { + use crate::migration::rebinding::{approve_rebinding, create_rebind_token}; + + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; + vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); + let vendor_id = VendorIDStruct { len: 4, vendor_id }; + + let rebind_token = create_rebind_token(rebind_info)?; + let token = rebind_token.token; + + let mut payload = vec![0u8; MAX_SPDM_VENDOR_DEFINED_PAYLOAD_SIZE]; + let mut writer = Writer::init(&mut payload); + let mut cnt = 0; + + let vdm_exchange_migration_info = VdmMessage { + major_version: VDM_MESSAGE_MAJOR_VERSION, + minor_version: VDM_MESSAGE_MINOR_VERSION, + op_code: VdmMessageOpCode::ExchangeRebindInfoReq, + element_count: VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_REQ_COUNT, + }; + + cnt += vdm_exchange_migration_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + // Rebind session token + let rebind_session_token_element = VdmMessageElement { + element_type: VdmMessageElementType::RebindSessionToken, + length: token.len() as u16, + }; + cnt += rebind_session_token_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&token) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + spdm_requester.common.reset_buffer_via_request_code( + SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + None, + ); + + let mut send_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let mut writer = Writer::init(&mut send_buffer); + + let request_header = SpdmMessageHeader { + version: spdm_requester.common.negotiate_info.spdm_version_sel, + request_response_code: SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, + }; + let request_payload = SpdmVdmRequestPayload { + standard_id: RegistryOrStandardsBodyID::IANA, + vendor_id, + req_length: cnt as u32, + req_payload: payload, + }; + let mut send_used = 0; + send_used += request_header + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + send_used += request_payload.spdm_encode(&mut spdm_requester.common, &mut writer)?; + + let mut receive_buffer = [0u8; config::MAX_SPDM_MSG_SIZE]; + let response = spdm_requester + .send_spdm_vendor_defined_request_ex( + session_id, + &send_buffer[..send_used], + &mut receive_buffer, + ) + .await?; + + // Format checks + let mut reader = Reader::init(response); + let _response_header = + SpdmMessageHeader::read(&mut reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let response_payload = + SpdmVdmResponsePayload::spdm_read(&mut spdm_requester.common, &mut reader) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + let reader = + &mut Reader::init(&response_payload.rsp_payload[..response_payload.rsp_length as usize]); + let vdm_message = VdmMessage::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_message.major_version != VDM_MESSAGE_MAJOR_VERSION { + error!( + "Invalid VDM message major_version: {:x?}\n", + vdm_message.major_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.minor_version != VDM_MESSAGE_MINOR_VERSION { + error!( + "Invalid VDM message minor_version: {:x?}\n", + vdm_message.minor_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.op_code != VdmMessageOpCode::ExchangeRebindInfoRsp { + error!("Invalid VDM message op_code: {:x?}\n", vdm_message.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_message.element_count != VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_RSP_COUNT { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_message.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + approve_rebinding(rebind_info, &rebind_token).map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + + Ok(()) +} diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 1fb971ea..21700c8a 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -3,6 +3,13 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent use crate::mig_policy; +#[cfg(feature = "policy_v2")] +use crate::migration::{ + rebinding::{write_rebinding_session_token, write_servtd_rebind_attr, RebindingInfo}, + servtd_ext::{write_approved_servtd_ext_hash, ServtdExt}, +}; +#[cfg(feature = "policy_v2")] +use crate::ratls::gen_tdreport; use crate::{ config::get_policy, event_log::get_event_log, @@ -38,12 +45,22 @@ use zeroize::Zeroize; extern crate alloc; #[repr(C)] -pub struct ResponderContextEx { +pub struct ResponderContextEx<'a> { pub responder_context: ResponderContext, pub remote_policy: Vec, + pub info: ResponderContextExInfo<'a>, + #[cfg(feature = "policy_v2")] + pub servtd_ext: Option, } -impl ResponderContextEx { +pub enum ResponderContextExInfo<'a> { + MigrationInformation(&'a MigtdMigrationInformation), + #[cfg(feature = "policy_v2")] + RebindInformation(&'a RebindingInfo<'a>), + None, +} + +impl ResponderContextEx<'_> { pub fn inner(&self) -> &ResponderContext { &self.responder_context } @@ -59,9 +76,9 @@ pub unsafe fn upcast_mut(inner: &mut ResponderContext) -> &mut ResponderContextE &mut *outer_ptr } -pub fn spdm_responder( +pub fn spdm_responder<'a, T: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static>( stream: T, -) -> Result { +) -> Result, SpdmStatus> { let transport = MigtdTransport { transport: stream }; let device_io = Arc::new(Mutex::new(transport)); @@ -118,13 +135,16 @@ pub fn spdm_responder let responder_context_ex = ResponderContextEx { responder_context, remote_policy: Vec::new(), + info: ResponderContextExInfo::None, + #[cfg(feature = "policy_v2")] + servtd_ext: None, }; Ok(responder_context_ex) } pub async fn spdm_responder_transfer_msk( - spdm_responder_ex: &mut ResponderContextEx, + spdm_responder_ex: &mut ResponderContextEx<'_>, mig_info: &MigtdMigrationInformation, #[cfg(feature = "policy_v2")] remote_policy: Vec, ) -> Result<(), SpdmStatus> { @@ -387,7 +407,7 @@ pub fn handle_exchange_mig_attest_info_req( error!("Invalid VDM message op_code: {:x?}\n", vdm_request.op_code); return Err(SPDM_STATUS_INVALID_MSG_FIELD); } - if vdm_request.element_count != VDM_MESSAGE_EXCHANGE_MIG_ATTEST_INFO_ELEMENT_COUNT { + if vdm_request.element_count != VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT { error!( "Invalid VDM message element_count: {:x?}\n", vdm_request.element_count @@ -562,7 +582,7 @@ pub fn handle_exchange_mig_attest_info_req( major_version: VDM_MESSAGE_MAJOR_VERSION, minor_version: VDM_MESSAGE_MINOR_VERSION, op_code: VdmMessageOpCode::ExchangeMigrationAttestInfoRsp, - element_count: VDM_MESSAGE_EXCHANGE_MIG_ATTEST_INFO_ELEMENT_COUNT, + element_count: VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT, }; cnt += vdm_exchange_attest_info @@ -762,6 +782,458 @@ pub fn handle_exchange_mig_info_req( Ok(cnt) } +#[cfg(feature = "policy_v2")] +pub fn handle_exchange_rebind_attest_info_req( + responder_context: &mut ResponderContext, + session_id: Option, + vdm_request: &VdmMessage, + reader: &mut Reader<'_>, + vendor_defined_rsp_payload: &mut [u8], +) -> SpdmResult { + // Rebinding messages must be handled only when rebinding info is set. + let spdm_responder_ex = unsafe { upcast_mut(responder_context) }; + if !matches!( + &spdm_responder_ex.info, + ResponderContextExInfo::RebindInformation(_) + ) { + error!("Rebinding info is not set in responder context.\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let session_id = if session_id.is_some() { + session_id + } else { + responder_context.common.runtime_info.get_last_session_id() + }; + if session_id.is_none() { + error!("Cannot transfer attestation info before key exchange.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let session_id = session_id.unwrap(); + + let session = responder_context + .common + .get_session_via_id(session_id) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if session + .runtime_info + .vdm_message_transcript_before_finish + .is_some() + { + error!("Attestation info has already been exchanged.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + if responder_context.common.provision_info.my_pub_key.is_none() + || responder_context + .common + .provision_info + .peer_pub_key + .is_none() + { + error!("Cannot transfer attestation info without provisioning pub_key.\n"); + return Err(SPDM_STATUS_UNSUPPORTED_CAP); + } + + if vdm_request.major_version != VDM_MESSAGE_MAJOR_VERSION { + error!( + "Invalid VDM message major_version: {:x?}\n", + vdm_request.major_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.minor_version != VDM_MESSAGE_MINOR_VERSION { + error!( + "Invalid VDM message minor_version: {:x?}\n", + vdm_request.minor_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.op_code != VdmMessageOpCode::ExchangeRebindAttestInfoReq { + error!("Invalid VDM message op_code: {:x?}\n", vdm_request.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.element_count != VDM_MESSAGE_EXCHANGE_ATTEST_INFO_WITH_HISTORY_INFO_ELEMENT_COUNT + { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_request.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let th1 = if let Some(s) = responder_context.common.get_session_via_id(session_id) { + s.get_th1() + } else { + error!("Cannot get TH1 from session.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + }; + + let report_data_prefix = "MigTDRsp".as_bytes(); + let report_data_prefix_len = report_data_prefix.len(); + // Build concatenated slice: "MigTDRsp" || th1 + let th1_len = th1.data_size as usize; + // th1 for SHA-384 should be 48 bytes; 8 (prefix) + 48 digest = 56 bytes needed. + if th1_len > SPDM_MAX_HASH_SIZE { + error!("th1 length is too large: {}\n", th1_len); + return Err(SPDM_STATUS_BUFFER_FULL); + } + let mut report_data = [0u8; "MigTDRsp".len() + SPDM_MAX_HASH_SIZE]; + // Copy prefix + report_data[..report_data_prefix_len].copy_from_slice(report_data_prefix); + report_data[report_data_prefix_len..report_data_prefix_len + th1_len] + .copy_from_slice(&th1.data[..th1_len]); + + //TD report src + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::TdReportMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let td_report_src = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let td_report_src_vec = td_report_src.to_vec(); + + //event log src + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::EventLogMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let event_log_src = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let event_log_src_vec = event_log_src.to_vec(); + + //mig policy src + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::MigPolicyMy { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let mig_policy_hash_src = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let mig_policy_hash_src_vec = mig_policy_hash_src.to_vec(); + + // SERVTD_EXT + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::SerVtdExt { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let servtd_ext = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let servtd_ext_vec = servtd_ext.to_vec(); + + // TD report init + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::TdReportInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let td_report_init = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let td_report_init_vec = td_report_init.to_vec(); + + // event log init + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::EventLogInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let event_log_init = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let event_log_init_vec = event_log_init.to_vec(); + + // mig policy init hash + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::MigPolicyInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let mig_policy_init_hash_src = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + // attestation verification + #[cfg(not(feature = "test_disable_ra_and_accept_all"))] + { + let pre_session_data = unsafe { + let spdm_responder_ex = upcast_mut(responder_context); + spdm_responder_ex.remote_policy.as_slice() + }; + + //FIXME: Enable easier access to pre-session data, aligned to tls fixme. + let remote_policy_size = u32::from_le_bytes( + pre_session_data + .get(..4) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)? + .try_into() + .unwrap(), + ) as usize; + 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( + pre_session_data + .get(init_policy_offset..4 + init_policy_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) + .ok_or(MigrationResult::InvalidPolicyError)?; + + let remote_policy_hash = + digest_sha384(remote_policy).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + if mig_policy_hash_src_vec != remote_policy_hash { + error!( + "The received mig policy hash does not match the expected remote policy hash!\n" + ); + 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" + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let policy_check_result = mig_policy::authenticate_rebinding_old( + &td_report_src_vec, + &event_log_src_vec, + remote_policy, + init_policy, + &event_log_init_vec, + &td_report_init_vec, + &servtd_ext_vec, + ); + if let Err(e) = &policy_check_result { + error!("Policy v2 check failed, below is the detail information:\n"); + error!("{:x?}\n", e); + let session = responder_context + .common + .get_session_via_id(session_id) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + session.teardown(); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + } + + unsafe { + let spdm_responder_ex = upcast_mut(responder_context); + spdm_responder_ex.servtd_ext = ServtdExt::read_from_bytes(&servtd_ext_vec); + }; + + let mut writer = Writer::init(vendor_defined_rsp_payload); + let mut cnt = 0; + + let vdm_exchange_attest_info = VdmMessage { + major_version: VDM_MESSAGE_MAJOR_VERSION, + minor_version: VDM_MESSAGE_MINOR_VERSION, + op_code: VdmMessageOpCode::ExchangeRebindAttestInfoRsp, + element_count: VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT, + }; + + cnt += vdm_exchange_attest_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + //TD report dst + let td_report_dst = gen_tdreport(&report_data[..report_data_prefix_len + th1_len]) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + let td_report_dst_bytes = td_report_dst.as_bytes(); + + if td_report_dst_bytes.len() > u16::MAX as usize { + error!( + "Td report size is too large: {}\n", + td_report_dst_bytes.len() + ); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let tdreport_element = VdmMessageElement { + element_type: VdmMessageElementType::TdReportMy, + length: td_report_dst_bytes.len() as u16, + }; + cnt += tdreport_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(td_report_dst_bytes) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //event log dst + let event_log_dst = get_event_log().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if event_log_dst.len() > u16::MAX as usize { + error!("Event log size is too large: {}\n", event_log_dst.len()); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let event_log_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogMy, + length: event_log_dst.len() as u16, + }; + cnt += event_log_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(event_log_dst) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //mig policy dst + let mig_policy_dst = get_policy().ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + let mig_policy_dst_hash = + digest_sha384(mig_policy_dst).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + let mig_policy_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyMy, + length: mig_policy_dst_hash.len() as u16, + }; + cnt += mig_policy_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&mig_policy_dst_hash) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + Ok(cnt) +} + +#[cfg(feature = "policy_v2")] +pub fn handle_exchange_rebind_info_req( + responder_context: &mut ResponderContext, + session_id: Option, + vdm_request: &VdmMessage, + reader: &mut Reader<'_>, + vendor_defined_rsp_payload: &mut [u8], +) -> SpdmResult { + // Rebinding messages must be handled only when rebinding info is set. + let spdm_responder_ex = unsafe { upcast_mut(responder_context) }; + if !matches!( + &spdm_responder_ex.info, + ResponderContextExInfo::RebindInformation(_) + ) { + error!("Rebinding info is not set in responder context.\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + // The VDM message for secret migration info exchange MUST be sent after mutual attested session establishment. + let session_id = if let Some(sid) = session_id { + sid + } else { + return Err(SPDM_STATUS_INVALID_PARAMETER); + }; + + let session = responder_context + .common + .get_session_via_id(session_id) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if session.get_session_state() + != spdmlib::common::session::SpdmSessionState::SpdmSessionEstablished + || session + .runtime_info + .vdm_message_transcript_before_finish + .is_none() + { + error!("Migration info received while session is not established!\n"); + session.teardown(); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + + if vdm_request.major_version != VDM_MESSAGE_MAJOR_VERSION { + error!( + "Invalid VDM message major_version: {:x?}\n", + vdm_request.major_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.minor_version != VDM_MESSAGE_MINOR_VERSION { + error!( + "Invalid VDM message minor_version: {:x?}\n", + vdm_request.minor_version + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.op_code != VdmMessageOpCode::ExchangeRebindInfoReq { + error!("Invalid VDM message op_code: {:x?}\n", vdm_request.op_code); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + if vdm_request.element_count != VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_REQ_COUNT { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_request.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let rebind_session_token_element = + VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if rebind_session_token_element.element_type != VdmMessageElementType::RebindSessionToken + || rebind_session_token_element.length + != VDM_MESSAGE_EXCHANGE_REBIND_INFO_SESSION_TOKEN_SIZE + { + error!("invalid rebind session token!\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + let mut token = <[u8; 32]>::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + let servtd_ext = unsafe { + let spdm_responder_ex = upcast_mut(responder_context); + spdm_responder_ex + .servtd_ext + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)? + }; + + write_rebinding_session_token(&token)?; + write_approved_servtd_ext_hash(&servtd_ext.calculate_approved_servtd_ext_hash()?)?; + write_servtd_rebind_attr(&servtd_ext.cur_servtd_attr)?; + token.zeroize(); + + let mut writer = Writer::init(vendor_defined_rsp_payload); + let mut cnt = 0; + let vdm_exchange_mig_info = VdmMessage { + major_version: VDM_MESSAGE_MAJOR_VERSION, + minor_version: VDM_MESSAGE_MINOR_VERSION, + op_code: VdmMessageOpCode::ExchangeRebindInfoRsp, + element_count: VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_RSP_COUNT, + }; + cnt += vdm_exchange_mig_info + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + Ok(cnt) +} + pub static SECRET_ASYM_IMPL_INSTANCE: SpdmSecretAsymSign = SpdmSecretAsymSign { sign_cb: asym_sign }; diff --git a/src/migtd/src/spdm/spdm_vdm.rs b/src/migtd/src/spdm/spdm_vdm.rs index cb86b0e7..38620949 100644 --- a/src/migtd/src/spdm/spdm_vdm.rs +++ b/src/migtd/src/spdm/spdm_vdm.rs @@ -24,11 +24,15 @@ pub const VDM_MESSAGE_MAJOR_VERSION: u8 = 0; pub const VDM_MESSAGE_MINOR_VERSION: u8 = 0; pub const VDM_MESSAGE_EXCHANGE_PUB_KEY_ELEMENT_COUNT: u8 = 1; -pub const VDM_MESSAGE_EXCHANGE_MIG_ATTEST_INFO_ELEMENT_COUNT: u8 = 3; +pub const VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT: u8 = 3; +pub const VDM_MESSAGE_EXCHANGE_ATTEST_INFO_WITH_HISTORY_INFO_ELEMENT_COUNT: u8 = 7; pub const VDM_MESSAGE_EXCHANGE_MIG_INFO_ELEMENT_COUNT: u8 = 2; +pub const VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_REQ_COUNT: u8 = 1; +pub const VDM_MESSAGE_EXCHANGE_REBIND_INFO_ELEMENT_RSP_COUNT: u8 = 0; pub const VDM_MESSAGE_EXCHANGE_MIG_INFO_MIGRATION_VERSION_SIZE: u16 = 4; pub const VDM_MESSAGE_EXCHANGE_MIG_INFO_MIGRATION_SESSION_KEY_SIZE: u16 = 32; +pub const VDM_MESSAGE_EXCHANGE_REBIND_INFO_SESSION_TOKEN_SIZE: u16 = 32; enum_builder! { @U8 @@ -40,11 +44,11 @@ enum_builder! { ExchangeMigrationAttestInfoReq => 0x03, ExchangeMigrationAttestInfoRsp => 0x04, ExchangeMigrationInfoReq => 0x05, - ExchangeMigrationInfoRsp => 0x06 -// ExchangeRebindAttestInfoReq => 0x07, -// ExchangeRebindAttestInfoRsp => 0x08, -// ExchangeRebindInfoReq => 0x09, -// ExchangeRebindInfoRsp => 0x0A + ExchangeMigrationInfoRsp => 0x06, + ExchangeRebindAttestInfoReq => 0x07, + ExchangeRebindAttestInfoRsp => 0x08, + ExchangeRebindInfoReq => 0x09, + ExchangeRebindInfoRsp => 0x0A } } @@ -97,15 +101,15 @@ enum_builder! { QuoteMy => 0x03, EventLogMy => 0x04, MigPolicyMy => 0x05, -// SerVtdExt => 0x10, -// TdReportInit => 0x12, -// EventLogInit => 0x14, -// MigPolicyInit => 0x15, + SerVtdExt => 0x10, + TdReportInit => 0x12, + EventLogInit => 0x14, + MigPolicyInit => 0x15, MigrationExportVersion => 0x81, MigrationImportVersion => 0x82, ForwardMigrationSessionKey => 0x83, - BackwardMigrationSessionKey => 0x84 -// RebindSessionToken => 0x85, + BackwardMigrationSessionKey => 0x84, + RebindSessionToken => 0x85 } } @@ -459,6 +463,22 @@ pub fn migtd_vdm_msg_rsp_dispatcher_ex<'a>( &mut reader, &mut vdm_rsp_payload.rsp_payload, ), + #[cfg(feature = "policy_v2")] + VdmMessageOpCode::ExchangeRebindAttestInfoReq => handle_exchange_rebind_attest_info_req( + responder_context, + session_id, + &vdm_request, + &mut reader, + &mut vdm_rsp_payload.rsp_payload, + ), + #[cfg(feature = "policy_v2")] + VdmMessageOpCode::ExchangeRebindInfoReq => handle_exchange_rebind_info_req( + responder_context, + session_id, + &vdm_request, + &mut reader, + &mut vdm_rsp_payload.rsp_payload, + ), _ => Err(SPDM_STATUS_INVALID_MSG_FIELD), }; if let Ok(vdm_payload_size) = vdm_payload_size { @@ -544,7 +564,8 @@ pub fn migtd_vdm_msg_rsp_dispatcher_ex<'a>( .runtime_info .vdm_message_transcript_before_key_exchange = Some(transcript_before_key_exchange); } - VdmMessageOpCode::ExchangeMigrationAttestInfoReq => { + VdmMessageOpCode::ExchangeMigrationAttestInfoReq + | VdmMessageOpCode::ExchangeRebindAttestInfoReq => { let vdm_attest_info_src_hash = match digest_sha384(req_bytes) { Ok(hash) => hash, Err(_) => { @@ -594,7 +615,7 @@ pub fn migtd_vdm_msg_rsp_dispatcher_ex<'a>( } } } - VdmMessageOpCode::ExchangeMigrationInfoReq => {} + VdmMessageOpCode::ExchangeMigrationInfoReq | VdmMessageOpCode::ExchangeRebindInfoReq => {} _ => {} }; From e746e2c316668d7a8b45a12a2fd8dda8f5fb7e42 Mon Sep 17 00:00:00 2001 From: siweicai Date: Sun, 18 Jan 2026 18:56:14 +0800 Subject: [PATCH 5/7] Add spdm_mig.rs to align spdm interfaces with rebinding --- deps/td-shim | 2 +- src/migtd/src/spdm/mod.rs | 5 +- src/migtd/src/spdm/spdm_mig.rs | 87 ++++++++++++++++++++++++++++++++++ src/migtd/src/spdm/spdm_req.rs | 38 +-------------- src/migtd/src/spdm/spdm_rsp.rs | 27 ----------- 5 files changed, 92 insertions(+), 67 deletions(-) create mode 100644 src/migtd/src/spdm/spdm_mig.rs diff --git a/deps/td-shim b/deps/td-shim index 3e604bfb..f70c7c19 160000 --- a/deps/td-shim +++ b/deps/td-shim @@ -1 +1 @@ -Subproject commit 3e604bfbd6118dcffc04b5a8147687053cd843cd +Subproject commit f70c7c190a59769fd0e611f259d589aff1cd6b15 diff --git a/src/migtd/src/spdm/mod.rs b/src/migtd/src/spdm/mod.rs index 6922af8a..b95560b2 100644 --- a/src/migtd/src/spdm/mod.rs +++ b/src/migtd/src/spdm/mod.rs @@ -4,6 +4,7 @@ #![cfg(feature = "spdm_attestation")] +mod spdm_mig; #[cfg(feature = "policy_v2")] mod spdm_rebind; mod spdm_req; @@ -27,14 +28,14 @@ use zeroize::ZeroizeOnDrop; use async_io::AsyncRead; use async_io::AsyncWrite; use crypto::hash::digest_sha384; +pub use spdm_mig::spdm_requester_transfer_msk; +pub use spdm_mig::spdm_responder_transfer_msk; #[cfg(feature = "policy_v2")] pub use spdm_rebind::spdm_requester_rebind_old; #[cfg(feature = "policy_v2")] pub use spdm_rebind::spdm_responder_rebind_new; pub use spdm_req::spdm_requester; -pub use spdm_req::spdm_requester_transfer_msk; pub use spdm_rsp::spdm_responder; -pub use spdm_rsp::spdm_responder_transfer_msk; pub use spdm_vdm::*; diff --git a/src/migtd/src/spdm/spdm_mig.rs b/src/migtd/src/spdm/spdm_mig.rs new file mode 100644 index 00000000..11671061 --- /dev/null +++ b/src/migtd/src/spdm/spdm_mig.rs @@ -0,0 +1,87 @@ +// Copyright (c) 2026 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +use crate::{ + migration::MigtdMigrationInformation, + spdm::{ + spdm_req::{ + send_and_receive_pub_key, send_and_receive_sdm_exchange_migration_info, + send_and_receive_sdm_migration_attest_info, + }, + spdm_rsp::{rsp_handle_message, ResponderContextEx}, + PrivateKeyDer, SpdmAppContextData, + }, +}; +use alloc::boxed::Box; +use alloc::vec::Vec; +use codec::{Codec, Writer}; +use spdmlib::{ + error::{SpdmStatus, SPDM_STATUS_BUFFER_FULL}, + protocol::SpdmMeasurementSummaryHashType, + requester::RequesterContext, +}; +use zeroize::Zeroize; + +pub async fn spdm_requester_transfer_msk( + spdm_requester: &mut RequesterContext, + mig_info: &MigtdMigrationInformation, + #[cfg(feature = "policy_v2")] remote_policy: Vec, +) -> Result<(), SpdmStatus> { + Box::pin(spdm_requester.send_receive_spdm_version()).await?; + Box::pin(spdm_requester.send_receive_spdm_capability()).await?; + Box::pin(spdm_requester.send_receive_spdm_algorithm()).await?; + + Box::pin(send_and_receive_pub_key(spdm_requester)).await?; + let session_id = Box::pin(spdm_requester.send_receive_spdm_key_exchange( + 0xff, + SpdmMeasurementSummaryHashType::SpdmMeasurementSummaryHashTypeNone, + )) + .await?; + + Box::pin(send_and_receive_sdm_migration_attest_info( + spdm_requester, + session_id, + #[cfg(feature = "policy_v2")] + remote_policy, + )) + .await?; + + Box::pin(spdm_requester.send_receive_spdm_finish(Some(0xff), session_id)).await?; + Box::pin(send_and_receive_sdm_exchange_migration_info( + spdm_requester, + mig_info, + Some(session_id), + )) + .await?; + Box::pin(spdm_requester.send_receive_spdm_end_session(session_id)).await?; + + Ok(()) +} + +pub async fn spdm_responder_transfer_msk( + spdm_responder_ex: &mut ResponderContextEx<'_>, + mig_info: &MigtdMigrationInformation, + #[cfg(feature = "policy_v2")] remote_policy: Vec, +) -> Result<(), SpdmStatus> { + #[cfg(not(feature = "policy_v2"))] + let remote_policy = Vec::new(); + + spdm_responder_ex.remote_policy = remote_policy; + + let spdm_responder = &mut spdm_responder_ex.responder_context; + let mut writer = Writer::init(&mut spdm_responder.common.app_context_data_buffer); + + let responder_app_context = SpdmAppContextData { + migration_info: mig_info.clone(), + private_key: PrivateKeyDer::default(), + }; + responder_app_context + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + + Box::pin(rsp_handle_message(spdm_responder)).await?; + spdm_responder.common.app_context_data_buffer.zeroize(); + + Ok(()) +} diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index 4e35ec01..07b6d1e2 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -83,42 +83,6 @@ pub fn spdm_requester Ok(requester_context) } -pub async fn spdm_requester_transfer_msk( - spdm_requester: &mut RequesterContext, - mig_info: &MigtdMigrationInformation, - #[cfg(feature = "policy_v2")] remote_policy: Vec, -) -> Result<(), SpdmStatus> { - Box::pin(spdm_requester.send_receive_spdm_version()).await?; - Box::pin(spdm_requester.send_receive_spdm_capability()).await?; - Box::pin(spdm_requester.send_receive_spdm_algorithm()).await?; - - Box::pin(send_and_receive_pub_key(spdm_requester)).await?; - let session_id = Box::pin(spdm_requester.send_receive_spdm_key_exchange( - 0xff, - SpdmMeasurementSummaryHashType::SpdmMeasurementSummaryHashTypeNone, - )) - .await?; - - Box::pin(send_and_receive_sdm_migration_attest_info( - spdm_requester, - session_id, - #[cfg(feature = "policy_v2")] - remote_policy, - )) - .await?; - - Box::pin(spdm_requester.send_receive_spdm_finish(Some(0xff), session_id)).await?; - Box::pin(send_and_receive_sdm_exchange_migration_info( - spdm_requester, - mig_info, - Some(session_id), - )) - .await?; - Box::pin(spdm_requester.send_receive_spdm_end_session(session_id)).await?; - - Ok(()) -} - pub async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> SpdmResult { let signing_key = EcdsaPk::new().map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; let my_pub_key = signing_key.public_key_spki(); @@ -600,7 +564,7 @@ pub async fn send_and_receive_sdm_migration_attest_info( Ok(()) } -async fn send_and_receive_sdm_exchange_migration_info( +pub async fn send_and_receive_sdm_exchange_migration_info( spdm_requester: &mut RequesterContext, mig_info: &MigtdMigrationInformation, session_id: Option, diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 21700c8a..7716e506 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -143,33 +143,6 @@ pub fn spdm_responder<'a, T: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'sta Ok(responder_context_ex) } -pub async fn spdm_responder_transfer_msk( - spdm_responder_ex: &mut ResponderContextEx<'_>, - mig_info: &MigtdMigrationInformation, - #[cfg(feature = "policy_v2")] remote_policy: Vec, -) -> Result<(), SpdmStatus> { - #[cfg(not(feature = "policy_v2"))] - let remote_policy = Vec::new(); - - spdm_responder_ex.remote_policy = remote_policy; - - let spdm_responder = &mut spdm_responder_ex.responder_context; - let mut writer = Writer::init(&mut spdm_responder.common.app_context_data_buffer); - - let responder_app_context = SpdmAppContextData { - migration_info: mig_info.clone(), - private_key: PrivateKeyDer::default(), - }; - responder_app_context - .encode(&mut writer) - .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; - - Box::pin(rsp_handle_message(spdm_responder)).await?; - spdm_responder.common.app_context_data_buffer.zeroize(); - - Ok(()) -} - pub async fn rsp_handle_message(spdm_responder: &mut ResponderContext) -> Result<(), SpdmStatus> { let mut sid = None; loop { From 986917cd70a544ae45cc4e173eef27d2596ab349 Mon Sep 17 00:00:00 2001 From: siweicai Date: Mon, 19 Jan 2026 09:31:07 +0800 Subject: [PATCH 6/7] Move Migration Info of spdm responder to extend context Move info data from context data to responder context ex. --- src/migtd/src/spdm/mod.rs | 29 +--------------------- src/migtd/src/spdm/spdm_mig.rs | 10 ++++---- src/migtd/src/spdm/spdm_rebind.rs | 3 +-- src/migtd/src/spdm/spdm_req.rs | 1 - src/migtd/src/spdm/spdm_rsp.rs | 41 +++++++++++++++++++++++-------- 5 files changed, 38 insertions(+), 46 deletions(-) diff --git a/src/migtd/src/spdm/mod.rs b/src/migtd/src/spdm/mod.rs index b95560b2..ce2beb7a 100644 --- a/src/migtd/src/spdm/mod.rs +++ b/src/migtd/src/spdm/mod.rs @@ -40,7 +40,6 @@ pub use spdm_rsp::spdm_responder; pub use spdm_vdm::*; use crate::migration::MigrationResult; -use crate::migration::MigtdMigrationInformation; use crate::spdm::vmcall_msg::VMCALL_SPDM_MESSAGE_HEADER_SIZE; pub struct MigtdTransport { @@ -171,23 +170,12 @@ impl Codec for PrivateKeyDer { #[derive(Debug)] struct SpdmAppContextData { - pub migration_info: MigtdMigrationInformation, pub private_key: PrivateKeyDer, } impl Codec for SpdmAppContextData { fn encode(&self, bytes: &mut Writer) -> Result { let mut size = 0; - size += self.migration_info.mig_request_id.encode(bytes)?; - size += self.migration_info.migration_source.encode(bytes)?; - size += self.migration_info.target_td_uuid.encode(bytes)?; - size += self.migration_info.binding_handle.encode(bytes)?; - - #[cfg(not(feature = "vmcall-raw"))] - { - size += self.migration_info.mig_policy_id.encode(bytes)?; - size += self.migration_info.communication_id.encode(bytes)? - } size += self.private_key.encode(bytes)?; @@ -195,24 +183,9 @@ impl Codec for SpdmAppContextData { } fn read(reader: &mut Reader) -> Option { - let mut migration_info = MigtdMigrationInformation::default(); - migration_info.mig_request_id = u64::read(reader)?; - migration_info.migration_source = u8::read(reader)?; - migration_info.target_td_uuid = <[u64; 4]>::read(reader)?; - migration_info.binding_handle = u64::read(reader)?; - - #[cfg(not(feature = "vmcall-raw"))] - { - migration_info.mig_policy_id = u64::read(reader)?; - migration_info.communication_id = u64::read(reader)?; - } - let private_key = PrivateKeyDer::read(reader)?; - Some(Self { - migration_info, - private_key, - }) + Some(Self { private_key }) } } diff --git a/src/migtd/src/spdm/spdm_mig.rs b/src/migtd/src/spdm/spdm_mig.rs index 11671061..3d3f542f 100644 --- a/src/migtd/src/spdm/spdm_mig.rs +++ b/src/migtd/src/spdm/spdm_mig.rs @@ -9,7 +9,7 @@ use crate::{ send_and_receive_pub_key, send_and_receive_sdm_exchange_migration_info, send_and_receive_sdm_migration_attest_info, }, - spdm_rsp::{rsp_handle_message, ResponderContextEx}, + spdm_rsp::{rsp_handle_message, ResponderContextEx, ResponderContextExInfo}, PrivateKeyDer, SpdmAppContextData, }, }; @@ -59,21 +59,21 @@ pub async fn spdm_requester_transfer_msk( Ok(()) } -pub async fn spdm_responder_transfer_msk( - spdm_responder_ex: &mut ResponderContextEx<'_>, - mig_info: &MigtdMigrationInformation, +pub async fn spdm_responder_transfer_msk<'a>( + spdm_responder_ex: &mut ResponderContextEx<'a>, + mig_info: &'a MigtdMigrationInformation, #[cfg(feature = "policy_v2")] remote_policy: Vec, ) -> Result<(), SpdmStatus> { #[cfg(not(feature = "policy_v2"))] let remote_policy = Vec::new(); spdm_responder_ex.remote_policy = remote_policy; + spdm_responder_ex.info = ResponderContextExInfo::MigrationInformation(mig_info); let spdm_responder = &mut spdm_responder_ex.responder_context; let mut writer = Writer::init(&mut spdm_responder.common.app_context_data_buffer); let responder_app_context = SpdmAppContextData { - migration_info: mig_info.clone(), private_key: PrivateKeyDer::default(), }; responder_app_context diff --git a/src/migtd/src/spdm/spdm_rebind.rs b/src/migtd/src/spdm/spdm_rebind.rs index cfb3c2f1..f9c2f366 100644 --- a/src/migtd/src/spdm/spdm_rebind.rs +++ b/src/migtd/src/spdm/spdm_rebind.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: BSD-2-Clause-Patent use crate::{ - migration::{rebinding::RebindingInfo, MigtdMigrationInformation}, + migration::rebinding::RebindingInfo, spdm::{ spdm_req::{ send_and_receive_pub_key, send_and_receive_sdm_rebind_attest_info, @@ -71,7 +71,6 @@ pub async fn spdm_responder_rebind_new<'a>( let mut writer = Writer::init(&mut spdm_responder.common.app_context_data_buffer); let responder_app_context = SpdmAppContextData { - migration_info: MigtdMigrationInformation::default(), private_key: PrivateKeyDer::default(), }; responder_app_context diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index 07b6d1e2..00e5a2d8 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -90,7 +90,6 @@ pub async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> //Save private to spdm context for signing let private_key = signing_key.private_key(); let requester_app_context = SpdmAppContextData { - migration_info: MigtdMigrationInformation::default(), private_key: PrivateKeyDer::from(private_key), }; let writer = &mut Writer::init(&mut spdm_requester.common.app_context_data_buffer); diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index 7716e506..d76f79e3 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -69,7 +69,6 @@ impl ResponderContextEx<'_> { } } -#[cfg(feature = "policy_v2")] pub unsafe fn upcast_mut(inner: &mut ResponderContext) -> &mut ResponderContextEx { let ptr = inner as *mut ResponderContext as *mut u8; let outer_ptr = ptr.sub(0) as *mut ResponderContextEx; @@ -327,6 +326,16 @@ pub fn handle_exchange_mig_attest_info_req( reader: &mut Reader<'_>, vendor_defined_rsp_payload: &mut [u8], ) -> SpdmResult { + // Migration messages must be handled only when migration info is set. + let spdm_responder_ex = unsafe { upcast_mut(responder_context) }; + if !matches!( + &spdm_responder_ex.info, + ResponderContextExInfo::MigrationInformation(_) + ) { + error!("Remigrationbinding info is not set in responder context.\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + let session_id = if session_id.is_some() { session_id } else { @@ -620,6 +629,16 @@ pub fn handle_exchange_mig_info_req( reader: &mut Reader<'_>, vendor_defined_rsp_payload: &mut [u8], ) -> SpdmResult { + // Migration messages must be handled only when migration info is set. + let spdm_responder_ex = unsafe { upcast_mut(responder_context) }; + if !matches!( + &spdm_responder_ex.info, + ResponderContextExInfo::MigrationInformation(_) + ) { + error!("Remigrationbinding info is not set in responder context.\n"); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + // The VDM message for secret migration info exchange MUST be sent after mutual attested session establishment. let session_id = if let Some(sid) = session_id { sid @@ -698,17 +717,19 @@ pub fn handle_exchange_mig_info_req( }, }; - let mut reader = Reader::init(responder_context.common.app_context_data_buffer.as_ref()); - let responder_app_context = - SpdmAppContextData::read(&mut reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; - let mut exchange_information = exchange_info(&responder_app_context.migration_info, false)?; + let spdm_responder_ex = unsafe { upcast_mut(responder_context) }; + let migration_info = match &spdm_responder_ex.info { + ResponderContextExInfo::MigrationInformation(info) => info, + _ => { + error!("Migration info is not set in responder context.\n"); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + }; + let mut exchange_information = exchange_info(migration_info, false)?; let mig_ver = cal_mig_version(false, &exchange_information, &remote_information)?; - set_mig_version(&responder_app_context.migration_info, mig_ver)?; - write_msk( - &responder_app_context.migration_info, - &remote_information.key, - )?; + set_mig_version(migration_info, mig_ver)?; + write_msk(migration_info, &remote_information.key)?; log::info!("Set MSK and report status\n"); exchange_information.key.clear(); remote_information.key.clear(); From d5e07b3fc46bcbf0af7a44362913e8d10a0b09c9 Mon Sep 17 00:00:00 2001 From: siweicai Date: Mon, 19 Jan 2026 15:51:30 +0800 Subject: [PATCH 7/7] Add History info support in spdm attestation for migration --- src/migtd/src/mig_policy.rs | 61 +++++++++++++++++- src/migtd/src/spdm/spdm_mig.rs | 1 + src/migtd/src/spdm/spdm_req.rs | 103 +++++++++++++++++++++++++++++- src/migtd/src/spdm/spdm_rsp.rs | 112 ++++++++++++++++++++++++++++++--- 4 files changed, 267 insertions(+), 10 deletions(-) diff --git a/src/migtd/src/mig_policy.rs b/src/migtd/src/mig_policy.rs index 1bb0ebfd..010d8344 100644 --- a/src/migtd/src/mig_policy.rs +++ b/src/migtd/src/mig_policy.rs @@ -225,6 +225,65 @@ mod v2 { Ok(suppl_data) } + pub fn authenticate_migration_source_with_history_info( + quote_src: &[u8], + event_log_src: &[u8], + mig_policy_src: &[u8], + init_policy: &[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)?; + let (evaluation_data_src, _verified_policy_src, suppl_data) = authenticate_remote_common( + quote_src, + event_log_src, + mig_policy_src, + policy_issuer_chain, + )?; + let relative_reference = get_local_tcb_evaluation_info()?; + let policy = get_verified_policy().ok_or(PolicyError::InvalidParameter)?; + + policy + .policy_data + .evaluate_policy_backward(&evaluation_data_src, &relative_reference)?; + + // Verify the td report init / event log init / policy init + 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 _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, + 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)?; + policy + .policy_data + .evaluate_policy_common(&evaluation_data_src, &relative_reference)?; + + // If backward policy exists, evaluate the migration src based on it. + let relative_reference = get_local_tcb_evaluation_info()?; + policy + .policy_data + .evaluate_policy_backward(&evaluation_data_src, &relative_reference)?; + + Ok(suppl_data) + } + // Authenticate the migtd-new from migtd-old side pub fn authenticate_rebinding_new( tdreport_dst: &[u8], @@ -380,7 +439,7 @@ mod v2 { Ok(rtmrs) } - fn verify_policy_and_event_log<'p>( + pub fn verify_policy_and_event_log<'p>( event_log: &[u8], mig_policy: &'p [u8], policy_issuer_chain: &[u8], diff --git a/src/migtd/src/spdm/spdm_mig.rs b/src/migtd/src/spdm/spdm_mig.rs index 3d3f542f..c0d37a2d 100644 --- a/src/migtd/src/spdm/spdm_mig.rs +++ b/src/migtd/src/spdm/spdm_mig.rs @@ -42,6 +42,7 @@ pub async fn spdm_requester_transfer_msk( Box::pin(send_and_receive_sdm_migration_attest_info( spdm_requester, session_id, + mig_info, #[cfg(feature = "policy_v2")] remote_policy, )) diff --git a/src/migtd/src/spdm/spdm_req.rs b/src/migtd/src/spdm/spdm_req.rs index 00e5a2d8..76f04b64 100644 --- a/src/migtd/src/spdm/spdm_req.rs +++ b/src/migtd/src/spdm/spdm_req.rs @@ -249,6 +249,7 @@ pub async fn send_and_receive_pub_key(spdm_requester: &mut RequesterContext) -> pub async fn send_and_receive_sdm_migration_attest_info( spdm_requester: &mut RequesterContext, session_id: u32, + mig_info: &MigtdMigrationInformation, #[cfg(feature = "policy_v2")] remote_policy: Vec, ) -> SpdmResult { if spdm_requester.common.provision_info.my_pub_key.is_none() @@ -258,6 +259,28 @@ pub async fn send_and_receive_sdm_migration_attest_info( return Err(SPDM_STATUS_UNSUPPORTED_CAP); } + #[cfg(feature = "policy_v2")] + let is_history_info_supported = { + use crate::migration::servtd_ext::read_servtd_ext; + let _servtd_ext = read_servtd_ext(mig_info.binding_handle, &mig_info.target_td_uuid); + + // Fixme: add history info in migration requests. + //let tdreport_init = get_init_tdreprot(mig_info.binding_handle); + //let event_log_init = get_init_event_log(mig_info.binding_handle); + //let mig_policy_init = get_init_policy(mig_info.binding_handle); + //if servtd_ext.is_err() + // || tdreport_init.is_none() + // || event_log_init.is_none() + // || mig_policy_init.is_none() + //{ + false + //} else { + // true + //} + }; + #[cfg(not(feature = "policy_v2"))] + let is_history_info_supported = false; + let mut vendor_id = [0u8; MAX_SPDM_VENDOR_DEFINED_VENDOR_ID_LEN]; vendor_id[..VDM_MESSAGE_VENDOR_ID_LEN].copy_from_slice(&VDM_MESSAGE_VENDOR_ID); let vendor_id = VendorIDStruct { len: 4, vendor_id }; @@ -270,7 +293,11 @@ pub async fn send_and_receive_sdm_migration_attest_info( major_version: VDM_MESSAGE_MAJOR_VERSION, minor_version: VDM_MESSAGE_MINOR_VERSION, op_code: VdmMessageOpCode::ExchangeMigrationAttestInfoReq, - element_count: VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT, + element_count: if is_history_info_supported { + VDM_MESSAGE_EXCHANGE_ATTEST_INFO_WITH_HISTORY_INFO_ELEMENT_COUNT + } else { + VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT + }, }; cnt += vdm_exchange_attest_info @@ -367,6 +394,80 @@ pub async fn send_and_receive_sdm_migration_attest_info( .extend_from_slice(&mig_policy_src_hash) .ok_or(SPDM_STATUS_BUFFER_FULL)?; + #[cfg(feature = "policy_v2")] + if is_history_info_supported { + //SERVTD_EXT + use crate::migration::servtd_ext::read_servtd_ext; + let servtd_ext = read_servtd_ext(mig_info.binding_handle, &mig_info.target_td_uuid) + .map_err(|_| SPDM_STATUS_INVALID_STATE_LOCAL)?; + let servtd_ext_element = VdmMessageElement { + element_type: VdmMessageElementType::SerVtdExt, + length: servtd_ext.as_bytes().len() as u16, + }; + cnt += servtd_ext_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(servtd_ext.as_bytes()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //TD report init + // Fixme: add history info in migration requests. + let tdreport_init = []; + //get_init_tdreprot(mig_info.binding_handle).ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if tdreport_init.len() > u16::MAX as usize { + error!("Tdreport size is too large: {}\n", tdreport_init.len()); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let tdreport_init_element = VdmMessageElement { + element_type: VdmMessageElementType::TdReportInit, + length: tdreport_init.len() as u16, + }; + cnt += tdreport_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(tdreport_init.as_slice()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //event log init + let event_log_init = []; + //get_init_event_log(mig_info.binding_handle).ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + if event_log_init.len() > u16::MAX as usize { + error!( + "Event log init size is too large: {}\n", + event_log_init.len() + ); + return Err(SPDM_STATUS_INVALID_STATE_LOCAL); + } + let event_log_init_element = VdmMessageElement { + element_type: VdmMessageElementType::EventLogInit, + length: event_log_init.len() as u16, + }; + cnt += event_log_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(event_log_init.as_slice()) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + + //mig policy init hash + let mig_policy_init = []; + //get_init_policy(mig_info.binding_handle).ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + let mig_policy_init_hash = + digest_sha384(&mig_policy_init).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + let mig_policy_init_element = VdmMessageElement { + element_type: VdmMessageElementType::MigPolicyInit, + length: mig_policy_init_hash.len() as u16, + }; + cnt += mig_policy_init_element + .encode(&mut writer) + .map_err(|_| SPDM_STATUS_BUFFER_FULL)?; + cnt += writer + .extend_from_slice(&mig_policy_init_hash) + .ok_or(SPDM_STATUS_BUFFER_FULL)?; + } + spdm_requester.common.reset_buffer_via_request_code( SpdmRequestResponseCode::SpdmRequestVendorDefinedRequest, None, diff --git a/src/migtd/src/spdm/spdm_rsp.rs b/src/migtd/src/spdm/spdm_rsp.rs index d76f79e3..24eeb746 100644 --- a/src/migtd/src/spdm/spdm_rsp.rs +++ b/src/migtd/src/spdm/spdm_rsp.rs @@ -389,13 +389,18 @@ pub fn handle_exchange_mig_attest_info_req( error!("Invalid VDM message op_code: {:x?}\n", vdm_request.op_code); return Err(SPDM_STATUS_INVALID_MSG_FIELD); } - if vdm_request.element_count != VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT { - error!( - "Invalid VDM message element_count: {:x?}\n", - vdm_request.element_count - ); - return Err(SPDM_STATUS_INVALID_MSG_FIELD); - } + + let is_history_info_included = match vdm_request.element_count { + VDM_MESSAGE_EXCHANGE_ATTEST_INFO_ELEMENT_COUNT => false, + VDM_MESSAGE_EXCHANGE_ATTEST_INFO_WITH_HISTORY_INFO_ELEMENT_COUNT => true, + _ => { + error!( + "Invalid VDM message element_count: {:x?}\n", + vdm_request.element_count + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + }; let th1 = if let Some(s) = responder_context.common.get_session_via_id(session_id) { s.get_th1() @@ -522,7 +527,98 @@ pub fn handle_exchange_mig_attest_info_req( .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; #[cfg(feature = "policy_v2")] - { + if is_history_info_included { + let remote_policy = unsafe { + let spdm_responder_ex = upcast_mut(responder_context); + spdm_responder_ex.remote_policy.as_slice() + }; + let remote_policy_hash = + digest_sha384(remote_policy).map_err(|_| SPDM_STATUS_CRYPTO_ERROR)?; + if mig_policy_hash_src != remote_policy_hash.as_slice() { + error!( + "The received mig policy hash does not match the expected remote policy hash!\n" + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + + // SERVTD_EXT + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::SerVtdExt { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let servtd_ext = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let servtd_ext_vec = servtd_ext.to_vec(); + + // TD report init + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::TdReportInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let td_report_init = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let td_report_init_vec = td_report_init.to_vec(); + + // event log init + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::EventLogInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let event_log_init = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + let event_log_init_vec = event_log_init.to_vec(); + + // mig policy init hash + let vdm_element = VdmMessageElement::read(reader).ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + if vdm_element.element_type != VdmMessageElementType::MigPolicyInit { + error!( + "Invalid VDM message element_type: {:x?}\n", + vdm_element.element_type + ); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + }; + let mig_policy_init_hash_src = reader + .take(vdm_element.length as usize) + .ok_or(SPDM_STATUS_INVALID_MSG_SIZE)?; + + #[cfg(not(feature = "test_disable_ra_and_accept_all"))] + { + let policy_check_result = mig_policy::authenticate_migration_source_with_history_info( + "e_src_vec, + &event_log_src_vec, + remote_policy, + &servtd_ext_vec, + &td_report_init_vec, + &event_log_init_vec, + mig_policy_init_hash_src, + ); + if let Err(e) = &policy_check_result { + error!("Policy v2 check failed, below is the detail information:\n"); + error!("{:x?}\n", e); + let session = responder_context + .common + .get_session_via_id(session_id) + .ok_or(SPDM_STATUS_INVALID_STATE_LOCAL)?; + session.teardown(); + return Err(SPDM_STATUS_INVALID_MSG_FIELD); + } + } + } else { let remote_policy = unsafe { let spdm_responder_ex = upcast_mut(responder_context); spdm_responder_ex.remote_policy.as_slice()