underhill_attestation/igvm_attest/
ak_cert.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The module for `AK_CERT_REQUEST` request type that supports parsing the
5//! response.
6use crate::igvm_attest::Error as CommonError;
7use crate::igvm_attest::parse_response_header;
8
9use thiserror::Error;
10
11/// AkCertError is returned by parse_ak_cert_response() in emuplat/tpm.rs
12#[derive(Debug, Error)]
13pub enum AkCertError {
14    #[error(
15        "AK cert response is too small to parse. Found {size} bytes but expected at least {minimum_size}"
16    )]
17    SizeTooSmall { size: usize, minimum_size: usize },
18    #[error(
19        "AK cert response size {specified_size} specified in the header is larger then the actual size {size}"
20    )]
21    SizeMismatch { size: usize, specified_size: usize },
22    #[error(
23        "AK cert response header version {version} does match the expected version {expected_version}"
24    )]
25    HeaderVersionMismatch { version: u32, expected_version: u32 },
26    #[error("error in parsing response header")]
27    ParseHeader(#[source] CommonError),
28}
29
30/// Parse a `AK_CERT_REQUEST` response and return the payload (i.e., the AK cert).
31///
32/// Returns `Ok(Vec<u8>)` on successfully validating the response, otherwise returns an error.
33pub fn parse_response(response: &[u8]) -> Result<Vec<u8>, AkCertError> {
34    use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_RESPONSE_VERSION_1;
35    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
36    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
37
38    let header = parse_response_header(response).map_err(AkCertError::ParseHeader)?;
39
40    // Extract payload as per header version
41    // parse_response_header above has verified the header version already
42    let header_size = match header.version {
43        IGVM_ATTEST_RESPONSE_VERSION_1 => size_of::<IgvmAttestCommonResponseHeader>(),
44        _ => size_of::<IgvmAttestAkCertResponseHeader>(),
45    };
46
47    Ok(response[header_size..header.data_size as usize].to_vec())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
54    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
55    use zerocopy::FromBytes;
56
57    #[test]
58    fn test_undersized_response() {
59        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
60        let properly_sized_response: [u8; HEADER_SIZE] = [1; HEADER_SIZE];
61        let undersized_response = &properly_sized_response[..HEADER_SIZE - 1];
62
63        // Empty response counts as an undersized response
64        let result = parse_response(&[]);
65        assert!(result.is_err());
66        assert_eq!(
67            result.unwrap_err().to_string(),
68            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall { response_size: 0 })
69                .to_string()
70        );
71
72        // Response has to be at least `HEADER_SIZE` bytes long, so `HEADER_SIZE - 1` bytes is too small.
73        let undersized_parse_ = parse_response(undersized_response);
74        assert!(undersized_parse_.is_err());
75        assert_eq!(
76            undersized_parse_.unwrap_err().to_string(),
77            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall {
78                response_size: HEADER_SIZE - 1
79            })
80            .to_string()
81        );
82
83        // When we finally have `HEADER_SIZE` bytes, we no longer see the failure as `AkCertError::SizeTooSmall`,
84        // but we still see a different error since the response is not valid.
85        let properly_sized_parse = parse_response(&properly_sized_response);
86        assert!(
87            !properly_sized_parse
88                .unwrap_err()
89                .to_string()
90                .starts_with("AK cert response is too small to parse"),
91        );
92    }
93
94    #[test]
95    fn test_valid_response_size_match() {
96        const VALID_RESPONSE: [u8; 56] = [
97            0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
98            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
99            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
100            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
101        ];
102
103        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
104        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
105        assert!(result.is_ok());
106
107        let result = parse_response(&VALID_RESPONSE);
108        assert!(result.is_ok());
109
110        let payload = result.unwrap();
111        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
112        assert_eq!(payload.len(), data_size - HEADER_SIZE);
113        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
114    }
115
116    #[test]
117    fn test_valid_response_size_smaller_than_specified() {
118        const VALID_RESPONSE: [u8; 56] = [
119            0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
120            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
121            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
122            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
123        ];
124
125        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
126
127        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
128        assert!(result.is_ok());
129
130        let result = parse_response(&VALID_RESPONSE);
131        assert!(result.is_ok());
132
133        let payload = result.unwrap();
134        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
135        assert_eq!(payload.len(), data_size - HEADER_SIZE);
136        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
137    }
138}