1use base64_serde::base64_serde_type;
9use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_RESPONSE_CURRENT_VERSION;
10use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
11use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestHashType;
12use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestReportType;
13use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestType;
14use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
15use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestResponseVersion;
16use openhcl_attestation_protocol::igvm_attest::get::IgvmCapabilityBitMap;
17use openhcl_attestation_protocol::igvm_attest::get::IgvmErrorInfo;
18use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
19use tee_call::TeeType;
20use thiserror::Error;
21use zerocopy::FromBytes;
22use zerocopy::FromZeros;
23use zerocopy::IntoBytes;
24
25pub mod ak_cert;
26pub mod key_release;
27pub mod wrapped_key;
28
29base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
30
31#[expect(missing_docs)] #[derive(Debug, Error)]
33pub enum Error {
34 #[error(
35 "the size of the attestation report {report_size} is invalid, expected {expected_size}"
36 )]
37 InvalidAttestationReportSize {
38 report_size: usize,
39 expected_size: usize,
40 },
41 #[error("the size of the attestation response {response_size} is too small to parse")]
42 ResponseSizeTooSmall { response_size: usize },
43 #[error(
44 "the header of the attestation response (size {response_size}) is not in correct format"
45 )]
46 ResponseHeaderInvalidFormat { response_size: usize },
47 #[error(
48 "response size {specified_size} specified in the header not match the actual size {size}"
49 )]
50 ResponseSizeMismatch { size: usize, specified_size: usize },
51 #[error("response header version {version:?} larger than current version {latest_version:?}")]
52 InvalidResponseHeaderVersion {
53 version: IgvmAttestResponseVersion,
54 latest_version: IgvmAttestResponseVersion,
55 },
56 #[error(
57 "attest failed ({igvm_error_code}-{http_status_code}), retry recommendation ({retry_signal}), skip hw unsealing recommendation ({skip_hw_unsealing_signal})"
58 )]
59 Attestation {
60 igvm_error_code: u32,
61 http_status_code: u32,
62 retry_signal: bool,
63 skip_hw_unsealing_signal: bool,
64 },
65}
66
67pub enum ReportType {
69 Vbs,
71 Snp,
73 Tdx,
75 Cca,
77 Tvm,
79}
80
81impl ReportType {
82 fn to_external_type(&self) -> IgvmAttestReportType {
84 match self {
85 Self::Vbs => IgvmAttestReportType::VBS_VM_REPORT,
86 Self::Snp => IgvmAttestReportType::SNP_VM_REPORT,
87 Self::Tdx => IgvmAttestReportType::TDX_VM_REPORT,
88 Self::Cca => IgvmAttestReportType::CCA_VM_REPORT,
89 Self::Tvm => IgvmAttestReportType::TVM_REPORT,
90 }
91 }
92}
93
94pub struct IgvmAttestRequestHelper {
96 request_type: IgvmAttestRequestType,
98 report_type: ReportType,
100 runtime_claims: Vec<u8>,
102 runtime_claims_hash: [u8; tee_call::REPORT_DATA_SIZE],
105 hash_type: IgvmAttestHashType,
107}
108
109impl IgvmAttestRequestHelper {
110 pub fn prepare_key_release_request(
112 tee_type: TeeType,
113 rsa_exponent: &[u8],
114 rsa_modulus: &[u8],
115 host_time: i64,
116 attestation_vm_config: &AttestationVmConfig,
117 ) -> Self {
118 let report_type = match tee_type {
119 TeeType::Snp => ReportType::Snp,
120 TeeType::Tdx => ReportType::Tdx,
121 TeeType::Cca => ReportType::Cca,
122 TeeType::Vbs => ReportType::Vbs,
123 };
124
125 let attestation_vm_config =
126 attestation_vm_config_with_time(attestation_vm_config, host_time);
127 let runtime_claims =
128 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::key_release_request_runtime_claims(rsa_exponent, rsa_modulus, &attestation_vm_config);
129 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
130
131 let hash_type = IgvmAttestHashType::SHA_256;
132 let hash = crypto::sha_256::sha_256(runtime_claims.as_bytes());
133 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
134 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
135
136 Self {
137 request_type: IgvmAttestRequestType::KEY_RELEASE_REQUEST,
138 report_type,
139 runtime_claims,
140 runtime_claims_hash,
141 hash_type,
142 }
143 }
144
145 pub fn prepare_ak_cert_request(
147 tee_type: Option<TeeType>,
148 ak_pub_exponent: &[u8],
149 ak_pub_modulus: &[u8],
150 ek_pub_exponent: &[u8],
151 ek_pub_modulus: &[u8],
152 attestation_vm_config: &AttestationVmConfig,
153 guest_input: &[u8],
154 ) -> Self {
155 let report_type = match tee_type {
156 Some(TeeType::Snp) => ReportType::Snp,
157 Some(TeeType::Tdx) => ReportType::Tdx,
158 Some(TeeType::Cca) => ReportType::Cca,
159 Some(TeeType::Vbs) => ReportType::Vbs,
160 None => ReportType::Tvm,
161 };
162
163 let runtime_claims =
164 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::ak_cert_runtime_claims(
165 ak_pub_exponent,
166 ak_pub_modulus,
167 ek_pub_exponent,
168 ek_pub_modulus,
169 attestation_vm_config,
170 guest_input,
171 );
172
173 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
174
175 let hash_type = IgvmAttestHashType::SHA_256;
176 let hash = crypto::sha_256::sha_256(runtime_claims.as_bytes());
177 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
178 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
179
180 Self {
181 request_type: IgvmAttestRequestType::AK_CERT_REQUEST,
182 report_type,
183 runtime_claims,
184 runtime_claims_hash,
185 hash_type,
186 }
187 }
188
189 pub fn get_runtime_claims_hash(&self) -> &[u8; tee_call::REPORT_DATA_SIZE] {
191 &self.runtime_claims_hash
192 }
193
194 pub fn set_request_type(&mut self, request_type: IgvmAttestRequestType) {
196 self.request_type = request_type
197 }
198
199 pub fn create_request(
201 &self,
202 version: IgvmAttestRequestVersion,
203 attestation_report: &[u8],
204 ) -> Result<Vec<u8>, Error> {
205 create_request(
206 version,
207 self.request_type,
208 &self.runtime_claims,
209 attestation_report,
210 &self.report_type,
211 self.hash_type,
212 )
213 }
214}
215
216pub fn parse_response_header(response: &[u8]) -> Result<IgvmAttestCommonResponseHeader, Error> {
218 let header = IgvmAttestCommonResponseHeader::read_from_prefix(response)
221 .map_err(|_| Error::ResponseSizeTooSmall {
222 response_size: response.len(),
223 })?
224 .0; if header.data_size as usize > response.len() {
228 Err(Error::ResponseSizeMismatch {
229 size: response.len(),
230 specified_size: header.data_size as usize,
231 })?
232 }
233 if header.version > IGVM_ATTEST_RESPONSE_CURRENT_VERSION {
234 Err(Error::InvalidResponseHeaderVersion {
235 version: header.version,
236 latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION,
237 })?
238 }
239
240 if header.version >= IgvmAttestResponseVersion::VERSION_2 {
242 let igvm_error_info = IgvmErrorInfo::read_from_prefix(
244 &response[size_of::<IgvmAttestCommonResponseHeader>()..],
245 )
246 .map_err(|_| Error::ResponseHeaderInvalidFormat {
247 response_size: response.len(),
248 })?
249 .0; if 0 != igvm_error_info.error_code {
252 Err(Error::Attestation {
253 igvm_error_code: igvm_error_info.error_code,
254 http_status_code: igvm_error_info.http_status_code,
255 retry_signal: igvm_error_info.igvm_signal.retry(),
256 skip_hw_unsealing_signal: igvm_error_info.igvm_signal.skip_hw_unsealing(),
257 })?
258 }
259 }
260 Ok(IgvmAttestCommonResponseHeader {
261 data_size: header.data_size,
262 version: header.version,
263 })
264}
265
266fn create_request(
270 version: IgvmAttestRequestVersion,
271 request_type: IgvmAttestRequestType,
272 runtime_claims: &[u8],
273 attestation_report: &[u8],
274 report_type: &ReportType,
275 hash_type: IgvmAttestHashType,
276) -> Result<Vec<u8>, Error> {
277 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
278 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData;
279 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
280 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
281
282 let expected_report_size = get_report_size(report_type);
283 if attestation_report.len() != expected_report_size {
284 Err(Error::InvalidAttestationReportSize {
285 report_size: attestation_report.len(),
286 expected_size: expected_report_size,
287 })?
288 }
289
290 let runtime_claims_len = runtime_claims.len();
291 let include_extension = version >= IgvmAttestRequestVersion::VERSION_2;
293 let extension_size = if include_extension {
294 size_of::<IgvmAttestRequestDataExt>()
295 } else {
296 0
297 };
298 let report_size = size_of::<IgvmAttestRequestBase>() + extension_size + runtime_claims_len;
299 let user_data_size = size_of::<IgvmAttestRequestData>() + extension_size + runtime_claims_len;
300 let mut request = IgvmAttestRequestBase::new_zeroed();
301
302 request.header = IgvmAttestRequestHeader::new(report_size as u32, request_type, 0);
303
304 request.attestation_report[..attestation_report.len()].copy_from_slice(attestation_report);
305
306 request.request_data = IgvmAttestRequestData::new(
307 version,
308 user_data_size as u32,
309 report_type.to_external_type(),
310 hash_type,
311 runtime_claims_len as u32,
312 );
313
314 let mut buffer = Vec::with_capacity(report_size);
315 buffer.extend_from_slice(request.as_bytes());
316
317 if include_extension {
318 let capability_bitmap = IgvmCapabilityBitMap::new()
319 .with_error_code(true)
320 .with_retry(true)
321 .with_skip_hw_unsealing(true);
322 let ext = IgvmAttestRequestDataExt::new(capability_bitmap);
323 buffer.extend_from_slice(ext.as_bytes());
324 }
325
326 buffer.extend_from_slice(runtime_claims);
327
328 Ok(buffer)
329}
330
331fn get_report_size(report_type: &ReportType) -> usize {
333 match report_type {
334 ReportType::Vbs => openhcl_attestation_protocol::igvm_attest::get::VBS_VM_REPORT_SIZE,
335 ReportType::Snp => openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE,
336 ReportType::Tdx => openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE,
337 ReportType::Tvm => openhcl_attestation_protocol::igvm_attest::get::TVM_REPORT_SIZE,
338 ReportType::Cca => todo!(),
339 }
340}
341
342fn attestation_vm_config_with_time(
344 vm_config: &AttestationVmConfig,
345 host_epoch: i64,
346) -> AttestationVmConfig {
347 let mut vm_config = vm_config.clone();
348 vm_config.current_time = Some(host_epoch);
349 vm_config
350}
351
352fn runtime_claims_to_bytes(
354 runtime_claims: &openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims,
355) -> Vec<u8> {
356 let runtime_claims = serde_json::to_string(runtime_claims).expect("JSON serialization failed");
357 runtime_claims.as_bytes().to_vec()
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_create_request() {
366 use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_REQUEST_CURRENT_VERSION;
367
368 let result = create_request(
369 IGVM_ATTEST_REQUEST_CURRENT_VERSION,
370 IgvmAttestRequestType::AK_CERT_REQUEST,
371 &[],
372 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE],
373 &ReportType::Snp,
374 IgvmAttestHashType::SHA_256,
375 );
376 assert!(result.is_ok());
377
378 let result = create_request(
379 IGVM_ATTEST_REQUEST_CURRENT_VERSION,
380 IgvmAttestRequestType::AK_CERT_REQUEST,
381 &[],
382 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE + 1],
383 &ReportType::Snp,
384 IgvmAttestHashType::SHA_256,
385 );
386 assert!(result.is_err());
387 }
388
389 #[test]
390 fn test_create_request_version1_no_extension() {
391 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
392 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
393
394 let runtime_claims = vec![1u8, 2, 3];
395 let attestation_report =
396 vec![0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE];
397
398 let buffer = create_request(
399 IgvmAttestRequestVersion::VERSION_1,
400 IgvmAttestRequestType::AK_CERT_REQUEST,
401 &runtime_claims,
402 &attestation_report,
403 &ReportType::Snp,
404 IgvmAttestHashType::SHA_256,
405 )
406 .expect("request generation");
407
408 let (request, _) =
409 IgvmAttestRequestBase::read_from_prefix(&buffer).expect("parse IgvmAttestRequest");
410 assert_eq!(
411 request.request_data.version,
412 IgvmAttestRequestVersion::VERSION_1
413 );
414
415 let expected_size = (size_of::<
416 openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData,
417 >() + runtime_claims.len()) as u32;
418 assert_eq!(request.request_data.data_size, expected_size);
419
420 let header_size = size_of::<IgvmAttestRequestBase>();
421 assert_eq!(
422 buffer.len(),
423 header_size + runtime_claims.len(),
424 "no extension appended for version 1"
425 );
426 assert_eq!(&buffer[header_size..], runtime_claims.as_slice());
427 }
428
429 #[test]
430 fn test_create_request_version2_with_extension() {
431 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
432 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
433 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
434
435 let runtime_claims = vec![4u8, 5, 6, 7];
436 let attestation_report =
437 vec![0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE];
438
439 let buffer = create_request(
440 IgvmAttestRequestVersion::VERSION_2,
441 IgvmAttestRequestType::AK_CERT_REQUEST,
442 &runtime_claims,
443 &attestation_report,
444 &ReportType::Snp,
445 IgvmAttestHashType::SHA_256,
446 )
447 .expect("request generation");
448
449 let (request, _) =
450 IgvmAttestRequestBase::read_from_prefix(&buffer).expect("parse IgvmAttestRequest");
451 assert_eq!(
452 request.request_data.version,
453 IgvmAttestRequestVersion::VERSION_2
454 );
455
456 let expected_extension_size = size_of::<IgvmAttestRequestDataExt>();
457 let expected_size = (size_of::<
458 openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData,
459 >() + expected_extension_size
460 + runtime_claims.len()) as u32;
461 assert_eq!(request.request_data.data_size, expected_size);
462
463 let header_size = size_of::<IgvmAttestRequestBase>();
464 let ext_offset = header_size;
465
466 let (ext, _) = IgvmAttestRequestDataExt::read_from_prefix(&buffer[ext_offset..])
467 .expect("parse IgvmAttestRequestDataExt");
468 assert!(ext.capability_bitmap.error_code());
469 assert!(ext.capability_bitmap.retry());
470 assert!(ext.capability_bitmap.skip_hw_unsealing());
471
472 assert_eq!(
473 buffer.len(),
474 header_size + expected_extension_size + runtime_claims.len()
475 );
476 assert_eq!(
477 &buffer[header_size + expected_extension_size..],
478 runtime_claims.as_slice()
479 );
480 }
481
482 #[test]
483 fn test_transfer_key_jwk() {
484 const EXPECTED_JWK: &str = r#"[{"kid":"HCLTransferKey","key_ops":["encrypt"],"kty":"RSA","e":"RVhQT05FTlQ","n":"TU9EVUxVUw"}]"#;
485
486 let rsa_jwk = openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RsaJwk::get_transfer_key_jwks(
487 b"EXPONENT",
488 b"MODULUS",
489 );
490
491 let result = serde_json::to_string(&rsa_jwk);
492 assert!(result.is_ok());
493
494 let transfer_key_jwk = result.unwrap();
495 assert_eq!(transfer_key_jwk, EXPECTED_JWK);
496 }
497
498 #[test]
499 fn test_vm_configuration_no_time() {
500 const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":""}"#;
501
502 let attestation_vm_config = AttestationVmConfig {
503 current_time: None,
504 root_cert_thumbprint: String::new(),
505 console_enabled: false,
506 interactive_console_enabled: false,
507 secure_boot: false,
508 tpm_enabled: false,
509 tpm_persisted: false,
510 filtered_vpci_devices_allowed: true,
511 vm_unique_id: String::new(),
512 vmgs_provisioner: None,
513 };
514 let result = serde_json::to_string(&attestation_vm_config);
515 assert!(result.is_ok());
516
517 let vm_config = result.unwrap();
518 assert_eq!(vm_config, EXPECTED_JWK);
519 }
520
521 #[test]
522 fn test_vm_configuration_with_time() {
523 const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":""}"#;
524
525 let attestation_vm_config = AttestationVmConfig {
526 current_time: None,
527 root_cert_thumbprint: String::new(),
528 console_enabled: false,
529 interactive_console_enabled: false,
530 secure_boot: false,
531 tpm_enabled: false,
532 tpm_persisted: false,
533 filtered_vpci_devices_allowed: true,
534 vm_unique_id: String::new(),
535 vmgs_provisioner: None,
536 };
537 let attestation_vm_config =
538 attestation_vm_config_with_time(&attestation_vm_config, 1691103220);
539 let result = serde_json::to_string(&attestation_vm_config);
540 assert!(result.is_ok());
541
542 let vm_config = result.unwrap();
543 assert_eq!(vm_config, EXPECTED_JWK);
544 }
545
546 #[test]
547 fn test_empty_response() {
548 let result = parse_response_header(&[]);
549 assert!(result.is_err());
550 assert_eq!(
551 result.unwrap_err().to_string(),
552 Error::ResponseSizeTooSmall { response_size: 0 }.to_string()
553 );
554 }
555
556 #[test]
557 fn test_invalid_response_size_smaller_than_header_size() {
558 const INVALID_RESPONSE: [u8; 4] = [0x04, 0x00, 0x00, 0x00];
559 let result = parse_response_header(&INVALID_RESPONSE);
560 assert!(result.is_err());
561 assert_eq!(
562 result.unwrap_err().to_string(),
563 Error::ResponseSizeTooSmall { response_size: 4 }.to_string()
564 );
565 }
566
567 #[test]
568 fn test_valid_v1_response_size_match() {
569 const VALID_RESPONSE: [u8; 42] = [
570 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
571 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
572 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
573 ];
574
575 let result = parse_response_header(&VALID_RESPONSE);
576 assert!(result.is_ok());
577 let header = result.unwrap();
578 assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
579 assert_eq!(IgvmAttestResponseVersion::VERSION_1, header.version);
580 }
581
582 #[test]
583 fn test_valid_v2_response_size_match() {
584 const VALID_RESPONSE: [u8; 42] = [
585 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
586 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
587 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
588 ];
589
590 let result = parse_response_header(&VALID_RESPONSE);
591 assert!(result.is_ok());
592 let header = result.unwrap();
593 assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
594 assert_eq!(IgvmAttestResponseVersion::VERSION_2, header.version);
595 }
596
597 #[test]
598 fn test_valid_v1_response_size_smaller_than_specified() {
599 const VALID_RESPONSE: [u8; 42] = [
600 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
601 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
602 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
603 ];
604
605 let header = parse_response_header(&VALID_RESPONSE);
606 assert!(header.is_ok());
607 assert_eq!(0x29, header.unwrap().data_size as usize);
608 }
609
610 #[test]
611 fn test_valid_v2_response_size_smaller_than_specified() {
612 const VALID_RESPONSE: [u8; 42] = [
613 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
614 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
615 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
616 ];
617
618 let header = parse_response_header(&VALID_RESPONSE);
619 assert!(header.is_ok());
620 assert_eq!(0x29, header.unwrap().data_size as usize);
621 }
622
623 #[test]
624 fn test_invalid_v1_response_size() {
625 const INVALID_RESPONSE: [u8; 42] = [
626 0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
627 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
628 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
629 ];
630
631 let result = parse_response_header(&INVALID_RESPONSE);
632 assert!(result.is_err());
633 assert_eq!(
634 result.unwrap_err().to_string(),
635 Error::ResponseSizeMismatch {
636 size: INVALID_RESPONSE.len(),
637 specified_size: 0x2b
638 }
639 .to_string()
640 );
641 }
642
643 #[test]
644 fn test_invalid_v2_response_size() {
645 const INVALID_RESPONSE: [u8; 42] = [
646 0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
647 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
648 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
649 ];
650
651 let result = parse_response_header(&INVALID_RESPONSE);
652 assert!(result.is_err());
653 assert_eq!(
654 result.unwrap_err().to_string(),
655 Error::ResponseSizeMismatch {
656 size: INVALID_RESPONSE.len(),
657 specified_size: 0x2b
658 }
659 .to_string()
660 );
661 }
662
663 #[test]
664 fn test_invalid_header_version() {
665 const INVALID_RESPONSE: [u8; 42] = [
666 0x2a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
667 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
668 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
669 ];
670
671 let result = parse_response_header(&INVALID_RESPONSE);
672 assert!(result.is_err());
673 assert_eq!(
674 result.unwrap_err().to_string(),
675 Error::InvalidResponseHeaderVersion {
676 version: IgvmAttestResponseVersion(3),
677 latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION
678 }
679 .to_string()
680 );
681 }
682
683 #[test]
684 fn test_invalid_v2_response_size_smaller_than_specified_header_size() {
685 const INVALID_RESPONSE: [u8; 28] = [
686 0x1c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
687 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
688 ];
689
690 let result = parse_response_header(&INVALID_RESPONSE);
691 assert!(result.is_err());
692 assert_eq!(
693 result.unwrap_err().to_string(),
694 Error::ResponseHeaderInvalidFormat {
695 response_size: 0x1c
696 }
697 .to_string()
698 );
699 }
700
701 #[test]
702 fn test_failed_response_with_retryable_error() {
703 const INVALID_RESPONSE: [u8; 42] = [
705 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x93, 0x01,
706 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
707 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
708 ];
709
710 let result = parse_response_header(&INVALID_RESPONSE);
711 assert!(result.is_err());
712 assert_eq!(
713 result.unwrap_err().to_string(),
714 Error::Attestation {
715 igvm_error_code: 1103,
716 http_status_code: 403,
717 retry_signal: true,
718 skip_hw_unsealing_signal: false
719 }
720 .to_string()
721 );
722 }
723
724 #[test]
725 fn test_failed_response_with_non_retryable_error() {
726 const INVALID_RESPONSE: [u8; 42] = [
728 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0xf7, 0x01,
729 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
730 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
731 ];
732
733 let result = parse_response_header(&INVALID_RESPONSE);
734 assert!(result.is_err());
735 assert_eq!(
736 result.unwrap_err().to_string(),
737 Error::Attestation {
738 igvm_error_code: 1103,
739 http_status_code: 503,
740 retry_signal: false,
741 skip_hw_unsealing_signal: false
742 }
743 .to_string()
744 );
745 }
746
747 #[test]
748 fn test_failed_response_with_skip_hw_unsealing_signal() {
749 const INVALID_RESPONSE: [u8; 42] = [
752 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x90, 0x01,
753 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
754 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
755 ];
756
757 let result = parse_response_header(&INVALID_RESPONSE);
758 assert!(result.is_err());
759 assert_eq!(
760 result.unwrap_err().to_string(),
761 Error::Attestation {
762 igvm_error_code: 1103,
763 http_status_code: 400,
764 retry_signal: true,
765 skip_hw_unsealing_signal: true
766 }
767 .to_string()
768 );
769 }
770
771 #[test]
772 fn test_failed_response_with_skip_hw_unsealing_only() {
773 const INVALID_RESPONSE: [u8; 42] = [
776 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x90, 0x01,
777 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
778 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
779 ];
780
781 let result = parse_response_header(&INVALID_RESPONSE);
782 assert!(result.is_err());
783 assert_eq!(
784 result.unwrap_err().to_string(),
785 Error::Attestation {
786 igvm_error_code: 1103,
787 http_status_code: 400,
788 retry_signal: false,
789 skip_hw_unsealing_signal: true
790 }
791 .to_string()
792 );
793 }
794}