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