igvmfilegen/signed_measurement/
snp.rs1use super::SHA_384_OUTPUT_SIZE_BYTES;
7use crate::file_loader::DEFAULT_COMPATIBILITY_MASK;
8use igvm::IgvmDirectiveHeader;
9use igvm::IgvmInitializationHeader;
10use igvm_defs::IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY;
11use igvm_defs::IGVM_VHS_SNP_ID_BLOCK_SIGNATURE;
12use igvm_defs::IgvmPageDataType;
13use igvm_defs::PAGE_SIZE_4K;
14use sha2::Digest;
15use sha2::Sha384;
16use std::collections::HashMap;
17use thiserror::Error;
18use x86defs::snp::SnpPageInfo;
19use x86defs::snp::SnpPageType;
20use x86defs::snp::SnpPspIdBlock;
21use zerocopy::IntoBytes;
22
23#[derive(Debug, Error)]
24pub enum Error {
25 #[error("invalid parameter area index")]
26 InvalidParameterAreaIndex,
27 #[error("failed to sign temporary SNP ID block: {0}")]
28 TempSigning(String),
29}
30
31const SNP_ID_KEY_ALGORITHM_ECDSA_P384_SHA384: u32 = 1;
32const SNP_ECDSA_CURVE_P384: u32 = 2;
33const SNP_ECC_KEY_SIZE_BYTES: usize = 48;
34const SNP_ECC_COMPONENT_SIZE_BYTES: usize = 72;
35
36pub fn generate_snp_measurement(
41 initialization_headers: &[IgvmInitializationHeader],
42 directive_headers: &mut Vec<IgvmDirectiveHeader>,
43 svn: u32,
44) -> Result<[u8; SHA_384_OUTPUT_SIZE_BYTES], Error> {
45 let mut parameter_area_table = HashMap::new();
46 const PAGE_SIZE_4K_USIZE: usize = PAGE_SIZE_4K as usize;
47 let snp_compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
48
49 let mut launch_digest: [u8; SHA_384_OUTPUT_SIZE_BYTES] = [0; SHA_384_OUTPUT_SIZE_BYTES];
50 let zero_page: [u8; PAGE_SIZE_4K as usize] = [0; PAGE_SIZE_4K as usize];
51 let mut hasher = Sha384::new();
52
53 hasher.update(zero_page.as_bytes());
55 let zero_digest = hasher.finalize();
56
57 let mut padding_vec = vec![0; PAGE_SIZE_4K_USIZE];
59
60 let mut measure_page = |page_type: SnpPageType, gpa: u64, page_data: Option<&[u8]>| {
61 let mut hash = Sha384::new();
62 let hash_contents = match page_data {
63 Some(data) => {
64 match data.len() {
65 0 => zero_digest,
66 _ if data.len() < PAGE_SIZE_4K_USIZE => {
67 padding_vec.fill(0);
68 padding_vec[..data.len()].copy_from_slice(data);
69 hash.update(&padding_vec);
70 hash.finalize()
71 }
72 PAGE_SIZE_4K_USIZE => {
73 hash.update(data);
74 hash.finalize()
75 }
76 _ => {
77 todo!(
80 "unable to measure greater than 4k pages, len: {}",
81 data.len()
82 )
83 }
84 }
85 }
86 None => [0; SHA_384_OUTPUT_SIZE_BYTES].into(),
87 };
88
89 let info = SnpPageInfo {
90 digest_current: launch_digest,
91 contents: hash_contents.into(),
92 length: size_of::<SnpPageInfo>() as u16,
93 page_type,
94 imi_page_bit: 0,
95 lower_vmpl_permissions: 0,
96 gpa,
97 };
98
99 let mut hash = Sha384::new();
100 hash.update(info.as_bytes());
101 launch_digest = hash.finalize().into();
102 };
103
104 let mut policy: u64 = 0;
105
106 for header in initialization_headers {
107 if let IgvmInitializationHeader::GuestPolicy {
108 policy: snp_policy,
109 compatibility_mask,
110 } = header
111 {
112 assert_eq!(
113 compatibility_mask & snp_compatibility_mask,
114 snp_compatibility_mask
115 );
116 policy = *snp_policy;
117 }
118 }
119 assert_ne!(policy, 0);
120
121 for header in directive_headers.iter() {
123 if header
125 .compatibility_mask()
126 .map(|mask| mask & snp_compatibility_mask != snp_compatibility_mask)
127 .unwrap_or(false)
128 {
129 continue;
130 }
131
132 match header {
133 IgvmDirectiveHeader::ErrorRange { .. } => todo!("error range not implemented"),
134 IgvmDirectiveHeader::ParameterArea {
135 number_of_bytes,
136 parameter_area_index,
137 initial_data: _,
138 } => {
139 assert_eq!(
140 parameter_area_table.contains_key(¶meter_area_index),
141 false
142 );
143 assert_eq!(number_of_bytes % PAGE_SIZE_4K, 0);
144 parameter_area_table.insert(parameter_area_index, number_of_bytes);
145 }
146 IgvmDirectiveHeader::PageData {
147 gpa,
148 compatibility_mask,
149 flags,
150 data_type,
151 data,
152 } => {
153 assert_eq!(
154 compatibility_mask & snp_compatibility_mask,
155 snp_compatibility_mask
156 );
157
158 if flags.shared() {
160 continue;
161 }
162
163 let (page_type, data) = match *data_type {
164 IgvmPageDataType::SECRETS => (SnpPageType::SECRETS, None),
165 IgvmPageDataType::CPUID_DATA | IgvmPageDataType::CPUID_XF => {
166 (SnpPageType::CPUID, None)
167 }
168 _ => {
169 if flags.unmeasured() {
170 (SnpPageType::UNMEASURED, None)
171 } else {
172 (SnpPageType::NORMAL, Some(data.as_bytes()))
173 }
174 }
175 };
176
177 measure_page(page_type, *gpa, data);
178 }
179 IgvmDirectiveHeader::ParameterInsert(param) => {
180 assert_eq!(
181 param.compatibility_mask & snp_compatibility_mask,
182 snp_compatibility_mask
183 );
184
185 let parameter_area_size = parameter_area_table
186 .get(¶m.parameter_area_index)
187 .ok_or(Error::InvalidParameterAreaIndex)?;
188
189 for gpa in (param.gpa..param.gpa + *parameter_area_size).step_by(PAGE_SIZE_4K_USIZE)
190 {
191 measure_page(SnpPageType::UNMEASURED, gpa, None)
192 }
193 }
194 IgvmDirectiveHeader::SnpVpContext {
195 gpa,
196 compatibility_mask,
197 vp_index: _,
198 vmsa,
199 } => {
200 assert_eq!(
201 compatibility_mask & snp_compatibility_mask,
202 snp_compatibility_mask
203 );
204
205 let vmsa_bytes = vmsa.as_ref().as_bytes();
206 measure_page(SnpPageType::VMSA, *gpa, Some(vmsa_bytes));
207 }
208 _ => {}
209 }
210 }
211
212 const UNDERHILL_FAMILY_ID: [u8; 16] = [
214 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
215 0x00,
216 ];
217 let family_id = UNDERHILL_FAMILY_ID;
218 let image_id = *b"underhill\0\0\0\0\0\0\0";
219
220 let psp_id_block = SnpPspIdBlock {
222 ld: launch_digest,
223 version: 0x1,
224 guest_svn: svn,
225 policy,
226 family_id,
227 image_id,
228 };
229
230 tracing::info!("SNP ID Block {:x?}", psp_id_block);
232
233 let (id_key_signature, id_public_key) = sign_id_block_with_temp_key(&psp_id_block)?;
235 directive_headers.push(IgvmDirectiveHeader::SnpIdBlock {
236 compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
237 author_key_enabled: 0,
238 reserved: [0; 3],
239 ld: psp_id_block.ld,
240 family_id: psp_id_block.family_id,
241 image_id: psp_id_block.image_id,
242 version: psp_id_block.version,
243 guest_svn: psp_id_block.guest_svn,
244 id_key_algorithm: SNP_ID_KEY_ALGORITHM_ECDSA_P384_SHA384,
245 author_key_algorithm: 0,
246 id_key_signature: Box::new(id_key_signature),
247 id_public_key: Box::new(id_public_key),
248 author_key_signature: Box::new(IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
249 r_comp: [0; SNP_ECC_COMPONENT_SIZE_BYTES],
250 s_comp: [0; SNP_ECC_COMPONENT_SIZE_BYTES],
251 }),
252 author_public_key: Box::new(IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
253 curve: 0,
254 reserved: 0,
255 qx: [0; SNP_ECC_COMPONENT_SIZE_BYTES],
256 qy: [0; SNP_ECC_COMPONENT_SIZE_BYTES],
257 }),
258 });
259
260 Ok(psp_id_block.ld)
261}
262
263fn padded_le_component(input_be: &[u8]) -> [u8; SNP_ECC_COMPONENT_SIZE_BYTES] {
266 let mut out = [0u8; SNP_ECC_COMPONENT_SIZE_BYTES];
267 for (dst, src) in out.iter_mut().zip(input_be.iter().rev()) {
268 *dst = *src;
269 }
270 out
271}
272
273fn sign_id_block_with_temp_key(
277 id_block: &SnpPspIdBlock,
278) -> Result<
279 (
280 IGVM_VHS_SNP_ID_BLOCK_SIGNATURE,
281 IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY,
282 ),
283 Error,
284> {
285 use crypto::ecdsa::{EcdsaCurve, EcdsaKeyPair};
286
287 let key = EcdsaKeyPair::generate(EcdsaCurve::P384)
289 .map_err(|e| Error::TempSigning(format!("EcdsaKeyPair::generate: {e}")))?;
290
291 let mut hash = Sha384::new();
293 hash.update(id_block.as_bytes());
294 let id_block_hash: [u8; SHA_384_OUTPUT_SIZE_BYTES] = hash.finalize().into();
295
296 use base64::Engine as _;
297 let b64 = base64::engine::general_purpose::STANDARD;
298 tracing::info!("Input Hash Base64: {}", b64.encode(id_block_hash));
299 tracing::info!("Using Temporary Signing Key");
300
301 let signature = key
303 .sign_prehash(&id_block_hash)
304 .map_err(|e| Error::TempSigning(format!("sign_prehash: {e}")))?;
305
306 if signature.len() != SNP_ECC_KEY_SIZE_BYTES * 2 {
307 return Err(Error::TempSigning(format!(
308 "unexpected signature size {}",
309 signature.len()
310 )));
311 }
312
313 let (sig_r_be, sig_s_be) = signature.split_at(SNP_ECC_KEY_SIZE_BYTES);
314 let id_key_signature = IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
315 r_comp: padded_le_component(sig_r_be),
316 s_comp: padded_le_component(sig_s_be),
317 };
318
319 tracing::info!("Signature R Base64: {}", b64.encode(sig_r_be));
320 tracing::info!("Signature S Base64: {}", b64.encode(sig_s_be));
321
322 let public_key = key
324 .public_key_bytes()
325 .map_err(|e| Error::TempSigning(format!("public_key_bytes: {e}")))?;
326
327 if public_key.len() != SNP_ECC_KEY_SIZE_BYTES * 2 {
328 return Err(Error::TempSigning(format!(
329 "unexpected public key size {}",
330 public_key.len()
331 )));
332 }
333
334 let (qx_be, qy_be) = public_key.split_at(SNP_ECC_KEY_SIZE_BYTES);
335
336 tracing::info!("Public Key Qx Base64: {}", b64.encode(qx_be));
337 tracing::info!("Public Key Qy Base64: {}", b64.encode(qy_be));
338 let id_public_key = IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
339 curve: SNP_ECDSA_CURVE_P384,
340 reserved: 0,
341 qx: padded_le_component(qx_be),
342 qy: padded_le_component(qy_be),
343 };
344
345 Ok((id_key_signature, id_public_key))
346}