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