|
3 | 3 | # Distributed under the MIT software license, see the accompanying
|
4 | 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
5 | 5 |
|
6 |
| -import random |
| 6 | +from enum import Enum |
7 | 7 |
|
8 |
| -from test_framework.test_framework import BitcoinTestFramework |
9 |
| -from test_framework.crypto.ellswift import ellswift_create |
| 8 | +from test_framework.messages import MAGIC_BYTES |
10 | 9 | from test_framework.p2p import P2PInterface
|
| 10 | +from test_framework.test_framework import BitcoinTestFramework |
11 | 11 | from test_framework.v2_p2p import EncryptedP2PState
|
12 | 12 |
|
13 | 13 |
|
14 |
| -class TestEncryptedP2PState(EncryptedP2PState): |
15 |
| - """ Modify v2 P2P protocol functions for testing that "The responder waits until one byte is received which does |
16 |
| - not match the 16 bytes consisting of the network magic followed by "version\x00\x00\x00\x00\x00"." (see BIP 324) |
| 14 | +class TestType(Enum): |
| 15 | + """ Scenarios to be tested: |
17 | 16 |
|
18 |
| - - if `send_net_magic` is True, send first 4 bytes of ellswift (match network magic) else send remaining 60 bytes |
19 |
| - - `can_data_be_received` is a variable used to assert if data is received on recvbuf. |
20 |
| - - v2 TestNode shouldn't respond back if we send V1_PREFIX and data shouldn't be received on recvbuf. |
21 |
| - This state is represented using `can_data_be_received` = False. |
22 |
| - - v2 TestNode responds back when mismatch from V1_PREFIX happens and data can be received on recvbuf. |
23 |
| - This state is represented using `can_data_be_received` = True. |
| 17 | + 1. EARLY_KEY_RESPONSE - The responder needs to wait until one byte is received which does not match the 16 bytes |
| 18 | + consisting of network magic followed by "version\x00\x00\x00\x00\x00" before sending out its ellswift + garbage bytes |
24 | 19 | """
|
| 20 | + EARLY_KEY_RESPONSE = 0 |
25 | 21 |
|
26 |
| - def __init__(self): |
27 |
| - super().__init__(initiating=True, net='regtest') |
28 |
| - self.send_net_magic = True |
29 |
| - self.can_data_be_received = False |
30 |
| - |
31 |
| - def initiate_v2_handshake(self, garbage_len=random.randrange(4096)): |
32 |
| - """Initiator begins the v2 handshake by sending its ellswift bytes and garbage. |
33 |
| - Here, the 64 bytes ellswift is assumed to have it's 4 bytes match network magic bytes. It is sent in 2 phases: |
34 |
| - 1. when `send_network_magic` = True, send first 4 bytes of ellswift (matches network magic bytes) |
35 |
| - 2. when `send_network_magic` = False, send remaining 60 bytes of ellswift |
36 |
| - """ |
37 |
| - if self.send_net_magic: |
38 |
| - self.privkey_ours, self.ellswift_ours = ellswift_create() |
39 |
| - self.sent_garbage = random.randbytes(garbage_len) |
40 |
| - self.send_net_magic = False |
41 |
| - return b"\xfa\xbf\xb5\xda" |
42 |
| - else: |
43 |
| - self.can_data_be_received = True |
44 |
| - return self.ellswift_ours[4:] + self.sent_garbage |
45 | 22 |
|
| 23 | +class EarlyKeyResponseState(EncryptedP2PState): |
| 24 | + """ Modify v2 P2P protocol functions for testing EARLY_KEY_RESPONSE scenario""" |
| 25 | + def __init__(self, initiating, net): |
| 26 | + super().__init__(initiating=initiating, net=net) |
| 27 | + self.can_data_be_received = False # variable used to assert if data is received on recvbuf. |
| 28 | + |
| 29 | + def initiate_v2_handshake(self): |
| 30 | + """Send ellswift and garbage bytes in 2 parts when TestType = (EARLY_KEY_RESPONSE)""" |
| 31 | + self.generate_keypair_and_garbage() |
| 32 | + return b"" |
46 | 33 |
|
47 |
| -class PeerEarlyKey(P2PInterface): |
| 34 | + |
| 35 | +class MisbehavingV2Peer(P2PInterface): |
48 | 36 | """Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""
|
49 |
| - def __init__(self): |
| 37 | + def __init__(self, test_type): |
50 | 38 | super().__init__()
|
51 |
| - self.v2_state = None |
52 |
| - self.connection_opened = False |
| 39 | + self.test_type = test_type |
53 | 40 |
|
54 | 41 | def connection_made(self, transport):
|
55 |
| - """64 bytes ellswift is sent in 2 parts during `initial_v2_handshake()`""" |
56 |
| - self.v2_state = TestEncryptedP2PState() |
| 42 | + if self.test_type == TestType.EARLY_KEY_RESPONSE: |
| 43 | + self.v2_state = EarlyKeyResponseState(initiating=True, net='regtest') |
57 | 44 | super().connection_made(transport)
|
58 | 45 |
|
59 | 46 | def data_received(self, t):
|
60 |
| - # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens (send_net_magic = False) |
61 |
| - assert self.v2_state.can_data_be_received and not self.v2_state.send_net_magic |
| 47 | + if self.test_type == TestType.EARLY_KEY_RESPONSE: |
| 48 | + # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens |
| 49 | + assert self.v2_state.can_data_be_received |
| 50 | + else: |
| 51 | + super().data_received(t) |
62 | 52 |
|
63 |
| - def on_open(self): |
64 |
| - self.connection_opened = True |
65 | 53 |
|
66 |
| -class P2PEarlyKey(BitcoinTestFramework): |
| 54 | +class EncryptedP2PMisbehaving(BitcoinTestFramework): |
67 | 55 | def set_test_params(self):
|
68 | 56 | self.num_nodes = 1
|
69 | 57 | self.extra_args = [["-v2transport=1", "-peertimeout=3"]]
|
70 | 58 |
|
71 | 59 | def run_test(self):
|
| 60 | + self.test_earlykeyresponse() |
| 61 | + |
| 62 | + def test_earlykeyresponse(self): |
72 | 63 | self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')
|
73 | 64 | self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')
|
74 | 65 | node0 = self.nodes[0]
|
75 | 66 | self.log.info('Sending first 4 bytes of ellswift which match network magic')
|
76 | 67 | self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')
|
77 |
| - # send happens in `initiate_v2_handshake()` in `connection_made()` |
78 |
| - peer1 = node0.add_p2p_connection(PeerEarlyKey(), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False) |
79 |
| - self.wait_until(lambda: peer1.connection_opened) |
| 68 | + peer1 = node0.add_p2p_connection(MisbehavingV2Peer(TestType.EARLY_KEY_RESPONSE), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False) |
| 69 | + peer1.send_raw_message(MAGIC_BYTES['regtest']) |
80 | 70 | self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')
|
81 | 71 | self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')
|
82 |
| - ellswift_and_garbage_data = peer1.v2_state.initiate_v2_handshake() |
83 |
| - peer1.send_raw_message(ellswift_and_garbage_data) |
84 |
| - peer1.wait_for_disconnect(timeout=5) |
85 |
| - self.log.info('successful disconnection when MITM happens in the key exchange phase') |
| 72 | + peer1.v2_state.can_data_be_received = True |
| 73 | + peer1.send_raw_message(peer1.v2_state.ellswift_ours[4:] + peer1.v2_state.sent_garbage) |
| 74 | + with node0.assert_debug_log(['V2 handshake timeout peer=0']): |
| 75 | + peer1.wait_for_disconnect(timeout=5) |
| 76 | + self.log.info('successful disconnection since modified ellswift was sent as response') |
86 | 77 |
|
87 | 78 |
|
88 | 79 | if __name__ == '__main__':
|
89 |
| - P2PEarlyKey().main() |
| 80 | + EncryptedP2PMisbehaving().main() |
0 commit comments