underhill_attestation/igvm_attest/
mod.rs1use base64_serde::base64_serde_type;
9use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestHashType;
10use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestReportType;
11use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestType;
12use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
13use tee_call::TeeType;
14use thiserror::Error;
15use zerocopy::FromZeros;
16use zerocopy::IntoBytes;
17
18pub mod ak_cert;
19pub mod key_release;
20pub mod wrapped_key;
21
22base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
23
24#[expect(missing_docs)] #[derive(Debug, Error)]
26pub enum Error {
27 #[error(
28 "the size of the attestation report {report_size} is invalid, expected {expected_size}"
29 )]
30 InvalidAttestationReportSize {
31 report_size: usize,
32 expected_size: usize,
33 },
34}
35
36pub enum ReportType {
38 #[expect(dead_code)]
41 Vbs,
42 Snp,
44 Tdx,
46 Tvm,
48}
49
50impl ReportType {
51 fn to_external_type(&self) -> IgvmAttestReportType {
53 match self {
54 Self::Vbs => IgvmAttestReportType::VBS_VM_REPORT,
55 Self::Snp => IgvmAttestReportType::SNP_VM_REPORT,
56 Self::Tdx => IgvmAttestReportType::TDX_VM_REPORT,
57 Self::Tvm => IgvmAttestReportType::TVM_REPORT,
58 }
59 }
60}
61
62pub struct IgvmAttestRequestHelper {
64 request_type: IgvmAttestRequestType,
66 report_type: ReportType,
68 runtime_claims: Vec<u8>,
70 runtime_claims_hash: [u8; tee_call::REPORT_DATA_SIZE],
73 hash_type: IgvmAttestHashType,
75}
76
77impl IgvmAttestRequestHelper {
78 pub fn prepare_key_release_request(
80 tee_type: TeeType,
81 rsa_exponent: &[u8],
82 rsa_modulus: &[u8],
83 host_time: i64,
84 attestation_vm_config: &AttestationVmConfig,
85 ) -> Self {
86 let report_type = match tee_type {
87 TeeType::Snp => ReportType::Snp,
88 TeeType::Tdx => ReportType::Tdx,
89 };
90
91 let attestation_vm_config =
92 attestation_vm_config_with_time(attestation_vm_config, host_time);
93 let runtime_claims =
94 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::key_release_request_runtime_claims(rsa_exponent, rsa_modulus, &attestation_vm_config);
95 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
96
97 let hash_type = IgvmAttestHashType::SHA_256;
98 let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
99 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
100 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
101
102 Self {
103 request_type: IgvmAttestRequestType::KEY_RELEASE_REQUEST,
104 report_type,
105 runtime_claims,
106 runtime_claims_hash,
107 hash_type,
108 }
109 }
110
111 pub fn prepare_ak_cert_request(
113 tee_type: Option<TeeType>,
114 ak_pub_exponent: &[u8],
115 ak_pub_modulus: &[u8],
116 ek_pub_exponent: &[u8],
117 ek_pub_modulus: &[u8],
118 attestation_vm_config: &AttestationVmConfig,
119 guest_input: &[u8],
120 ) -> Self {
121 let report_type = match tee_type {
122 Some(TeeType::Snp) => ReportType::Snp,
123 Some(TeeType::Tdx) => ReportType::Tdx,
124 None => ReportType::Tvm,
125 };
126
127 let runtime_claims =
128 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::ak_cert_runtime_claims(
129 ak_pub_exponent,
130 ak_pub_modulus,
131 ek_pub_exponent,
132 ek_pub_modulus,
133 attestation_vm_config,
134 guest_input,
135 );
136
137 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
138
139 let hash_type = IgvmAttestHashType::SHA_256;
140 let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
141 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
142 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
143
144 Self {
145 request_type: IgvmAttestRequestType::AK_CERT_REQUEST,
146 report_type,
147 runtime_claims,
148 runtime_claims_hash,
149 hash_type,
150 }
151 }
152
153 pub fn get_runtime_claims_hash(&self) -> &[u8; tee_call::REPORT_DATA_SIZE] {
155 &self.runtime_claims_hash
156 }
157
158 pub fn set_request_type(&mut self, request_type: IgvmAttestRequestType) {
160 self.request_type = request_type
161 }
162
163 pub fn create_request(&self, attestation_report: &[u8]) -> Result<Vec<u8>, Error> {
165 create_request(
166 self.request_type,
167 &self.runtime_claims,
168 attestation_report,
169 &self.report_type,
170 self.hash_type,
171 )
172 }
173}
174
175fn create_request(
179 request_type: IgvmAttestRequestType,
180 runtime_claims: &[u8],
181 attestation_report: &[u8],
182 report_type: &ReportType,
183 hash_type: IgvmAttestHashType,
184) -> Result<Vec<u8>, Error> {
185 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequest;
186 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData;
187 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
188
189 let expected_report_size = get_report_size(report_type);
190 if attestation_report.len() != expected_report_size {
191 Err(Error::InvalidAttestationReportSize {
192 report_size: attestation_report.len(),
193 expected_size: expected_report_size,
194 })?
195 }
196
197 let report_size = size_of::<IgvmAttestRequest>() + runtime_claims.len();
198 let user_data_size = size_of::<IgvmAttestRequestData>() + runtime_claims.len();
199 let mut request = IgvmAttestRequest::new_zeroed();
200
201 request.header = IgvmAttestRequestHeader::new(report_size as u32, request_type, 0);
202
203 request.attestation_report[..attestation_report.len()].copy_from_slice(attestation_report);
204
205 request.request_data = IgvmAttestRequestData::new(
206 user_data_size as u32,
207 report_type.to_external_type(),
208 hash_type,
209 runtime_claims.len() as u32,
210 );
211
212 Ok([request.as_bytes(), runtime_claims].concat())
213}
214
215fn get_report_size(report_type: &ReportType) -> usize {
217 match report_type {
218 ReportType::Vbs => openhcl_attestation_protocol::igvm_attest::get::VBS_VM_REPORT_SIZE,
219 ReportType::Snp => openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE,
220 ReportType::Tdx => openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE,
221 ReportType::Tvm => openhcl_attestation_protocol::igvm_attest::get::TVM_REPORT_SIZE,
222 }
223}
224
225fn attestation_vm_config_with_time(
227 vm_config: &AttestationVmConfig,
228 host_epoch: i64,
229) -> AttestationVmConfig {
230 let mut vm_config = vm_config.clone();
231 vm_config.current_time = Some(host_epoch);
232 vm_config
233}
234
235fn runtime_claims_to_bytes(
237 runtime_claims: &openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims,
238) -> Vec<u8> {
239 let runtime_claims = serde_json::to_string(runtime_claims).expect("JSON serialization failed");
240 runtime_claims.as_bytes().to_vec()
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_create_request() {
249 let result = create_request(
250 IgvmAttestRequestType::AK_CERT_REQUEST,
251 &[],
252 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE],
253 &ReportType::Snp,
254 IgvmAttestHashType::SHA_256,
255 );
256 assert!(result.is_ok());
257
258 let result = create_request(
259 IgvmAttestRequestType::AK_CERT_REQUEST,
260 &[],
261 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE + 1],
262 &ReportType::Snp,
263 IgvmAttestHashType::SHA_256,
264 );
265 assert!(result.is_err());
266 }
267
268 #[test]
269 fn test_transfer_key_jwk() {
270 const EXPECTED_JWK: &str = r#"[{"kid":"HCLTransferKey","key_ops":["encrypt"],"kty":"RSA","e":"RVhQT05FTlQ","n":"TU9EVUxVUw"}]"#;
271
272 let rsa_jwk = openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RsaJwk::get_transfer_key_jwks(
273 b"EXPONENT",
274 b"MODULUS",
275 );
276
277 let result = serde_json::to_string(&rsa_jwk);
278 assert!(result.is_ok());
279
280 let transfer_key_jwk = result.unwrap();
281 assert_eq!(transfer_key_jwk, EXPECTED_JWK);
282 }
283
284 #[test]
285 fn test_vm_configuration_no_time() {
286 const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"vmUniqueId":""}"#;
287
288 let attestation_vm_config = AttestationVmConfig {
289 current_time: None,
290 root_cert_thumbprint: String::new(),
291 console_enabled: false,
292 secure_boot: false,
293 tpm_enabled: false,
294 tpm_persisted: false,
295 vm_unique_id: String::new(),
296 };
297 let result = serde_json::to_string(&attestation_vm_config);
298 assert!(result.is_ok());
299
300 let vm_config = result.unwrap();
301 assert_eq!(vm_config, EXPECTED_JWK);
302 }
303
304 #[test]
305 fn test_vm_configuration_with_time() {
306 const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"vmUniqueId":""}"#;
307
308 let attestation_vm_config = AttestationVmConfig {
309 current_time: None,
310 root_cert_thumbprint: String::new(),
311 console_enabled: false,
312 secure_boot: false,
313 tpm_enabled: false,
314 tpm_persisted: false,
315 vm_unique_id: String::new(),
316 };
317 let attestation_vm_config =
318 attestation_vm_config_with_time(&attestation_vm_config, 1691103220);
319 let result = serde_json::to_string(&attestation_vm_config);
320 assert!(result.is_ok());
321
322 let vm_config = result.unwrap();
323 assert_eq!(vm_config, EXPECTED_JWK);
324 }
325}