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::IGVM_ATTEST_RESPONSE_VERSION_2;
11use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
12use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestHashType;
13use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestReportType;
14use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestType;
15use openhcl_attestation_protocol::igvm_attest::get::IgvmCapabilityBitMap;
16use openhcl_attestation_protocol::igvm_attest::get::IgvmErrorInfo;
17use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
18use tee_call::TeeType;
19use thiserror::Error;
20use zerocopy::FromBytes;
21use zerocopy::FromZeros;
22use zerocopy::IntoBytes;
23
24pub mod ak_cert;
25pub mod key_release;
26pub mod wrapped_key;
27
28base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
29
30#[expect(missing_docs)] #[derive(Debug, Error)]
32pub enum Error {
33 #[error(
34 "the size of the attestation report {report_size} is invalid, expected {expected_size}"
35 )]
36 InvalidAttestationReportSize {
37 report_size: usize,
38 expected_size: usize,
39 },
40 #[error("the size of the attestation response {response_size} is too small to parse")]
41 ResponseSizeTooSmall { response_size: usize },
42 #[error(
43 "the header of the attestation response (size {response_size}) is not in correct format"
44 )]
45 ResponseHeaderInvalidFormat { response_size: usize },
46 #[error(
47 "response size {specified_size} specified in the header not match the actual size {size}"
48 )]
49 ResponseSizeMismatch { size: usize, specified_size: usize },
50 #[error("response header version {version} larger than current version {latest_version}")]
51 InvalidResponseHeaderVersion { version: u32, latest_version: u32 },
52 #[error(
53 "attest failed ({igvm_error_code}-{http_status_code}), retry recommendation ({retry_signal})"
54 )]
55 Attestation {
56 igvm_error_code: u32,
57 http_status_code: u32,
58 retry_signal: bool,
59 },
60}
61
62pub enum ReportType {
64 Vbs,
66 Snp,
68 Tdx,
70 Tvm,
72}
73
74impl ReportType {
75 fn to_external_type(&self) -> IgvmAttestReportType {
77 match self {
78 Self::Vbs => IgvmAttestReportType::VBS_VM_REPORT,
79 Self::Snp => IgvmAttestReportType::SNP_VM_REPORT,
80 Self::Tdx => IgvmAttestReportType::TDX_VM_REPORT,
81 Self::Tvm => IgvmAttestReportType::TVM_REPORT,
82 }
83 }
84}
85
86pub struct IgvmAttestRequestHelper {
88 request_type: IgvmAttestRequestType,
90 report_type: ReportType,
92 runtime_claims: Vec<u8>,
94 runtime_claims_hash: [u8; tee_call::REPORT_DATA_SIZE],
97 hash_type: IgvmAttestHashType,
99}
100
101impl IgvmAttestRequestHelper {
102 pub fn prepare_key_release_request(
104 tee_type: TeeType,
105 rsa_exponent: &[u8],
106 rsa_modulus: &[u8],
107 host_time: i64,
108 attestation_vm_config: &AttestationVmConfig,
109 ) -> Self {
110 let report_type = match tee_type {
111 TeeType::Snp => ReportType::Snp,
112 TeeType::Tdx => ReportType::Tdx,
113 TeeType::Vbs => ReportType::Vbs,
114 };
115
116 let attestation_vm_config =
117 attestation_vm_config_with_time(attestation_vm_config, host_time);
118 let runtime_claims =
119 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::key_release_request_runtime_claims(rsa_exponent, rsa_modulus, &attestation_vm_config);
120 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
121
122 let hash_type = IgvmAttestHashType::SHA_256;
123 let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
124 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
125 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
126
127 Self {
128 request_type: IgvmAttestRequestType::KEY_RELEASE_REQUEST,
129 report_type,
130 runtime_claims,
131 runtime_claims_hash,
132 hash_type,
133 }
134 }
135
136 pub fn prepare_ak_cert_request(
138 tee_type: Option<TeeType>,
139 ak_pub_exponent: &[u8],
140 ak_pub_modulus: &[u8],
141 ek_pub_exponent: &[u8],
142 ek_pub_modulus: &[u8],
143 attestation_vm_config: &AttestationVmConfig,
144 guest_input: &[u8],
145 ) -> Self {
146 let report_type = match tee_type {
147 Some(TeeType::Snp) => ReportType::Snp,
148 Some(TeeType::Tdx) => ReportType::Tdx,
149 Some(TeeType::Vbs) => ReportType::Vbs,
150 None => ReportType::Tvm,
151 };
152
153 let runtime_claims =
154 openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::ak_cert_runtime_claims(
155 ak_pub_exponent,
156 ak_pub_modulus,
157 ek_pub_exponent,
158 ek_pub_modulus,
159 attestation_vm_config,
160 guest_input,
161 );
162
163 let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
164
165 let hash_type = IgvmAttestHashType::SHA_256;
166 let hash = crate::crypto::sha_256(runtime_claims.as_bytes());
167 let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
168 runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
169
170 Self {
171 request_type: IgvmAttestRequestType::AK_CERT_REQUEST,
172 report_type,
173 runtime_claims,
174 runtime_claims_hash,
175 hash_type,
176 }
177 }
178
179 pub fn get_runtime_claims_hash(&self) -> &[u8; tee_call::REPORT_DATA_SIZE] {
181 &self.runtime_claims_hash
182 }
183
184 pub fn set_request_type(&mut self, request_type: IgvmAttestRequestType) {
186 self.request_type = request_type
187 }
188
189 pub fn create_request(&self, attestation_report: &[u8]) -> Result<Vec<u8>, Error> {
191 create_request(
192 self.request_type,
193 &self.runtime_claims,
194 attestation_report,
195 &self.report_type,
196 self.hash_type,
197 )
198 }
199}
200
201pub fn parse_response_header(response: &[u8]) -> Result<IgvmAttestCommonResponseHeader, Error> {
203 let header = IgvmAttestCommonResponseHeader::read_from_prefix(response)
206 .map_err(|_| Error::ResponseSizeTooSmall {
207 response_size: response.len(),
208 })?
209 .0; if header.data_size as usize > response.len() {
213 Err(Error::ResponseSizeMismatch {
214 size: response.len(),
215 specified_size: header.data_size as usize,
216 })?
217 }
218 if header.version > IGVM_ATTEST_RESPONSE_CURRENT_VERSION {
219 Err(Error::InvalidResponseHeaderVersion {
220 version: header.version,
221 latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION,
222 })?
223 }
224
225 if header.version >= IGVM_ATTEST_RESPONSE_VERSION_2 {
227 let igvm_error_info = IgvmErrorInfo::read_from_prefix(
229 &response[size_of::<IgvmAttestCommonResponseHeader>()..],
230 )
231 .map_err(|_| Error::ResponseHeaderInvalidFormat {
232 response_size: response.len(),
233 })?
234 .0; if 0 != igvm_error_info.error_code {
237 Err(Error::Attestation {
238 igvm_error_code: igvm_error_info.error_code,
239 http_status_code: igvm_error_info.http_status_code,
240 retry_signal: igvm_error_info.igvm_signal.retry(),
241 })?
242 }
243 }
244 Ok(IgvmAttestCommonResponseHeader {
245 data_size: header.data_size,
246 version: header.version,
247 })
248}
249
250fn create_request(
254 request_type: IgvmAttestRequestType,
255 runtime_claims: &[u8],
256 attestation_report: &[u8],
257 report_type: &ReportType,
258 hash_type: IgvmAttestHashType,
259) -> Result<Vec<u8>, Error> {
260 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequest;
261 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData;
262 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
263
264 let expected_report_size = get_report_size(report_type);
265 if attestation_report.len() != expected_report_size {
266 Err(Error::InvalidAttestationReportSize {
267 report_size: attestation_report.len(),
268 expected_size: expected_report_size,
269 })?
270 }
271
272 let report_size = size_of::<IgvmAttestRequest>() + runtime_claims.len();
273 let user_data_size = size_of::<IgvmAttestRequestData>() + runtime_claims.len();
274 let mut request = IgvmAttestRequest::new_zeroed();
275
276 request.header = IgvmAttestRequestHeader::new(report_size as u32, request_type, 0);
277
278 request.attestation_report[..attestation_report.len()].copy_from_slice(attestation_report);
279
280 request.request_data = IgvmAttestRequestData::new(
281 user_data_size as u32,
282 report_type.to_external_type(),
283 hash_type,
284 runtime_claims.len() as u32,
285 IgvmCapabilityBitMap::new()
286 .with_error_code(true)
287 .with_retry(true),
288 );
289
290 Ok([request.as_bytes(), runtime_claims].concat())
291}
292
293fn get_report_size(report_type: &ReportType) -> usize {
295 match report_type {
296 ReportType::Vbs => openhcl_attestation_protocol::igvm_attest::get::VBS_VM_REPORT_SIZE,
297 ReportType::Snp => openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE,
298 ReportType::Tdx => openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE,
299 ReportType::Tvm => openhcl_attestation_protocol::igvm_attest::get::TVM_REPORT_SIZE,
300 }
301}
302
303fn attestation_vm_config_with_time(
305 vm_config: &AttestationVmConfig,
306 host_epoch: i64,
307) -> AttestationVmConfig {
308 let mut vm_config = vm_config.clone();
309 vm_config.current_time = Some(host_epoch);
310 vm_config
311}
312
313fn runtime_claims_to_bytes(
315 runtime_claims: &openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims,
316) -> Vec<u8> {
317 let runtime_claims = serde_json::to_string(runtime_claims).expect("JSON serialization failed");
318 runtime_claims.as_bytes().to_vec()
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_create_request() {
327 let result = create_request(
328 IgvmAttestRequestType::AK_CERT_REQUEST,
329 &[],
330 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE],
331 &ReportType::Snp,
332 IgvmAttestHashType::SHA_256,
333 );
334 assert!(result.is_ok());
335
336 let result = create_request(
337 IgvmAttestRequestType::AK_CERT_REQUEST,
338 &[],
339 &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE + 1],
340 &ReportType::Snp,
341 IgvmAttestHashType::SHA_256,
342 );
343 assert!(result.is_err());
344 }
345
346 #[test]
347 fn test_transfer_key_jwk() {
348 const EXPECTED_JWK: &str = r#"[{"kid":"HCLTransferKey","key_ops":["encrypt"],"kty":"RSA","e":"RVhQT05FTlQ","n":"TU9EVUxVUw"}]"#;
349
350 let rsa_jwk = openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RsaJwk::get_transfer_key_jwks(
351 b"EXPONENT",
352 b"MODULUS",
353 );
354
355 let result = serde_json::to_string(&rsa_jwk);
356 assert!(result.is_ok());
357
358 let transfer_key_jwk = result.unwrap();
359 assert_eq!(transfer_key_jwk, EXPECTED_JWK);
360 }
361
362 #[test]
363 fn test_vm_configuration_no_time() {
364 const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":""}"#;
365
366 let attestation_vm_config = AttestationVmConfig {
367 current_time: None,
368 root_cert_thumbprint: String::new(),
369 console_enabled: false,
370 secure_boot: false,
371 tpm_enabled: false,
372 tpm_persisted: false,
373 filtered_vpci_devices_allowed: true,
374 vm_unique_id: String::new(),
375 };
376 let result = serde_json::to_string(&attestation_vm_config);
377 assert!(result.is_ok());
378
379 let vm_config = result.unwrap();
380 assert_eq!(vm_config, EXPECTED_JWK);
381 }
382
383 #[test]
384 fn test_vm_configuration_with_time() {
385 const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":""}"#;
386
387 let attestation_vm_config = AttestationVmConfig {
388 current_time: None,
389 root_cert_thumbprint: String::new(),
390 console_enabled: false,
391 secure_boot: false,
392 tpm_enabled: false,
393 tpm_persisted: false,
394 filtered_vpci_devices_allowed: true,
395 vm_unique_id: String::new(),
396 };
397 let attestation_vm_config =
398 attestation_vm_config_with_time(&attestation_vm_config, 1691103220);
399 let result = serde_json::to_string(&attestation_vm_config);
400 assert!(result.is_ok());
401
402 let vm_config = result.unwrap();
403 assert_eq!(vm_config, EXPECTED_JWK);
404 }
405
406 #[test]
407 fn test_empty_response() {
408 let result = parse_response_header(&[]);
409 assert!(result.is_err());
410 assert_eq!(
411 result.unwrap_err().to_string(),
412 Error::ResponseSizeTooSmall { response_size: 0 }.to_string()
413 );
414 }
415
416 #[test]
417 fn test_invalid_response_size_smaller_than_header_size() {
418 const INVALID_RESPONSE: [u8; 4] = [0x04, 0x00, 0x00, 0x00];
419 let result = parse_response_header(&INVALID_RESPONSE);
420 assert!(result.is_err());
421 assert_eq!(
422 result.unwrap_err().to_string(),
423 Error::ResponseSizeTooSmall { response_size: 4 }.to_string()
424 );
425 }
426
427 #[test]
428 fn test_valid_v1_response_size_match() {
429 const VALID_RESPONSE: [u8; 42] = [
430 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
431 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
432 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
433 ];
434
435 let result = parse_response_header(&VALID_RESPONSE);
436 assert!(result.is_ok());
437 let header = result.unwrap();
438 assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
439 assert_eq!(1, header.version);
440 }
441
442 #[test]
443 fn test_valid_v2_response_size_match() {
444 const VALID_RESPONSE: [u8; 42] = [
445 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
446 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
447 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
448 ];
449
450 let result = parse_response_header(&VALID_RESPONSE);
451 assert!(result.is_ok());
452 let header = result.unwrap();
453 assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
454 assert_eq!(2, header.version);
455 }
456
457 #[test]
458 fn test_valid_v1_response_size_smaller_than_specified() {
459 const VALID_RESPONSE: [u8; 42] = [
460 0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
461 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
462 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
463 ];
464
465 let header = parse_response_header(&VALID_RESPONSE);
466 assert!(header.is_ok());
467 assert_eq!(0x29, header.unwrap().data_size as usize);
468 }
469
470 #[test]
471 fn test_valid_v2_response_size_smaller_than_specified() {
472 const VALID_RESPONSE: [u8; 42] = [
473 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
474 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
475 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
476 ];
477
478 let header = parse_response_header(&VALID_RESPONSE);
479 assert!(header.is_ok());
480 assert_eq!(0x29, header.unwrap().data_size as usize);
481 }
482
483 #[test]
484 fn test_invalid_v1_response_size() {
485 const INVALID_RESPONSE: [u8; 42] = [
486 0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
487 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
488 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
489 ];
490
491 let result = parse_response_header(&INVALID_RESPONSE);
492 assert!(result.is_err());
493 assert_eq!(
494 result.unwrap_err().to_string(),
495 Error::ResponseSizeMismatch {
496 size: INVALID_RESPONSE.len(),
497 specified_size: 0x2b
498 }
499 .to_string()
500 );
501 }
502
503 #[test]
504 fn test_invalid_v2_response_size() {
505 const INVALID_RESPONSE: [u8; 42] = [
506 0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
507 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
508 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
509 ];
510
511 let result = parse_response_header(&INVALID_RESPONSE);
512 assert!(result.is_err());
513 assert_eq!(
514 result.unwrap_err().to_string(),
515 Error::ResponseSizeMismatch {
516 size: INVALID_RESPONSE.len(),
517 specified_size: 0x2b
518 }
519 .to_string()
520 );
521 }
522
523 #[test]
524 fn test_invalid_header_version() {
525 const INVALID_RESPONSE: [u8; 42] = [
526 0x2a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
527 0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
528 0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
529 ];
530
531 let result = parse_response_header(&INVALID_RESPONSE);
532 assert!(result.is_err());
533 assert_eq!(
534 result.unwrap_err().to_string(),
535 Error::InvalidResponseHeaderVersion {
536 version: 3,
537 latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION
538 }
539 .to_string()
540 );
541 }
542
543 #[test]
544 fn test_invalid_v2_response_size_smaller_than_specified_header_size() {
545 const INVALID_RESPONSE: [u8; 28] = [
546 0x1c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
547 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
548 ];
549
550 let result = parse_response_header(&INVALID_RESPONSE);
551 assert!(result.is_err());
552 assert_eq!(
553 result.unwrap_err().to_string(),
554 Error::ResponseHeaderInvalidFormat {
555 response_size: 0x1c
556 }
557 .to_string()
558 );
559 }
560
561 #[test]
562 fn test_failed_response_with_retryable_error() {
563 const INVALID_RESPONSE: [u8; 42] = [
565 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x93, 0x01,
566 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
567 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
568 ];
569
570 let result = parse_response_header(&INVALID_RESPONSE);
571 assert!(result.is_err());
572 assert_eq!(
573 result.unwrap_err().to_string(),
574 Error::Attestation {
575 igvm_error_code: 1103,
576 http_status_code: 403,
577 retry_signal: true
578 }
579 .to_string()
580 );
581 }
582
583 #[test]
584 fn test_failed_response_with_non_retryable_error() {
585 const INVALID_RESPONSE: [u8; 42] = [
587 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0xf7, 0x01,
588 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
589 0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
590 ];
591
592 let result = parse_response_header(&INVALID_RESPONSE);
593 assert!(result.is_err());
594 assert_eq!(
595 result.unwrap_err().to_string(),
596 Error::Attestation {
597 igvm_error_code: 1103,
598 http_status_code: 503,
599 retry_signal: false
600 }
601 .to_string()
602 );
603 }
604}