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