Skip to main content

matrix_sdk_crypto/file_encryption/
attachments.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::io::{Error as IoError, Read};
16
17use aes::{
18    Aes256,
19    cipher::{KeyIvInit, StreamCipher},
20};
21use rand::{Rng, rng};
22use ruma::{
23    events::room::{
24        EncryptedFile, EncryptedFileHash, EncryptedFileHashAlgorithm, EncryptedFileHashes,
25        EncryptedFileInfo, V2EncryptedFileInfo,
26    },
27    serde::Base64,
28};
29use serde::{Deserialize, Serialize};
30use sha2::{Digest, Sha256};
31use thiserror::Error;
32
33const IV_SIZE: usize = 16;
34const KEY_SIZE: usize = 32;
35const HASH_SIZE: usize = 32;
36
37type Aes256Ctr = ctr::Ctr128BE<Aes256>;
38
39/// A wrapper that transparently encrypts anything that implements `Read` as an
40/// Matrix attachment.
41pub struct AttachmentDecryptor<'a, R: Read> {
42    inner: &'a mut R,
43    expected_hash: [u8; HASH_SIZE],
44    sha: Sha256,
45    aes: Aes256Ctr,
46}
47
48#[cfg(not(tarpaulin_include))]
49impl<'a, R: 'a + Read + std::fmt::Debug> std::fmt::Debug for AttachmentDecryptor<'a, R> {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.debug_struct("AttachmentDecryptor")
52            .field("inner", &self.inner)
53            .field("expected_hash", &self.expected_hash)
54            .finish()
55    }
56}
57
58impl<R: Read> Read for AttachmentDecryptor<'_, R> {
59    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
60        let read_bytes = self.inner.read(buf)?;
61
62        if read_bytes == 0 {
63            let hash = self.sha.finalize_reset();
64
65            if hash.as_slice() == self.expected_hash.as_slice() {
66                Ok(0)
67            } else {
68                Err(IoError::other("Hash mismatch while decrypting"))
69            }
70        } else {
71            self.sha.update(&buf[0..read_bytes]);
72            self.aes.apply_keystream(&mut buf[0..read_bytes]);
73
74            Ok(read_bytes)
75        }
76    }
77}
78
79/// Error type for attachment decryption.
80#[derive(Error, Debug)]
81pub enum DecryptorError {
82    /// Some data in the encrypted attachment coldn't be decoded, this may be a
83    /// hash, the secret key, or the initialization vector.
84    #[error(transparent)]
85    Decode(#[from] vodozemac::Base64DecodeError),
86    /// A hash is missing from the encryption info.
87    #[error("The encryption info is missing a hash")]
88    MissingHash,
89    /// The supplied data was encrypted with an unknown version of the
90    /// attachment encryption spec.
91    #[error("Unknown version for the encrypted attachment.")]
92    UnknownVersion,
93}
94
95impl<'a, R: Read + 'a> AttachmentDecryptor<'a, R> {
96    /// Wrap the given reader decrypting all the data we read from it.
97    ///
98    /// # Arguments
99    ///
100    /// - `reader` - The `Reader` that should be wrapped and decrypted.
101    /// - `info` - The encryption info that is necessary to decrypt data from
102    ///   the reader.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// # use std::io::{Cursor, Read};
108    /// # use matrix_sdk_crypto::{AttachmentEncryptor, AttachmentDecryptor};
109    /// let data = "Hello world".to_owned();
110    /// let mut cursor = Cursor::new(data.clone());
111    ///
112    /// let mut encryptor = AttachmentEncryptor::new(&mut cursor);
113    ///
114    /// let mut encrypted = Vec::new();
115    /// encryptor.read_to_end(&mut encrypted).unwrap();
116    /// let info = encryptor.finish();
117    ///
118    /// let mut cursor = Cursor::new(encrypted);
119    /// let mut decryptor = AttachmentDecryptor::new(&mut cursor, info).unwrap();
120    /// let mut decrypted_data = Vec::new();
121    /// decryptor.read_to_end(&mut decrypted_data).unwrap();
122    ///
123    /// let decrypted = String::from_utf8(decrypted_data).unwrap();
124    /// ```
125    pub fn new(
126        input: &'a mut R,
127        info: MediaEncryptionInfo,
128    ) -> Result<AttachmentDecryptor<'a, R>, DecryptorError> {
129        let EncryptedFileInfo::V2(encryption_info) = info.encryption_info else {
130            return Err(DecryptorError::UnknownVersion);
131        };
132
133        let Some(EncryptedFileHash::Sha256(hash)) =
134            info.hashes.get(&EncryptedFileHashAlgorithm::Sha256)
135        else {
136            return Err(DecryptorError::MissingHash);
137        };
138        let hash = hash.clone().into_inner();
139        let key = encryption_info.k.as_inner();
140        let iv = encryption_info.iv.as_inner();
141
142        let sha = Sha256::default();
143
144        let aes = Aes256Ctr::new(key.into(), iv.into());
145
146        Ok(AttachmentDecryptor { inner: input, expected_hash: hash, sha, aes })
147    }
148}
149
150/// A wrapper that transparently encrypts anything that implements `Read`.
151pub struct AttachmentEncryptor<'a, R: Read + ?Sized> {
152    finished: bool,
153    inner: &'a mut R,
154    key: [u8; KEY_SIZE],
155    iv: [u8; IV_SIZE],
156    hashes: EncryptedFileHashes,
157    aes: Aes256Ctr,
158    sha: Sha256,
159}
160
161#[cfg(not(tarpaulin_include))]
162impl<'a, R: 'a + Read + std::fmt::Debug + ?Sized> std::fmt::Debug for AttachmentEncryptor<'a, R> {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        f.debug_struct("AttachmentEncryptor")
165            .field("inner", &self.inner)
166            .field("finished", &self.finished)
167            .finish()
168    }
169}
170
171impl<'a, R: Read + ?Sized + 'a> Read for AttachmentEncryptor<'a, R> {
172    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
173        let read_bytes = self.inner.read(buf)?;
174
175        if read_bytes == 0 {
176            Ok(0)
177        } else {
178            self.aes.apply_keystream(&mut buf[0..read_bytes]);
179            self.sha.update(&buf[0..read_bytes]);
180
181            Ok(read_bytes)
182        }
183    }
184}
185
186impl<'a, R: Read + ?Sized + 'a> AttachmentEncryptor<'a, R> {
187    /// Wrap the given reader encrypting all the data we read from it.
188    ///
189    /// After all the reads are done, and all the data is encrypted that we wish
190    /// to encrypt a call to [`finish()`](#method.finish) is necessary to get
191    /// the decryption key for the data.
192    ///
193    /// # Arguments
194    ///
195    /// - `reader` - The `Reader` that should be wrapped and encrypted.
196    ///
197    /// # Panics
198    ///
199    /// Panics if we can't generate enough random data to create a fresh
200    /// encryption key.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// # use std::io::{Cursor, Read};
206    /// # use matrix_sdk_crypto::AttachmentEncryptor;
207    /// let data = "Hello world".to_owned();
208    /// let mut cursor = Cursor::new(data.clone());
209    ///
210    /// let mut encryptor = AttachmentEncryptor::new(&mut cursor);
211    ///
212    /// let mut encrypted = Vec::new();
213    /// encryptor.read_to_end(&mut encrypted).unwrap();
214    /// let key = encryptor.finish();
215    /// ```
216    pub fn new(reader: &'a mut R) -> Self {
217        let mut key = [0u8; KEY_SIZE];
218        let mut iv = [0u8; IV_SIZE];
219
220        let mut rng = rng();
221
222        rng.fill_bytes(&mut key);
223        // Only populate the first 8 bytes with randomness, the rest is 0
224        // initialized for the counter.
225        rng.fill_bytes(&mut iv[0..8]);
226
227        let key_array = &key.into();
228
229        let aes = Aes256Ctr::new(key_array, &iv.into());
230
231        AttachmentEncryptor {
232            finished: false,
233            inner: reader,
234            iv,
235            key,
236            hashes: EncryptedFileHashes::new(),
237            aes,
238            sha: Sha256::default(),
239        }
240    }
241
242    /// Consume the encryptor and get the encryption key.
243    pub fn finish(mut self) -> MediaEncryptionInfo {
244        let hash = self.sha.finalize();
245        self.hashes.insert(EncryptedFileHash::Sha256(Base64::new(hash.into())));
246
247        MediaEncryptionInfo {
248            encryption_info: V2EncryptedFileInfo::encode(self.key, self.iv).into(),
249            hashes: self.hashes,
250        }
251    }
252}
253
254/// Struct holding all the information that is needed to decrypt an encrypted
255/// file.
256#[derive(Debug, Serialize, Deserialize)]
257pub struct MediaEncryptionInfo {
258    /// The information about the file's encryption.
259    #[serde(flatten)]
260    pub encryption_info: EncryptedFileInfo,
261    /// The hashes that can be used to check the validity of the file.
262    pub hashes: EncryptedFileHashes,
263}
264
265impl From<EncryptedFile> for MediaEncryptionInfo {
266    fn from(file: EncryptedFile) -> Self {
267        Self { encryption_info: file.info, hashes: file.hashes }
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use std::io::{Cursor, Read};
274
275    use serde_json::json;
276
277    use super::{AttachmentDecryptor, AttachmentEncryptor, MediaEncryptionInfo};
278
279    const EXAMPLE_DATA: &[u8] = &[
280        179, 154, 118, 127, 186, 127, 110, 33, 203, 33, 33, 134, 67, 100, 173, 46, 235, 27, 215,
281        172, 36, 26, 75, 47, 33, 160,
282    ];
283
284    fn example_key_json() -> serde_json::Value {
285        json!({
286            "v": "v2",
287            "key": {
288                "kty": "oct",
289                "alg": "A256CTR",
290                "ext": true,
291                "k": "Voq2nkPme_x8no5-Tjq_laDAdxE6iDbxnlQXxwFPgE4",
292                "key_ops": ["decrypt", "encrypt"]
293            },
294            "iv": "i0DovxYdJEcAAAAAAAAAAA",
295            "hashes": {
296                "sha256": "ANdt819a8bZl4jKy3Z+jcqtiNICa2y0AW4BBJ/iQRAU"
297            }
298        })
299    }
300
301    fn example_key() -> MediaEncryptionInfo {
302        serde_json::from_value(example_key_json()).unwrap()
303    }
304
305    #[test]
306    fn media_encryption_info_serde_roundtrip() {
307        let json = example_key_json();
308
309        let info = serde_json::from_value::<MediaEncryptionInfo>(json.clone()).unwrap();
310
311        let serialized_info = serde_json::to_value(&info).unwrap();
312        assert_eq!(serialized_info, json);
313    }
314
315    #[test]
316    fn encrypt_decrypt_cycle() {
317        let data = "Hello world".to_owned();
318        let mut cursor = Cursor::new(data.clone());
319
320        let mut encryptor = AttachmentEncryptor::new(&mut cursor);
321
322        let mut encrypted = Vec::new();
323
324        encryptor.read_to_end(&mut encrypted).unwrap();
325        let key = encryptor.finish();
326        assert_ne!(encrypted.as_slice(), data.as_bytes());
327
328        let mut cursor = Cursor::new(encrypted);
329        let mut decryptor = AttachmentDecryptor::new(&mut cursor, key).unwrap();
330        let mut decrypted_data = Vec::new();
331
332        decryptor.read_to_end(&mut decrypted_data).unwrap();
333
334        let decrypted = String::from_utf8(decrypted_data).unwrap();
335
336        assert_eq!(data, decrypted);
337    }
338
339    #[test]
340    fn real_decrypt() {
341        let mut cursor = Cursor::new(EXAMPLE_DATA.to_vec());
342        let key = example_key();
343
344        let mut decryptor = AttachmentDecryptor::new(&mut cursor, key).unwrap();
345        let mut decrypted_data = Vec::new();
346
347        decryptor.read_to_end(&mut decrypted_data).unwrap();
348        let decrypted = String::from_utf8(decrypted_data).unwrap();
349
350        assert_eq!("It's a secret to everybody", decrypted);
351    }
352
353    #[test]
354    fn decrypt_invalid_hash() {
355        let mut cursor = Cursor::new("fake message");
356        let key = example_key();
357
358        let mut decryptor = AttachmentDecryptor::new(&mut cursor, key).unwrap();
359        let mut decrypted_data = Vec::new();
360
361        decryptor.read_to_end(&mut decrypted_data).unwrap_err();
362    }
363}