chacha20poly1305: enable simultaneous writing+encryption
[rust-lightning] / lightning / src / util / chacha20poly1305rfc.rs
1 // ring has a garbage API so its use is avoided, but rust-crypto doesn't have RFC-variant poly1305
2 // Instead, we steal rust-crypto's implementation and tweak it to match the RFC.
3 //
4 // This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5 // or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7 // You may not use this file except in accordance with one or both of these
8 // licenses.
9 //
10 // This is a port of Andrew Moons poly1305-donna
11 // https://github.com/floodyberry/poly1305-donna
12
13 use util::ser::{Writeable, Writer};
14 use io::{self, Write};
15
16 #[cfg(not(fuzzing))]
17 mod real_chachapoly {
18         use util::chacha20::ChaCha20;
19         use util::poly1305::Poly1305;
20         use bitcoin::hashes::cmp::fixed_time_eq;
21
22         #[derive(Clone, Copy)]
23         pub struct ChaCha20Poly1305RFC {
24                 cipher: ChaCha20,
25                 mac: Poly1305,
26                 finished: bool,
27                 data_len: usize,
28                 aad_len: u64,
29         }
30
31         impl ChaCha20Poly1305RFC {
32                 #[inline]
33                 fn pad_mac_16(mac: &mut Poly1305, len: usize) {
34                         if len % 16 != 0 {
35                                 mac.input(&[0; 16][0..16 - (len % 16)]);
36                         }
37                 }
38                 pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
39                         assert!(key.len() == 16 || key.len() == 32);
40                         assert!(nonce.len() == 12);
41
42                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
43                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
44
45                         let mut cipher = ChaCha20::new(key, &nonce[4..]);
46                         let mut mac_key = [0u8; 64];
47                         let zero_key = [0u8; 64];
48                         cipher.process(&zero_key, &mut mac_key);
49
50                         let mut mac = Poly1305::new(&mac_key[..32]);
51                         mac.input(aad);
52                         ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
53
54                         ChaCha20Poly1305RFC {
55                                 cipher,
56                                 mac,
57                                 finished: false,
58                                 data_len: 0,
59                                 aad_len: aad.len() as u64,
60                         }
61                 }
62
63                 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
64                         assert!(input.len() == output.len());
65                         assert!(self.finished == false);
66                         self.cipher.process(input, output);
67                         self.data_len += input.len();
68                         self.mac.input(output);
69                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
70                         self.finished = true;
71                         self.mac.input(&self.aad_len.to_le_bytes());
72                         self.mac.input(&(self.data_len as u64).to_le_bytes());
73                         self.mac.raw_result(out_tag);
74                 }
75
76                 // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
77                 // below.
78                 pub(super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
79                         debug_assert!(self.finished == false);
80                         self.cipher.process_in_place(input_output);
81                         self.data_len += input_output.len();
82                         self.mac.input(input_output);
83                 }
84
85                 // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
86                 // encrypting and calculate the tag.
87                 pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
88                         debug_assert!(self.finished == false);
89                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
90                         self.finished = true;
91                         self.mac.input(&self.aad_len.to_le_bytes());
92                         self.mac.input(&(self.data_len as u64).to_le_bytes());
93                         self.mac.raw_result(out_tag);
94                 }
95
96                 pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
97                         assert!(input.len() == output.len());
98                         assert!(self.finished == false);
99
100                         self.finished = true;
101
102                         self.mac.input(input);
103
104                         self.data_len += input.len();
105                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
106                         self.mac.input(&self.aad_len.to_le_bytes());
107                         self.mac.input(&(self.data_len as u64).to_le_bytes());
108
109                         let mut calc_tag =  [0u8; 16];
110                         self.mac.raw_result(&mut calc_tag);
111                         if fixed_time_eq(&calc_tag, tag) {
112                                 self.cipher.process(input, output);
113                                 true
114                         } else {
115                                 false
116                         }
117                 }
118         }
119 }
120 #[cfg(not(fuzzing))]
121 pub use self::real_chachapoly::ChaCha20Poly1305RFC;
122
123 /// Enables simultaneously writing and encrypting a byte stream into a Writer.
124 struct ChaChaPolyWriter<'a, W: Writer> {
125         pub chacha: &'a mut ChaCha20Poly1305RFC,
126         pub write: &'a mut W,
127 }
128
129 impl<'a, W: Writer> Writer for ChaChaPolyWriter<'a, W> {
130         // Encrypt then write bytes from `src` into Self::write.
131         // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
132         // complete.
133         fn write_all(&mut self, src: &[u8]) -> Result<(), io::Error> {
134                 let mut src_idx = 0;
135                 while src_idx < src.len() {
136                         let mut write_buffer = [0; 8192];
137                         let bytes_written = (&mut write_buffer[..]).write(&src[src_idx..]).expect("In-memory writes can't fail");
138                         self.chacha.encrypt_in_place(&mut write_buffer[..bytes_written]);
139                         self.write.write_all(&write_buffer[..bytes_written])?;
140                         src_idx += bytes_written;
141                 }
142                 Ok(())
143         }
144 }
145
146 /// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
147 /// serialized. This allows us to avoid an intermediate Vec allocation.
148 pub(crate) struct ChaChaPolyWriteAdapter<'a, W: Writeable> {
149         pub rho: [u8; 32],
150         pub writeable: &'a W,
151 }
152
153 impl<'a, W: Writeable> ChaChaPolyWriteAdapter<'a, W> {
154         #[allow(unused)] // This will be used for onion messages soon
155         pub fn new(rho: [u8; 32], writeable: &'a W) -> ChaChaPolyWriteAdapter<'a, W> {
156                 Self { rho, writeable }
157         }
158 }
159
160 impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
161         // Simultaneously write and encrypt Self::writeable.
162         fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
163                 let mut chacha = ChaCha20Poly1305RFC::new(&self.rho, &[0; 12], &[]);
164                 let mut chacha_stream = ChaChaPolyWriter { chacha: &mut chacha, write: w };
165                 self.writeable.write(&mut chacha_stream)?;
166                 let mut tag = [0 as u8; 16];
167                 chacha.finish_and_get_tag(&mut tag);
168                 tag.write(w)?;
169
170                 Ok(())
171         }
172 }
173
174 #[cfg(fuzzing)]
175 mod fuzzy_chachapoly {
176         #[derive(Clone, Copy)]
177         pub struct ChaCha20Poly1305RFC {
178                 tag: [u8; 16],
179                 finished: bool,
180         }
181         impl ChaCha20Poly1305RFC {
182                 pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
183                         assert!(key.len() == 16 || key.len() == 32);
184                         assert!(nonce.len() == 12);
185
186                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
187                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
188
189                         let mut tag = [0; 16];
190                         tag.copy_from_slice(&key[0..16]);
191
192                         ChaCha20Poly1305RFC {
193                                 tag,
194                                 finished: false,
195                         }
196                 }
197
198                 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
199                         assert!(input.len() == output.len());
200                         assert!(self.finished == false);
201
202                         output.copy_from_slice(&input);
203                         out_tag.copy_from_slice(&self.tag);
204                         self.finished = true;
205                 }
206
207                 pub(super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) {
208                         assert!(self.finished == false);
209                         self.finished = true;
210                 }
211
212                 pub(super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
213                         out_tag.copy_from_slice(&self.tag);
214                         self.finished = true;
215                 }
216
217                 pub fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
218                         assert!(input.len() == output.len());
219                         assert!(self.finished == false);
220
221                         if tag[..] != self.tag[..] { return false; }
222                         output.copy_from_slice(input);
223                         self.finished = true;
224                         true
225                 }
226         }
227 }
228 #[cfg(fuzzing)]
229 pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;