Skip to content

Commit 75a54f7

Browse files
TheBlueMattshaavan
authored andcommitted
Move Poly1305 fuzzing logic from chacha20poly1305rfc.rs
Rather than skipping compilation of `poly1305.rs` when building for fuzzing and relying on `ChaCha20Poly1305` to do the fuzzing variants, implement an actual fuzz wrapper in `poly1305.rs`, keeping the same fuzz MAC structure that we already have. We also add a fuzzing implementation of `fixed_time_eq` which does a simple comparison, to allow the fuzzer to "see into" the comparison in some cases. Best reviewed with `-b`.
1 parent e0288e7 commit 75a54f7

File tree

3 files changed

+524
-576
lines changed

3 files changed

+524
-576
lines changed

lightning/src/crypto/chacha20poly1305rfc.rs

Lines changed: 122 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -10,247 +10,148 @@
1010
// This is a port of Andrew Moons poly1305-donna
1111
// https://github.com/floodyberry/poly1305-donna
1212

13-
#[cfg(not(fuzzing))]
14-
mod real_chachapoly {
15-
use super::super::chacha20::ChaCha20;
16-
use super::super::fixed_time_eq;
17-
use super::super::poly1305::Poly1305;
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);
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-
out_tag.copy_from_slice(&self.mac.result());
71-
}
72-
73-
pub fn encrypt_full_message_in_place(
74-
&mut self, input_output: &mut [u8], out_tag: &mut [u8],
75-
) {
76-
self.encrypt_in_place(input_output);
77-
self.finish_and_get_tag(out_tag);
78-
}
79-
80-
// Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
81-
// below.
82-
pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
83-
debug_assert!(!self.finished);
84-
self.cipher.process_in_place(input_output);
85-
self.data_len += input_output.len();
86-
self.mac.input(input_output);
87-
}
88-
89-
// If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
90-
// encrypting and calculate the tag.
91-
pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
92-
debug_assert!(!self.finished);
93-
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
94-
self.finished = true;
95-
self.mac.input(&self.aad_len.to_le_bytes());
96-
self.mac.input(&(self.data_len as u64).to_le_bytes());
97-
out_tag.copy_from_slice(&self.mac.result());
98-
}
99-
100-
/// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents
101-
/// into `output`. Note that, because `output` is not touched until the `tag` is checked,
102-
/// this decryption is *variable time*.
103-
pub fn variable_time_decrypt(
104-
&mut self, input: &[u8], output: &mut [u8], tag: &[u8],
105-
) -> Result<(), ()> {
106-
assert!(input.len() == output.len());
107-
assert!(!self.finished);
108-
109-
self.finished = true;
110-
111-
self.mac.input(input);
112-
113-
self.data_len += input.len();
114-
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
115-
self.mac.input(&self.aad_len.to_le_bytes());
116-
self.mac.input(&(self.data_len as u64).to_le_bytes());
117-
118-
let calc_tag = self.mac.result();
119-
if fixed_time_eq(&calc_tag, tag) {
120-
self.cipher.process(input, output);
121-
Ok(())
122-
} else {
123-
Err(())
124-
}
125-
}
126-
127-
pub fn check_decrypt_in_place(
128-
&mut self, input_output: &mut [u8], tag: &[u8],
129-
) -> Result<(), ()> {
130-
self.decrypt_in_place(input_output);
131-
if self.finish_and_check_tag(tag) {
132-
Ok(())
133-
} else {
134-
Err(())
135-
}
136-
}
137-
138-
/// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it
139-
/// later when decryption finishes.
140-
///
141-
/// Should never be `pub` because the public API should always enforce tag checking.
142-
pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) {
143-
debug_assert!(!self.finished);
144-
self.mac.input(input_output);
145-
self.data_len += input_output.len();
146-
self.cipher.process_in_place(input_output);
147-
}
148-
149-
/// If we were previously decrypting with `just_decrypt_in_place`, this method must be used
150-
/// to check the tag. Returns whether or not the tag is valid.
151-
pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
152-
debug_assert!(!self.finished);
153-
self.finished = true;
154-
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
155-
self.mac.input(&self.aad_len.to_le_bytes());
156-
self.mac.input(&(self.data_len as u64).to_le_bytes());
13+
use super::chacha20::ChaCha20;
14+
use super::fixed_time_eq;
15+
use super::poly1305::Poly1305;
16+
17+
pub struct ChaCha20Poly1305RFC {
18+
cipher: ChaCha20,
19+
mac: Poly1305,
20+
finished: bool,
21+
data_len: usize,
22+
aad_len: u64,
23+
}
15724

