7
7
// You may not use this file except in accordance with one or both of these
8
8
// licenses.
9
9
10
- use lightning :: ln :: peer_channel_encryptor :: PeerChannelEncryptor ;
10
+ use bitcoin :: secp256k1 ;
11
11
12
12
use bitcoin:: secp256k1:: key:: { PublicKey , SecretKey } ;
13
-
13
+ use lightning:: ln:: peers:: conduit:: Conduit ;
14
+ use lightning:: ln:: peers:: handshake:: PeerHandshake ;
14
15
use utils:: test_logger;
15
16
16
- #[ inline]
17
- fn slice_to_be16 ( v : & [ u8 ] ) -> u16 {
18
- ( ( v[ 0 ] as u16 ) << 8 * 1 ) |
19
- ( ( v[ 1 ] as u16 ) << 8 * 0 )
17
+ // Test structure used to generate "random" values based on input data. It is used throughout
18
+ // the various test cases to send random messages between nodes and to ensure the code does not fail
19
+ // unexpectedly.
20
+ pub struct FuzzGen < ' a > {
21
+ read_pos : usize ,
22
+ data : & ' a [ u8 ] ,
20
23
}
21
24
22
- #[ inline]
23
- pub fn do_test ( data : & [ u8 ] ) {
24
- let mut read_pos = 0 ;
25
- macro_rules! get_slice {
26
- ( $len: expr) => {
27
- {
28
- let slice_len = $len as usize ;
29
- if data. len( ) < read_pos + slice_len {
30
- return ;
31
- }
32
- read_pos += slice_len;
33
- & data[ read_pos - slice_len..read_pos]
25
+ impl < ' a > FuzzGen < ' a > {
26
+ pub fn new ( data : & ' a [ u8 ] ) -> Self {
27
+ Self {
28
+ read_pos : 0 ,
29
+ data
30
+ }
31
+ }
32
+
33
+ pub fn generate_bytes ( & mut self , num : usize ) -> Result < & ' a [ u8 ] , String > {
34
+ if self . data . len ( ) < self . read_pos + num {
35
+ return Err ( "out of bytes" . to_string ( ) ) ;
36
+ }
37
+
38
+ self . read_pos += num;
39
+ Ok ( & self . data [ self . read_pos - num..self . read_pos ] )
40
+ }
41
+
42
+ pub fn generate_secret_key ( & mut self ) -> Result < SecretKey , String > {
43
+ // Loop through the input 32 bytes at a time until a valid
44
+ // secret key can be created or we run out of bytes
45
+ loop {
46
+ match SecretKey :: from_slice ( self . generate_bytes ( 32 ) ?) {
47
+ Ok ( key) => { return Ok ( key) } ,
48
+ _ => { }
34
49
}
35
50
}
36
51
}
37
52
38
- let our_network_key = match SecretKey :: from_slice ( get_slice ! ( 32 ) ) {
39
- Ok ( key) => key,
40
- Err ( _) => return ,
53
+ pub fn generate_bool ( & mut self ) -> Result < bool , String > {
54
+ let next_byte = self . generate_bytes ( 1 ) ?;
55
+ Ok ( next_byte[ 0 ] & 1 == 1 )
56
+ }
57
+ }
58
+
59
+ struct TestCtx {
60
+ initiator_static_public_key : PublicKey ,
61
+ initiator_handshake : PeerHandshake ,
62
+ responder_static_public_key : PublicKey ,
63
+ responder_handshake : PeerHandshake ,
64
+ act1 : Vec < u8 >
65
+ }
66
+
67
+ impl TestCtx {
68
+ fn make ( generator : & mut FuzzGen ) -> Result < Self , String > {
69
+ let curve = secp256k1:: Secp256k1 :: new ( ) ;
70
+
71
+ // Generate needed keys for successful handshake
72
+ let initiator_static_private_key = generator. generate_secret_key ( ) ?;
73
+ let initiator_static_public_key = PublicKey :: from_secret_key ( & curve, & initiator_static_private_key) ;
74
+ let initiator_ephemeral_private_key = generator. generate_secret_key ( ) ?;
75
+ let responder_static_private_key = generator. generate_secret_key ( ) ?;
76
+ let responder_static_public_key = PublicKey :: from_secret_key ( & curve, & responder_static_private_key) ;
77
+ let responder_ephemeral_private_key = generator. generate_secret_key ( ) ?;
78
+
79
+ let ( act1, initiator_handshake) = PeerHandshake :: create_and_initialize_outbound ( & initiator_static_private_key, & responder_static_public_key, & initiator_ephemeral_private_key) ;
80
+ let responder_handshake = PeerHandshake :: new_inbound ( & responder_static_private_key, & responder_ephemeral_private_key) ;
81
+
82
+ Ok ( TestCtx {
83
+ initiator_static_public_key,
84
+ initiator_handshake,
85
+ responder_static_public_key,
86
+ responder_handshake,
87
+ act1
88
+ } )
89
+ }
90
+ }
91
+
92
+ // Common test function that sends encrypted messages between two conduits until the source data
93
+ // runs out.
94
+ #[ inline]
95
+ fn do_conduit_tests ( generator : & mut FuzzGen , initiator_conduit : & mut Conduit , responder_conduit : & mut Conduit ) -> Result < ( ) , String > {
96
+ // Keep sending messages back and forth until the input data is consumed
97
+ loop {
98
+ // Randomly generate message length
99
+ let msg_len_raw = generator. generate_bytes ( 1 ) ?;
100
+ let msg_len = msg_len_raw[ 0 ] as usize ;
101
+
102
+ // Randomly generate message
103
+ let sender_unencrypted_msg = generator. generate_bytes ( msg_len) ?;
104
+
105
+ // randomly choose sender of message
106
+ let receiver_unencrypted_msg = if generator. generate_bool ( ) ? {
107
+ let encrypted_msg = initiator_conduit. encrypt ( sender_unencrypted_msg) ;
108
+ responder_conduit. decrypt_single_message ( Some ( & encrypted_msg) )
109
+ } else {
110
+ let encrypted_msg = responder_conduit. encrypt ( sender_unencrypted_msg) ;
111
+ initiator_conduit. decrypt_single_message ( Some ( & encrypted_msg) )
112
+ } ;
113
+
114
+ assert_eq ! ( sender_unencrypted_msg, receiver_unencrypted_msg. unwrap( ) . as_slice( ) ) ;
115
+ }
116
+ }
117
+
118
+ // This test completes a valid handshake based on "random" private keys and then sends variable
119
+ // length encrypted messages between two conduits to validate that they can communicate.
120
+ #[ inline]
121
+ fn do_completed_handshake_test ( generator : & mut FuzzGen ) -> Result < ( ) , String > {
122
+ let mut test_ctx = TestCtx :: make ( generator) ?;
123
+
124
+ // The handshake should complete with any valid private keys
125
+ let act2 = test_ctx. responder_handshake . process_act ( & test_ctx. act1 ) . unwrap ( ) . 0 . unwrap ( ) ;
126
+ let ( act3, ( mut initiator_conduit, responder_pubkey) ) = match test_ctx. initiator_handshake . process_act ( & act2) {
127
+ Ok ( ( Some ( act3) , Some ( ( conduit, remote_pubkey) ) ) ) => {
128
+ ( act3, ( conduit, remote_pubkey) )
129
+ }
130
+ _ => panic ! ( "handshake failed" )
41
131
} ;
42
- let ephemeral_key = match SecretKey :: from_slice ( get_slice ! ( 32 ) ) {
43
- Ok ( key) => key,
44
- Err ( _) => return ,
132
+
133
+ let ( mut responder_conduit, initiator_pubkey) = match test_ctx. responder_handshake . process_act ( & act3) {
134
+ Ok ( ( None , Some ( ( conduit, remote_pubkey) ) ) ) => {
135
+ ( conduit, remote_pubkey)
136
+ }
137
+ _ => panic ! ( "handshake failed" )
45
138
} ;
46
139
47
- let mut crypter = if get_slice ! ( 1 ) [ 0 ] != 0 {
48
- let their_pubkey = match PublicKey :: from_slice ( get_slice ! ( 33 ) ) {
49
- Ok ( key) => key,
50
- Err ( _) => return ,
51
- } ;
52
- let mut crypter = PeerChannelEncryptor :: new_outbound ( their_pubkey, ephemeral_key) ;
53
- crypter. get_act_one ( ) ;
54
- match crypter. process_act_two ( get_slice ! ( 50 ) , & our_network_key) {
55
- Ok ( _) => { } ,
56
- Err ( _) => return ,
140
+ // The handshake should complete with each peer knowing the static_public_key of the remote peer
141
+ assert_eq ! ( initiator_pubkey, test_ctx. initiator_static_public_key) ;
142
+ assert_eq ! ( responder_pubkey, test_ctx. responder_static_public_key) ;
143
+
144
+ // The nodes should be able to communicate over the conduit
145
+ do_conduit_tests ( generator, & mut initiator_conduit, & mut responder_conduit) ?;
146
+
147
+ unreachable ! ( ) ;
148
+ }
149
+
150
+ // This test variant goes through the peer handshake between the initiator and responder with
151
+ // "random" failures to verify that nothing panics.
152
+ // In the event of an error from process_act() we validate that the input data was generated
153
+ // randomly to ensure real act generation still produces valid transitions.
154
+ #[ inline]
155
+ fn do_handshake_test ( generator : & mut FuzzGen ) -> Result < ( ) , String > {
156
+ let mut test_ctx = TestCtx :: make ( generator) ?;
157
+ let mut used_generated_data = false ;
158
+
159
+ // Possibly generate bad data for act1 and ensure that the responder does not panic
160
+ let mut act1 = test_ctx. act1 ;
161
+ if generator. generate_bool ( ) ? {
162
+ act1 = ( generator. generate_bytes ( 50 ) ?) . to_vec ( ) ;
163
+ used_generated_data = true ;
164
+ }
165
+
166
+ let mut act2 = match test_ctx. responder_handshake . process_act ( & act1) {
167
+ Ok ( ( Some ( act2) , None ) ) => {
168
+ act2
57
169
}
58
- assert ! ( crypter. is_ready_for_encryption( ) ) ;
59
- crypter
60
- } else {
61
- let mut crypter = PeerChannelEncryptor :: new_inbound ( & our_network_key) ;
62
- match crypter. process_act_one_with_keys ( get_slice ! ( 50 ) , & our_network_key, ephemeral_key) {
63
- Ok ( _) => { } ,
64
- Err ( _) => return ,
170
+ Err ( _) => {
171
+ assert ! ( used_generated_data) ;
172
+ return Err ( "generated expected failure with bad data" . to_string ( ) ) ;
65
173
}
66
- match crypter. process_act_three ( get_slice ! ( 66 ) ) {
67
- Ok ( _) => { } ,
68
- Err ( _) => return ,
174
+ _ => panic ! ( "responder required to output act bytes and no conduit/pubkey" )
175
+ } ;
176
+
177
+ // Possibly generate bad data for act2 and ensure that the initiator does not panic
178
+ if generator. generate_bool ( ) ? {
179
+ act2 = ( generator. generate_bytes ( 50 ) ?) . to_vec ( ) ;
180
+ used_generated_data = true ;
181
+ }
182
+
183
+ let ( mut act3, ( mut initiator_conduit, responder_pubkey) ) = match test_ctx. initiator_handshake . process_act ( & act2) {
184
+ Ok ( ( Some ( act3) , Some ( ( conduit, remote_pubkey) ) ) ) => {
185
+ ( act3, ( conduit, remote_pubkey) )
69
186
}
70
- assert ! ( crypter. is_ready_for_encryption( ) ) ;
71
- crypter
187
+ Err ( _) => {
188
+ assert ! ( used_generated_data) ;
189
+ return Err ( "generated expected failure with bad data" . to_string ( ) ) ;
190
+ }
191
+ _ => panic ! ( "initiator required to output act bytes and no conduit/pubkey" )
72
192
} ;
73
- loop {
74
- if get_slice ! ( 1 ) [ 0 ] == 0 {
75
- crypter. encrypt_message ( get_slice ! ( slice_to_be16( get_slice!( 2 ) ) ) ) ;
76
- } else {
77
- let len = match crypter. decrypt_length_header ( get_slice ! ( 16 +2 ) ) {
78
- Ok ( len) => len,
79
- Err ( _) => return ,
80
- } ;
81
- match crypter. decrypt_message ( get_slice ! ( len as usize + 16 ) ) {
82
- Ok ( _) => { } ,
83
- Err ( _) => return ,
84
- }
193
+
194
+ // Possibly generate bad data for act3 and ensure that the responder does not panic
195
+ if generator. generate_bool ( ) ? {
196
+ act3 = ( generator. generate_bytes ( 66 ) ?) . to_vec ( ) ;
197
+ used_generated_data = true ;
198
+ }
199
+
200
+ let ( mut responder_conduit, initiator_pubkey) = match test_ctx. responder_handshake . process_act ( & act3) {
201
+ Ok ( ( None , Some ( ( conduit, remote_pubkey) ) ) ) => {
202
+ ( conduit, remote_pubkey)
203
+ }
204
+ Err ( _) => {
205
+ // extremely unlikely we randomly generate a correct act3, but if so.. reset this
206
+ assert ! ( used_generated_data) ;
207
+ return Err ( "generated expected failure with bad data" . to_string ( ) ) ;
208
+ } ,
209
+ _ => panic ! ( "responder required to output conduit/remote pubkey and no act bytes" )
210
+ } ;
211
+
212
+ // The handshake should complete with each peer knowing the static_public_key of the remote peer
213
+ if initiator_pubkey != test_ctx. initiator_static_public_key {
214
+ assert ! ( used_generated_data) ;
215
+ return Ok ( ( ) ) ;
216
+ }
217
+ if responder_pubkey != test_ctx. responder_static_public_key {
218
+ assert ! ( used_generated_data) ;
219
+ return Ok ( ( ) ) ;
220
+ }
221
+
222
+ // The nodes should be able to communicate over the conduit
223
+ do_conduit_tests ( generator, & mut initiator_conduit, & mut responder_conduit) ?;
224
+
225
+ unreachable ! ( ) ;
226
+ }
227
+
228
+ #[ inline]
229
+ fn do_test ( data : & [ u8 ] ) {
230
+ let mut generator = FuzzGen :: new ( data) ;
231
+
232
+ // Based on a "random" bool, decide which test variant to run
233
+ let do_valid_handshake = match generator. generate_bool ( ) {
234
+ Ok ( value) => { value } ,
235
+ _ => { return }
236
+ } ;
237
+
238
+ if do_valid_handshake {
239
+ match do_completed_handshake_test ( & mut generator) {
240
+ _ => { }
241
+ }
242
+ } else {
243
+ match do_handshake_test ( & mut generator) {
244
+ _ => { }
85
245
}
86
246
}
87
247
}
@@ -94,3 +254,58 @@ pub fn peer_crypt_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
94
254
pub extern "C" fn peer_crypt_run ( data : * const u8 , datalen : usize ) {
95
255
do_test ( unsafe { std:: slice:: from_raw_parts ( data, datalen) } ) ;
96
256
}
257
+
258
+ #[ cfg( test) ]
259
+ mod test {
260
+ use super :: * ;
261
+
262
+ #[ test]
263
+ fn data_generator_empty ( ) {
264
+ let mut generator = FuzzGen :: new ( & [ ] ) ;
265
+ assert_eq ! ( generator. generate_bool( ) . err( ) , Some ( "out of bytes" . to_string( ) ) ) ;
266
+ }
267
+
268
+ #[ test]
269
+ fn data_generator_bool_true ( ) {
270
+ let mut generator = FuzzGen :: new ( & [ 1 ] ) ;
271
+ assert ! ( generator. generate_bool( ) . unwrap( ) ) ;
272
+ }
273
+
274
+ #[ test]
275
+ fn data_generator_bool_false ( ) {
276
+ let mut generator = FuzzGen :: new ( & [ 0 ] ) ;
277
+ assert ! ( !generator. generate_bool( ) . unwrap( ) ) ;
278
+ }
279
+
280
+ #[ test]
281
+ fn data_generator_bool_then_error ( ) {
282
+ let mut generator = FuzzGen :: new ( & [ 1 ] ) ;
283
+ assert ! ( generator. generate_bool( ) . unwrap( ) ) ;
284
+ assert_eq ! ( generator. generate_bool( ) . err( ) , Some ( "out of bytes" . to_string( ) ) ) ;
285
+ }
286
+
287
+ #[ test]
288
+ fn data_generator_bytes_too_many ( ) {
289
+ let mut generator = FuzzGen :: new ( & [ 1 , 2 , 3 , 4 ] ) ;
290
+ assert_eq ! ( generator. generate_bytes( 5 ) . err( ) , Some ( "out of bytes" . to_string( ) ) ) ;
291
+ }
292
+
293
+ #[ test]
294
+ fn data_generator_bytes ( ) {
295
+ let input = [ 1 , 2 , 3 , 4 ] ;
296
+ let mut generator = FuzzGen :: new ( & input) ;
297
+ let result = generator. generate_bytes ( 4 ) . unwrap ( ) ;
298
+ assert_eq ! ( result, input) ;
299
+ }
300
+
301
+ #[ test]
302
+ fn data_generator_bytes_sequential ( ) {
303
+ let input = [ 1 , 2 , 3 , 4 ] ;
304
+ let mut generator = FuzzGen :: new ( & input) ;
305
+ let result = generator. generate_bytes ( 2 ) . unwrap ( ) ;
306
+ assert_eq ! ( result, & input[ ..2 ] ) ;
307
+ let result = generator. generate_bytes ( 2 ) . unwrap ( ) ;
308
+ assert_eq ! ( result, & input[ 2 ..] ) ;
309
+ assert_eq ! ( generator. generate_bytes( 1 ) . err( ) , Some ( "out of bytes" . to_string( ) ) ) ;
310
+ }
311
+ }
0 commit comments