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
49    Ok(response[header_size..header.data_size as usize].to_vec())
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
56    use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
57    use zerocopy::FromBytes;
58
59    #[test]
60    fn test_undersized_response() {
61        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
62        let properly_sized_response: [u8; HEADER_SIZE] = [1; HEADER_SIZE];
63        let undersized_response = &properly_sized_response[..HEADER_SIZE - 1];
64
65        // Empty response counts as an undersized response
66        let result = parse_response(&[]);
67        assert!(result.is_err());
68        assert_eq!(
69            result.unwrap_err().to_string(),
70            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall { response_size: 0 })
71                .to_string()
72        );
73
74        // Response has to be at least `HEADER_SIZE` bytes long, so `HEADER_SIZE - 1` bytes is too small.
75        let undersized_parse_ = parse_response(undersized_response);
76        assert!(undersized_parse_.is_err());
77        assert_eq!(
78            undersized_parse_.unwrap_err().to_string(),
79            AkCertError::ParseHeader(CommonError::ResponseSizeTooSmall {
80                response_size: HEADER_SIZE - 1
81            })
82            .to_string()
83        );
84
85        // When we finally have `HEADER_SIZE` bytes, we no longer see the failure as `AkCertError::SizeTooSmall`,
86        // but we still see a different error since the response is not valid.
87        let properly_sized_parse = parse_response(&properly_sized_response);
88        assert!(
89            !properly_sized_parse
90                .unwrap_err()
91                .to_string()
92                .starts_with("AK cert response is too small to parse"),
93        );
94    }
95
96    #[test]
97    fn test_valid_response_size_match() {
98        const VALID_RESPONSE: [u8; 56] = [
99            0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
100            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
101            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
102            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
103        ];
104
105        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
106        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
107        assert!(result.is_ok());
108
109        let result = parse_response(&VALID_RESPONSE);
110        assert!(result.is_ok());
111
112        let payload = result.unwrap();
113        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
114        assert_eq!(payload.len(), data_size - HEADER_SIZE);
115        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
116    }
117
118    #[test]
119    fn test_valid_response_size_smaller_than_specified() {
120        const VALID_RESPONSE: [u8; 56] = [
121            0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
122            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
123            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
124            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
125        ];
126
127        const HEADER_SIZE: usize = size_of::<IgvmAttestCommonResponseHeader>();
128
129        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
130        assert!(result.is_ok());
131
132        let result = parse_response(&VALID_RESPONSE);
133        assert!(result.is_ok());
134
135        let payload = result.unwrap();
136        let data_size = parse_response_header(&VALID_RESPONSE).unwrap().data_size as usize;
137        assert_eq!(payload.len(), data_size - HEADER_SIZE);
138        assert_eq!(payload, &VALID_RESPONSE[HEADER_SIZE..data_size]);
139    }
140}