Skip to content

Commit ba0ea9a

Browse files
TheBlueMattshaavan
authored andcommitted
Add a ChaChaDualPolyReadAdapter that allows an optional AAD
`ChaChaPolyReadAdapter` decodes an arbitrary object and checks the poly1305 tag. In the coming commits, we'll need a variant of this which allows for an *optional* AAD in the poly1305 tag, accepting either tag as valid, but indicating to the caller whether the AAD was used. We could use the actual AAD setup in poly1305, which puts the AAD first in the MAC (and then pads it out to a multiple of 16 bytes), but since we're gonna check both with and without, its nice to instead put the AAD at the end, enabling us to only calculate most of the hash once before cloning its state and adding the AAD block. We do this by swapping the AAD and the data being MAC'd in the AAD-containing MAC check (but leaving them where they belong for the non-AAD-containing MAC check). We also add a corresponding `chachapoly_encrypt_with_swapped_aad` which allows encrypting with the new MAC format.
1 parent 75a54f7 commit ba0ea9a

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

lightning/src/crypto/streams.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use crate::crypto::chacha20::ChaCha20;
22
use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
3+
use crate::crypto::fixed_time_eq;
4+
use crate::crypto::poly1305::Poly1305;
35

46
use crate::io::{self, Read, Write};
57
use crate::ln::msgs::DecodeError;
68
use crate::util::ser::{
79
FixedLengthReader, LengthLimitedRead, LengthReadableArgs, Readable, Writeable, Writer,
810
};
911

12+
use alloc::vec::Vec;
13+
1014
pub(crate) struct ChaChaReader<'a, R: io::Read> {
1115
pub chacha: &'a mut ChaCha20,
1216
pub read: R,
@@ -49,6 +53,132 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
4953
}
5054
}
5155

