igvmfilegen/signed_measurement/
tdx.rs1use super::SHA_384_OUTPUT_SIZE_BYTES;
7use crate::file_loader::DEFAULT_COMPATIBILITY_MASK;
8use igvm::IgvmDirectiveHeader;
9use igvm_defs::PAGE_SIZE_4K;
10use sha2::Digest;
11use sha2::Sha384;
12use std::collections::HashMap;
13use thiserror::Error;
14use zerocopy::FromBytes;
15use zerocopy::Immutable;
16use zerocopy::IntoBytes;
17use zerocopy::KnownLayout;
18
19#[derive(Debug, Error)]
20pub enum Error {
21 #[error("invalid parameter area index")]
22 InvalidParameterAreaIndex,
23}
24
25#[repr(C)]
27#[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes)]
28pub struct TdxPageAdd {
29 pub operation: [u8; 16],
31 pub gpa: u64,
33 pub mbz: [u8; 104],
35}
36
37const TDX_EXTEND_CHUNK_SIZE: usize = 256;
38
39#[repr(C)]
41#[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes)]
42pub struct TdxMrExtend {
43 pub operation: [u8; 16],
45 pub gpa: u64,
47 pub mbz: [u8; 104],
49 pub data: [u8; TDX_EXTEND_CHUNK_SIZE],
51}
52
53pub fn generate_tdx_measurement(
55 directive_headers: &[IgvmDirectiveHeader],
56) -> Result<[u8; SHA_384_OUTPUT_SIZE_BYTES], Error> {
57 let mut parameter_area_table = HashMap::new();
58 const PAGE_SIZE_4K_USIZE: usize = PAGE_SIZE_4K as usize;
59 let tdx_compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
60 let mut padding_vec = vec![0; PAGE_SIZE_4K_USIZE];
62 let mut hasher = Sha384::new();
63
64 let mut measure_page = |gpa: u64, page_data: Option<&[u8]>| {
65 let page_add = TdxPageAdd {
67 operation: *b"MEM.PAGE.ADD\0\0\0\0",
68 gpa,
69 mbz: [0; 104],
70 };
71 hasher.update(page_add.as_bytes());
72
73 if let Some(data) = page_data {
75 let data = match data.len() {
76 0 => None,
77 PAGE_SIZE_4K_USIZE => Some(data),
78 _ if data.len() < PAGE_SIZE_4K_USIZE => {
79 padding_vec.fill(0);
80 padding_vec[..data.len()].copy_from_slice(data);
81 Some(padding_vec.as_slice())
82 }
83 _ => {
84 panic!("Unexpected data size");
85 }
86 };
87
88 for offset in (0..PAGE_SIZE_4K).step_by(TDX_EXTEND_CHUNK_SIZE) {
90 let mut mr_extend = TdxMrExtend {
91 operation: *b"MR.EXTEND\0\0\0\0\0\0\0",
92 gpa: gpa + offset,
93 mbz: [0; 104],
94 data: [0; TDX_EXTEND_CHUNK_SIZE],
95 };
96
97 if let Some(data) = data {
99 mr_extend.data.copy_from_slice(
100 &data[offset as usize..offset as usize + TDX_EXTEND_CHUNK_SIZE],
101 );
102 }
103 hasher.update(mr_extend.as_bytes());
104 }
105 };
106 };
107
108 for header in directive_headers {
110 if header
112 .compatibility_mask()
113 .map(|mask| mask & tdx_compatibility_mask != tdx_compatibility_mask)
114 .unwrap_or(false)
115 {
116 continue;
117 }
118
119 match header {
120 IgvmDirectiveHeader::ParameterArea {
121 number_of_bytes,
122 parameter_area_index,
123 initial_data: _,
124 } => {
125 assert_eq!(
126 parameter_area_table.contains_key(¶meter_area_index),
127 false
128 );
129 assert_eq!(number_of_bytes % PAGE_SIZE_4K, 0);
130 parameter_area_table.insert(parameter_area_index, number_of_bytes);
131 }
132 IgvmDirectiveHeader::PageData {
133 gpa,
134 compatibility_mask,
135 flags,
136 data_type: _,
137 data,
138 } => {
139 assert_eq!(
140 compatibility_mask & tdx_compatibility_mask,
141 tdx_compatibility_mask
142 );
143
144 if flags.shared() {
146 continue;
147 }
148
149 let data = if flags.unmeasured() {
151 None
152 } else {
153 Some(data.as_bytes())
154 };
155
156 measure_page(*gpa, data);
157 }
158 IgvmDirectiveHeader::ParameterInsert(param) => {
159 assert_eq!(
160 param.compatibility_mask & tdx_compatibility_mask,
161 tdx_compatibility_mask
162 );
163
164 let parameter_area_size = parameter_area_table
165 .get(¶m.parameter_area_index)
166 .ok_or(Error::InvalidParameterAreaIndex)?;
167
168 for gpa in (param.gpa..param.gpa + *parameter_area_size).step_by(PAGE_SIZE_4K_USIZE)
169 {
170 measure_page(gpa, None);
171 }
172 }
173 _ => {}
174 }
175 }
176
177 let mrtd: [u8; SHA_384_OUTPUT_SIZE_BYTES] = hasher.finalize().into();
178 tracing::info!("MRTD: {}", hex::encode_upper(mrtd));
179 Ok(mrtd)
180}