Merge pull request #108 from TheBlueMatt/2018-08-fuzz-fixes
[rust-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 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 // This is a port of Andrew Moons poly1305-donna
11 // https://github.com/floodyberry/poly1305-donna
12
13 #[cfg(not(feature = "fuzztarget"))]
14 mod real_chachapoly {
15         use crypto::aead::{AeadEncryptor,AeadDecryptor};
16         use crypto::symmetriccipher::SynchronousStreamCipher;
17         use crypto::poly1305::Poly1305;
18         use crypto::mac::Mac;
19         use crypto::util::fixed_time_eq;
20
21         pub use crypto::chacha20::ChaCha20;
22
23         use util::byte_utils;
24
25         #[derive(Clone, Copy)]
26         pub struct ChaCha20Poly1305RFC {
27                 cipher  : ChaCha20,
28                 mac: Poly1305,
29                 finished: bool,
30                 data_len: usize,
31                 aad_len: u64,
32         }
33
34         impl ChaCha20Poly1305RFC {
35                 #[inline]
36                 fn pad_mac_16(mac: &mut Poly1305, len: usize) {
37                         if len % 16 != 0 {
38                                 mac.input(&[0; 16][0..16 - (len % 16)]);
39                         }
40                 }
41                 pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
42                         assert!(key.len() == 16 || key.len() == 32);
43                         assert!(nonce.len() == 12);
44
45                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
46                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
47
48                         let mut cipher = ChaCha20::new(key, &nonce[4..]);
49                         let mut mac_key = [0u8; 64];
50                         let zero_key = [0u8; 64];
51                         cipher.process(&zero_key, &mut mac_key);
52
53                         let mut mac = Poly1305::new(&mac_key[..32]);
54                         mac.input(aad);
55                         ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
56
57                         ChaCha20Poly1305RFC {
58                                 cipher: cipher,
59                                 mac: mac,
60                                 finished: false,
61                                 data_len: 0,
62                                 aad_len: aad.len() as u64,
63                         }
64                 }
65         }
66
67         impl AeadEncryptor for ChaCha20Poly1305RFC {
68                 fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
69                         assert!(input.len() == output.len());
70                         assert!(self.finished == false);
71                         self.cipher.process(input, output);
72                         self.data_len += input.len();
73                         self.mac.input(output);
74                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
75                         self.finished = true;
76                         self.mac.input(&byte_utils::le64_to_array(self.aad_len));
77                         self.mac.input(&byte_utils::le64_to_array(self.data_len as u64));
78                         self.mac.raw_result(out_tag);
79                 }
80         }
81
82         impl AeadDecryptor for ChaCha20Poly1305RFC {
83                 fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
84                         assert!(input.len() == output.len());
85                         assert!(self.finished == false);
86
87                         self.finished = true;
88
89                         self.mac.input(input);
90
91                         self.data_len += input.len();
92                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
93                         self.mac.input(&byte_utils::le64_to_array(self.aad_len));
94                         self.mac.input(&byte_utils::le64_to_array(self.data_len as u64));
95
96                         let mut calc_tag =  [0u8; 16];
97                         self.mac.raw_result(&mut calc_tag);
98                         if fixed_time_eq(&calc_tag, tag) {
99                                 self.cipher.process(input, output);
100                                 true
101                         } else {
102                                 false
103                         }
104                 }
105         }
106 }
107 #[cfg(not(feature = "fuzztarget"))]
108 pub use self::real_chachapoly::{ChaCha20Poly1305RFC, ChaCha20};
109
110 #[cfg(feature = "fuzztarget")]
111 mod fuzzy_chachapoly {
112         use crypto::aead::{AeadEncryptor,AeadDecryptor};
113         use crypto::symmetriccipher::SynchronousStreamCipher;
114
115         #[derive(Clone, Copy)]
116         pub struct ChaCha20Poly1305RFC {
117                 tag: [u8; 16],
118                 finished: bool,
119         }
120         impl ChaCha20Poly1305RFC {
121                 pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
122                         assert!(key.len() == 16 || key.len() == 32);
123                         assert!(nonce.len() == 12);
124
125                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
126                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
127
128                         let mut tag = [0; 16];
129                         tag.copy_from_slice(&key[0..16]);
130
131                         ChaCha20Poly1305RFC {
132                                 tag,
133                                 finished: false,
134                         }
135                 }
136         }
137
138         impl AeadEncryptor for ChaCha20Poly1305RFC {
139                 fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
140                         assert!(input.len() == output.len());
141                         assert!(self.finished == false);
142
143                         output.copy_from_slice(&input);
144                         out_tag.copy_from_slice(&self.tag);
145                         self.finished = true;
146                 }
147         }
148
149         impl AeadDecryptor for ChaCha20Poly1305RFC {
150                 fn decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> bool {
151                         assert!(input.len() == output.len());
152                         assert!(self.finished == false);
153
154                         if tag[..] != self.tag[..] { return false; }
155                         output.copy_from_slice(input);
156                         self.finished = true;
157                         true
158                 }
159         }
160
161         pub struct ChaCha20 {}
162
163         impl ChaCha20 {
164                 pub fn new(key: &[u8], nonce: &[u8]) -> ChaCha20 {
165                         assert!(key.len() == 16 || key.len() == 32);
166                         assert!(nonce.len() == 8 || nonce.len() == 12);
167                         Self {}
168                 }
169         }
170
171         impl SynchronousStreamCipher for ChaCha20 {
172                 fn process(&mut self, input: &[u8], output: &mut [u8]) {
173                         output.copy_from_slice(input);
174                 }
175         }
176 }
177 #[cfg(feature = "fuzztarget")]
178 pub use self::fuzzy_chachapoly::{ChaCha20Poly1305RFC, ChaCha20};