56+
/// Encrypts the provided plaintext with the given key using ChaCha20Poly1305 in the modified
57+
/// with-AAD form used in [`ChaChaDualPolyReadAdapter`].
58+
pub(crate) fn chachapoly_encrypt_with_swapped_aad(
59+
mut plaintext: Vec<u8>, key: [u8; 32], aad: [u8; 32],
60+
) -> Vec<u8> {
61+
let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
62+
let mut mac_key = [0u8; 64];
63+
chacha.process_in_place(&mut mac_key);
64+
65+
let mut mac = Poly1305::new(&mac_key[..32]);
66+
chacha.process_in_place(&mut plaintext[..]);
67+
mac.input(&plaintext[..]);
68+
69+
if plaintext.len() % 16 != 0 {
70+
mac.input(&[0; 16][0..16 - (plaintext.len() % 16)]);
71+
}
72+
73+
mac.input(&aad[..]);
74+
// Note that we don't need to pad the AAD since its a multiple of 16 bytes
75+
76+
mac.input(&(plaintext.len() as u64).to_le_bytes());
77+
mac.input(&32u64.to_le_bytes());
78+
79+
plaintext.extend_from_slice(&mac.result());
80+
plaintext
81+
}
82+
83+
/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted
84+
/// and deserialized. This allows us to avoid an intermediate Vec allocation.
85+
///
86+
/// This variant of [`ChaChaPolyReadAdapter`] calculates Poly1305 tags twice, once using the given
87+
/// key and once with the given 32-byte AAD appended after the encrypted stream, accepting either
88+
/// being correct as sufficient.
89+
///
90+
/// Note that we do *not* use the provided AAD as the standard ChaCha20Poly1305 AAD as that would
91+
/// require placing it first and prevent us from avoiding redundant Poly1305 rounds. Instead, the
92+
/// ChaCha20Poly1305 MAC check is tweaked to move the AAD to *after* the the contents being
93+
/// checked, effectively treating the contents as the AAD for the AAD-containing MAC but behaving
94+
/// like classic ChaCha20Poly1305 for the non-AAD-containing MAC.
95+
pub(crate) struct ChaChaDualPolyReadAdapter<R: Readable> {
96+
pub readable: R,
97+
pub used_aad: bool,
98+
}
99+
100+
impl<T: Readable> LengthReadableArgs<([u8; 32], [u8; 32])> for ChaChaDualPolyReadAdapter<T> {
101+
// Simultaneously read and decrypt an object from a LengthLimitedRead storing it in
102+
// Self::readable. LengthLimitedRead must be used instead of std::io::Read because we need the
103+
// total length to separate out the tag at the end.
104+
fn read<R: LengthLimitedRead>(
105+
r: &mut R, params: ([u8; 32], [u8; 32]),
106+
) -> Result<Self, DecodeError> {
107+
if r.remaining_bytes() < 16 {
108+
return Err(DecodeError::InvalidValue);
109+
}
110+
let (key, aad) = params;
111+
112+
let mut chacha = ChaCha20::new(&key[..], &[0; 12]);
113+
let mut mac_key = [0u8; 64];
114+
chacha.process_in_place(&mut mac_key);
115+
116+
#[cfg(not(fuzzing))]
117+
let mut mac = Poly1305::new(&mac_key[..32]);
118+
#[cfg(fuzzing)]
119+
let mut mac = Poly1305::new(&key);
120+
121+
let decrypted_len = r.remaining_bytes() - 16;
122+
let s = FixedLengthReader::new(r, decrypted_len);
123+
let mut chacha_stream =
124+
ChaChaDualPolyReader { chacha: &mut chacha, poly: &mut mac, read_len: 0, read: s };
125+
126+
let readable: T = Readable::read(&mut chacha_stream)?;
127+
chacha_stream.read.eat_remaining()?;
128+
129+
let read_len = chacha_stream.read_len;
130+
131+
if read_len % 16 != 0 {
132+
mac.input(&[0; 16][0..16 - (read_len % 16)]);
133+
}
134+
135+
let mut mac_aad = mac;
136+
137+
mac_aad.input(&aad[..]);
138+
// Note that we don't need to pad the AAD since its a multiple of 16 bytes
139+
140+
// For the AAD-containing MAC, swap the AAD and the read data, effectively.
141+
mac_aad.input(&(read_len as u64).to_le_bytes());
142+
mac_aad.input(&32u64.to_le_bytes());
143+
144+
// For the non-AAD-containing MAC, leave the data and AAD where they belong.
145+
mac.input(&0u64.to_le_bytes());
146+
mac.input(&(read_len as u64).to_le_bytes());
147+
148+
let mut tag = [0 as u8; 16];
149+
r.read_exact(&mut tag)?;
150+
if fixed_time_eq(&mac.result(), &tag) {
151+
Ok(Self { readable, used_aad: false })
152+
} else if fixed_time_eq(&mac_aad.result(), &tag) {
153+
Ok(Self { readable, used_aad: true })
154+
} else {
155+
return Err(DecodeError::InvalidValue);
156+
}
157+
}
158+
}
159+
160+
struct ChaChaDualPolyReader<'a, R: Read> {
161+
chacha: &'a mut ChaCha20,
162+
poly: &'a mut Poly1305,
163+
read_len: usize,
164+
pub read: R,
165+
}
166+
167+
impl<'a, R: Read> Read for ChaChaDualPolyReader<'a, R> {
168+
// Decrypts bytes from Self::read into `dest`.
169+
// After all reads complete, the caller must compare the expected tag with
170+
// the result of `Poly1305::result()`.
171+
fn read(&mut self, dest: &mut [u8]) -> Result<usize, io::Error> {
172+
let res = self.read.read(dest)?;
173+
if res > 0 {
174+
self.poly.input(&dest[0..res]);
175+
self.chacha.process_in_place(&mut dest[0..res]);
176+
self.read_len += res;
177+
}
178+
Ok(res)
179+
}
180+
}
181+
52182
/// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
53183
/// deserialized. This allows us to avoid an intermediate Vec allocation.
54184
pub(crate) struct ChaChaPolyReadAdapter<R: Readable> {

0 commit comments

Comments
 (0)