1010// This is a port of Andrew Moons poly1305-donna
1111// https://github.com/floodyberry/poly1305-donna
1212
13+ use ln:: msgs:: DecodeError ;
14+ use util:: ser:: { FixedLengthReader , LengthRead , LengthReadableArgs , Readable , Writeable , Writer } ;
15+ use io:: { self , Read , Write } ;
16+
1317#[ cfg( not( fuzzing) ) ]
1418mod real_chachapoly {
1519 use util:: chacha20:: ChaCha20 ;
@@ -70,6 +74,26 @@ mod real_chachapoly {
7074 self . mac . raw_result ( out_tag) ;
7175 }
7276
77+ // Encrypt `input_output` in-place. To finish and calculate the tag, use `finish_and_get_tag`
78+ // below.
79+ pub ( super ) fn encrypt_in_place ( & mut self , input_output : & mut [ u8 ] ) {
80+ debug_assert ! ( self . finished == false ) ;
81+ self . cipher . process_in_place ( input_output) ;
82+ self . data_len += input_output. len ( ) ;
83+ self . mac . input ( input_output) ;
84+ }
85+
86+ // If we were previously encrypting with `encrypt_in_place`, this method can be used to finish
87+ // encrypting and calculate the tag.
88+ pub ( super ) fn finish_and_get_tag ( & mut self , out_tag : & mut [ u8 ] ) {
89+ debug_assert ! ( self . finished == false ) ;
90+ ChaCha20Poly1305RFC :: pad_mac_16 ( & mut self . mac , self . data_len ) ;
91+ self . finished = true ;
92+ self . mac . input ( & self . aad_len . to_le_bytes ( ) ) ;
93+ self . mac . input ( & ( self . data_len as u64 ) . to_le_bytes ( ) ) ;
94+ self . mac . raw_result ( out_tag) ;
95+ }
96+
7397 pub fn decrypt ( & mut self , input : & [ u8 ] , output : & mut [ u8 ] , tag : & [ u8 ] ) -> bool {
7498 assert ! ( input. len( ) == output. len( ) ) ;
7599 assert ! ( self . finished == false ) ;
@@ -92,11 +116,141 @@ mod real_chachapoly {
92116 false
93117 }
94118 }
119+
120+ // Decrypt in place, without checking the tag. Use `finish_and_check_tag` to check it
121+ // later when decryption finishes.
122+ //
123+ // Should never be `pub` because the public API should always enforce tag checking.
124+ pub ( super ) fn decrypt_in_place ( & mut self , input_output : & mut [ u8 ] ) {
125+ debug_assert ! ( self . finished == false ) ;
126+ self . mac . input ( input_output) ;
127+ self . data_len += input_output. len ( ) ;
128+ self . cipher . process_in_place ( input_output) ;
129+ }
130+
131+ // If we were previously decrypting with `decrypt_in_place`, this method must be used to finish
132+ // decrypting and check the tag. Returns whether or not the tag is valid.
133+ pub ( super ) fn finish_and_check_tag ( & mut self , tag : & [ u8 ] ) -> bool {
134+ debug_assert ! ( self . finished == false ) ;
135+ self . finished = true ;
136+ ChaCha20Poly1305RFC :: pad_mac_16 ( & mut self . mac , self . data_len ) ;
137+ self . mac . input ( & self . aad_len . to_le_bytes ( ) ) ;
138+ self . mac . input ( & ( self . data_len as u64 ) . to_le_bytes ( ) ) ;
139+
140+ let mut calc_tag = [ 0u8 ; 16 ] ;
141+ self . mac . raw_result ( & mut calc_tag) ;
142+ if fixed_time_eq ( & calc_tag, tag) {
143+ true
144+ } else {
145+ false
146+ }
147+ }
95148 }
96149}
97150#[ cfg( not( fuzzing) ) ]
98151pub use self :: real_chachapoly:: ChaCha20Poly1305RFC ;
99152
153+ /// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
154+ struct ChaChaPolyReader < ' a , R : Read > {
155+ pub chacha : & ' a mut ChaCha20Poly1305RFC ,
156+ pub read : R ,
157+ }
158+
159+ impl < ' a , R : Read > Read for ChaChaPolyReader < ' a , R > {
160+ // Decrypt bytes from Self::read into `dest`.
161+ // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
162+ // complete.
163+ fn read ( & mut self , dest : & mut [ u8 ] ) -> Result < usize , io:: Error > {
164+ let res = self . read . read ( dest) ?;
165+ if res > 0 {
166+ self . chacha . decrypt_in_place ( & mut dest[ 0 ..res] ) ;
167+ }
168+ Ok ( res)
169+ }
170+ }
171+
172+ /// Enables simultaneously writing and encrypting a byte stream into a Writer.
173+ struct ChaChaPolyWriter < ' a , W : Writer > {
174+ pub chacha : & ' a mut ChaCha20Poly1305RFC ,
175+ pub write : & ' a mut W ,
176+ }
177+
178+ impl < ' a , W : Writer > Writer for ChaChaPolyWriter < ' a , W > {
179+ // Encrypt then write bytes from `src` into Self::write.
180+ // `ChaCha20Poly1305RFC::finish_and_get_tag` can be called to retrieve the tag after all writes
181+ // complete.
182+ fn write_all ( & mut self , src : & [ u8 ] ) -> Result < ( ) , io:: Error > {
183+ let mut src_idx = 0 ;
184+ while src_idx < src. len ( ) {
185+ let mut write_buffer = [ 0 ; 8192 ] ;
186+ let bytes_written = ( & mut write_buffer[ ..] ) . write ( & src[ src_idx..] ) . expect ( "In-memory writes can't fail" ) ;
187+ self . chacha . encrypt_in_place ( & mut write_buffer[ ..bytes_written] ) ;
188+ self . write . write_all ( & write_buffer[ ..bytes_written] ) ?;
189+ src_idx += bytes_written;
190+ }
191+ Ok ( ( ) )
192+ }
193+ }
194+
195+ /// Enables the use of the serialization macros for objects that need to be simultaneously encrypted and
196+ /// serialized. This allows us to avoid an intermediate Vec allocation.
197+ pub ( crate ) struct ChaChaPolyWriteAdapter < ' a , W : Writeable > {
198+ pub rho : [ u8 ; 32 ] ,
199+ pub writeable : & ' a W ,
200+ }
201+
202+ impl < ' a , W : Writeable > ChaChaPolyWriteAdapter < ' a , W > {
203+ #[ allow( unused) ] // This will be used for onion messages soon
204+ pub fn new ( rho : [ u8 ; 32 ] , writeable : & ' a W ) -> ChaChaPolyWriteAdapter < ' a , W > {
205+ Self { rho, writeable }
206+ }
207+ }
208+
209+ impl < ' a , T : Writeable > Writeable for ChaChaPolyWriteAdapter < ' a , T > {
210+ // Simultaneously write and encrypt Self::writeable.
211+ fn write < W : Writer > ( & self , w : & mut W ) -> Result < ( ) , io:: Error > {
212+ let mut chacha = ChaCha20Poly1305RFC :: new ( & self . rho , & [ 0 ; 12 ] , & [ ] ) ;
213+ let mut chacha_stream = ChaChaPolyWriter { chacha : & mut chacha, write : w } ;
214+ self . writeable . write ( & mut chacha_stream) ?;
215+ let mut tag = [ 0 as u8 ; 16 ] ;
216+ chacha. finish_and_get_tag ( & mut tag) ;
217+ tag. write ( w) ?;
218+
219+ Ok ( ( ) )
220+ }
221+ }
222+
223+ /// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
224+ /// deserialized. This allows us to avoid an intermediate Vec allocation.
225+ pub ( crate ) struct ChaChaPolyReadAdapter < R : Readable > {
226+ #[ allow( unused) ] // This will be used soon for onion messages
227+ pub readable : R ,
228+ }
229+
230+ impl < T : Readable > LengthReadableArgs < [ u8 ; 32 ] > for ChaChaPolyReadAdapter < T > {
231+ // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable.
232+ // LengthRead must be used instead of std::io::Read because we need the total length to separate
233+ // out the tag at the end.
234+ fn read < R : LengthRead > ( mut r : & mut R , secret : [ u8 ; 32 ] ) -> Result < Self , DecodeError > {
235+ if r. total_bytes ( ) < 16 { return Err ( DecodeError :: InvalidValue ) }
236+
237+ let mut chacha = ChaCha20Poly1305RFC :: new ( & secret, & [ 0 ; 12 ] , & [ ] ) ;
238+ let decrypted_len = r. total_bytes ( ) - 16 ;
239+ let s = FixedLengthReader :: new ( & mut r, decrypted_len) ;
240+ let mut chacha_stream = ChaChaPolyReader { chacha : & mut chacha, read : s } ;
241+ let readable: T = Readable :: read ( & mut chacha_stream) ?;
242+ chacha_stream. read . eat_remaining ( ) ?;
243+
244+ let mut tag = [ 0 as u8 ; 16 ] ;
245+ r. read_exact ( & mut tag) ?;
246+ if !chacha. finish_and_check_tag ( & tag) {
247+ return Err ( DecodeError :: InvalidValue )
248+ }
249+
250+ Ok ( Self { readable } )
251+ }
252+ }
253+
100254#[ cfg( fuzzing) ]
101255mod fuzzy_chachapoly {
102256 #[ derive( Clone , Copy ) ]
@@ -130,6 +284,16 @@ mod fuzzy_chachapoly {
130284 self . finished = true ;
131285 }
132286
287+ pub ( super ) fn encrypt_in_place ( & mut self , _input_output : & mut [ u8 ] ) {
288+ assert ! ( self . finished == false ) ;
289+ self . finished = true ;
290+ }
291+
292+ pub ( super ) fn finish_and_get_tag ( & mut self , out_tag : & mut [ u8 ] ) {
293+ out_tag. copy_from_slice ( & self . tag ) ;
294+ self . finished = true ;
295+ }
296+
133297 pub fn decrypt ( & mut self , input : & [ u8 ] , output : & mut [ u8 ] , tag : & [ u8 ] ) -> bool {
134298 assert ! ( input. len( ) == output. len( ) ) ;
135299 assert ! ( self . finished == false ) ;
@@ -139,7 +303,106 @@ mod fuzzy_chachapoly {
139303 self . finished = true ;
140304 true
141305 }
306+
307+ pub ( super ) fn decrypt_in_place ( & mut self , _input : & mut [ u8 ] ) {
308+ assert ! ( self . finished == false ) ;
309+ }
310+
311+ pub ( super ) fn finish_and_check_tag ( & mut self , tag : & [ u8 ] ) -> bool {
312+ if tag[ ..] != self . tag [ ..] { return false ; }
313+ self . finished = true ;
314+ true
315+ }
142316 }
143317}
144318#[ cfg( fuzzing) ]
145319pub use self :: fuzzy_chachapoly:: ChaCha20Poly1305RFC ;
320+
321+ #[ cfg( test) ]
322+ mod tests {
323+ use ln:: msgs:: DecodeError ;
324+ use super :: { ChaChaPolyReadAdapter , ChaChaPolyWriteAdapter } ;
325+ use util:: ser:: { self , FixedLengthReader , LengthReadableArgs , Writeable } ;
326+
327+ // Used for for testing various lengths of serialization.
328+ #[ derive( Debug , PartialEq ) ]
329+ struct TestWriteable {
330+ field1 : Vec < u8 > ,
331+ field2 : Vec < u8 > ,
332+ field3 : Vec < u8 > ,
333+ }
334+ impl_writeable_tlv_based ! ( TestWriteable , {
335+ ( 1 , field1, vec_type) ,
336+ ( 2 , field2, vec_type) ,
337+ ( 3 , field3, vec_type) ,
338+ } ) ;
339+
340+ #[ test]
341+ fn test_chacha_stream_adapters ( ) {
342+ // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an
343+ // encrypted object.
344+ macro_rules! check_object_read_write {
345+ ( $obj: expr) => {
346+ // First, serialize the object, encrypted with ChaCha20Poly1305.
347+ let rho = [ 42 ; 32 ] ;
348+ let writeable_len = $obj. serialized_length( ) as u64 + 16 ;
349+ let write_adapter = ChaChaPolyWriteAdapter :: new( rho, & $obj) ;
350+ let encrypted_writeable_bytes = write_adapter. encode( ) ;
351+ let encrypted_writeable = & encrypted_writeable_bytes[ ..] ;
352+
353+ // Now deserialize the object back and make sure it matches the original.
354+ let mut rd = FixedLengthReader :: new( encrypted_writeable, writeable_len) ;
355+ let read_adapter = <ChaChaPolyReadAdapter <TestWriteable >>:: read( & mut rd, rho) . unwrap( ) ;
356+ assert_eq!( $obj, read_adapter. readable) ;
357+ } ;
358+ }
359+
360+ // Try a big object that will require multiple write buffers.
361+ let big_writeable = TestWriteable {
362+ field1 : vec ! [ 43 ] ,
363+ field2 : vec ! [ 44 ; 4192 ] ,
364+ field3 : vec ! [ 45 ; 4192 + 1 ] ,
365+ } ;
366+ check_object_read_write ! ( big_writeable) ;
367+
368+ // Try a small object that fits into one write buffer.
369+ let small_writeable = TestWriteable {
370+ field1 : vec ! [ 43 ] ,
371+ field2 : vec ! [ 44 ] ,
372+ field3 : vec ! [ 45 ] ,
373+ } ;
374+ check_object_read_write ! ( small_writeable) ;
375+ }
376+
377+ fn do_chacha_stream_adapters_ser_macros ( ) -> Result < ( ) , DecodeError > {
378+ let writeable = TestWriteable {
379+ field1 : vec ! [ 43 ] ,
380+ field2 : vec ! [ 44 ; 4192 ] ,
381+ field3 : vec ! [ 45 ; 4192 + 1 ] ,
382+ } ;
383+
384+ // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305.
385+ let rho = [ 42 ; 32 ] ;
386+ let write_adapter = ChaChaPolyWriteAdapter :: new ( rho, & writeable) ;
387+ let mut writer = ser:: VecWriter ( Vec :: new ( ) ) ;
388+ encode_tlv_stream ! ( & mut writer, {
389+ ( 1 , write_adapter, required) ,
390+ } ) ;
391+
392+ // Now deserialize the object back and make sure it matches the original.
393+ let mut read_adapter: Option < ChaChaPolyReadAdapter < TestWriteable > > = None ;
394+ decode_tlv_stream ! ( & writer. 0 [ ..] , {
395+ ( 1 , read_adapter, ( option: LengthReadableArgs , rho) ) ,
396+ } ) ;
397+ assert_eq ! ( writeable, read_adapter. unwrap( ) . readable) ;
398+
399+ Ok ( ( ) )
400+ }
401+
402+ #[ test]
403+ fn chacha_stream_adapters_ser_macros ( ) {
404+ // Test that our stream adapters work as expected with the TLV macros.
405+ // This also serves to test the `option: $trait` variant of the `decode_tlv` ser macro.
406+ do_chacha_stream_adapters_ser_macros ( ) . unwrap ( )
407+ }
408+ }
0 commit comments