Skip to main content

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    #[error("invalid response header version: {0}")]
29    InvalidResponseVersion(u32),
30}
31
32/// Parse a `AK_CERT_REQUEST` response and return the payload (i.e., the AK cert).
33///
34/// Returns `Ok(Vec<u8>)` on successfully validating the response, otherwise returns an error.
35pub fn parse_response(response: &[u8]) -> Result<Vec<u8>, AkCertError> {
36    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
37    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
38    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestResponseVersion;
39
40    let header = parse_response_header(response).map_err(AkCertError::ParseHeader)?;
41
42    // Extract payload as per header version
43    let header_size = match header.version {
44        IgvmAttestResponseVersion::VERSION_1 => size_of::<IgvmAttestCommonResponseHeader>(),
45        IgvmAttestResponseVersion::VERSION_2 => size_of::<IgvmAttestAkCertResponseHeader>(),
46        invalid_version => return Err(AkCertError::InvalidResponseVersion(invalid_version.0)),
47    };
48    let data_size = header.data_size as usize;
49
50    if data_size < header_size {
51        return Err(AkCertError::SizeTooSmall {
52            size: data_size,
53            minimum_size: header_size,
54        });
55    }
56
57    Ok(response[header_size..data_size].to_vec())
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
64    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
65    use zerocopy::FromBytes;
66
67    #[test]
68    fn test_undersized_response() {
69        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
70        let properly_sized_response: [u8; HEADER_SIZE] = [1; HEADER_SIZE];
71        let undersized_response = &properly_sized_response[..HEADER_SIZE - 1];
72
73        // Empty response counts as an undersized response
74        let result = parse_response(&[]);
75        assert!(result.is_err());
76        assert_eq!(
77            result.unwrap_err().to_string(),
78            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall { response_size: 0 })
79                .to_string()
80        );
81
82        // Response has to be at least `HEADER_SIZE` bytes long, so `HEADER_SIZE - 1` bytes is too small.
83        let undersized_parse_ = parse_response(undersized_response);
84        assert!(undersized_parse_.is_err());
85        assert_eq!(
86            undersized_parse_.unwrap_err().to_string(),
87            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall {
88                response_size: HEADER_SIZE - 1
89            })
90            .to_string()
91        );
92
93        // When we finally have `HEADER_SIZE` bytes, we no longer see the failure as `AkCertError::SizeTooSmall`,
94        // but we still see a different error since the response is not valid.
95        let properly_sized_parse = parse_response(&properly_sized_response);
96        assert!(
97            !properly_sized_parse
98                .unwrap_err()
99                .to_string()
100                .starts_with("AK cert response is too small to parse"),
101        );
102    }
103
104    #[test]
105    fn test_valid_response_size_match() {
106        const VALID_RESPONSE: [u8; 56] = [
107            0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
108            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
109            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
110            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
111        ];
112
113        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
114        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
115        assert!(result.is_ok());
116
117        let result = parse_response(&VALID_RESPONSE);
118        assert!(result.is_ok());
119
120        let payload = result.unwrap();
121        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
122        assert_eq!(payload.len(), data_size - HEADER_SIZE);
123        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
124    }
125
126    #[test]
127    fn test_parse_response_small_size() {
128        let mut response = [0u8; 8];
129        // data_size = 4 (little-endian u32)
130        response[0..4].copy_from_slice(&4u32.to_le_bytes());
131        // version = VERSION_1 = 1 (little-endian u32)
132        response[4..8].copy_from_slice(&1u32.to_le_bytes());
133
134        assert!(parse_response(&response).is_err());
135    }
136
137    #[test]
138    fn test_valid_response_size_smaller_than_specified() {
139        const VALID_RESPONSE: [u8; 56] = [
140            0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
141            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
142            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
143            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
144        ];
145
146        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
147
148        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
149        assert!(result.is_ok());
150
151        let result = parse_response(&VALID_RESPONSE);
152        assert!(result.is_ok());
153
154        let payload = result.unwrap();
155        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
156        assert_eq!(payload.len(), data_size - HEADER_SIZE);
157        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
158    }
159}