10
10
// This is a port of Andrew Moons poly1305-donna
11
11
// https://github.com/floodyberry/poly1305-donna
12
12
13
- use util:: ser:: { Writeable , Writer } ;
14
- use io:: { self , Write } ;
13
+ use ln:: msgs:: DecodeError ;
14
+ use util:: ser:: { FixedLengthReader , LengthRead , LengthReadableArgs , Readable , Writeable , Writer } ;
15
+ use io:: { self , Read , Write } ;
15
16
16
17
#[ cfg( not( fuzzing) ) ]
17
18
mod real_chachapoly {
@@ -93,22 +94,48 @@ mod real_chachapoly {
93
94
}
94
95
95
96
pub fn decrypt ( & mut self , input : & [ u8 ] , output : & mut [ u8 ] , tag : & [ u8 ] ) -> bool {
96
- assert ! ( input. len( ) == output. len( ) ) ;
97
- assert ! ( self . finished == false ) ;
97
+ if self . decrypt_inner ( input, Some ( output) , Some ( tag) ) {
98
+ self . cipher . process ( input, output) ;
99
+ return true
100
+ }
101
+ false
102
+ }
98
103
99
- self . finished = true ;
104
+ // Decrypt in place, and check the tag if it's provided. If the tag is not provided, then
105
+ // `finish_and_check_tag` may be called to check it later.
106
+ pub fn decrypt_in_place ( & mut self , input : & mut [ u8 ] , tag : Option < & [ u8 ] > ) -> bool {
107
+ if self . decrypt_inner ( input, None , tag) {
108
+ self . cipher . process_in_place ( input) ;
109
+ return true
110
+ }
111
+ false
112
+ }
113
+
114
+ fn decrypt_inner ( & mut self , input : & [ u8 ] , output : Option < & mut [ u8 ] > , tag : Option < & [ u8 ] > ) -> bool {
115
+ if let Some ( output) = output {
116
+ assert ! ( input. len( ) == output. len( ) ) ;
117
+ }
118
+ assert ! ( self . finished == false ) ;
100
119
101
120
self . mac . input ( input) ;
102
121
103
122
self . data_len += input. len ( ) ;
123
+
124
+ if let Some ( tag) = tag {
125
+ return self . finish_and_check_tag ( tag)
126
+ }
127
+ true
128
+ }
129
+
130
+ pub fn finish_and_check_tag ( & mut self , tag : & [ u8 ] ) -> bool {
131
+ self . finished = true ;
104
132
ChaCha20Poly1305RFC :: pad_mac_16 ( & mut self . mac , self . data_len ) ;
105
133
self . mac . input ( & self . aad_len . to_le_bytes ( ) ) ;
106
134
self . mac . input ( & ( self . data_len as u64 ) . to_le_bytes ( ) ) ;
107
135
108
136
let mut calc_tag = [ 0u8 ; 16 ] ;
109
137
self . mac . raw_result ( & mut calc_tag) ;
110
138
if fixed_time_eq ( & calc_tag, tag) {
111
- self . cipher . process ( input, output) ;
112
139
true
113
140
} else {
114
141
false
@@ -119,6 +146,25 @@ mod real_chachapoly {
119
146
#[ cfg( not( fuzzing) ) ]
120
147
pub use self :: real_chachapoly:: ChaCha20Poly1305RFC ;
121
148
149
+ /// Enables simultaneously reading and decrypting a ChaCha20Poly1305RFC stream from a std::io::Read.
150
+ pub ( crate ) struct ChaChaPolyReader < ' a , R : Read > {
151
+ pub chacha : & ' a mut ChaCha20Poly1305RFC ,
152
+ pub read : R ,
153
+ }
154
+
155
+ impl < ' a , R : Read > Read for ChaChaPolyReader < ' a , R > {
156
+ // Decrypt bytes from Self::read into `dest`.
157
+ // `ChaCha20Poly1305RFC::finish_and_check_tag` must be called to check the tag after all reads
158
+ // complete.
159
+ fn read ( & mut self , dest : & mut [ u8 ] ) -> Result < usize , io:: Error > {
160
+ let res = self . read . read ( dest) ?;
161
+ if res > 0 {
162
+ self . chacha . decrypt_in_place ( & mut dest[ 0 ..res] , None ) ;
163
+ }
164
+ Ok ( res)
165
+ }
166
+ }
167
+
122
168
/// Enables simultaneously writing and encrypting a byte stream into a Writer.
123
169
pub ( crate ) struct ChaChaPolyWriter < ' a , W : Writer > {
124
170
pub chacha : & ' a mut ChaCha20Poly1305RFC ,
@@ -173,6 +219,36 @@ impl<'a, T: Writeable> Writeable for ChaChaPolyWriteAdapter<'a, T> {
173
219
}
174
220
}
175
221
222
+ /// Enables the use of the serialization macros for objects that need to be simultaneously decrypted and
223
+ /// deserialized. This allows us to avoid an intermediate Vec allocation.
224
+ pub ( crate ) struct ChaChaPolyReadAdapter < R : Readable > {
225
+ #[ allow( unused) ] // This will be used soon for onion messages
226
+ pub readable : R ,
227
+ }
228
+
229
+ impl < T : Readable > LengthReadableArgs < [ u8 ; 32 ] > for ChaChaPolyReadAdapter < T > {
230
+ // Simultaneously read and decrypt an object from a LengthRead, storing it in Self::readable.
231
+ // LengthRead must be used instead of std::io::Read because we need the total length to separate
232
+ // out the tag at the end.
233
+ fn read < R : LengthRead > ( mut r : & mut R , secret : [ u8 ; 32 ] ) -> Result < Self , DecodeError > {
234
+ if r. total_bytes ( ) < 16 { return Err ( DecodeError :: InvalidValue ) }
235
+
236
+ let mut chacha = ChaCha20Poly1305RFC :: new ( & secret, & [ 0 ; 12 ] , & [ ] ) ;
237
+ let decrypted_len = r. total_bytes ( ) - 16 ;
238
+ let s = FixedLengthReader :: new ( & mut r, decrypted_len) ;
239
+ let mut chacha_stream = ChaChaPolyReader { chacha : & mut chacha, read : s } ;
240
+ let readable: T = Readable :: read ( & mut chacha_stream) ?;
241
+
242
+ let mut tag = [ 0 as u8 ; 16 ] ;
243
+ r. read_exact ( & mut tag) ?;
244
+ if !chacha. finish_and_check_tag ( & tag) {
245
+ return Err ( DecodeError :: InvalidValue )
246
+ }
247
+
248
+ Ok ( Self { readable } )
249
+ }
250
+ }
251
+
176
252
#[ cfg( fuzzing) ]
177
253
mod fuzzy_chachapoly {
178
254
#[ derive( Clone , Copy ) ]
@@ -228,7 +304,111 @@ mod fuzzy_chachapoly {
228
304
self . finished = true ;
229
305
true
230
306
}
307
+
308
+ pub fn decrypt_in_place ( & mut self , _input : & mut [ u8 ] , tag : Option < & [ u8 ] > ) -> bool {
309
+ assert ! ( self . finished == false ) ;
310
+ if let Some ( tag) = tag {
311
+ if tag[ ..] != self . tag [ ..] { return false ; }
312
+ }
313
+ self . finished = true ;
314
+ true
315
+ }
316
+
317
+ pub fn finish_and_check_tag ( & mut self , tag : & [ u8 ] ) -> bool {
318
+ if tag[ ..] != self . tag [ ..] { return false ; }
319
+ self . finished = true ;
320
+ true
321
+ }
231
322
}
232
323
}
233
324
#[ cfg( fuzzing) ]
234
325
pub use self :: fuzzy_chachapoly:: ChaCha20Poly1305RFC ;
326
+
327
+ #[ cfg( test) ]
328
+ mod tests {
329
+ use ln:: msgs:: DecodeError ;
330
+ use super :: { ChaChaPolyReadAdapter , ChaChaPolyWriteAdapter } ;
331
+ use util:: ser:: { self , FixedLengthReader , LengthReadableArgs , Writeable } ;
332
+
333
+ // Used for for testing various lengths of serialization.
334
+ #[ derive( Debug , PartialEq ) ]
335
+ struct TestWriteable {
336
+ field1 : Vec < u8 > ,
337
+ field2 : Vec < u8 > ,
338
+ field3 : Vec < u8 > ,
339
+ }
340
+ impl_writeable_tlv_based ! ( TestWriteable , {
341
+ ( 1 , field1, vec_type) ,
342
+ ( 2 , field2, vec_type) ,
343
+ ( 3 , field3, vec_type) ,
344
+ } ) ;
345
+
346
+ #[ test]
347
+ fn test_chacha_stream_adapters ( ) {
348
+ // Check that ChaChaPolyReadAdapter and ChaChaPolyWriteAdapter correctly encode and decode an
349
+ // encrypted object.
350
+ macro_rules! check_object_read_write {
351
+ ( $obj: expr) => {
352
+ // First, serialize the object, encrypted with ChaCha20Poly1305.
353
+ let rho = [ 42 ; 32 ] ;
354
+ let writeable_len = $obj. serialized_length( ) as u64 + 16 ;
355
+ let write_adapter = ChaChaPolyWriteAdapter :: new( rho, & $obj) ;
356
+ let encrypted_writeable_bytes = write_adapter. encode( ) ;
357
+ let encrypted_writeable = & encrypted_writeable_bytes[ ..] ;
358
+
359
+ // Now deserialize the object back and make sure it matches the original.
360
+ let mut rd = FixedLengthReader :: new( encrypted_writeable, writeable_len) ;
361
+ let read_adapter = <ChaChaPolyReadAdapter <TestWriteable >>:: read( & mut rd, rho) . unwrap( ) ;
362
+ assert_eq!( $obj, read_adapter. readable) ;
363
+ } ;
364
+ }
365
+
366
+ // Try a big object that will require multiple write buffers.
367
+ let big_writeable = TestWriteable {
368
+ field1 : vec ! [ 43 ] ,
369
+ field2 : vec ! [ 44 ; 4192 ] ,
370
+ field3 : vec ! [ 45 ; 4192 + 1 ] ,
371
+ } ;
372
+ check_object_read_write ! ( big_writeable) ;
373
+
374
+ // Try a small object that fits into one write buffer.
375
+ let small_writeable = TestWriteable {
376
+ field1 : vec ! [ 43 ] ,
377
+ field2 : vec ! [ 44 ] ,
378
+ field3 : vec ! [ 45 ] ,
379
+ } ;
380
+ check_object_read_write ! ( small_writeable) ;
381
+ }
382
+
383
+ fn do_chacha_stream_adapters_ser_macros ( ) -> Result < ( ) , DecodeError > {
384
+ let writeable = TestWriteable {
385
+ field1 : vec ! [ 43 ] ,
386
+ field2 : vec ! [ 44 ; 4192 ] ,
387
+ field3 : vec ! [ 45 ; 4192 + 1 ] ,
388
+ } ;
389
+
390
+ // First, serialize the object into a TLV stream, encrypted with ChaCha20Poly1305.
391
+ let rho = [ 42 ; 32 ] ;
392
+ let write_adapter = ChaChaPolyWriteAdapter :: new ( rho, & writeable) ;
393
+ let mut writer = ser:: VecWriter ( Vec :: new ( ) ) ;
394
+ encode_tlv_stream ! ( & mut writer, {
395
+ ( 1 , write_adapter, required) ,
396
+ } ) ;
397
+
398
+ // Now deserialize the object back and make sure it matches the original.
399
+ let mut read_adapter: Option < ChaChaPolyReadAdapter < TestWriteable > > = None ;
400
+ decode_tlv_stream ! ( & writer. 0 [ ..] , {
401
+ ( 1 , read_adapter, ( read_arg, rho) ) ,
402
+ } ) ;
403
+ assert_eq ! ( writeable, read_adapter. unwrap( ) . readable) ;
404
+
405
+ Ok ( ( ) )
406
+ }
407
+
408
+ #[ test]
409
+ fn chacha_stream_adapters_ser_macros ( ) {
410
+ // Test that our stream adapters work as expected with the TLV macros.
411
+ // This also serves to test the `LengthReadableArgs` variant of the `decode_tlv` ser macro.
412
+ do_chacha_stream_adapters_ser_macros ( ) . unwrap ( )
413
+ }
414
+ }
0 commit comments