libsignal_service/
attachment_cipher.rs

1use aes::cipher::block_padding::Pkcs7;
2use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
3use hmac::{Hmac, Mac};
4use sha2::Sha256;
5
6type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
7type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
8
9#[derive(thiserror::Error, Debug, PartialEq, Eq)]
10pub enum AttachmentCipherError {
11    #[error("MAC verification error")]
12    MacError,
13    #[error("Padding verification error")]
14    PaddingError,
15}
16
17/// Encrypts an attachment in place, given the key material.
18///
19/// The Vec will be reused when it has enough space to house the MAC,
20/// otherwise reallocation might happen.
21#[tracing::instrument(skip(iv, key, plaintext))]
22pub fn encrypt_in_place(iv: [u8; 16], key: [u8; 64], plaintext: &mut Vec<u8>) {
23    let aes_half = &key[..32];
24    let mac_half = &key[32..];
25
26    let plaintext_len = plaintext.len();
27    plaintext.reserve(plaintext.len() + 16 + 16);
28
29    // Prepend IV
30    plaintext.extend(&[0u8; 16]);
31    plaintext.copy_within(..plaintext_len, 16);
32    plaintext[0..16].copy_from_slice(&iv);
33
34    // Pad with zeroes for padding
35    plaintext.extend(&[0u8; 16]);
36
37    let cipher = Aes256CbcEnc::new(aes_half.into(), &iv.into());
38
39    let buffer = plaintext;
40    let ciphertext_slice = cipher
41        .encrypt_padded_mut::<Pkcs7>(&mut buffer[16..], plaintext_len)
42        .expect("encrypted ciphertext");
43    let ciphertext_len = ciphertext_slice.len();
44    // Correct length for padding
45    buffer.truncate(16 + ciphertext_len);
46
47    // Compute and append MAC
48    let mut mac = Hmac::<Sha256>::new_from_slice(mac_half)
49        .expect("fixed length key material");
50    mac.update(buffer);
51    buffer.extend(mac.finalize().into_bytes());
52}
53
54/// Decrypts an attachment in place, given the key material.
55///
56/// On error, ciphertext is not changed.
57#[tracing::instrument(skip(key, ciphertext))]
58pub fn decrypt_in_place(
59    key: [u8; 64],
60    ciphertext: &mut Vec<u8>,
61) -> Result<(), AttachmentCipherError> {
62    let aes_half = &key[..32];
63    let mac_half = &key[32..];
64
65    let ciphertext_len = ciphertext.len();
66
67    let (buffer, their_mac) = ciphertext.split_at_mut(ciphertext_len - 32);
68
69    // Compute and append MAC
70    let mut mac = Hmac::<Sha256>::new_from_slice(mac_half)
71        .expect("fixed length key material");
72    mac.update(buffer);
73    mac.verify_slice(their_mac)
74        .map_err(|_| AttachmentCipherError::MacError)?;
75
76    let (iv, buffer) = buffer.split_at_mut(16);
77
78    let cipher = Aes256CbcDec::new(aes_half.into(), (&*iv).into());
79
80    let plaintext_slice = cipher
81        .decrypt_padded_mut::<Pkcs7>(buffer)
82        .map_err(|_| AttachmentCipherError::PaddingError)?;
83
84    let plaintext_len = plaintext_slice.len();
85
86    // Get rid of IV and MAC
87    ciphertext.copy_within(16..(plaintext_len + 16), 0);
88    ciphertext.truncate(plaintext_len);
89
90    Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    use rand::prelude::*;
98
99    #[test]
100    fn attachment_encrypt_decrypt() -> Result<(), AttachmentCipherError> {
101        let mut key = [0u8; 64];
102        let mut iv = [0u8; 16];
103        rand::thread_rng().fill_bytes(&mut key);
104        rand::thread_rng().fill_bytes(&mut iv);
105
106        let plaintext = b"Peter Parker";
107        let mut buf = Vec::from(plaintext as &[u8]);
108        encrypt_in_place(iv, key, &mut buf);
109        assert_ne!(&buf, &plaintext);
110        decrypt_in_place(key, &mut buf)?;
111        assert_eq!(&buf, &plaintext);
112        Ok(())
113    }
114
115    #[test]
116    fn attachment_encrypt_decrypt_empty() -> Result<(), AttachmentCipherError> {
117        let mut key = [0u8; 64];
118        let mut iv = [0u8; 16];
119        rand::thread_rng().fill_bytes(&mut key);
120        rand::thread_rng().fill_bytes(&mut iv);
121        let plaintext = b"";
122        let mut buf = Vec::from(plaintext as &[u8]);
123        encrypt_in_place(iv, key, &mut buf);
124        assert_ne!(&buf, &plaintext);
125        decrypt_in_place(key, &mut buf)?;
126        assert_eq!(&buf, &plaintext);
127        Ok(())
128    }
129
130    #[test]
131    fn attachment_encrypt_decrypt_bad_key() {
132        let mut key = [0u8; 64];
133        let mut iv = [0u8; 16];
134        rand::thread_rng().fill_bytes(&mut key);
135        rand::thread_rng().fill_bytes(&mut iv);
136        let plaintext = b"Peter Parker";
137        let mut buf = Vec::from(plaintext as &[u8]);
138        encrypt_in_place(iv, key, &mut buf);
139
140        // Generate bad key
141        rand::thread_rng().fill_bytes(&mut key);
142        assert_eq!(
143            decrypt_in_place(key, &mut buf).unwrap_err(),
144            AttachmentCipherError::MacError
145        );
146        assert_ne!(&buf, &plaintext);
147    }
148
149    #[test]
150    fn know_answer_test_attachment() -> Result<(), AttachmentCipherError> {
151        let mut ciphertext = include!("kat.bin.rs");
152        let key_material = [
153            52, 102, 97, 87, 153, 192, 64, 116, 93, 96, 57, 110, 6, 197, 208,
154            85, 49, 249, 154, 137, 116, 124, 112, 107, 8, 158, 48, 4, 8, 66,
155            173, 5, 28, 16, 199, 226, 234, 38, 69, 167, 163, 34, 107, 164, 15,
156            118, 101, 146, 34, 213, 85, 164, 110, 83, 129, 245, 62, 44, 158,
157            78, 205, 62, 153, 108,
158        ];
159
160        decrypt_in_place(key_material, &mut ciphertext)?;
161        // This 32 is given by the AttachmentPointer
162        ciphertext.truncate(32);
163        assert_eq!(ciphertext, b"test for libsignal-service-rust\n");
164        Ok(())
165    }
166}