Skip to main content

underhill_attestation/igvm_attest/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The module helps preparing requests and parsing responses that are
5//! sent to and received from the IGVm agent runs on the host via GET
6//! `IGVM_ATTEST` host request.
7
8use base64_serde::base64_serde_type;
9use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_REQUEST_CURRENT_VERSION;
10use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_RESPONSE_CURRENT_VERSION;
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::IgvmAttestRequestVersion;
16use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestResponseVersion;
17use openhcl_attestation_protocol::igvm_attest::get::IgvmCapabilityBitMap;
18use openhcl_attestation_protocol::igvm_attest::get::IgvmErrorInfo;
19use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationVmConfig;
20use tee_call::TeeType;
21use thiserror::Error;
22use zerocopy::FromBytes;
23use zerocopy::FromZeros;
24use zerocopy::IntoBytes;
25
26pub mod ak_cert;
27pub mod key_release;
28pub mod wrapped_key;
29
30base64_serde_type!(Base64Url, base64::engine::general_purpose::URL_SAFE_NO_PAD);
31
32#[expect(missing_docs)] // self-explanatory fields
33#[derive(Debug, Error)]
34pub enum Error {
35    #[error(
36        "the size of the attestation report {report_size} is invalid, expected {expected_size}"
37    )]
38    InvalidAttestationReportSize {
39        report_size: usize,
40        expected_size: usize,
41    },
42    #[error("the size of the attestation response {response_size} is too small to parse")]
43    ResponseSizeTooSmall { response_size: usize },
44    #[error(
45        "the header of the attestation response (size {response_size}) is not in correct format"
46    )]
47    ResponseHeaderInvalidFormat { response_size: usize },
48    #[error(
49        "response size {specified_size} specified in the header not match the actual size {size}"
50    )]
51    ResponseSizeMismatch { size: usize, specified_size: usize },
52    #[error("response header version {version:?} larger than current version {latest_version:?}")]
53    InvalidResponseHeaderVersion {
54        version: IgvmAttestResponseVersion,
55        latest_version: IgvmAttestResponseVersion,
56    },
57    #[error(
58        "attest failed ({igvm_error_code}-{http_status_code}), retry recommendation ({retry_signal}), skip hw unsealing recommendation ({skip_hw_unsealing_signal})"
59    )]
60    Attestation {
61        igvm_error_code: u32,
62        http_status_code: u32,
63        retry_signal: bool,
64        skip_hw_unsealing_signal: bool,
65    },
66}
67
68/// Rust-style enum for `IgvmAttestReportType`
69pub enum ReportType {
70    /// VBS report
71    Vbs,
72    /// SNP report
73    Snp,
74    /// TDX report
75    Tdx,
76    /// CCA report
77    Cca,
78    /// Trusted VM report
79    Tvm,
80}
81
82impl ReportType {
83    /// Map the value to `IgvmAttestReportType`
84    fn to_external_type(&self) -> IgvmAttestReportType {
85        match self {
86            Self::Vbs => IgvmAttestReportType::VBS_VM_REPORT,
87            Self::Snp => IgvmAttestReportType::SNP_VM_REPORT,
88            Self::Tdx => IgvmAttestReportType::TDX_VM_REPORT,
89            Self::Cca => IgvmAttestReportType::CCA_VM_REPORT,
90            Self::Tvm => IgvmAttestReportType::TVM_REPORT,
91        }
92    }
93}
94
95/// Helper struct to create `IgvmAttestRequest` in raw bytes.
96pub struct IgvmAttestRequestHelper {
97    /// The request type.
98    request_type: IgvmAttestRequestType,
99    /// The report type.
100    report_type: ReportType,
101    /// Raw bytes of `RuntimeClaims`.
102    runtime_claims: Vec<u8>,
103    /// Whether the serialized runtime claims include AK certificate metadata.
104    has_ak_cert_metadata: bool,
105    /// The hash of the `runtime_claims` to be included in the
106    /// `report_data` field of the attestation report.
107    runtime_claims_hash: [u8; tee_call::REPORT_DATA_SIZE],
108    /// THe hash type of the `runtime_claims_hash`.
109    hash_type: IgvmAttestHashType,
110}
111
112impl IgvmAttestRequestHelper {
113    /// Prepare the data necessary for creating the `KEY_RELEASE` request.
114    pub fn prepare_key_release_request(
115        tee_type: TeeType,
116        rsa_exponent: &[u8],
117        rsa_modulus: &[u8],
118        host_time: i64,
119        attestation_vm_config: &AttestationVmConfig,
120    ) -> Self {
121        let report_type = match tee_type {
122            TeeType::Snp => ReportType::Snp,
123            TeeType::Tdx => ReportType::Tdx,
124            TeeType::Cca => ReportType::Cca,
125            TeeType::Vbs => ReportType::Vbs,
126        };
127
128        let attestation_vm_config =
129            attestation_vm_config_with_time(attestation_vm_config, host_time);
130        let runtime_claims =
131            openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::key_release_request_runtime_claims(rsa_exponent, rsa_modulus, &attestation_vm_config);
132        let runtime_claims = runtime_claims_to_bytes(&runtime_claims);
133
134        let hash_type = IgvmAttestHashType::SHA_256;
135        let hash = crypto::sha_256::sha_256(runtime_claims.as_bytes());
136        let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
137        runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
138
139        Self {
140            request_type: IgvmAttestRequestType::KEY_RELEASE_REQUEST,
141            report_type,
142            runtime_claims,
143            has_ak_cert_metadata: false,
144            runtime_claims_hash,
145            hash_type,
146        }
147    }
148
149    /// Prepare the data necessary for creating the `AK_CERT` request.
150    pub fn prepare_ak_cert_request(
151        tee_type: Option<TeeType>,
152        ak_pub_exponent: &[u8],
153        ak_pub_modulus: &[u8],
154        ek_pub_exponent: &[u8],
155        ek_pub_modulus: &[u8],
156        attestation_vm_config: &AttestationVmConfig,
157        guest_input: &[u8],
158        akcert: Option<
159            openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AkCertMetadata,
160        >,
161    ) -> Self {
162        let report_type = match tee_type {
163            Some(TeeType::Snp) => ReportType::Snp,
164            Some(TeeType::Tdx) => ReportType::Tdx,
165            Some(TeeType::Cca) => ReportType::Cca,
166            Some(TeeType::Vbs) => ReportType::Vbs,
167            None => ReportType::Tvm,
168        };
169
170        let mut runtime_claims =
171            openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims::ak_cert_runtime_claims(
172                ak_pub_exponent,
173                ak_pub_modulus,
174                ek_pub_exponent,
175                ek_pub_modulus,
176                attestation_vm_config,
177                guest_input,
178            );
179        runtime_claims.akcert = akcert;
180
181        let mut serialized_claims = runtime_claims_to_bytes(&runtime_claims);
182        if runtime_claims.akcert.is_some()
183            && request_size(IGVM_ATTEST_REQUEST_CURRENT_VERSION, serialized_claims.len())
184                > get_protocol::IGVM_ATTEST_MSG_REQ_REPORT_MAX_SIZE
185        {
186            // Unlikely with ordinary production certificate names and key IDs,
187            // but unusually large metadata or JSON expansion can exceed the GET
188            // limit. Omit the metadata before hashing to request a replacement.
189            runtime_claims.akcert = None;
190            serialized_claims = runtime_claims_to_bytes(&runtime_claims);
191        }
192        let has_ak_cert_metadata = runtime_claims.akcert.is_some();
193        let runtime_claims = serialized_claims;
194
195        let hash_type = IgvmAttestHashType::SHA_256;
196        let hash = crypto::sha_256::sha_256(runtime_claims.as_bytes());
197        let mut runtime_claims_hash = [0u8; tee_call::REPORT_DATA_SIZE];
198        runtime_claims_hash[0..hash.len()].copy_from_slice(&hash);
199
200        Self {
201            request_type: IgvmAttestRequestType::AK_CERT_REQUEST,
202            report_type,
203            runtime_claims,
204            has_ak_cert_metadata,
205            runtime_claims_hash,
206            hash_type,
207        }
208    }
209
210    /// Whether the final runtime claims include AK certificate metadata.
211    pub fn has_ak_cert_metadata(&self) -> bool {
212        self.has_ak_cert_metadata
213    }
214
215    /// Return the complete wire request size, including any versioned extension.
216    pub fn request_size(&self, version: IgvmAttestRequestVersion) -> usize {
217        request_size(version, self.runtime_claims.len())
218    }
219
220    /// Return the `runtime_claims_hash`.
221    pub fn get_runtime_claims_hash(&self) -> &[u8; tee_call::REPORT_DATA_SIZE] {
222        &self.runtime_claims_hash
223    }
224
225    /// Set the `request_type`.
226    pub fn set_request_type(&mut self, request_type: IgvmAttestRequestType) {
227        self.request_type = request_type
228    }
229
230    /// Create the request in raw bytes.
231    pub fn create_request(
232        &self,
233        version: IgvmAttestRequestVersion,
234        attestation_report: &[u8],
235    ) -> Result<Vec<u8>, Error> {
236        create_request(
237            version,
238            self.request_type,
239            &self.runtime_claims,
240            attestation_report,
241            &self.report_type,
242            self.hash_type,
243        )
244    }
245}
246
247/// Verify response header and try to extract IgvmErrorInfo from the header
248pub fn parse_response_header(response: &[u8]) -> Result<IgvmAttestCommonResponseHeader, Error> {
249    // Extract common header fields regardless of header version or request type
250    // For V1 request, response buffer should be empty in case of attestation failure
251    let header = IgvmAttestCommonResponseHeader::read_from_prefix(response)
252        .map_err(|_| Error::ResponseSizeTooSmall {
253            response_size: response.len(),
254        })?
255        .0; // TODO: zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
256
257    // Check header data_size and version
258    if header.data_size as usize > response.len() {
259        Err(Error::ResponseSizeMismatch {
260            size: response.len(),
261            specified_size: header.data_size as usize,
262        })?
263    }
264    let response = &response[..header.data_size as usize];
265    if response.len() < size_of::<IgvmAttestCommonResponseHeader>() {
266        return Err(Error::ResponseSizeTooSmall {
267            response_size: response.len(),
268        });
269    }
270    if header.version > IGVM_ATTEST_RESPONSE_CURRENT_VERSION {
271        Err(Error::InvalidResponseHeaderVersion {
272            version: header.version,
273            latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION,
274        })?
275    }
276
277    // IgvmErrorInfo is added in response header since version 2
278    if header.version >= IgvmAttestResponseVersion::VERSION_2 {
279        // Extract result info from response header
280        let igvm_error_info = IgvmErrorInfo::read_from_prefix(
281            &response[size_of::<IgvmAttestCommonResponseHeader>()..],
282        )
283        .map_err(|_| Error::ResponseHeaderInvalidFormat {
284            response_size: response.len(),
285        })?
286        .0; // TODO: zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
287
288        if 0 != igvm_error_info.error_code {
289            Err(Error::Attestation {
290                igvm_error_code: igvm_error_info.error_code,
291                http_status_code: igvm_error_info.http_status_code,
292                retry_signal: igvm_error_info.igvm_signal.retry(),
293                skip_hw_unsealing_signal: igvm_error_info.igvm_signal.skip_hw_unsealing(),
294            })?
295        }
296    }
297    Ok(IgvmAttestCommonResponseHeader {
298        data_size: header.data_size,
299        version: header.version,
300    })
301}
302
303/// Calculate the complete request size without generating a TEE report.
304fn request_size(version: IgvmAttestRequestVersion, runtime_claims_len: usize) -> usize {
305    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
306    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
307
308    let extension_size = if version >= IgvmAttestRequestVersion::VERSION_2 {
309        size_of::<IgvmAttestRequestDataExt>()
310    } else {
311        0
312    };
313    size_of::<IgvmAttestRequestBase>() + extension_size + runtime_claims_len
314}
315
316/// Create a request in raw bytes.
317/// A request looks like:
318///   `IgvmAttestRequestBase` (raw bytes) | `IgvmAttestRequestDataExt` (raw bytes) if version >= 2 | `runtime_claims` (raw bytes)
319fn create_request(
320    version: IgvmAttestRequestVersion,
321    request_type: IgvmAttestRequestType,
322    runtime_claims: &[u8],
323    attestation_report: &[u8],
324    report_type: &ReportType,
325    hash_type: IgvmAttestHashType,
326) -> Result<Vec<u8>, Error> {
327    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
328    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData;
329    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
330    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestHeader;
331
332    let expected_report_size = get_report_size(report_type);
333    if attestation_report.len() != expected_report_size {
334        Err(Error::InvalidAttestationReportSize {
335            report_size: attestation_report.len(),
336            expected_size: expected_report_size,
337        })?
338    }
339
340    let runtime_claims_len = runtime_claims.len();
341    // Determine if request data extension structure is needed (introduced in version 2)
342    let include_extension = version >= IgvmAttestRequestVersion::VERSION_2;
343    let report_size = request_size(version, runtime_claims_len);
344    let user_data_size =
345        report_size - size_of::<IgvmAttestRequestBase>() + size_of::<IgvmAttestRequestData>();
346    let mut request = IgvmAttestRequestBase::new_zeroed();
347
348    request.header = IgvmAttestRequestHeader::new(report_size as u32, request_type, 0);
349
350    request.attestation_report[..attestation_report.len()].copy_from_slice(attestation_report);
351
352    request.request_data = IgvmAttestRequestData::new(
353        version,
354        user_data_size as u32,
355        report_type.to_external_type(),
356        hash_type,
357        runtime_claims_len as u32,
358    );
359
360    let mut buffer = Vec::with_capacity(report_size);
361    buffer.extend_from_slice(request.as_bytes());
362
363    if include_extension {
364        let capability_bitmap = IgvmCapabilityBitMap::new()
365            .with_error_code(true)
366            .with_retry(true)
367            .with_skip_hw_unsealing(true)
368            .with_use_rsa_aes_key_wrap_384(true)
369            // Signal the IGVM Agent to fetch the CoRIM launch endorsement.
370            // TDX only for now.
371            .with_corim_endorsement(matches!(report_type, &ReportType::Tdx));
372        let ext = IgvmAttestRequestDataExt::new(capability_bitmap);
373        buffer.extend_from_slice(ext.as_bytes());
374    }
375
376    buffer.extend_from_slice(runtime_claims);
377
378    Ok(buffer)
379}
380
381/// Get the expected size of the given report type.
382fn get_report_size(report_type: &ReportType) -> usize {
383    match report_type {
384        ReportType::Vbs => openhcl_attestation_protocol::igvm_attest::get::VBS_VM_REPORT_SIZE,
385        ReportType::Snp => openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE,
386        ReportType::Tdx => openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE,
387        ReportType::Tvm => openhcl_attestation_protocol::igvm_attest::get::TVM_REPORT_SIZE,
388        ReportType::Cca => todo!(),
389    }
390}
391
392/// Helper function that returns the given config with the `current_time` set.
393fn attestation_vm_config_with_time(
394    vm_config: &AttestationVmConfig,
395    host_epoch: i64,
396) -> AttestationVmConfig {
397    let mut vm_config = vm_config.clone();
398    vm_config.current_time = Some(host_epoch);
399    vm_config
400}
401
402/// Helper function that converts the `RuntimeClaims` to raw bytes.
403fn runtime_claims_to_bytes(
404    runtime_claims: &openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims,
405) -> Vec<u8> {
406    let runtime_claims = serde_json::to_string(runtime_claims).expect("JSON serialization failed");
407    runtime_claims.as_bytes().to_vec()
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413    use crypto::rsa::RsaKeyPair;
414    use crypto::rsa::RsaPublicKeyComponents;
415    use get_protocol::IGVM_ATTEST_MSG_REQ_REPORT_MAX_SIZE;
416    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
417    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
418    use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AkCertMetadata;
419    use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::AttestationTpmVersion;
420    use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::HardwareSealingPolicy;
421    use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims;
422    use test_with_tracing::test;
423
424    const AK_CERT_TEES: [Option<TeeType>; 5] = [
425        Some(TeeType::Snp),
426        Some(TeeType::Tdx),
427        Some(TeeType::Cca),
428        Some(TeeType::Vbs),
429        None,
430    ];
431
432    struct AkCertTestInput {
433        ak: RsaPublicKeyComponents,
434        ek: RsaPublicKeyComponents,
435        config: AttestationVmConfig,
436        guest_input: Vec<u8>,
437    }
438
439    impl AkCertTestInput {
440        fn new() -> Self {
441            Self {
442                ak: RsaKeyPair::generate(2048).unwrap().to_components().unwrap(),
443                ek: RsaKeyPair::generate(2048).unwrap().to_components().unwrap(),
444                config: AttestationVmConfig {
445                    current_time: None,
446                    root_cert_thumbprint: "A".repeat(64),
447                    console_enabled: false,
448                    interactive_console_enabled: false,
449                    ipmi_enabled: false,
450                    secure_boot: true,
451                    tpm_enabled: true,
452                    tpm_version: AttestationTpmVersion::V185,
453                    tpm_persisted: true,
454                    hardware_sealing_policy: HardwareSealingPolicy::Signer,
455                    filtered_vpci_devices_allowed: false,
456                    vm_unique_id: "01234567-89ab-cdef-0123-456789abcdef".to_owned(),
457                    vmgs_provisioner: None,
458                },
459                guest_input: vec![0x42; 32],
460            }
461        }
462
463        fn claims(&self, metadata: Option<AkCertMetadata>) -> Vec<u8> {
464            let mut claims = RuntimeClaims::ak_cert_runtime_claims(
465                &self.ak.public_exponent,
466                &self.ak.modulus,
467                &self.ek.public_exponent,
468                &self.ek.modulus,
469                &self.config,
470                &self.guest_input,
471            );
472            claims.akcert = metadata;
473            runtime_claims_to_bytes(&claims)
474        }
475
476        fn prepare(
477            &self,
478            tee: Option<TeeType>,
479            metadata: Option<AkCertMetadata>,
480        ) -> IgvmAttestRequestHelper {
481            IgvmAttestRequestHelper::prepare_ak_cert_request(
482                tee,
483                &self.ak.public_exponent,
484                &self.ak.modulus,
485                &self.ek.public_exponent,
486                &self.ek.modulus,
487                &self.config,
488                &self.guest_input,
489                metadata,
490            )
491        }
492
493        fn metadata_for_request_size(&self, size: usize) -> AkCertMetadata {
494            let certificate = test_igvm_agent_lib::ak_cert::certificate();
495            let mut metadata = ak_cert::parse_metadata(&certificate).unwrap();
496            metadata.subject.clear();
497            let base_size = size_of::<IgvmAttestRequestBase>()
498                + size_of::<IgvmAttestRequestDataExt>()
499                + self.claims(Some(metadata.clone())).len();
500            metadata.subject = "s".repeat(size.checked_sub(base_size).unwrap());
501            metadata
502        }
503
504        fn assert_fallback(&self, metadata: AkCertMetadata) {
505            let oversized_claims = self.claims(Some(metadata.clone()));
506            assert!(
507                size_of::<IgvmAttestRequestBase>()
508                    + size_of::<IgvmAttestRequestDataExt>()
509                    + oversized_claims.len()
510                    > IGVM_ATTEST_MSG_REQ_REPORT_MAX_SIZE
511            );
512            for (tee, same_tee) in AK_CERT_TEES.into_iter().zip(AK_CERT_TEES) {
513                let fallback = self.prepare(tee, Some(metadata.clone()));
514                let without_metadata = self.prepare(same_tee, None);
515                assert!(!fallback.has_ak_cert_metadata());
516                assert!(!without_metadata.has_ak_cert_metadata());
517                assert_eq!(fallback.runtime_claims, self.claims(None));
518                assert_eq!(fallback.runtime_claims, without_metadata.runtime_claims);
519                assert_eq!(
520                    fallback.get_runtime_claims_hash(),
521                    without_metadata.get_runtime_claims_hash()
522                );
523                assert_ne!(
524                    &fallback.get_runtime_claims_hash()[..32],
525                    crypto::sha_256::sha_256(&oversized_claims).as_ref()
526                );
527                assert_request_layout_and_hash(&fallback);
528                assert!(
529                    fallback.request_size(IGVM_ATTEST_REQUEST_CURRENT_VERSION)
530                        <= IGVM_ATTEST_MSG_REQ_REPORT_MAX_SIZE
531                );
532            }
533        }
534    }
535
536    fn assert_request_layout_and_hash(helper: &IgvmAttestRequestHelper) {
537        let hash = crypto::sha_256::sha_256(&helper.runtime_claims);
538        assert_eq!(&helper.get_runtime_claims_hash()[..32], hash.as_ref());
539        assert_eq!(&helper.get_runtime_claims_hash()[32..], &[0; 32]);
540        for version in [
541            IgvmAttestRequestVersion::VERSION_1,
542            IgvmAttestRequestVersion::VERSION_2,
543        ] {
544            let extension_size = if version == IgvmAttestRequestVersion::VERSION_2 {
545                size_of::<IgvmAttestRequestDataExt>()
546            } else {
547                0
548            };
549            let claims_offset = size_of::<IgvmAttestRequestBase>() + extension_size;
550            assert_eq!(
551                helper.request_size(version),
552                claims_offset + helper.runtime_claims.len()
553            );
554            // CCA report serialization is not implemented yet. Its claims,
555            // report-data hash, and size accounting can still be checked.
556            if matches!(helper.report_type, ReportType::Cca) {
557                continue;
558            }
559            let report = vec![0x5a; get_report_size(&helper.report_type)];
560            let wire = helper.create_request(version, &report).unwrap();
561            assert_eq!(wire.len(), helper.request_size(version));
562            assert_eq!(&wire[claims_offset..], &helper.runtime_claims);
563            let (base, _) = IgvmAttestRequestBase::read_from_prefix(&wire).unwrap();
564            assert_eq!(base.header.report_size as usize, wire.len());
565            assert_eq!(base.request_data.version, version);
566            assert_eq!(
567                base.request_data.report_type,
568                helper.report_type.to_external_type()
569            );
570            assert_eq!(&base.attestation_report[..report.len()], &report);
571        }
572    }
573
574    #[test]
575    fn test_ak_cert_metadata_size_boundary() {
576        let input = AkCertTestInput::new();
577        assert_eq!(IGVM_ATTEST_MSG_REQ_REPORT_MAX_SIZE, 4096);
578        assert_eq!(
579            IGVM_ATTEST_REQUEST_CURRENT_VERSION,
580            IgvmAttestRequestVersion::VERSION_2
581        );
582        for size in [4096, 4097] {
583            let metadata = input.metadata_for_request_size(size);
584            let claims = input.claims(Some(metadata.clone()));
585            // Independently serialize the unfiltered request to pin the exact
586            // boundary, including the V2 extension rather than just the JSON.
587            let raw = create_request(
588                IGVM_ATTEST_REQUEST_CURRENT_VERSION,
589                IgvmAttestRequestType::AK_CERT_REQUEST,
590                &claims,
591                &[],
592                &ReportType::Tvm,
593                IgvmAttestHashType::SHA_256,
594            )
595            .unwrap();
596            assert_eq!(raw.len(), size);
597            assert!(size_of::<IgvmAttestRequestBase>() + claims.len() <= 4096);
598            if size == 4096 {
599                for tee in AK_CERT_TEES {
600                    let helper = input.prepare(tee, Some(metadata.clone()));
601                    assert!(helper.has_ak_cert_metadata());
602                    assert_eq!(helper.runtime_claims, claims);
603                    assert_eq!(
604                        helper.request_size(IGVM_ATTEST_REQUEST_CURRENT_VERSION),
605                        4096
606                    );
607                    assert_request_layout_and_hash(&helper);
608                }
609            } else {
610                input.assert_fallback(metadata);
611            }
612        }
613    }
614
615    #[test]
616    fn test_ak_cert_metadata_large_hex_authority_fallback() {
617        let input = AkCertTestInput::new();
618        let mut metadata = input.metadata_for_request_size(4096);
619        metadata.subject.clear();
620        metadata.authority = hex::encode_upper([0xab; 2048]);
621        input.assert_fallback(metadata);
622    }
623
624    #[test]
625    fn test_ak_cert_metadata_escaped_common_names_fallback() {
626        let input = AkCertTestInput::new();
627        for escaped_name in ['"', '\\', '\n', '\0'] {
628            for issuer in [false, true] {
629                let mut metadata = input.metadata_for_request_size(4096);
630                let name = if issuer {
631                    &mut metadata.issuer
632                } else {
633                    &mut metadata.subject
634                };
635                // Replace one ASCII byte with one byte that expands in JSON:
636                // the unescaped metadata length still fits the limit exactly.
637                assert!(name.is_ascii() && !name.is_empty());
638                name.replace_range(..1, &escaped_name.to_string());
639                input.assert_fallback(metadata);
640            }
641        }
642    }
643
644    #[test]
645    fn test_metadata_free_guest_v1_and_key_release_unchanged() {
646        let mut input = AkCertTestInput::new();
647        // A guest NV report can exceed the GET limit and must not be truncated.
648        input.guest_input = vec![0xab; 4096];
649        for (tee, same_tee) in AK_CERT_TEES.into_iter().zip(AK_CERT_TEES) {
650            let guest = input.prepare(tee, None);
651            assert!(!guest.has_ak_cert_metadata());
652            assert_eq!(guest.runtime_claims, input.claims(None));
653            assert!(guest.request_size(IgvmAttestRequestVersion::VERSION_1) > 4096);
654            assert_request_layout_and_hash(&guest);
655
656            if let Some(tee) = same_tee {
657                let key_release = IgvmAttestRequestHelper::prepare_key_release_request(
658                    tee,
659                    &input.ak.public_exponent,
660                    &input.ak.modulus,
661                    12345,
662                    &input.config,
663                );
664                assert!(!key_release.has_ak_cert_metadata());
665                let claims = RuntimeClaims::key_release_request_runtime_claims(
666                    &input.ak.public_exponent,
667                    &input.ak.modulus,
668                    &attestation_vm_config_with_time(&input.config, 12345),
669                );
670                assert_eq!(key_release.runtime_claims, runtime_claims_to_bytes(&claims));
671                assert_request_layout_and_hash(&key_release);
672            }
673        }
674    }
675
676    #[test]
677    fn test_ak_cert_metadata_claims_are_measured() {
678        use openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RuntimeClaims;
679
680        let config = AttestationVmConfig {
681            current_time: None,
682            root_cert_thumbprint: String::new(),
683            console_enabled: false,
684            interactive_console_enabled: false,
685            ipmi_enabled: false,
686            secure_boot: false,
687            tpm_enabled: true,
688            tpm_version: AttestationTpmVersion::V185,
689            tpm_persisted: true,
690            hardware_sealing_policy: HardwareSealingPolicy::Signer,
691            filtered_vpci_devices_allowed: false,
692            vm_unique_id: String::new(),
693            vmgs_provisioner: None,
694        };
695        let certificate = test_igvm_agent_lib::ak_cert::certificate();
696        let mut metadata = ak_cert::parse_metadata(&certificate).unwrap();
697        metadata.subject = "AK \"subject\"\\\n".to_owned();
698        for tee in [
699            Some(TeeType::Snp),
700            Some(TeeType::Tdx),
701            Some(TeeType::Cca),
702            Some(TeeType::Vbs),
703            None,
704        ] {
705            let with_cert = IgvmAttestRequestHelper::prepare_ak_cert_request(
706                tee,
707                &[1],
708                &[2],
709                &[3],
710                &[4],
711                &config,
712                &[],
713                Some(metadata.clone()),
714            );
715            let claims: serde_json::Value =
716                serde_json::from_slice(&with_cert.runtime_claims).unwrap();
717            assert_eq!(
718                claims["akcert"],
719                serde_json::json!({
720                    "issuer": metadata.issuer,
721                    "subject": metadata.subject,
722                    "not_before": metadata.not_before,
723                    "not_after": metadata.not_after,
724                    "authority": metadata.authority,
725                })
726            );
727            let hash = crypto::sha_256::sha_256(&with_cert.runtime_claims);
728            assert_eq!(&with_cert.get_runtime_claims_hash()[..32], hash.as_ref());
729            assert_eq!(&with_cert.get_runtime_claims_hash()[32..], &[0; 32]);
730
731            let without_cert = IgvmAttestRequestHelper::prepare_ak_cert_request(
732                None,
733                &[1],
734                &[2],
735                &[3],
736                &[4],
737                &config,
738                &[],
739                None,
740            );
741            let claims: serde_json::Value =
742                serde_json::from_slice(&without_cert.runtime_claims).unwrap();
743            assert!(claims.get("akcert").is_none());
744            let decoded: RuntimeClaims =
745                serde_json::from_slice(&without_cert.runtime_claims).unwrap();
746            assert!(decoded.akcert.is_none());
747            assert_ne!(
748                with_cert.get_runtime_claims_hash(),
749                without_cert.get_runtime_claims_hash()
750            );
751
752            let legacy =
753                RuntimeClaims::ak_cert_runtime_claims(&[1], &[2], &[3], &[4], &config, &[]);
754            assert_eq!(
755                without_cert.runtime_claims,
756                serde_json::to_vec(&legacy).unwrap()
757            );
758        }
759        let key_release = RuntimeClaims::key_release_request_runtime_claims(&[1], &[2], &config);
760        let claims = serde_json::to_value(key_release).unwrap();
761        assert!(claims.get("akcert").is_none());
762    }
763
764    #[test]
765    fn test_create_request() {
766        use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_REQUEST_CURRENT_VERSION;
767
768        let result = create_request(
769            IGVM_ATTEST_REQUEST_CURRENT_VERSION,
770            IgvmAttestRequestType::AK_CERT_REQUEST,
771            &[],
772            &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE],
773            &ReportType::Snp,
774            IgvmAttestHashType::SHA_256,
775        );
776        assert!(result.is_ok());
777
778        let result = create_request(
779            IGVM_ATTEST_REQUEST_CURRENT_VERSION,
780            IgvmAttestRequestType::AK_CERT_REQUEST,
781            &[],
782            &[0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE + 1],
783            &ReportType::Snp,
784            IgvmAttestHashType::SHA_256,
785        );
786        assert!(result.is_err());
787    }
788
789    #[test]
790    fn test_create_request_version1_no_extension() {
791        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
792        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
793
794        let runtime_claims = vec![1u8, 2, 3];
795        let attestation_report =
796            vec![0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE];
797
798        let buffer = create_request(
799            IgvmAttestRequestVersion::VERSION_1,
800            IgvmAttestRequestType::AK_CERT_REQUEST,
801            &runtime_claims,
802            &attestation_report,
803            &ReportType::Snp,
804            IgvmAttestHashType::SHA_256,
805        )
806        .expect("request generation");
807
808        let (request, _) =
809            IgvmAttestRequestBase::read_from_prefix(&buffer).expect("parse IgvmAttestRequest");
810        assert_eq!(
811            request.request_data.version,
812            IgvmAttestRequestVersion::VERSION_1
813        );
814
815        let expected_size = (size_of::<
816            openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData,
817        >() + runtime_claims.len()) as u32;
818        assert_eq!(request.request_data.data_size, expected_size);
819
820        let header_size = size_of::<IgvmAttestRequestBase>();
821        assert_eq!(
822            buffer.len(),
823            header_size + runtime_claims.len(),
824            "no extension appended for version 1"
825        );
826        assert_eq!(&buffer[header_size..], runtime_claims.as_slice());
827    }
828
829    #[test]
830    fn test_create_request_version2_with_extension() {
831        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
832        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
833        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
834
835        let runtime_claims = vec![4u8, 5, 6, 7];
836        let attestation_report =
837            vec![0u8; openhcl_attestation_protocol::igvm_attest::get::SNP_VM_REPORT_SIZE];
838
839        let buffer = create_request(
840            IgvmAttestRequestVersion::VERSION_2,
841            IgvmAttestRequestType::AK_CERT_REQUEST,
842            &runtime_claims,
843            &attestation_report,
844            &ReportType::Snp,
845            IgvmAttestHashType::SHA_256,
846        )
847        .expect("request generation");
848
849        let (request, _) =
850            IgvmAttestRequestBase::read_from_prefix(&buffer).expect("parse IgvmAttestRequest");
851        assert_eq!(
852            request.request_data.version,
853            IgvmAttestRequestVersion::VERSION_2
854        );
855
856        let expected_extension_size = size_of::<IgvmAttestRequestDataExt>();
857        let expected_size = (size_of::<
858            openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestData,
859        >() + expected_extension_size
860            + runtime_claims.len()) as u32;
861        assert_eq!(request.request_data.data_size, expected_size);
862
863        let header_size = size_of::<IgvmAttestRequestBase>();
864        let ext_offset = header_size;
865
866        let (ext, _) = IgvmAttestRequestDataExt::read_from_prefix(&buffer[ext_offset..])
867            .expect("parse IgvmAttestRequestDataExt");
868        assert!(ext.capability_bitmap.error_code());
869        assert!(ext.capability_bitmap.retry());
870        assert!(ext.capability_bitmap.skip_hw_unsealing());
871        // CoRIM endorsement is requested for TDX only; an SNP request must not
872        // set the bit.
873        assert!(!ext.capability_bitmap.corim_endorsement());
874
875        assert_eq!(
876            buffer.len(),
877            header_size + expected_extension_size + runtime_claims.len()
878        );
879        assert_eq!(
880            &buffer[header_size + expected_extension_size..],
881            runtime_claims.as_slice()
882        );
883    }
884
885    #[test]
886    fn test_create_request_version2_tdx_requests_corim() {
887        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestBase;
888        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestDataExt;
889        use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestRequestVersion;
890
891        let runtime_claims = vec![4u8, 5, 6, 7];
892        let attestation_report =
893            vec![0u8; openhcl_attestation_protocol::igvm_attest::get::TDX_VM_REPORT_SIZE];
894
895        let buffer = create_request(
896            IgvmAttestRequestVersion::VERSION_2,
897            IgvmAttestRequestType::KEY_RELEASE_REQUEST,
898            &runtime_claims,
899            &attestation_report,
900            &ReportType::Tdx,
901            IgvmAttestHashType::SHA_256,
902        )
903        .expect("request generation");
904
905        let header_size = size_of::<IgvmAttestRequestBase>();
906        let (ext, _) = IgvmAttestRequestDataExt::read_from_prefix(&buffer[header_size..])
907            .expect("parse IgvmAttestRequestDataExt");
908        // TDX requests must signal the IGVM Agent to fetch the CoRIM endorsement.
909        assert!(ext.capability_bitmap.corim_endorsement());
910    }
911
912    #[test]
913    fn test_transfer_key_jwk() {
914        const EXPECTED_JWK: &str = r#"[{"kid":"HCLTransferKey","key_ops":["encrypt"],"kty":"RSA","e":"RVhQT05FTlQ","n":"TU9EVUxVUw"}]"#;
915
916        let rsa_jwk = openhcl_attestation_protocol::igvm_attest::get::runtime_claims::RsaJwk::get_transfer_key_jwks(
917            b"EXPONENT",
918            b"MODULUS",
919        );
920
921        let result = serde_json::to_string(&rsa_jwk);
922        assert!(result.is_ok());
923
924        let transfer_key_jwk = result.unwrap();
925        assert_eq!(transfer_key_jwk, EXPECTED_JWK);
926    }
927
928    #[test]
929    fn test_vm_configuration_no_time() {
930        const EXPECTED_JWK: &str = r#"{"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"ipmi-enabled":true,"secure-boot":false,"tpm-enabled":false,"tpm-version":"1.38","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"signer"}"#;
931
932        let attestation_vm_config = AttestationVmConfig {
933            current_time: None,
934            root_cert_thumbprint: String::new(),
935            console_enabled: false,
936            interactive_console_enabled: false,
937            ipmi_enabled: true,
938            secure_boot: false,
939            tpm_enabled: false,
940            tpm_version: AttestationTpmVersion::V138,
941            tpm_persisted: false,
942            hardware_sealing_policy: HardwareSealingPolicy::Signer,
943            filtered_vpci_devices_allowed: true,
944            vm_unique_id: String::new(),
945            vmgs_provisioner: None,
946        };
947        let result = serde_json::to_string(&attestation_vm_config);
948        assert!(result.is_ok());
949
950        let vm_config = result.unwrap();
951        assert_eq!(vm_config, EXPECTED_JWK);
952    }
953
954    #[test]
955    fn test_vm_configuration_with_time() {
956        const EXPECTED_JWK: &str = r#"{"current-time":1691103220,"root-cert-thumbprint":"","console-enabled":false,"interactive-console-enabled":false,"ipmi-enabled":false,"secure-boot":false,"tpm-enabled":false,"tpm-version":"185","tpm-persisted":false,"filtered-vpci-devices-allowed":true,"vmUniqueId":"","hardware-sealing-policy":"hash"}"#;
957
958        let attestation_vm_config = AttestationVmConfig {
959            current_time: None,
960            root_cert_thumbprint: String::new(),
961            console_enabled: false,
962            interactive_console_enabled: false,
963            ipmi_enabled: false,
964            secure_boot: false,
965            tpm_enabled: false,
966            tpm_version: AttestationTpmVersion::V185,
967            tpm_persisted: false,
968            hardware_sealing_policy: HardwareSealingPolicy::Hash,
969            filtered_vpci_devices_allowed: true,
970            vm_unique_id: String::new(),
971            vmgs_provisioner: None,
972        };
973        let attestation_vm_config =
974            attestation_vm_config_with_time(&attestation_vm_config, 1691103220);
975        let result = serde_json::to_string(&attestation_vm_config);
976        assert!(result.is_ok());
977
978        let vm_config = result.unwrap();
979        assert_eq!(vm_config, EXPECTED_JWK);
980    }
981
982    #[test]
983    fn test_empty_response() {
984        let result = parse_response_header(&[]);
985        assert!(result.is_err());
986        assert_eq!(
987            result.unwrap_err().to_string(),
988            Error::ResponseSizeTooSmall { response_size: 0 }.to_string()
989        );
990    }
991
992    #[test]
993    fn test_invalid_response_size_smaller_than_header_size() {
994        const INVALID_RESPONSE: [u8; 4] = [0x04, 0x00, 0x00, 0x00];
995        let result = parse_response_header(&INVALID_RESPONSE);
996        assert!(result.is_err());
997        assert_eq!(
998            result.unwrap_err().to_string(),
999            Error::ResponseSizeTooSmall { response_size: 4 }.to_string()
1000        );
1001    }
1002
1003    #[test]
1004    fn test_valid_v1_response_size_match() {
1005        const VALID_RESPONSE: [u8; 42] = [
1006            0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
1007            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
1008            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1009        ];
1010
1011        let result = parse_response_header(&VALID_RESPONSE);
1012        assert!(result.is_ok());
1013        let header = result.unwrap();
1014        assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
1015        assert_eq!(IgvmAttestResponseVersion::VERSION_1, header.version);
1016    }
1017
1018    #[test]
1019    fn test_valid_v2_response_size_match() {
1020        const VALID_RESPONSE: [u8; 42] = [
1021            0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1022            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1023            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1024        ];
1025
1026        let result = parse_response_header(&VALID_RESPONSE);
1027        assert!(result.is_ok());
1028        let header = result.unwrap();
1029        assert_eq!(VALID_RESPONSE.len(), header.data_size as usize);
1030        assert_eq!(IgvmAttestResponseVersion::VERSION_2, header.version);
1031    }
1032
1033    #[test]
1034    fn test_valid_v1_response_size_smaller_than_specified() {
1035        const VALID_RESPONSE: [u8; 42] = [
1036            0x29, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
1037            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
1038            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1039        ];
1040
1041        let header = parse_response_header(&VALID_RESPONSE);
1042        assert!(header.is_ok());
1043        assert_eq!(0x29, header.unwrap().data_size as usize);
1044    }
1045
1046    #[test]
1047    fn test_valid_v2_response_size_smaller_than_specified() {
1048        const VALID_RESPONSE: [u8; 42] = [
1049            0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1050            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1051            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1052        ];
1053
1054        let header = parse_response_header(&VALID_RESPONSE);
1055        assert!(header.is_ok());
1056        assert_eq!(0x29, header.unwrap().data_size as usize);
1057    }
1058
1059    #[test]
1060    fn test_invalid_v1_response_size() {
1061        const INVALID_RESPONSE: [u8; 42] = [
1062            0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
1063            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
1064            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1065        ];
1066
1067        let result = parse_response_header(&INVALID_RESPONSE);
1068        assert!(result.is_err());
1069        assert_eq!(
1070            result.unwrap_err().to_string(),
1071            Error::ResponseSizeMismatch {
1072                size: INVALID_RESPONSE.len(),
1073                specified_size: 0x2b
1074            }
1075            .to_string()
1076        );
1077    }
1078
1079    #[test]
1080    fn test_invalid_v2_response_size() {
1081        const INVALID_RESPONSE: [u8; 42] = [
1082            0x2b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
1083            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
1084            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1085        ];
1086
1087        let result = parse_response_header(&INVALID_RESPONSE);
1088        assert!(result.is_err());
1089        assert_eq!(
1090            result.unwrap_err().to_string(),
1091            Error::ResponseSizeMismatch {
1092                size: INVALID_RESPONSE.len(),
1093                specified_size: 0x2b
1094            }
1095            .to_string()
1096        );
1097    }
1098
1099    #[test]
1100    fn test_invalid_header_version() {
1101        const INVALID_RESPONSE: [u8; 42] = [
1102            0x2a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
1103            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
1104            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1105        ];
1106
1107        let result = parse_response_header(&INVALID_RESPONSE);
1108        assert!(result.is_err());
1109        assert_eq!(
1110            result.unwrap_err().to_string(),
1111            Error::InvalidResponseHeaderVersion {
1112                version: IgvmAttestResponseVersion(3),
1113                latest_version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION
1114            }
1115            .to_string()
1116        );
1117    }
1118
1119    #[test]
1120    fn test_invalid_v2_response_size_smaller_than_specified_header_size() {
1121        const INVALID_RESPONSE: [u8; 28] = [
1122            0x1c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1123            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1124        ];
1125
1126        let result = parse_response_header(&INVALID_RESPONSE);
1127        assert!(result.is_err());
1128        assert_eq!(
1129            result.unwrap_err().to_string(),
1130            Error::ResponseHeaderInvalidFormat {
1131                response_size: 0x1c
1132            }
1133            .to_string()
1134        );
1135    }
1136
1137    #[test]
1138    fn test_failed_response_with_retryable_error() {
1139        // error_code: 1103 (0x44f), http_status_code: 403 (0x193), retryable
1140        const INVALID_RESPONSE: [u8; 42] = [
1141            0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x93, 0x01,
1142            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1143            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1144        ];
1145
1146        let result = parse_response_header(&INVALID_RESPONSE);
1147        assert!(result.is_err());
1148        assert_eq!(
1149            result.unwrap_err().to_string(),
1150            Error::Attestation {
1151                igvm_error_code: 1103,
1152                http_status_code: 403,
1153                retry_signal: true,
1154                skip_hw_unsealing_signal: false
1155            }
1156            .to_string()
1157        );
1158    }
1159
1160    #[test]
1161    fn test_failed_response_with_non_retryable_error() {
1162        // error_code: 1103 (0x44f), http_status_code: 503 (0x1f7), not retryable
1163        const INVALID_RESPONSE: [u8; 42] = [
1164            0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0xf7, 0x01,
1165            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1166            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1167        ];
1168
1169        let result = parse_response_header(&INVALID_RESPONSE);
1170        assert!(result.is_err());
1171        assert_eq!(
1172            result.unwrap_err().to_string(),
1173            Error::Attestation {
1174                igvm_error_code: 1103,
1175                http_status_code: 503,
1176                retry_signal: false,
1177                skip_hw_unsealing_signal: false
1178            }
1179            .to_string()
1180        );
1181    }
1182
1183    #[test]
1184    fn test_failed_response_with_skip_hw_unsealing_signal() {
1185        // error_code: 1103 (0x44f), http_status_code: 400 (0x190),
1186        // igvm_signal: retry=true, skip_hw_unsealing=true (0x03 = bits 0 and 1 set)
1187        const INVALID_RESPONSE: [u8; 42] = [
1188            0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x90, 0x01,
1189            0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1190            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1191        ];
1192
1193        let result = parse_response_header(&INVALID_RESPONSE);
1194        assert!(result.is_err());
1195        assert_eq!(
1196            result.unwrap_err().to_string(),
1197            Error::Attestation {
1198                igvm_error_code: 1103,
1199                http_status_code: 400,
1200                retry_signal: true,
1201                skip_hw_unsealing_signal: true
1202            }
1203            .to_string()
1204        );
1205    }
1206
1207    #[test]
1208    fn test_failed_response_with_skip_hw_unsealing_only() {
1209        // error_code: 1103 (0x44f), http_status_code: 400 (0x190),
1210        // igvm_signal: retry=false, skip_hw_unsealing=true (0x02 = bit 1 set)
1211        const INVALID_RESPONSE: [u8; 42] = [
1212            0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4f, 0x04, 0x00, 0x00, 0x90, 0x01,
1213            0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1214            0x00, 0x00, 0x00, 0x00, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
1215        ];
1216
1217        let result = parse_response_header(&INVALID_RESPONSE);
1218        assert!(result.is_err());
1219        assert_eq!(
1220            result.unwrap_err().to_string(),
1221            Error::Attestation {
1222                igvm_error_code: 1103,
1223                http_status_code: 400,
1224                retry_signal: false,
1225                skip_hw_unsealing_signal: true
1226            }
1227            .to_string()
1228        );
1229    }
1230}