1use crate::aes::IvAndCiphertext;
2use crate::v4::MAGIC;
3use crate::{
4 Error,
5 aes::{
6 EncryptionKey, PlaintextDocument, aes_encrypt_with_iv, decrypt_document_with_attached_iv,
7 },
8 icl_header_v3::V3DocumentHeader,
9 signing::AES_KEY_LEN,
10};
11use bytes::Bytes;
12use protobuf::Message;
13
14const IV_LEN: usize = 12;
15const GCM_TAG_LEN: usize = 16;
16
17const V3: u8 = 3u8;
18
19pub const VERSION_AND_MAGIC: [u8; 5] = [V3, MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3]];
21
22const MAGIC_HEADER_LEN: usize = 5;
24const HEADER_LEN_LEN: usize = 2;
26const DETACHED_HEADER_LEN: usize = MAGIC_HEADER_LEN + HEADER_LEN_LEN;
27
28#[derive(Debug, Clone)]
32pub struct EncryptedPayload {
33 v3_document_header: V3DocumentHeader,
34 iv_and_ciphertext: IvAndCiphertext,
35}
36
37impl TryFrom<Vec<u8>> for EncryptedPayload {
38 type Error = Error;
39 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
40 let value_len = value.len();
41 if value_len < DETACHED_HEADER_LEN {
42 Err(Error::EdocTooShort(value_len))?
43 };
44 let (magic_header, header_len_and_rest) = value.split_at(MAGIC_HEADER_LEN);
45 let (header_len_len, header_and_cipher) = header_len_and_rest.split_at(HEADER_LEN_LEN);
46 if magic_header != [&[V3], &MAGIC[..]].concat() {
47 Err(Error::NoIronCoreMagic)?
48 };
49 let header_len = u16::from_be_bytes(
50 header_len_len
51 .try_into()
52 .expect("This is safe as we split off 2 bytes."),
53 ) as usize;
54 if header_and_cipher.len() < header_len {
55 Err(Error::HeaderParseErr(format!(
56 "Proto header length specified: {}, bytes remaining: {}",
57 header_len,
58 header_and_cipher.len()
59 )))?
60 };
61 let (header, iv_and_cipher) = header_and_cipher.split_at(header_len);
62 let v3_document_header: V3DocumentHeader =
63 Message::parse_from_bytes(header).map_err(|_| {
64 Error::HeaderParseErr("Unable to parse header as V3DocumentHeader".to_string())
65 })?;
66 Ok(EncryptedPayload {
67 v3_document_header,
68 iv_and_ciphertext: iv_and_cipher.to_vec().into(),
69 })
70 }
71}
72
73impl EncryptedPayload {
74 pub fn decrypt(self, key: &EncryptionKey) -> Result<PlaintextDocument, Error> {
76 if verify_signature(key.0, &self.v3_document_header) {
77 decrypt_document_with_attached_iv(key, &self.iv_and_ciphertext)
78 } else {
79 Err(Error::DecryptError(
80 "Signature validation failed.".to_string(),
81 ))
82 }
83 }
84}
85
86struct V3Signature {
87 iv: [u8; IV_LEN],
88 gcm_tag: [u8; GCM_TAG_LEN],
89}
90
91fn decompose_signature(sig: &Bytes) -> Option<V3Signature> {
92 if sig.len() < IV_LEN + GCM_TAG_LEN {
93 None
94 } else {
95 let (iv, _) = sig.split_at(IV_LEN);
96 let (_, gcm_tag) = sig.split_at(sig.len() - GCM_TAG_LEN);
97 Some(V3Signature {
98 iv: iv.try_into().unwrap(), gcm_tag: gcm_tag.try_into().unwrap(), })
101 }
102}
103
104pub fn verify_signature(key: [u8; AES_KEY_LEN], v3_header: &V3DocumentHeader) -> bool {
105 if v3_header.header.is_none() || !v3_header.has_saas_shield() || v3_header.sig.is_empty() {
108 true
109 } else {
110 let maybe_sig = decompose_signature(&v3_header.sig);
111 match maybe_sig {
112 Some(sig) => aes_encrypt_with_iv(
113 crate::aes::EncryptionKey(key),
114 &v3_header
115 .saas_shield()
116 .write_to_bytes()
117 .expect("Writing proto to bytes failed."),
118 sig.iv,
119 &[],
120 )
121 .map(|(_, new_sig)| {
122 let new_sig_length = new_sig.0.len();
123 let (_, new_gcm_tag) = new_sig.0.split_at(new_sig_length - GCM_TAG_LEN);
124 new_gcm_tag == sig.gcm_tag
125 })
126 .unwrap_or(false),
127 _ => false,
128 }
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use hex_literal::hex;
136 use itertools::Itertools;
137
138 #[test]
139 fn verify_known_good_sig_in_v3_header() {
140 let dek: [u8; 32] = (0..32).into_iter().collect_vec().try_into().unwrap();
142 let proto_bytes = vec![
143 10, 28, 49, 113, -17, 60, -119, -97, -121, 94, 89, 92, 34, 19, -54, -49, -110, -121,
144 -57, -116, -15, -106, 69, -116, -42, -112, 84, 73, -128, -57, 26, 10, 10, 8, 116, 101,
145 110, 97, 110, 116, 73, 100,
146 ]
147 .into_iter()
148 .map(|x| x as u8)
149 .collect_vec();
150 let header = Message::parse_from_bytes(&proto_bytes).unwrap();
151 assert!(verify_signature(dek, &header))
152 }
153
154 #[test]
155 fn verify_known_bad_sig_in_v3_header() {
156 let proto_bytes = hex!("0a030102031a0a0a0874656e616e744964");
163 let dek: [u8; 32] = (0..32).into_iter().collect_vec().try_into().unwrap();
164 let header = Message::parse_from_bytes(&proto_bytes).unwrap();
165 assert!(!verify_signature(dek, &header));
166 }
167
168 #[test]
169 fn verify_empty_v3_header() {
170 let dek: [u8; 32] = (0..32).into_iter().collect_vec().try_into().unwrap();
171 let empty_header = V3DocumentHeader::new();
172 assert!(verify_signature(dek, &empty_header))
173 }
174
175 #[test]
176 fn verify_empty_sig_v3_header() {
177 let proto_bytes = hex!("0a001a0a0a0874656e616e744964");
184 let dek: [u8; 32] = (0..32).into_iter().collect_vec().try_into().unwrap();
185 let header = Message::parse_from_bytes(&proto_bytes).unwrap();
186 assert!(verify_signature(dek, &header));
187 }
188
189 #[test]
190 fn decompose_signature_works() {
191 let sig_1 = (0..28).collect_vec();
192 let decomposed_1 = decompose_signature(&sig_1.into()).unwrap();
193 let expected_iv_1 = (0..12).collect_vec();
194 let expected_tag_1 = (12..28).collect_vec();
195 assert_eq!(&decomposed_1.iv[..], expected_iv_1);
196 assert_eq!(&decomposed_1.gcm_tag[..], expected_tag_1);
197
198 let sig_2 = (0..100).collect_vec();
199 let decomposed_2 = decompose_signature(&sig_2.into()).unwrap();
200 let expected_iv_2 = (0..12).collect_vec();
201 let expected_tag_2 = (84..100).collect_vec();
202 assert_eq!(&decomposed_2.iv[..], expected_iv_2);
203 assert_eq!(&decomposed_2.gcm_tag[..], expected_tag_2);
204
205 let sig_3 = (0..10).collect_vec();
206 let decomposed_failure = decompose_signature(&sig_3.into());
207 assert!(decomposed_failure.is_none());
208 }
209
210 #[test]
211 fn encrypted_payload_too_short() {
212 let document = vec![3, 73, 82, 79, 78, 0];
213 let err = EncryptedPayload::try_from(document);
214 assert!(matches!(err, Err(Error::EdocTooShort(_))))
215 }
216
217 #[test]
218 fn encrypted_payload_invalid_header_len() {
219 let document = vec![
220 3, 73, 82, 79, 78, 0, 100, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
221 ];
222 let err = EncryptedPayload::try_from(document);
223 assert!(matches!(err, Err(Error::HeaderParseErr(_))))
224 }
225
226 #[test]
227 fn encrypted_payload_no_magic() {
228 let document = vec![1, 73, 82, 79, 78, 0, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
229 let err = EncryptedPayload::try_from(document);
230 assert!(matches!(err, Err(Error::NoIronCoreMagic)))
231 }
232
233 #[test]
234 fn form_good_encrypted_payload() {
235 let document = vec![
236 3, 73, 82, 79, 78, 0, 42, 10, 28, 20, 31, 98, 61, 23, 74, 221, 61, 102, 44, 153, 142,
237 172, 70, 145, 180, 36, 193, 133, 249, 72, 1, 181, 31, 205, 205, 1, 197, 26, 10, 10, 8,
238 116, 101, 110, 97, 110, 116, 73, 100, 49, 113, 239, 60, 137, 159, 135, 94, 89, 92, 34,
239 19, 231, 165, 112, 184, 171, 237, 133, 20, 97, 193, 60, 0, 85, 139, 184, 144, 44, 184,
240 129, 210, 203, 21, 167, 53, 17, 51, 49, 42, 92, 207, 102, 98, 174, 198, 128, 199, 19,
241 42, 145, 251, 86, 201, 214, 33, 117, 232, 18, 93,
242 ];
243 let payload = EncryptedPayload::try_from(document);
244 assert!(payload.is_ok());
245 }
246
247 #[test]
248 fn decrypt_good_document() {
249 let dek = EncryptionKey((0..32).collect_vec().try_into().unwrap());
250 let document = vec![
251 3, 73, 82, 79, 78, 0, 42, 10, 28, 20, 31, 98, 61, 23, 74, 221, 61, 102, 44, 153, 142,
252 172, 70, 145, 180, 36, 193, 133, 249, 72, 1, 181, 31, 205, 205, 1, 197, 26, 10, 10, 8,
253 116, 101, 110, 97, 110, 116, 73, 100, 49, 113, 239, 60, 137, 159, 135, 94, 89, 92, 34,
254 19, 231, 165, 112, 184, 171, 237, 133, 20, 97, 193, 60, 0, 85, 139, 184, 144, 44, 184,
255 129, 210, 203, 21, 167, 53, 17, 51, 49, 42, 92, 207, 102, 98, 174, 198, 128, 199, 19,
256 42, 145, 251, 86, 201, 214, 33, 117, 232, 18, 93,
257 ];
258 let payload = EncryptedPayload::try_from(document).unwrap();
259 let decrypted = payload.decrypt(&dek).unwrap();
260 assert_eq!(decrypted.0, (0..32).collect_vec());
261 }
262
263 #[test]
264 fn decrypt_bad_signature_document() {
265 let dek = EncryptionKey((0..32).collect_vec().try_into().unwrap());
266 let document = vec![
267 3, 73, 82, 79, 78, 0, 42, 10, 28, 20, 32, 98, 61, 23, 74, 221, 61, 102, 44, 153, 142,
268 172, 70, 145, 180, 36, 193, 133, 249, 72, 1, 181, 31, 205, 205, 1, 197, 26, 10, 10, 8,
269 116, 101, 110, 97, 110, 116, 73, 100, 49, 113, 239, 60, 137, 159, 135, 94, 89, 92, 34,
270 19, 231, 165, 112, 184, 171, 237, 133, 20, 97, 193, 60, 0, 85, 139, 184, 144, 44, 184,
271 129, 210, 203, 21, 167, 53, 17, 51, 49, 42, 92, 207, 102, 98, 174, 198, 128, 199, 19,
272 42, 145, 251, 86, 201, 214, 33, 117, 232, 18, 93,
273 ];
274 let payload = EncryptedPayload::try_from(document).unwrap();
275 let err = payload.decrypt(&dek).unwrap_err();
276 assert!(matches!(err, Error::DecryptError(_)));
277 }
278}