1use aes::cipher::consts::{U32, U64};
2use aes::cipher::{block_padding::Pkcs7, Array, BlockModeEncrypt};
3use aes::cipher::{BlockModeDecrypt, KeyIvInit};
4use hmac::{Hmac, KeyInit, Mac};
5use sha2::Sha256;
6
7type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
8type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
9
10#[derive(thiserror::Error, Debug, PartialEq, Eq)]
11pub enum AttachmentCipherError {
12 #[error("MAC verification error")]
13 MacError,
14 #[error("Padding verification error")]
15 PaddingError,
16}
17
18#[tracing::instrument(skip(iv, key, plaintext))]
23pub fn encrypt_in_place(iv: [u8; 16], key: [u8; 64], plaintext: &mut Vec<u8>) {
24 let key: &Array<u8, U64> = (&key).into();
25 let (aes_half, mac_half) = key.split_ref::<U32>();
26
27 let plaintext_len = plaintext.len();
28 plaintext.reserve(plaintext.len() + 16 + 16);
29
30 plaintext.extend(&[0u8; 16]);
32 plaintext.copy_within(..plaintext_len, 16);
33 plaintext[0..16].copy_from_slice(&iv);
34
35 plaintext.extend(&[0u8; 16]);
37
38 let cipher = Aes256CbcEnc::new(aes_half, &iv.into());
39
40 let buffer = plaintext;
41 let ciphertext_slice = cipher
42 .encrypt_padded::<Pkcs7>(&mut buffer[16..], plaintext_len)
43 .expect("encrypted ciphertext");
44 let ciphertext_len = ciphertext_slice.len();
45 buffer.truncate(16 + ciphertext_len);
47
48 let mut mac = Hmac::<Sha256>::new_from_slice(mac_half)
50 .expect("fixed length key material");
51 mac.update(buffer);
52 buffer.extend(mac.finalize().into_bytes());
53}
54
55#[tracing::instrument(skip(key, ciphertext))]
59pub fn decrypt_in_place(
60 key: [u8; 64],
61 ciphertext: &mut Vec<u8>,
62) -> Result<(), AttachmentCipherError> {
63 let key: &Array<u8, U64> = (&key).into();
64 let (aes_half, mac_half) = key.split_ref::<U32>();
65
66 let ciphertext_len = ciphertext.len();
67
68 let (buffer, their_mac) = ciphertext.split_at_mut(ciphertext_len - 32);
69
70 let mut mac = Hmac::<Sha256>::new_from_slice(mac_half)
72 .expect("fixed length key material");
73 mac.update(buffer);
74 mac.verify_slice(their_mac)
75 .map_err(|_| AttachmentCipherError::MacError)?;
76
77 let (iv, buffer) = buffer
78 .split_first_chunk_mut::<16>()
79 .ok_or(AttachmentCipherError::PaddingError)?;
80
81 let cipher = Aes256CbcDec::new(aes_half, (&*iv).into());
82
83 let plaintext_slice = cipher
84 .decrypt_padded::<Pkcs7>(buffer)
85 .map_err(|_| AttachmentCipherError::PaddingError)?;
86
87 let plaintext_len = plaintext_slice.len();
88
89 ciphertext.copy_within(16..(plaintext_len + 16), 0);
91 ciphertext.truncate(plaintext_len);
92
93 Ok(())
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 use rand::prelude::*;
101
102 #[test]
103 fn attachment_encrypt_decrypt() -> Result<(), AttachmentCipherError> {
104 let mut key = [0u8; 64];
105 let mut iv = [0u8; 16];
106 rand::rng().fill_bytes(&mut key);
107 rand::rng().fill_bytes(&mut iv);
108
109 let plaintext = b"Peter Parker";
110 let mut buf = Vec::from(plaintext as &[u8]);
111 encrypt_in_place(iv, key, &mut buf);
112 assert_ne!(&buf, &plaintext);
113 decrypt_in_place(key, &mut buf)?;
114 assert_eq!(&buf, &plaintext);
115 Ok(())
116 }
117
118 #[test]
119 fn attachment_encrypt_decrypt_empty() -> Result<(), AttachmentCipherError> {
120 let mut key = [0u8; 64];
121 let mut iv = [0u8; 16];
122 rand::rng().fill_bytes(&mut key);
123 rand::rng().fill_bytes(&mut iv);
124 let plaintext = b"";
125 let mut buf = Vec::from(plaintext as &[u8]);
126 encrypt_in_place(iv, key, &mut buf);
127 assert_ne!(&buf, &plaintext);
128 decrypt_in_place(key, &mut buf)?;
129 assert_eq!(&buf, &plaintext);
130 Ok(())
131 }
132
133 #[test]
134 fn attachment_encrypt_decrypt_bad_key() {
135 let mut key = [0u8; 64];
136 let mut iv = [0u8; 16];
137 rand::rng().fill_bytes(&mut key);
138 rand::rng().fill_bytes(&mut iv);
139 let plaintext = b"Peter Parker";
140 let mut buf = Vec::from(plaintext as &[u8]);
141 encrypt_in_place(iv, key, &mut buf);
142
143 rand::rng().fill_bytes(&mut key);
145 assert_eq!(
146 decrypt_in_place(key, &mut buf).unwrap_err(),
147 AttachmentCipherError::MacError
148 );
149 assert_ne!(&buf, &plaintext);
150 }
151
152 #[test]
153 fn know_answer_test_attachment() -> Result<(), AttachmentCipherError> {
154 let mut ciphertext = include!("kat.bin.rs");
155 let key_material = [
156 52, 102, 97, 87, 153, 192, 64, 116, 93, 96, 57, 110, 6, 197, 208,
157 85, 49, 249, 154, 137, 116, 124, 112, 107, 8, 158, 48, 4, 8, 66,
158 173, 5, 28, 16, 199, 226, 234, 38, 69, 167, 163, 34, 107, 164, 15,
159 118, 101, 146, 34, 213, 85, 164, 110, 83, 129, 245, 62, 44, 158,
160 78, 205, 62, 153, 108,
161 ];
162
163 decrypt_in_place(key_material, &mut ciphertext)?;
164 ciphertext.truncate(32);
166 assert_eq!(ciphertext, b"test for libsignal-service-rust\n");
167 Ok(())
168 }
169}