10
10
// This is a port of Andrew Moons poly1305-donna
11
11
// https://github.com/floodyberry/poly1305-donna
12
12
13
+ use ln:: msgs:: DecodeError ;
14
+ use util:: ser:: { FixedLengthReader , LengthRead , LengthReadableArgs , Readable , Writeable , Writer } ;
15
+ use io:: { self , Read , Write } ;
16
+
13
17
#[ cfg( not( fuzzing) ) ]
14
18
mod real_chachapoly {
15
19
use util:: chacha20:: ChaCha20 ;
@@ -70,6 +74,26 @@ mod real_chachapoly {
70
74
self . mac . raw_result ( out_tag) ;
71
75
}
72
76
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
+
73
97
pub fn decrypt ( & mut self , input : & [ u8 ] , output : & mut [ u8 ] , tag : & [ u8 ] ) -> bool {
74
98
assert ! ( input. len( ) == output. len( ) ) ;
75
99
assert ! ( self . finished == false ) ;
@@ -92,11 +116,141 @@ mod real_chachapoly {
92
116
false
93
117
}
94
118
}
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
+ }
95
148
}
96
149
}
97
150
#[ cfg( not( fuzzing) ) ]
98
151
pub use self :: real_chachapoly:: ChaCha20Poly1305RFC ;
99
152
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
+
100
254
#[ cfg( fuzzing) ]
101
255
mod fuzzy_chachapoly {
102
256
#[ derive( Clone , Copy ) ]
@@ -130,6 +284,16 @@ mod fuzzy_chachapoly {
130
284
self . finished = true ;
131
285
}
132
286
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
+
133
297
pub fn decrypt ( & mut self , input : & [ u8 ] , output : & mut [ u8 ] , tag : & [ u8 ] ) -> bool {
134
298
assert ! ( input. len( ) == output. len( ) ) ;
135
299
assert ! ( self . finished == false ) ;
@@ -139,7 +303,106 @@ mod fuzzy_chachapoly {
139
303
self . finished = true ;
140
304
true
141
305
}
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
+ }
142
316
}
143
317
}
144
318
#[ cfg( fuzzing) ]
145
319
pub 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