1use crate::igvm_attest::Error as CommonError;
8use crate::igvm_attest::parse_response_header;
9use openhcl_attestation_protocol::igvm_attest::cps;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
13pub(crate) enum WrappedKeyError {
14 #[error("failed to deserialize the response payload into JSON: {json_data}")]
15 WrappedKeyResponsePayloadToJson {
16 #[source]
17 json_err: serde_json::Error,
18 json_data: String,
19 },
20 #[error("the response payload size is too small to parse")]
21 PayloadSizeTooSmall,
22 #[error("error in response header)")]
23 ParseHeader(#[source] CommonError),
24 #[error("invalid response header version: {0}")]
25 InvalidResponseVersion(u32),
26}
27
28pub struct IgvmWrappedKeyParsedResponse {
30 pub wrapped_key: Vec<u8>,
32 pub key_reference: Vec<u8>,
34}
35
36pub fn parse_response(response: &[u8]) -> Result<IgvmWrappedKeyParsedResponse, WrappedKeyError> {
41 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestCommonResponseHeader;
42 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestResponseVersion;
43 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestWrappedKeyResponseHeader;
44
45 const CIPHER_TEXT_KEY: &str = r#"{"ciphertext":""}"#;
47 const MINIMUM_WRAPPED_KEY_SIZE: usize = 256;
48 const MINIMUM_WRAPPED_KEY_BASE64_URL_SIZE: usize = MINIMUM_WRAPPED_KEY_SIZE / 3 * 4;
49 const MINIMUM_PAYLOAD_SIZE: usize = CIPHER_TEXT_KEY.len() + MINIMUM_WRAPPED_KEY_BASE64_URL_SIZE;
50
51 let header = parse_response_header(response).map_err(WrappedKeyError::ParseHeader)?;
52
53 let header_size = match header.version {
55 IgvmAttestResponseVersion::VERSION_1 => size_of::<IgvmAttestCommonResponseHeader>(),
56 IgvmAttestResponseVersion::VERSION_2 => size_of::<IgvmAttestWrappedKeyResponseHeader>(),
57 invalid_version => return Err(WrappedKeyError::InvalidResponseVersion(invalid_version.0)),
58 };
59 let payload = &response[header_size..header.data_size as usize];
60
61 if payload.len() < MINIMUM_PAYLOAD_SIZE {
62 Err(WrappedKeyError::PayloadSizeTooSmall)?
63 }
64 let payload = String::from_utf8_lossy(payload);
65 let payload: cps::VmmdBlob = serde_json::from_str(&payload).map_err(|json_err| {
66 WrappedKeyError::WrappedKeyResponsePayloadToJson {
67 json_err,
68 json_data: payload.to_string(),
69 }
70 })?;
71 let wrapped_key = payload
72 .disk_encryption_settings
73 .encryption_info
74 .aes_info
75 .ciphertext;
76
77 let key_reference = payload
78 .disk_encryption_settings
79 .encryption_info
80 .key_reference
81 .to_string()
82 .as_bytes()
83 .to_vec();
84
85 Ok(IgvmWrappedKeyParsedResponse {
86 wrapped_key,
87 key_reference,
88 })
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use openhcl_attestation_protocol::igvm_attest::get::IgvmErrorInfo;
95 use zerocopy::IntoBytes;
96
97 const KEY_REFERENCE: &str = r#"{
98 "key_info": {
99 "host": "name"
100 },
101 "attestation_info": {
102 "host": "attestation_name"
103 }
104}"#;
105
106 #[test]
107 fn test_response() {
108 const JSON_DATA: &str = r#"
109{
110 "version": "1.0",
111 "DiskEncryptionSettings": {
112 "encryption_info": {
113 "aes_info": {
114 "ciphertext": "Q0lQSEVSVEVYVA==",
115 "algorithm": "AES_256_WRAP_PAD",
116 "creation_time": "2023-11-03T22:58:59.7967119Z"
117 },
118 "key_reference": {
119 "key_info": {
120 "auth_method": "msi",
121 "host": "HOST",
122 "key_name": "cvmps-pmk-key",
123 "key_version": "58bf696275cd4b6d8150bb3376981076",
124 "aad_msi_res_id": "<identity resource id>",
125 "tenant_id": "33e01921-4d64-4f8c-a055-5bdaffd5e33d"
126 },
127 "attestation_info": {
128 "host": "HOST"
129 }
130 }
131 },
132 "recoverykey_info": {
133 "wrapped_key": "WRAPPEDKEY",
134 "os_type": "Windows",
135 "encryption_scheme": "WindowsBitLocker",
136 "algorithm_type": "RSA-OAEP-256",
137 "key_id": "KEYID"
138 }
139 }
140}"#;
141
142 let result = serde_json::from_str(JSON_DATA);
143 assert!(result.is_ok());
144 let payload: cps::VmmdBlob = result.unwrap();
145 assert_eq!(
146 payload
147 .disk_encryption_settings
148 .encryption_info
149 .aes_info
150 .ciphertext,
151 b"CIPHERTEXT"
152 );
153 }
154
155 #[test]
156 fn test_response_without_key_reference() {
157 const JSON_DATA: &str = r#"
158{
159 "DiskEncryptionSettings": {
160 "encryption_info": {
161 "aes_info": {
162 "ciphertext": "TESTKEY",
163 "algorithm": "AES_256_WRAP_PAD",
164 "creation_time": "2023-11-03T22:58:59.7967119Z"
165 }
166 }
167 }
168}"#;
169 let result: Result<cps::VmmdBlob, _> = serde_json::from_str(JSON_DATA);
170 assert!(result.is_err());
172 }
173
174 fn mock_response() -> Vec<u8> {
175 use openhcl_attestation_protocol::igvm_attest::get::IGVM_ATTEST_RESPONSE_CURRENT_VERSION;
176 use openhcl_attestation_protocol::igvm_attest::get::IgvmAttestWrappedKeyResponseHeader;
177
178 const WRAPPED_KEY: [u8; 256] = [
179 0x9d, 0x72, 0x81, 0xbc, 0x6d, 0x0c, 0xeb, 0x8f, 0x32, 0xb9, 0xc3, 0xd0, 0xd2, 0x58,
180 0x89, 0x2f, 0x49, 0xb4, 0x40, 0xb1, 0x3d, 0xb1, 0x2f, 0x1e, 0x9c, 0xb5, 0x46, 0x4a,
181 0x4a, 0x87, 0xbe, 0x97, 0xf5, 0xa2, 0x90, 0x7a, 0xd1, 0x7d, 0x6c, 0x91, 0x8a, 0x46,
182 0x9e, 0xc1, 0x87, 0x9c, 0xa9, 0xb2, 0xcd, 0xc2, 0x6e, 0x6c, 0xdc, 0xda, 0xdd, 0x79,
183 0x64, 0x25, 0x7a, 0xd7, 0xb9, 0x5d, 0xd3, 0xc7, 0x82, 0x0d, 0x4a, 0xb1, 0x86, 0xe2,
184 0x78, 0xc1, 0x94, 0xe4, 0x81, 0x9b, 0x48, 0xba, 0x90, 0xcb, 0x79, 0x51, 0x0c, 0xda,
185 0x98, 0x69, 0xed, 0xc7, 0xc9, 0x0b, 0xde, 0xb5, 0x9a, 0xcb, 0xcc, 0x16, 0x06, 0xa7,
186 0x66, 0xfe, 0xd7, 0x41, 0xe6, 0x71, 0xcb, 0x16, 0xb1, 0x16, 0xf8, 0x05, 0x41, 0x9a,
187 0x6b, 0x99, 0xa3, 0xc9, 0x3c, 0x7c, 0xa3, 0x26, 0x37, 0x0c, 0xb0, 0x87, 0x6b, 0x2a,
188 0xde, 0x9c, 0xce, 0x1a, 0xe8, 0x71, 0xe9, 0xce, 0xf8, 0x53, 0x75, 0xfd, 0x95, 0x47,
189 0xf8, 0x60, 0x21, 0xd5, 0xce, 0x33, 0xca, 0x9b, 0x6b, 0x7c, 0xa9, 0x73, 0xe8, 0x5a,
190 0x6e, 0x91, 0x57, 0x9c, 0xb1, 0xa1, 0x02, 0xce, 0x67, 0x0e, 0x8f, 0xac, 0x14, 0x0f,
191 0xa7, 0x08, 0x7e, 0xa8, 0xb3, 0xb9, 0x25, 0x36, 0x41, 0xae, 0x37, 0x59, 0xf8, 0x0d,
192 0x11, 0xc0, 0x81, 0xd9, 0x6f, 0x6b, 0xb1, 0xc3, 0xd1, 0xe3, 0xdd, 0xa9, 0x6d, 0x16,
193 0xb2, 0x34, 0xe1, 0xf3, 0xa1, 0xa2, 0x86, 0x83, 0x65, 0x3d, 0x48, 0x9e, 0xa0, 0x50,
194 0x15, 0xce, 0x0b, 0x06, 0x0a, 0x87, 0x89, 0x97, 0x42, 0x3d, 0x92, 0x1e, 0xab, 0x91,
195 0x62, 0x47, 0x31, 0xfb, 0xca, 0x43, 0xa5, 0x12, 0x2a, 0x2c, 0xde, 0x4a, 0xdc, 0x7a,
196 0x7f, 0x38, 0x18, 0xe0, 0x4d, 0xbe, 0xf3, 0xf2, 0xc3, 0xb9, 0x22, 0x22, 0x43, 0x19,
197 0xdb, 0x0b, 0x47, 0xc7,
198 ];
199
200 let aes_info = cps::AesInfo {
201 ciphertext: WRAPPED_KEY.to_vec(),
202 };
203
204 let result = serde_json::from_str(KEY_REFERENCE);
205 assert!(result.is_ok());
206 let key_reference = result.unwrap();
207
208 let encryption_info = cps::EncryptionInfo {
209 aes_info,
210 key_reference,
211 };
212 let disk_encryption_settings = cps::DiskEncryptionSettings { encryption_info };
213 let payload = cps::VmmdBlob {
214 disk_encryption_settings,
215 };
216
217 let result = serde_json::to_string(&payload);
218 assert!(result.is_ok());
219 let payload = result.unwrap();
220
221 let header = IgvmAttestWrappedKeyResponseHeader {
222 data_size: (payload.len() + size_of::<IgvmAttestWrappedKeyResponseHeader>()) as u32,
223 version: IGVM_ATTEST_RESPONSE_CURRENT_VERSION,
224 error_info: IgvmErrorInfo::default(),
225 };
226 [header.as_bytes(), payload.as_bytes()].concat()
227 }
228
229 #[test]
230 fn test_mock_response() {
231 let response = mock_response();
232 let result = parse_response(&response);
233 assert!(result.is_ok());
234 let igvm_wrapped_key = result.unwrap();
235 assert!(!igvm_wrapped_key.wrapped_key.is_empty());
236
237 let result = serde_json::from_str(KEY_REFERENCE);
238 assert!(result.is_ok());
239 let expected_key_reference: serde_json::Value = result.unwrap();
240 assert_eq!(
241 igvm_wrapped_key.key_reference,
242 expected_key_reference.to_string().as_bytes()
243 );
244 }
245}