158-
let calc_tag = self.mac.result();
159-
if fixed_time_eq(&calc_tag, tag) {
160-
true
161-
} else {
162-
false
163-
}
25+
impl ChaCha20Poly1305RFC {
26+
#[inline]
27+
fn pad_mac_16(mac: &mut Poly1305, len: usize) {
28+
if len % 16 != 0 {
29+
mac.input(&[0; 16][0..16 - (len % 16)]);
16430
}
16531
}
166-
}
167-
#[cfg(not(fuzzing))]
168-
pub use self::real_chachapoly::ChaCha20Poly1305RFC;
169-
170-
#[cfg(fuzzing)]
171-
mod fuzzy_chachapoly {
172-
#[derive(Clone, Copy)]
173-
pub struct ChaCha20Poly1305RFC {
174-
tag: [u8; 16],
175-
finished: bool,
32+
pub fn new(key: &[u8], nonce: &[u8], aad: &[u8]) -> ChaCha20Poly1305RFC {
33+
assert!(key.len() == 16 || key.len() == 32);
34+
assert!(nonce.len() == 12);
35+
36+
// Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
37+
assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
38+
39+
let mut cipher = ChaCha20::new(key, &nonce[4..]);
40+
let mut mac_key = [0u8; 64];
41+
let zero_key = [0u8; 64];
42+
cipher.process(&zero_key, &mut mac_key);
43+
44+
#[cfg(not(fuzzing))]
45+
let mut mac = Poly1305::new(&mac_key[..32]);
46+
#[cfg(fuzzing)]
47+
let mut mac = Poly1305::new(&key);
48+
mac.input(aad);
49+
ChaCha20Poly1305RFC::pad_mac_16(&mut mac, aad.len());
50+
51+
ChaCha20Poly1305RFC { cipher, mac, finished: false, data_len: 0, aad_len: aad.len() as u64 }
17652
}
177-
impl ChaCha20Poly1305RFC {
178-
pub fn new(key: &[u8], nonce: &[u8], _aad: &[u8]) -> ChaCha20Poly1305RFC {
179-
assert!(key.len() == 16 || key.len() == 32);
180-
assert!(nonce.len() == 12);
181-
182-
// Ehh, I'm too lazy to *also* tweak ChaCha20 to make it RFC-compliant
183-
assert!(nonce[0] == 0 && nonce[1] == 0 && nonce[2] == 0 && nonce[3] == 0);
18453

185-
let mut tag = [0; 16];
186-
tag.copy_from_slice(&key[0..16]);
54+
pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
55+
assert!(input.len() == output.len());
56+
assert!(!self.finished);
57+
self.cipher.process(input, output);
58+
self.data_len += input.len();
59+
self.mac.input(output);
60+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
61+
self.finished = true;
62+
self.mac.input(&self.aad_len.to_le_bytes());
63+
self.mac.input(&(self.data_len as u64).to_le_bytes());
64+
out_tag.copy_from_slice(&self.mac.result());
65+
}
18766

188-
ChaCha20Poly1305RFC { tag, finished: false }
189-
}
67+
pub fn encrypt_full_message_in_place(&mut self, input_output: &mut [u8], out_tag: &mut [u8]) {
68+
self.encrypt_in_place(input_output);
69+
self.finish_and_get_tag(out_tag);
70+
}
19071

191-
pub fn encrypt(&mut self, input: &[u8], output: &mut [u8], out_tag: &mut [u8]) {
192-
assert!(input.len() == output.len());
193-
assert!(self.finished == false);
72+
// Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
73+
// below.
74+
pub(in super::super) fn encrypt_in_place(&mut self, input_output: &mut [u8]) {
75+
debug_assert!(!self.finished);
76+
self.cipher.process_in_place(input_output);
77+
self.data_len += input_output.len();
78+
self.mac.input(input_output);
79+
}
19480

195-
output.copy_from_slice(&input);
196-
out_tag.copy_from_slice(&self.tag);
197-
self.finished = true;
198-
}
81+
// If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
82+
// encrypting and calculate the tag.
83+
pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
84+
debug_assert!(!self.finished);
85+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
86+
self.finished = true;
87+
self.mac.input(&self.aad_len.to_le_bytes());
88+
self.mac.input(&(self.data_len as u64).to_le_bytes());
89+
out_tag.copy_from_slice(&self.mac.result());
90+
}
19991

200-
pub fn encrypt_full_message_in_place(
201-
&mut self, input_output: &mut [u8], out_tag: &mut [u8],
202-
) {
203-
self.encrypt_in_place(input_output);
204-
self.finish_and_get_tag(out_tag);
205-
}
92+
/// Decrypt the `input`, checking the given `tag` prior to writing the decrypted contents
93+
/// into `output`. Note that, because `output` is not touched until the `tag` is checked,
94+
/// this decryption is *variable time*.
95+
pub fn variable_time_decrypt(
96+
&mut self, input: &[u8], output: &mut [u8], tag: &[u8],
97+
) -> Result<(), ()> {
98+
assert!(input.len() == output.len());
99+
assert!(!self.finished);
206100

207-
pub(in super::super) fn encrypt_in_place(&mut self, _input_output: &mut [u8]) {
208-
assert!(self.finished == false);
209-
}
101+
self.finished = true;
210102

211-
pub(in super::super) fn finish_and_get_tag(&mut self, out_tag: &mut [u8]) {
212-
assert!(self.finished == false);
213-
out_tag.copy_from_slice(&self.tag);
214-
self.finished = true;
215-
}
103+
self.mac.input(input);
216104

217-
pub fn variable_time_decrypt(
218-
&mut self, input: &[u8], output: &mut [u8], tag: &[u8],
219-
) -> Result<(), ()> {
220-
assert!(input.len() == output.len());
221-
assert!(self.finished == false);
105+
self.data_len += input.len();
106+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
107+
self.mac.input(&self.aad_len.to_le_bytes());
108+
self.mac.input(&(self.data_len as u64).to_le_bytes());
222109

223-
if tag[..] != self.tag[..] {
224-
return Err(());
225-
}
226-
output.copy_from_slice(input);
227-
self.finished = true;
110+
let calc_tag = self.mac.result();
111+
if fixed_time_eq(&calc_tag, tag) {
112+
self.cipher.process(input, output);
228113
Ok(())
114+
} else {
115+
Err(())
229116
}
117+
}
230118

231-
pub fn check_decrypt_in_place(
232-
&mut self, input_output: &mut [u8], tag: &[u8],
233-
) -> Result<(), ()> {
234-
self.decrypt_in_place(input_output);
235-
if self.finish_and_check_tag(tag) {
236-
Ok(())
237-
} else {
238-
Err(())
239-
}
119+
pub fn check_decrypt_in_place(
120+
&mut self, input_output: &mut [u8], tag: &[u8],
121+
) -> Result<(), ()> {
122+
self.decrypt_in_place(input_output);
123+
if self.finish_and_check_tag(tag) {
124+
Ok(())
125+
} else {
126+
Err(())
240127
}
128+
}
241129

242-
pub(in super::super) fn decrypt_in_place(&mut self, _input: &mut [u8]) {
243-
assert!(self.finished == false);
244-
}
130+
/// Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it
131+
/// later when decryption finishes.
132+
///
133+
/// Should never be `pub` because the public API should always enforce tag checking.
134+
pub(in super::super) fn decrypt_in_place(&mut self, input_output: &mut [u8]) {
135+
debug_assert!(!self.finished);
136+
self.mac.input(input_output);
137+
self.data_len += input_output.len();
138+
self.cipher.process_in_place(input_output);
139+
}
245140

246-
pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
247-
if tag[..] != self.tag[..] {
248-
return false;
249-
}
250-
self.finished = true;
141+
/// If we were previously decrypting with `just_decrypt_in_place`, this method must be used
142+
/// to check the tag. Returns whether or not the tag is valid.
143+
pub(in super::super) fn finish_and_check_tag(&mut self, tag: &[u8]) -> bool {
144+
debug_assert!(!self.finished);
145+
self.finished = true;
146+
ChaCha20Poly1305RFC::pad_mac_16(&mut self.mac, self.data_len);
147+
self.mac.input(&self.aad_len.to_le_bytes());
148+
self.mac.input(&(self.data_len as u64).to_le_bytes());
149+
150+
let calc_tag = self.mac.result();
151+
if fixed_time_eq(&calc_tag, tag) {
251152
true
153+
} else {
154+
false
252155
}
253156
}
254157
}
255-
#[cfg(fuzzing)]
256-
pub use self::fuzzy_chachapoly::ChaCha20Poly1305RFC;

lightning/src/crypto/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#[cfg(not(fuzzing))]
2-
use bitcoin::hashes::cmp::fixed_time_eq;
2+
pub(crate) use bitcoin::hashes::cmp::fixed_time_eq;
3+
4+
#[cfg(fuzzing)]
5+
fn fixed_time_eq(a: &[u8], b: &[u8]) -> bool {
6+
assert_eq!(a.len(), b.len());
7+
a == b
8+
}
39

410
pub(crate) mod chacha20;
511
pub(crate) mod chacha20poly1305rfc;
6-
#[cfg(not(fuzzing))]
712
pub(crate) mod poly1305;
813
pub(crate) mod streams;
914
pub(crate) mod utils;

0 commit comments

Comments
 (0)