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.
6
7use openhcl_attestation_protocol::igvm_attest::get::AK_CERT_RESPONSE_HEADER_VERSION;
8use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestAkCertResponseHeader;
9use thiserror::Error;
10use zerocopy::FromBytes;
11
12/// AkCertError is returned by parse_ak_cert_response() in emuplat/tpm.rs
13#[derive(Debug, Error)]
14pub enum AkCertError {
15    #[error(
16        "AK cert response is too small to parse. Found {size} bytes but expected at least {minimum_size}"
17    )]
18    SizeTooSmall { size: usize, minimum_size: usize },
19    #[error(
20        "AK cert response size {specified_size} specified in the header is larger then the actual size {size}"
21    )]
22    SizeMismatch { size: usize, specified_size: usize },
23    #[error(
24        "AK cert response header version {version} does match the expected version {expected_version}"
25    )]
26    HeaderVersionMismatch { version: u32, expected_version: u32 },
27}
28
29/// Parse a `AK_CERT_REQUEST` response and return the payload (i.e., the AK cert).
30///
31/// Returns `Ok(Vec<u8>)` on successfully validating the response, otherwise returns an error.
32pub fn parse_response(response: &[u8]) -> Result<Vec<u8>, AkCertError> {
33    const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
34
35    let header = IgvmAttestAkCertResponseHeader::read_from_prefix(response)
36        .map_err(|_| AkCertError::SizeTooSmall {
37            size: response.len(),
38            minimum_size: HEADER_SIZE,
39        })? // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
40        .0;
41
42    let size = header.data_size as usize;
43    if size > response.len() {
44        Err(AkCertError::SizeMismatch {
45            size: response.len(),
46            specified_size: size,
47        })?
48    }
49
50    if header.version != AK_CERT_RESPONSE_HEADER_VERSION {
51        Err(AkCertError::HeaderVersionMismatch {
52            version: header.version,
53            expected_version: AK_CERT_RESPONSE_HEADER_VERSION,
54        })?
55    }
56
57    Ok(response[HEADER_SIZE..size].to_vec())
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn test_undersized_response() {
66        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
67        let properly_sized_response: [u8; HEADER_SIZE] = [1; HEADER_SIZE];
68        let undersized_response = &properly_sized_response[..HEADER_SIZE - 1];
69
70        // Empty response counts as an undersized response
71        let result = parse_response(&[]);
72        assert!(result.is_err());
73        assert_eq!(
74            result.unwrap_err().to_string(),
75            "AK cert response is too small to parse. Found 0 bytes but expected at least 8"
76        );
77
78        // Response has to be at least `HEADER_SIZE` bytes long, so `HEADER_SIZE - 1` bytes is too small.
79        let undersized_parse_ = parse_response(undersized_response);
80        assert!(undersized_parse_.is_err());
81        assert_eq!(
82            undersized_parse_.unwrap_err().to_string(),
83            format!(
84                "AK cert response is too small to parse. Found {} bytes but expected at least {}",
85                HEADER_SIZE - 1,
86                HEADER_SIZE
87            )
88        );
89
90        // When we finally have `HEADER_SIZE` bytes, we no longer see the failure as `AkCertError::SizeTooSmall`,
91        // but we still see a different error since the response is not valid.
92        let properly_sized_parse = parse_response(&properly_sized_response);
93        assert!(
94            !properly_sized_parse
95                .unwrap_err()
96                .to_string()
97                .starts_with("AK cert response is too small to parse"),
98        );
99    }
100
101    #[test]
102    fn test_valid_response_size_match() {
103        const VALID_RESPONSE: [u8; 56] = [
104            0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
105            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
106            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
107            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
108        ];
109
110        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
111
112        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
113        assert!(result.is_ok());
114        let header = result.unwrap().0;
115
116        let result = parse_response(&VALID_RESPONSE);
117        assert!(result.is_ok());
118
119        let payload = result.unwrap();
120        assert_eq!(payload.len(), header.data_size as usize - HEADER_SIZE);
121        assert_eq!(
122            payload,
123            &VALID_RESPONSE[HEADER_SIZE..header.data_size as usize]
124        );
125    }
126
127    #[test]
128    fn test_valid_response_size_smaller_than_specified() {
129        const VALID_RESPONSE: [u8; 56] = [
130            0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
131            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
132            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
133            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
134        ];
135
136        const HEADER_SIZE: usize = size_of::<IgvmAttestAkCertResponseHeader>();
137
138        let result = IgvmAttestAkCertResponseHeader::read_from_prefix(&VALID_RESPONSE);
139        assert!(result.is_ok());
140        let header = result.unwrap().0;
141
142        let result = parse_response(&VALID_RESPONSE);
143        assert!(result.is_ok());
144
145        let payload = result.unwrap();
146        assert_eq!(payload.len(), header.data_size as usize - HEADER_SIZE);
147        assert_eq!(
148            payload,
149            &VALID_RESPONSE[HEADER_SIZE..header.data_size as usize]
150        );
151    }
152
153    #[test]
154    fn test_invalid_header_version() {
155        const INVALID_RESPONSE: [u8; 56] = [
156            0x38, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
157            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
158            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
159            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
160        ];
161
162        let result = parse_response(&INVALID_RESPONSE);
163        assert!(result.is_err());
164    }
165
166    #[test]
167    fn test_invalid_response_size() {
168        const INVALID_RESPONSE: [u8; 56] = [
169            0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x82, 0x03, 0xeb, 0x30, 0x82,
170            0x02, 0xd3, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x3b, 0xa3, 0x33, 0x97, 0xef,
171            0x2f, 0x9e, 0xef, 0xbd, 0x35, 0x5e, 0xda, 0xdd, 0x27, 0x38, 0x42, 0x30, 0x0d, 0x06,
172            0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25,
173        ];
174
175        let result = parse_response(&INVALID_RESPONSE);
176        assert!(result.is_err());
177    }
178}