]> git.bitcoin.ninja Git - rust-lightning/blob - lightning/src/crypto/chacha20poly1305rfc.rs
Merge pull request #2905 from tnull/2024-02-expose-init-features
[rust-lightning] / lightning / src / crypto / 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 #[cfg(not(fuzzing))]
14 mod real_chachapoly {
15         use super::super::chacha20::ChaCha20;
16         use super::super::poly1305::Poly1305;
17         use super::super::fixed_time_eq;
18
19         #[derive(Clone, Copy)]
20         pub struct ChaCha20Poly1305RFC {
21                 cipher: ChaCha20,
22                 mac: Poly1305,
23                 finished: bool,
24                 data_len: usize,
25                 aad_len: u64,
26         }
27
28         impl ChaCha20Poly1305RFC {
29                 #[inline]
30                 fn pad_mac_16(mac: &mut Poly1305, len: usize) {
31                         if len % 16 != 0 {
32                                 mac.input(&[0; 16][0..16 - (len % 16)]);
33                         }
34                 }
35                 pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
36                         assert!(key.len() == 16 || key.len() == 32);
37                         assert!(nonce.len() == 12);
38
39                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
40                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
41
42                         let mut cipher = ChaCha20::new(key, &nonce[4..]);
43                         let mut mac_key = [0u8; 64];
44                         let zero_key = [0u8; 64];
45                         cipher.process(&zero_key, &mut mac_key);
46
47                         let mut mac = Poly1305::new(&mac_key[..32]);
48                         mac.input(aad);
49                         ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
50
51                         ChaCha20Poly1305RFC {
52                                 cipher,
53                                 mac,
54                                 finished: false,
55                                 data_len: 0,
56                                 aad_len: aad.len() as u64,
57                         }
58                 }
59
60                 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
61                         assert!(input.len() == output.len());
62                         assert!(self.finished == false);
63                         self.cipher.process(input, output);
64                         self.data_len += input.len();
65                         self.mac.input(output);
66                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
67                         self.finished = true;
68                         self.mac.input(&self.aad_len.to_le_bytes());
69                         self.mac.input(&(self.data_len as u64).to_le_bytes());
70                         self.mac.raw_result(out_tag);
71                 }
72
73                 pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) {
74                         self.encrypt_in_place(input_output);
75                         self.finish_and_get_tag(out_tag);
76                 }
77
78                 // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
79                 // below.
80                 pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
81                         debug_assert!(self.finished == false);
82                         self.cipher.process_in_place(input_output);
83                         self.data_len += input_output.len();
84                         self.mac.input(input_output);
85                 }
86
87                 // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
88                 // encrypting and calculate the tag.
89                 pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
90                         debug_assert!(self.finished == false);
91                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
92                         self.finished = true;
93                         self.mac.input(&self.aad_len.to_le_bytes());
94                         self.mac.input(&(self.data_len as u64).to_le_bytes());
95                         self.mac.raw_result(out_tag);
96                 }
97
98                 /// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents
99                 /// into `output`. Note that, because `output` is not touched until the `tag` is checked,
100                 /// this decryption is *variable time*.
101                 pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> {
102                         assert!(input.len() == output.len());
103                         assert!(self.finished == false);
104
105                         self.finished = true;
106
107                         self.mac.input(input);
108
109                         self.data_len += input.len();
110                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
111                         self.mac.input(&self.aad_len.to_le_bytes());
112                         self.mac.input(&(self.data_len as u64).to_le_bytes());
113
114                         let mut calc_tag =  [0u8; 16];
115                         self.mac.raw_result(&mut calc_tag);
116                         if fixed_time_eq(&calc_tag, tag) {
117                                 self.cipher.process(input, output);
118                                 Ok(())
119                         } else {
120                                 Err(())
121                         }
122                 }
123
124                 pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> {
125                         self.decrypt_in_place(input_output);
126                         if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) }
127                 }
128
129                 /// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it
130                 /// later when decryption finishes.
131                 ///
132                 /// Should never be `pub` because the public API should always enforce tag checking.
133                 pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) {
134                         debug_assert!(self.finished == false);
135                         self.mac.input(input_output);
136                         self.data_len += input_output.len();
137                         self.cipher.process_in_place(input_output);
138                 }
139
140                 /// If we were previously decrypting with `just_decrypt_in_place`, this method must be used
141                 /// to check the tag. Returns whether or not the tag is valid.
142                 pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
143                         debug_assert!(self.finished == false);
144                         self.finished = true;
145                         ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
146                         self.mac.input(&self.aad_len.to_le_bytes());
147                         self.mac.input(&(self.data_len as u64).to_le_bytes());
148
149                         let mut calc_tag =  [0u8; 16];
150                         self.mac.raw_result(&mut calc_tag);
151                         if fixed_time_eq(&calc_tag, tag) {
152                                 true
153                         } else {
154                                 false
155                         }
156                 }
157         }
158 }
159 #[cfg(not(fuzzing))]
160 pub use self::real_chachapoly::ChaCha20Poly1305RFC;
161
162 #[cfg(fuzzing)]
163 mod fuzzy_chachapoly {
164         #[derive(Clone, Copy)]
165         pub struct ChaCha20Poly1305RFC {
166                 tag: [u8; 16],
167                 finished: bool,
168         }
169         impl ChaCha20Poly1305RFC {
170                 pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
171                         assert!(key.len() == 16 || key.len() == 32);
172                         assert!(nonce.len() == 12);
173
174                         // Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
175                         assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
176
177                         let mut tag = [0; 16];
178                         tag.copy_from_slice(&key[0..16]);
179
180                         ChaCha20Poly1305RFC {
181                                 tag,
182                                 finished: false,
183                         }
184                 }
185
186                 pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
187                         assert!(input.len() == output.len());
188                         assert!(self.finished == false);
189
190                         output.copy_from_slice(&input);
191                         out_tag.copy_from_slice(&self.tag);
192                         self.finished = true;
193                 }
194
195                 pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) {
196                         self.encrypt_in_place(input_output);
197                         self.finish_and_get_tag(out_tag);
198                 }
199
200                 pub(in super::super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) {
201                         assert!(self.finished == false);
202                 }
203
204                 pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
205                         assert!(self.finished == false);
206                         out_tag.copy_from_slice(&self.tag);
207                         self.finished = true;
208                 }
209
210                 pub fn variable_time_decrypt(&mut self, input: &[u8], output: &mut [u8], tag: &[u8]) -> Result<(), ()> {
211                         assert!(input.len() == output.len());
212                         assert!(self.finished == false);
213
214                         if tag[..] != self.tag[..] { return Err(()); }
215                         output.copy_from_slice(input);
216                         self.finished = true;
217                         Ok(())
218                 }
219
220                 pub fn check_decrypt_in_place(&mut self, input_output: &mut [u8], tag: &[u8]) -> Result<(), ()> {
221                         self.decrypt_in_place(input_output);
222                         if self.finish_and_check_tag(tag) { Ok(()) } else { Err(()) }
223                 }
224
225                 pub(in super::super) fn decrypt_in_place(&mut self, _input: &mut [u8]) {
226                         assert!(self.finished == false);
227                 }
228
229                 pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
230                         if tag[..] != self.tag[..] { return false; }
231                         self.finished = true;
232                         true
233                 }
234         }
235 }
236 #[cfg(fuzzing)]
237 pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;