async def establish_transport() -> TransportAPI: aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, alice_reader, alice_writer, token) transport = Transport( remote=alice_remote, private_key=alice_private_key, reader=alice_reader, writer=alice_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) return transport
async def establish_transport() -> None: aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, alice_reader, alice_writer, token) transport = Transport( remote=alice_remote, private_key=alice_private_key, reader=alice_reader, writer=alice_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) f_alice.set_result(transport) handshake_finished.set()
async def do_handshake() -> None: aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, alice_reader, alice_writer, cancel_token) transport = Transport( remote=alice_remote, private_key=alice_factory.privkey, reader=alice_reader, writer=alice_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) alice = alice_factory.create_peer(transport) f_alice.set_result(alice) handshake_finished.set()
async def test_server_incoming_connection(monkeypatch, server, event_loop): use_eip8 = False token = CancelToken("initiator") initiator = HandshakeInitiator(RECEIVER_REMOTE, INITIATOR_PRIVKEY, use_eip8, token) reader, writer = await initiator.connect() # Send auth init message to the server, then read and decode auth ack aes_secret, mac_secret, egress_mac, ingress_mac = await _handshake( initiator, reader, writer, token) transport = Transport( remote=RECEIVER_REMOTE, private_key=initiator.privkey, reader=reader, writer=writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) initiator_peer = ParagonPeer( transport=transport, context=ParagonContext(), token=token, ) # Perform p2p/sub-proto handshake, completing the full handshake and causing a new peer to be # added to the server's pool. await initiator_peer.do_p2p_handshake() await initiator_peer.do_sub_proto_handshake() # wait for peer to be processed while len(server.peer_pool) == 0: await asyncio.sleep(0) assert len(server.peer_pool.connected_nodes) == 1 receiver_peer = list(server.peer_pool.connected_nodes.values())[0] assert isinstance(receiver_peer, ParagonPeer) assert initiator_peer.sub_proto is not None assert initiator_peer.sub_proto.name == receiver_peer.sub_proto.name assert initiator_peer.sub_proto.version == receiver_peer.sub_proto.version # test public key here in order to not access private `_private_key` variable. assert receiver_peer.transport.public_key == RECEIVER_PRIVKEY.public_key
async def test_server_incoming_connection(monkeypatch, server, event_loop, receiver_remote, ): use_eip8 = False token = CancelToken("initiator") initiator = HandshakeInitiator(receiver_remote, INITIATOR_PRIVKEY, use_eip8, token) initiator_remote = NodeFactory( pubkey=INITIATOR_PUBKEY, address__ip='127.0.0.1', ) for _ in range(10): # The server isn't listening immediately so we give it a short grace # period while trying to connect. try: reader, writer = await initiator.connect() except ConnectionRefusedError: await asyncio.sleep(0) else: break else: raise AssertionError("Unable to connect within 10 loops") # Send auth init message to the server, then read and decode auth ack aes_secret, mac_secret, egress_mac, ingress_mac = await _handshake( initiator, reader, writer, token) transport = Transport( remote=receiver_remote, private_key=initiator.privkey, reader=reader, writer=writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) factory = ParagonPeerFactory( initiator.privkey, context=ParagonContext(), token=token, ) handshakers = await factory.get_handshakers() devp2p_handshake_params = DevP2PHandshakeParamsFactory( listen_port=initiator_remote.address.tcp_port, ) multiplexer, devp2p_receipt, protocol_receipts = await negotiate_protocol_handshakes( transport=transport, p2p_handshake_params=devp2p_handshake_params, protocol_handshakers=handshakers, token=token, ) connection = Connection( multiplexer=multiplexer, devp2p_receipt=devp2p_receipt, protocol_receipts=protocol_receipts, is_dial_out=False, ) initiator_peer = factory.create_peer(connection=connection) # wait for peer to be processed for _ in range(100): if len(server.peer_pool) > 0: break await asyncio.sleep(0) assert len(server.peer_pool.connected_nodes) == 1 receiver_peer = list(server.peer_pool.connected_nodes.values())[0] assert isinstance(receiver_peer, ParagonPeer) assert initiator_peer.sub_proto is not None assert initiator_peer.sub_proto.name == receiver_peer.sub_proto.name assert initiator_peer.sub_proto.version == receiver_peer.sub_proto.version assert initiator_peer.remote.pubkey == RECEIVER_PRIVKEY.public_key
async def get_directly_linked_peers_without_handshake( alice_factory: BasePeerFactory = None, bob_factory: BasePeerFactory = None) -> Tuple[BasePeer, BasePeer]: """ See get_directly_linked_peers(). Neither the P2P handshake nor the sub-protocol handshake will be performed here. """ cancel_token = CancelToken("get_directly_linked_peers_without_handshake") if alice_factory is None: alice_factory = ParagonPeerFactory( privkey=ecies.generate_privkey(), context=ParagonContext(), token=cancel_token, ) if bob_factory is None: bob_factory = ParagonPeerFactory( privkey=ecies.generate_privkey(), context=ParagonContext(), token=cancel_token, ) alice_private_key = alice_factory.privkey bob_private_key = bob_factory.privkey alice_remote = kademlia.Node(bob_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) bob_remote = kademlia.Node(alice_private_key.public_key, kademlia.Address('0.0.0.0', 0, 0)) use_eip8 = False initiator = auth.HandshakeInitiator(alice_remote, alice_private_key, use_eip8, cancel_token) f_alice: 'asyncio.Future[BasePeer]' = asyncio.Future() handshake_finished = asyncio.Event() ( (alice_reader, alice_writer), (bob_reader, bob_writer), ) = get_directly_connected_streams() async def do_handshake() -> None: aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, alice_reader, alice_writer, cancel_token) transport = Transport( remote=alice_remote, private_key=alice_factory.privkey, reader=alice_reader, writer=alice_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) alice = alice_factory.create_peer(transport) f_alice.set_result(alice) handshake_finished.set() asyncio.ensure_future(do_handshake()) use_eip8 = False responder = auth.HandshakeResponder(bob_remote, bob_private_key, use_eip8, cancel_token) auth_cipher = await bob_reader.read(constants.ENCRYPTED_AUTH_MSG_LEN) initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_cipher, bob_private_key) responder_nonce = keccak(os.urandom(constants.HASH_LEN)) auth_ack_msg = responder.create_auth_ack_message(responder_nonce) auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg) bob_writer.write(auth_ack_ciphertext) await handshake_finished.wait() alice = await f_alice aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, responder_nonce, initiator_ephemeral_pubkey, auth_cipher, auth_ack_ciphertext) assert egress_mac.digest() == alice.transport._ingress_mac.digest() assert ingress_mac.digest() == alice.transport._egress_mac.digest() transport = Transport( remote=bob_remote, private_key=bob_factory.privkey, reader=bob_reader, writer=bob_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) bob = bob_factory.create_peer(transport) return alice, bob
async def test_handshake(): # TODO: this test should be re-written to not depend on functionality in the `ETHPeer` class. cancel_token = CancelToken("test_handshake") use_eip8 = False initiator_remote = kademlia.Node( keys.PrivateKey(test_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(test_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( test_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(test_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(test_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( test_values['receiver_ephemeral_private_key']) # Check that the auth message generated by the initiator is what we expect. Notice that we # can't use the auth_init generated here because the non-deterministic prefix would cause the # derived secrets to not match the expected values. _auth_init = initiator.create_auth_message(test_values['initiator_nonce']) assert len(_auth_init) == len(test_values['auth_plaintext']) assert _auth_init[65:] == test_values['auth_plaintext'][ 65:] # starts with non deterministic k # Check that encrypting and decrypting the auth_init gets us the orig msg. _auth_init_ciphertext = initiator.encrypt_auth_message(_auth_init) assert _auth_init == ecies.decrypt(_auth_init_ciphertext, responder.privkey) # Check that the responder correctly decodes the auth msg. auth_msg_ciphertext = test_values['auth_ciphertext'] initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_msg_ciphertext, responder.privkey) assert initiator_nonce == test_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( test_values['initiator_ephemeral_private_key']).public_key) # Check that the auth_ack msg generated by the responder is what we expect. auth_ack_msg = responder.create_auth_ack_message( test_values['receiver_nonce']) assert auth_ack_msg == test_values['authresp_plaintext'] # Check that the secrets derived from ephemeral key agreements match the expected values. auth_ack_ciphertext = test_values['authresp_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, test_values['receiver_nonce'], initiator_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert aes_secret == test_values['aes_secret'] assert mac_secret == test_values['mac_secret'] # Test values are from initiator perspective, so they're reversed here. assert ingress_mac.digest() == test_values['initial_egress_MAC'] assert egress_mac.digest() == test_values['initial_ingress_MAC'] # Check that the initiator secrets match as well. responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( test_values['authresp_ciphertext']) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert initiator_aes_secret == aes_secret assert initiator_mac_secret == mac_secret assert initiator_ingress_mac.digest() == test_values['initial_ingress_MAC'] assert initiator_egress_mac.digest() == test_values['initial_egress_MAC'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() capabilities = (('paragon', 1), ) initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_p2p_protocol = P2PProtocolV5(initiator_transport, 0, False) initiator_multiplexer = Multiplexer( transport=initiator_transport, base_protocol=initiator_p2p_protocol, protocols=(), ) initiator_multiplexer.get_base_protocol().send( Hello( HelloPayload( client_version_string='initiator', capabilities=capabilities, listen_port=30303, version=DEVP2P_V5, remote_public_key=initiator.privkey.public_key.to_bytes(), ))) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_p2p_protocol = P2PProtocolV5(responder_transport, 0, False) responder_multiplexer = Multiplexer( transport=responder_transport, base_protocol=responder_p2p_protocol, protocols=(), ) responder_multiplexer.get_base_protocol().send( Hello( HelloPayload( client_version_string='responder', capabilities=capabilities, listen_port=30303, version=DEVP2P_V5, remote_public_key=responder.privkey.public_key.to_bytes(), ))) async with initiator_multiplexer.multiplex(): async with responder_multiplexer.multiplex(): initiator_stream = initiator_multiplexer.stream_protocol_messages( initiator_p2p_protocol, ) responder_stream = responder_multiplexer.stream_protocol_messages( responder_p2p_protocol, ) initiator_hello = await asyncio.wait_for( initiator_stream.asend(None), timeout=0.1) responder_hello = await asyncio.wait_for( responder_stream.asend(None), timeout=0.1) await initiator_stream.aclose() await responder_stream.aclose() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)
async def test_handshake_eip8(): cancel_token = CancelToken("test_handshake_eip8") use_eip8 = True initiator_remote = kademlia.Node( keys.PrivateKey(eip8_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(eip8_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(eip8_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(eip8_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( eip8_values['receiver_ephemeral_private_key']) auth_init_ciphertext = eip8_values['auth_init_ciphertext'] # Check that we can decrypt/decode the EIP-8 auth init message. initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_init_ciphertext, responder.privkey) assert initiator_nonce == eip8_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']).public_key) responder_nonce = eip8_values['receiver_nonce'] auth_ack_ciphertext = eip8_values['auth_ack_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, responder_nonce, initiator_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the responder match the expected values. assert aes_secret == eip8_values['expected_aes_secret'] assert mac_secret == eip8_values['expected_mac_secret'] # Also according to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md, running B's # ingress-mac keccak state on the string "foo" yields the following hash: ingress_mac_copy = ingress_mac.copy() ingress_mac_copy.update(b'foo') assert ingress_mac_copy.hexdigest() == ( '0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5') responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( auth_ack_ciphertext) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the initiator match the expected values. assert initiator_aes_secret == eip8_values['expected_aes_secret'] assert initiator_mac_secret == eip8_values['expected_mac_secret'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() capabilities = (('testing', 1), ) initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_p2p_protocol = P2PProtocolV5(initiator_transport, 0, False) initiator_multiplexer = Multiplexer( transport=initiator_transport, base_protocol=initiator_p2p_protocol, protocols=(), ) initiator_multiplexer.get_base_protocol().send( Hello( HelloPayload( client_version_string='initiator', capabilities=capabilities, listen_port=30303, version=DEVP2P_V5, remote_public_key=initiator.privkey.public_key.to_bytes(), ))) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_p2p_protocol = P2PProtocolV4(responder_transport, 0, False) responder_multiplexer = Multiplexer( transport=responder_transport, base_protocol=responder_p2p_protocol, protocols=(), ) responder_multiplexer.get_base_protocol().send( Hello( HelloPayload( client_version_string='responder', capabilities=capabilities, listen_port=30303, version=DEVP2P_V4, remote_public_key=responder.privkey.public_key.to_bytes(), ))) async with initiator_multiplexer.multiplex(): async with responder_multiplexer.multiplex(): initiator_stream = initiator_multiplexer.stream_protocol_messages( initiator_p2p_protocol, ) responder_stream = responder_multiplexer.stream_protocol_messages( responder_p2p_protocol, ) initiator_hello = await initiator_stream.asend(None) responder_hello = await responder_stream.asend(None) await initiator_stream.aclose() await responder_stream.aclose() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)
async def TransportPairFactory( *, alice_remote: NodeAPI = None, alice_private_key: keys.PrivateKey = None, bob_remote: NodeAPI = None, bob_private_key: keys.PrivateKey = None, token: CancelToken = None, use_eip8: bool = False, ) -> Tuple[TransportAPI, TransportAPI]: if token is None: token = CancelTokenFactory(name='TransportPairFactory') if alice_private_key is None: alice_private_key = PrivateKeyFactory() if alice_remote is None: alice_remote = NodeFactory(pubkey=alice_private_key.public_key) if bob_private_key is None: bob_private_key = PrivateKeyFactory() if bob_remote is None: bob_remote = NodeFactory(pubkey=bob_private_key.public_key) assert alice_private_key.public_key == alice_remote.pubkey assert bob_private_key.public_key == bob_remote.pubkey assert alice_private_key != bob_private_key initiator = auth.HandshakeInitiator(bob_remote, alice_private_key, use_eip8, token) bob_peername = (bob_remote.address.ip, bob_remote.address.udp_port, bob_remote.address.tcp_port) alice_peername = (alice_remote.address.ip, alice_remote.address.udp_port, alice_remote.address.tcp_port) # noqa: E501 ( (alice_reader, alice_writer), (bob_reader, bob_writer), ) = get_directly_connected_streams( bob_extra_info={'peername': bob_peername}, alice_extra_info={'peername': alice_peername}, ) async def establish_transport() -> TransportAPI: aes_secret, mac_secret, egress_mac, ingress_mac = await auth._handshake( initiator, alice_reader, alice_writer, token) transport = Transport( remote=alice_remote, private_key=alice_private_key, reader=alice_reader, writer=alice_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) return transport alice_transport, bob_transport = await asyncio.wait_for(asyncio.gather( establish_transport(), Transport.receive_connection( reader=bob_reader, writer=bob_writer, private_key=bob_private_key, token=token, ), ), timeout=1) return alice_transport, bob_transport
async def test_handshake(): # TODO: this test should be re-written to not depend on functionality in the `ETHPeer` class. cancel_token = CancelToken("test_handshake") use_eip8 = False initiator_remote = kademlia.Node( keys.PrivateKey(test_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(test_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( test_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(test_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(test_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( test_values['receiver_ephemeral_private_key']) # Check that the auth message generated by the initiator is what we expect. Notice that we # can't use the auth_init generated here because the non-deterministic prefix would cause the # derived secrets to not match the expected values. _auth_init = initiator.create_auth_message(test_values['initiator_nonce']) assert len(_auth_init) == len(test_values['auth_plaintext']) assert _auth_init[65:] == test_values['auth_plaintext'][ 65:] # starts with non deterministic k # Check that encrypting and decrypting the auth_init gets us the orig msg. _auth_init_ciphertext = initiator.encrypt_auth_message(_auth_init) assert _auth_init == ecies.decrypt(_auth_init_ciphertext, responder.privkey) # Check that the responder correctly decodes the auth msg. auth_msg_ciphertext = test_values['auth_ciphertext'] initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_msg_ciphertext, responder.privkey) assert initiator_nonce == test_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( test_values['initiator_ephemeral_private_key']).public_key) # Check that the auth_ack msg generated by the responder is what we expect. auth_ack_msg = responder.create_auth_ack_message( test_values['receiver_nonce']) assert auth_ack_msg == test_values['authresp_plaintext'] # Check that the secrets derived from ephemeral key agreements match the expected values. auth_ack_ciphertext = test_values['authresp_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, test_values['receiver_nonce'], initiator_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert aes_secret == test_values['aes_secret'] assert mac_secret == test_values['mac_secret'] # Test values are from initiator perspective, so they're reversed here. assert ingress_mac.digest() == test_values['initial_egress_MAC'] assert egress_mac.digest() == test_values['initial_ingress_MAC'] # Check that the initiator secrets match as well. responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( test_values['authresp_ciphertext']) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext) assert initiator_aes_secret == aes_secret assert initiator_mac_secret == mac_secret assert initiator_ingress_mac.digest() == test_values['initial_ingress_MAC'] assert initiator_egress_mac.digest() == test_values['initial_egress_MAC'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_peer = ParagonPeer( transport=initiator_transport, context=ParagonContext(), ) initiator_peer.base_protocol.send_handshake( client_version_string=initiator_peer.context.client_version_string, capabilities=initiator_peer.capabilities, listen_port=initiator_peer.context.listen_port, ) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_peer = ParagonPeer( transport=responder_transport, context=ParagonContext(), ) responder_peer.base_protocol.send_handshake( client_version_string=responder_peer.context.client_version_string, capabilities=responder_peer.capabilities, listen_port=responder_peer.context.listen_port, ) # The handshake msgs sent by each peer (above) are going to be fed directly into their remote's # reader, and thus the read_msg() calls will return immediately. responder_hello, _ = await responder_peer.read_msg() initiator_hello, _ = await initiator_peer.read_msg() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)
async def test_handshake_eip8(): cancel_token = CancelToken("test_handshake_eip8") use_eip8 = True initiator_remote = kademlia.Node( keys.PrivateKey(eip8_values['receiver_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(eip8_values['initiator_private_key']), use_eip8, cancel_token) initiator.ephemeral_privkey = keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']) responder_remote = kademlia.Node( keys.PrivateKey(eip8_values['initiator_private_key']).public_key, kademlia.Address('0.0.0.0', 0, 0)) responder = HandshakeResponder( responder_remote, keys.PrivateKey(eip8_values['receiver_private_key']), use_eip8, cancel_token) responder.ephemeral_privkey = keys.PrivateKey( eip8_values['receiver_ephemeral_private_key']) auth_init_ciphertext = eip8_values['auth_init_ciphertext'] # Check that we can decrypt/decode the EIP-8 auth init message. initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_init_ciphertext, responder.privkey) assert initiator_nonce == eip8_values['initiator_nonce'] assert initiator_ephemeral_pubkey == (keys.PrivateKey( eip8_values['initiator_ephemeral_private_key']).public_key) responder_nonce = eip8_values['receiver_nonce'] auth_ack_ciphertext = eip8_values['auth_ack_ciphertext'] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, responder_nonce, initiator_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the responder match the expected values. assert aes_secret == eip8_values['expected_aes_secret'] assert mac_secret == eip8_values['expected_mac_secret'] # Also according to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-8.md, running B's # ingress-mac keccak state on the string "foo" yields the following hash: ingress_mac_copy = ingress_mac.copy() ingress_mac_copy.update(b'foo') assert ingress_mac_copy.hexdigest() == ( '0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5') responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( auth_ack_ciphertext) (initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_init_ciphertext, auth_ack_ciphertext) # Check that the secrets derived by the initiator match the expected values. assert initiator_aes_secret == eip8_values['expected_aes_secret'] assert initiator_mac_secret == eip8_values['expected_mac_secret'] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() initiator_transport = Transport(remote=initiator_remote, private_key=initiator.privkey, reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac) initiator_peer = ParagonPeer( transport=initiator_transport, context=ParagonContext(), ) initiator_peer.base_protocol.send_handshake( client_version_string=initiator_peer.context.client_version_string, capabilities=initiator_peer.capabilities, listen_port=initiator_peer.context.listen_port, ) responder_transport = Transport( remote=responder_remote, private_key=responder.privkey, reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_peer = ParagonPeer( transport=responder_transport, context=ParagonContext(), ) responder_peer.base_protocol.send_handshake( client_version_string=responder_peer.context.client_version_string, capabilities=responder_peer.capabilities, listen_port=responder_peer.context.listen_port, ) # The handshake msgs sent by each peer (above) are going to be fed directly into their remote's # reader, and thus the read_msg() calls will return immediately. responder_hello, _ = await responder_peer.read_msg() initiator_hello, _ = await initiator_peer.read_msg() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)
async def test_server_incoming_connection(server, receiver_remote): use_eip8 = False initiator = HandshakeInitiator(receiver_remote, INITIATOR_PRIVKEY, use_eip8) initiator_remote = NodeFactory( pubkey=INITIATOR_PUBKEY, address__ip='127.0.0.1', ) for num_retries in range(10): # The server isn't listening immediately so we give it a short grace # period while trying to connect. try: reader, writer = await initiator.connect() except ConnectionRefusedError: await asyncio.sleep(0 + 0.001 * num_retries) else: break else: raise AssertionError("Unable to connect within 10 loops") # Send auth init message to the server, then read and decode auth ack aes_secret, mac_secret, egress_mac, ingress_mac = await _handshake( initiator, reader, writer) transport = Transport( remote=receiver_remote, private_key=initiator.privkey, reader=reader, writer=writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) factory = ParagonPeerFactory( initiator.privkey, context=ParagonContext(), ) handshakers = await factory.get_handshakers() devp2p_handshake_params = DevP2PHandshakeParamsFactory( listen_port=initiator_remote.address.tcp_port, ) multiplexer, devp2p_receipt, protocol_receipts = await negotiate_protocol_handshakes( transport=transport, p2p_handshake_params=devp2p_handshake_params, protocol_handshakers=handshakers, ) connection = Connection( multiplexer=multiplexer, devp2p_receipt=devp2p_receipt, protocol_receipts=protocol_receipts, is_dial_out=False, ) initiator_peer = factory.create_peer(connection=connection) # wait for peer to be processed for _ in range(100): if len(server.peer_pool) > 0: break await asyncio.sleep(0) assert len(server.peer_pool.connected_nodes) == 1 receiver_peer = list(server.peer_pool.connected_nodes.values())[0] assert isinstance(receiver_peer, ParagonPeer) assert initiator_peer.sub_proto is not None assert initiator_peer.sub_proto.name == receiver_peer.sub_proto.name assert initiator_peer.sub_proto.version == receiver_peer.sub_proto.version assert initiator_peer.remote.pubkey == RECEIVER_PRIVKEY.public_key # Our connections are created manually and we don't call run() on them, so we need to stop the # background streaming task or else we'll get asyncio warnings about task exceptions not being # retrieved. await initiator_peer.connection._multiplexer.stop_streaming() await receiver_peer.connection._multiplexer.stop_streaming()
async def _receive_handshake( self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: msg = await self.wait( reader.read(ENCRYPTED_AUTH_MSG_LEN), timeout=REPLY_TIMEOUT) peername = writer.get_extra_info("peername") if peername is None: socket = writer.get_extra_info("socket") sockname = writer.get_extra_info("sockname") raise HandshakeFailure( "Received incoming connection with no remote information:" f"socket={repr(socket)} sockname={sockname}" ) ip, socket, *_ = peername remote_address = Address(ip, socket) self.logger.debug("Receiving handshake from %s", remote_address) try: ephem_pubkey, initiator_nonce, initiator_pubkey = decode_authentication( msg, self.privkey) except DecryptionError as non_eip8_err: # Try to decode as EIP8 got_eip8 = True msg_size = big_endian_to_int(msg[:2]) remaining_bytes = msg_size - ENCRYPTED_AUTH_MSG_LEN + 2 msg += await self.wait( reader.read(remaining_bytes), timeout=REPLY_TIMEOUT) try: ephem_pubkey, initiator_nonce, initiator_pubkey = decode_authentication( msg, self.privkey) except DecryptionError as eip8_err: raise HandshakeFailure( f"Failed to decrypt both EIP8 handshake: {eip8_err} and " f"non-EIP8 handshake: {non_eip8_err}" ) else: got_eip8 = False initiator_remote = Node(initiator_pubkey, remote_address) responder = HandshakeResponder(initiator_remote, self.privkey, got_eip8, self.cancel_token) responder_nonce = secrets.token_bytes(HASH_LEN) auth_ack_msg = responder.create_auth_ack_message(responder_nonce) auth_ack_ciphertext = responder.encrypt_auth_ack_message(auth_ack_msg) # Use the `writer` to send the reply to the remote writer.write(auth_ack_ciphertext) await self.wait(writer.drain()) # Call `HandshakeResponder.derive_shared_secrets()` and use return values to create `Peer` aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce=initiator_nonce, responder_nonce=responder_nonce, remote_ephemeral_pubkey=ephem_pubkey, auth_init_ciphertext=msg, auth_ack_ciphertext=auth_ack_ciphertext ) transport = Transport( remote=initiator_remote, private_key=self.privkey, reader=reader, writer=writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) # Create and register peer in peer_pool peer = self.peer_pool.get_peer_factory().create_peer( transport=transport, inbound=True, ) if self.peer_pool.is_full: await peer.disconnect(DisconnectReason.too_many_peers) return elif not self.peer_pool.is_valid_connection_candidate(peer.remote): await peer.disconnect(DisconnectReason.useless_peer) return total_peers = len(self.peer_pool) inbound_peer_count = len([ peer for peer in self.peer_pool.connected_nodes.values() if peer.inbound ]) if total_peers > 1 and inbound_peer_count / total_peers > DIAL_IN_OUT_RATIO: # make sure to have at least 1/4 outbound connections await peer.disconnect(DisconnectReason.too_many_peers) else: # We use self.wait() here as a workaround for # https://github.com/ethereum/py-evm/issues/670. await self.wait(self.do_handshake(peer))