1 //! Core AEAD cipher implementation for (X)ChaCha20Poly1305.
2 
3 use ::cipher::{StreamCipher, StreamCipherSeek};
4 use aead::generic_array::GenericArray;
5 use aead::Error;
6 use core::convert::TryInto;
7 use poly1305::{
8     universal_hash::{NewUniversalHash, UniversalHash},
9     Poly1305,
10 };
11 use zeroize::Zeroize;
12 
13 use super::Tag;
14 
15 /// Size of a ChaCha20 block in bytes
16 const BLOCK_SIZE: usize = 64;
17 
18 /// Maximum number of blocks that can be encrypted with ChaCha20 before the
19 /// counter overflows.
20 const MAX_BLOCKS: usize = core::u32::MAX as usize;
21 
22 /// ChaCha20Poly1305 instantiated with a particular nonce
23 pub(crate) struct Cipher<C>
24 where
25     C: StreamCipher + StreamCipherSeek,
26 {
27     cipher: C,
28     mac: Poly1305,
29 }
30 
31 impl<C> Cipher<C>
32 where
33     C: StreamCipher + StreamCipherSeek,
34 {
35     /// Instantiate the underlying cipher with a particular nonce
new(mut cipher: C) -> Self36     pub(crate) fn new(mut cipher: C) -> Self {
37         // Derive Poly1305 key from the first 32-bytes of the ChaCha20 keystream
38         let mut mac_key = poly1305::Key::default();
39         cipher.apply_keystream(&mut *mac_key);
40         let mac = Poly1305::new(GenericArray::from_slice(&*mac_key));
41         mac_key.zeroize();
42 
43         // Set ChaCha20 counter to 1
44         cipher.seek(BLOCK_SIZE as u64);
45 
46         Self { cipher, mac }
47     }
48 
49     /// Encrypt the given message in-place, returning the authentication tag
encrypt_in_place_detached( mut self, associated_data: &[u8], buffer: &mut [u8], ) -> Result<Tag, Error>50     pub(crate) fn encrypt_in_place_detached(
51         mut self,
52         associated_data: &[u8],
53         buffer: &mut [u8],
54     ) -> Result<Tag, Error> {
55         if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS {
56             return Err(Error);
57         }
58 
59         self.mac.update_padded(associated_data);
60 
61         // TODO(tarcieri): interleave encryption with Poly1305
62         // See: <https://github.com/RustCrypto/AEADs/issues/74>
63         self.cipher.apply_keystream(buffer);
64         self.mac.update_padded(buffer);
65 
66         self.authenticate_lengths(associated_data, buffer)?;
67         Ok(self.mac.finalize().into_bytes())
68     }
69 
70     /// Decrypt the given message, first authenticating ciphertext integrity
71     /// and returning an error if it's been tampered with.
decrypt_in_place_detached( mut self, associated_data: &[u8], buffer: &mut [u8], tag: &Tag, ) -> Result<(), Error>72     pub(crate) fn decrypt_in_place_detached(
73         mut self,
74         associated_data: &[u8],
75         buffer: &mut [u8],
76         tag: &Tag,
77     ) -> Result<(), Error> {
78         if buffer.len() / BLOCK_SIZE >= MAX_BLOCKS {
79             return Err(Error);
80         }
81 
82         self.mac.update_padded(associated_data);
83         self.mac.update_padded(buffer);
84         self.authenticate_lengths(associated_data, buffer)?;
85 
86         // This performs a constant-time comparison using the `subtle` crate
87         if self.mac.verify(tag).is_ok() {
88             // TODO(tarcieri): interleave decryption with Poly1305
89             // See: <https://github.com/RustCrypto/AEADs/issues/74>
90             self.cipher.apply_keystream(buffer);
91             Ok(())
92         } else {
93             Err(Error)
94         }
95     }
96 
97     /// Authenticate the lengths of the associated data and message
authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error>98     fn authenticate_lengths(&mut self, associated_data: &[u8], buffer: &[u8]) -> Result<(), Error> {
99         let associated_data_len: u64 = associated_data.len().try_into().map_err(|_| Error)?;
100         let buffer_len: u64 = buffer.len().try_into().map_err(|_| Error)?;
101 
102         let mut block = GenericArray::default();
103         block[..8].copy_from_slice(&associated_data_len.to_le_bytes());
104         block[8..].copy_from_slice(&buffer_len.to_le_bytes());
105         self.mac.update(&block);
106 
107         Ok(())
108     }
109 }
110