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