Skip to content

Commit 5a6d96d

Browse files
committed
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).
1 parent 12c9fcd commit 5a6d96d

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

lightning/src/crypto/streams.rs

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

46
use crate::io::{self, Read, Write};
57
use crate::ln::msgs::DecodeError;
@@ -49,6 +51,104 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
4951
}
5052
}
5153

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

0 commit comments

Comments
 (0)