From d861a00d60109dbcc74e915e9e5ad8eacd0ae24e Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Sat, 27 Apr 2019 19:42:05 -0400 Subject: [PATCH 01/11] Scaffold security --- libp2p/security/__init__.py | 0 libp2p/security/secure_conn_interface.py | 21 +++++ libp2p/security/secure_transport_interface.py | 27 ++++++ libp2p/security/security_multistream.py | 87 +++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 libp2p/security/__init__.py create mode 100644 libp2p/security/secure_conn_interface.py create mode 100644 libp2p/security/secure_transport_interface.py create mode 100644 libp2p/security/security_multistream.py diff --git a/libp2p/security/__init__.py b/libp2p/security/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libp2p/security/secure_conn_interface.py b/libp2p/security/secure_conn_interface.py new file mode 100644 index 00000000..946d4f51 --- /dev/null +++ b/libp2p/security/secure_conn_interface.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod + +""" +Represents a secured connection object, which includes a connection and details about the security +involved in the secured connection + +Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go +""" +class ISecureConn(ABC): + + @abstractmethod + def get_conn(self): + """ + :return: connection object that has been made secure + """ + + def get_security_details(self): + """ + :return: map containing details about the connections security + """ + diff --git a/libp2p/security/secure_transport_interface.py b/libp2p/security/secure_transport_interface.py new file mode 100644 index 00000000..3e497190 --- /dev/null +++ b/libp2p/security/secure_transport_interface.py @@ -0,0 +1,27 @@ +import asyncio + +from abc import ABC, abstractmethod + +""" +Transport that is used to secure a connection. This transport is +chosen by a security transport multistream module. + +Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go +""" +class ISecureTransport(ABC): + + @abstractmethod + async def secure_inbound(self, conn): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are not the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + + @abstractmethod + async def secure_outbound(self, conn, peer_id): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ diff --git a/libp2p/security/security_multistream.py b/libp2p/security/security_multistream.py new file mode 100644 index 00000000..6fe45391 --- /dev/null +++ b/libp2p/security/security_multistream.py @@ -0,0 +1,87 @@ +import asyncio + +from abc import ABC, abstractmethod +from libp2p.protocol_muxer.multiselect_client import MultiselectClient +from libp2p.protocol_muxer.multiselect import Multiselect + +""" +Represents a secured connection object, which includes a connection and details about the security +involved in the secured connection + +Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go +""" +class SecurityMultistream(ABC): + + def __init__(): + # Map protocol to secure transport + self.transports = {} + + # Create multiselect + self.multiselect = Multiselect() + + # Create multiselect client + self.multiselect_client = MultiselectClient() + + def add_transport(self, protocol, transport): + # Associate protocol with transport + self.transports[protocol] = transport + + # Add protocol and handler to multiselect + # Note: None is added as the handler for the given protocol since + # we only care about selecting the protocol, not any handler function + self.multiselect.add_handler(protocol, None) + + + async def secure_inbound(self, conn): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are not the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + + # Select a secure transport + transport = await self.select_transport(conn, True) + + # Create secured connection + secure_conn = await transport.secure_inbound(conn) + + return secure_conn + + + async def secure_outbound(self, conn, peer_id): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + + # Select a secure transport + transport = await self.select_transport(conn, True) + + # Create secured connection + secure_conn = await transport.secure_outbound(conn, peer_id) + + return secure_conn + + + async def select_transport(self, conn, initiator): + """ + Select a transport that both us and the node on the + other end of conn support and agree on + :param conn: conn to choose a transport over + :param initiator: true if we are the initiator, false otherwise + :return: selected secure transport + """ + # TODO: Is conn acceptable to multiselect/multiselect_client instead of stream? In go repo, + # they pass in a raw conn (https://raw.githubusercontent.com/libp2p/go-conn-security-multistream/master/ssms.go) + + protocol = None + if initiator: + # Select protocol if initiator + protocol = await self.multiselect_client.select_one_of(list(self.transports.keys()), conn) + else: + # Select protocol if non-initiator + protocol = await self.multiselect.negotiate(conn) + + # Return transport from protocol + return self.transports[protocol] From eb26661652f12e0ab3473fbca6b61ca5bb378de0 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Sat, 27 Apr 2019 20:09:25 -0400 Subject: [PATCH 02/11] Remove outdated encryption folder --- libp2p/encryption/__init__.py | 0 libp2p/encryption/secio.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 libp2p/encryption/__init__.py delete mode 100644 libp2p/encryption/secio.py diff --git a/libp2p/encryption/__init__.py b/libp2p/encryption/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/libp2p/encryption/secio.py b/libp2p/encryption/secio.py deleted file mode 100644 index e69de29b..00000000 From 1fb4372ede5ffb3a42589cdc04c30458c47e7116 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Mon, 29 Apr 2019 18:05:38 -0400 Subject: [PATCH 03/11] Refine security --- libp2p/security/secure_conn_interface.py | 1 + libp2p/transport/upgrader.py | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libp2p/security/secure_conn_interface.py b/libp2p/security/secure_conn_interface.py index 946d4f51..3b5bb5c5 100644 --- a/libp2p/security/secure_conn_interface.py +++ b/libp2p/security/secure_conn_interface.py @@ -14,6 +14,7 @@ class ISecureConn(ABC): :return: connection object that has been made secure """ + @abstractmethod def get_security_details(self): """ :return: map containing details about the connections security diff --git a/libp2p/transport/upgrader.py b/libp2p/transport/upgrader.py index 9e311e37..f84fb400 100644 --- a/libp2p/transport/upgrader.py +++ b/libp2p/transport/upgrader.py @@ -10,16 +10,19 @@ class TransportUpgrader: def upgrade_listener(self, transport, listeners): """ - upgrade multiaddr listeners to libp2p-transport listeners - + Upgrade multiaddr listeners to libp2p-transport listeners """ - def upgrade_security(self): + def upgrade_security(self, conn, peer_id): + """ + Upgrade conn to be a secured connection + """ + # TODO: Do exchange to determine security module pass def upgrade_connection(self, conn, generic_protocol_handler, peer_id): """ - upgrade raw connection to muxed connection + Upgrade raw connection to muxed connection """ # For PoC, no security, default to mplex From 999e64854c5defcda51bd30b0049af3c498e993b Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Mon, 29 Apr 2019 18:05:49 -0400 Subject: [PATCH 04/11] Add security tests --- tests/security/insecure_security.py | 44 +++++++++++ tests/security/test_security_multistream.py | 88 +++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 tests/security/insecure_security.py create mode 100644 tests/security/test_security_multistream.py diff --git a/tests/security/insecure_security.py b/tests/security/insecure_security.py new file mode 100644 index 00000000..c7a53afd --- /dev/null +++ b/tests/security/insecure_security.py @@ -0,0 +1,44 @@ +from libp2p.security.security_transport_interface import ISecureTransport +from libp2p.security.security_conn_interface import ISecureConn + +class InsecureTransport(ISecureTransport): + + def __init__(self, transport_id): + self.transport_id = transport_id + + async def secure_inbound(self, conn): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are not the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + insecure_conn = InsecureConn(conn, self.transport_id) + return insecure_conn + + async def secure_outbound(self, conn, peer_id): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + insecure_conn = InsecureConn(conn, self.transport_id) + return insecure_conn + +class InsecureConn(ISecureConn): + + def __init__(self, conn, conn_id): + self.conn = conn + self.details = {} + self.details["conn_id"] = conn_id + + def get_conn(self): + """ + :return: connection object that has been made secure + """ + return self.conn + + def get_security_details(self): + """ + :return: map containing details about the connections security + """ + return self.details diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py new file mode 100644 index 00000000..25176f3e --- /dev/null +++ b/tests/security/test_security_multistream.py @@ -0,0 +1,88 @@ +import pytest + +from tests.utils import cleanup, set_up_nodes_by_transport_opt +from libp2p.security.security_multistream import SecurityMultistream +from tests.security.insecure_multistream import InsecureConn, InsecureTransport + +# TODO: Add tests for multiple streams being opened on different +# protocols through the same connection + + +async def perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator): + + # Create libp2p nodes and connect them, then secure the connection, then check + # the proper security was chosen + # TODO: implement -- note we need to introduce the notion of communicating over a raw connection + # for testing, we do NOT want to communicate over a stream so we can't just create two nodes + # and use their conn because our mplex will internally relay messages to a stream + conn = [] + + # Fill initiator + sec_multi_initiator = SecurityMultistream() + for i, transport in enumerate(transports_for_initiator): + sec_multi_initiator.add_transport(str(i), transport) + + # Fill non-initiator + sec_multi_noninitiator = SecurityMultistream() + for i, transport in enumerate(transports_for_noninitiator): + sec_multi_noninitiator.add_transport(str(i), transport) + + # Perform negotiation + tasks = [] + tasks.append(asyncio.ensure_future(sec_multi_initiator.secure_inbound(conn))) + tasks.append(asyncio.ensure_future(sec_multi_noninitiator.secure_inbound(conn))) + secured_conns = await asyncio.gather(*tasks) + + # Perform assertion + for conn in secured_conns: + assertion_func(conn.get_security_details()) + + # Success, terminate pending tasks. + await cleanup() + + +@pytest.mark.asyncio +async def test_single_protocol_succeeds(): + expected_selected_protocol = "/echo/1.0.0" + await perform_simple_test(expected_selected_protocol, + ["/echo/1.0.0"], ["/echo/1.0.0"]) + + +@pytest.mark.asyncio +async def test_single_protocol_fails(): + with pytest.raises(MultiselectClientError): + await perform_simple_test("", ["/echo/1.0.0"], + ["/potato/1.0.0"]) + + # Cleanup not reached on error + await cleanup() + + +@pytest.mark.asyncio +async def test_multiple_protocol_first_is_valid_succeeds(): + expected_selected_protocol = "/echo/1.0.0" + protocols_for_client = ["/echo/1.0.0", "/potato/1.0.0"] + protocols_for_listener = ["/foo/1.0.0", "/echo/1.0.0"] + await perform_simple_test(expected_selected_protocol, protocols_for_client, + protocols_for_listener) + + +@pytest.mark.asyncio +async def test_multiple_protocol_second_is_valid_succeeds(): + expected_selected_protocol = "/foo/1.0.0" + protocols_for_client = ["/rock/1.0.0", "/foo/1.0.0"] + protocols_for_listener = ["/foo/1.0.0", "/echo/1.0.0"] + await perform_simple_test(expected_selected_protocol, protocols_for_client, + protocols_for_listener) + + +@pytest.mark.asyncio +async def test_multiple_protocol_fails(): + protocols_for_client = ["/rock/1.0.0", "/foo/1.0.0", "/bar/1.0.0"] + protocols_for_listener = ["/aspyn/1.0.0", "/rob/1.0.0", "/zx/1.0.0", "/alex/1.0.0"] + with pytest.raises(MultiselectClientError): + await perform_simple_test("", protocols_for_client, + protocols_for_listener) + + # Cleanup not reached on error + await cleanup() From f59f27d4d071bc4f8f35726295bc551d9e897891 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Tue, 30 Apr 2019 03:09:05 -0400 Subject: [PATCH 05/11] Integrate security selectin into libp2p system --- libp2p/__init__.py | 5 +- libp2p/network/connection/raw_connection.py | 14 +++ libp2p/network/swarm.py | 12 +- libp2p/protocol_muxer/multiselect_client.py | 1 - libp2p/security/insecure_security.py | 44 +++++++ libp2p/security/security_multistream.py | 2 +- libp2p/stream_muxer/mplex/mplex.py | 9 +- .../muxed_connection_interface.py | 2 +- libp2p/transport/tcp/tcp.py | 5 +- libp2p/transport/upgrader.py | 16 ++- tests/security/test_security_multistream.py | 109 +++++++++--------- 11 files changed, 145 insertions(+), 74 deletions(-) create mode 100644 libp2p/security/insecure_security.py diff --git a/libp2p/__init__.py b/libp2p/__init__.py index 2e1cf88b..0c660e75 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -10,6 +10,7 @@ from .kademlia.routed_host import RoutedHost from .transport.upgrader import TransportUpgrader from .transport.tcp.tcp import TCP from .kademlia.network import KademliaServer +from libp2p.security.insecure_security import InsecureTransport async def cleanup_done_tasks(): @@ -71,7 +72,9 @@ def initialize_default_swarm( transport = [multiaddr.Multiaddr(t) for t in transport_opt] # TODO wire muxer up with swarm # muxer = muxer_opt or ["mplex/6.7.0"] - sec = sec_opt or ["secio"] + + # Use passed in security option or the default insecure option + sec = sec_opt or {"/insecure/1.0.0": InsecureTransport("insecure")} peerstore = peerstore_opt or PeerStore() upgrader = TransportUpgrader(sec, transport) swarm_opt = Swarm(id_opt, peerstore, upgrader) diff --git a/libp2p/network/connection/raw_connection.py b/libp2p/network/connection/raw_connection.py index 1700469f..66ef1f0c 100644 --- a/libp2p/network/connection/raw_connection.py +++ b/libp2p/network/connection/raw_connection.py @@ -1,3 +1,4 @@ +import asyncio from .raw_connection_interface import IRawConnection @@ -12,6 +13,19 @@ class RawConnection(IRawConnection): self._next_id = 0 if initiator else 1 self.initiator = initiator + async def write(self, data): + self.writer.write(data) + self.writer.write("\n".encode()) + await self.writer.drain() + + async def read(self): + line = await self.reader.readline() + adjusted_line = line.decode().rstrip('\n') + + # TODO: figure out a way to remove \n without going back and forth with + # encoding and decoding + return adjusted_line.encode() + def close(self): self.writer.close() diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index b30567b5..644554c9 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -69,8 +69,10 @@ class Swarm(INetwork): # Transport dials peer (gets back a raw conn) raw_conn = await self.transport.dial(multiaddr, self.self_id) - # Use upgrader to upgrade raw conn to muxed conn - muxed_conn = self.upgrader.upgrade_connection(raw_conn, \ + # Per, https://discuss.libp2p.io/t/multistream-security/130, we first secure + # the conn and then mux the conn + secured_conn = await self.upgrader.upgrade_security(raw_conn, peer_id, True) + muxed_conn = self.upgrader.upgrade_connection(secured_conn, \ self.generic_protocol_handler, peer_id) # Store muxed connection in connections @@ -145,7 +147,11 @@ class Swarm(INetwork): # to appropriate stream handler (using multiaddr) raw_conn = RawConnection(multiaddr.value_for_protocol('ip4'), multiaddr.value_for_protocol('tcp'), reader, writer, False) - muxed_conn = self.upgrader.upgrade_connection(raw_conn, \ + + # Per, https://discuss.libp2p.io/t/multistream-security/130, we first secure + # the conn and then mux the conn + secured_conn = await self.upgrader.upgrade_security(raw_conn, peer_id, False) + muxed_conn = self.upgrader.upgrade_connection(secured_conn, \ self.generic_protocol_handler, peer_id) # Store muxed_conn with peer id diff --git a/libp2p/protocol_muxer/multiselect_client.py b/libp2p/protocol_muxer/multiselect_client.py index dec54938..b93de4fb 100644 --- a/libp2p/protocol_muxer/multiselect_client.py +++ b/libp2p/protocol_muxer/multiselect_client.py @@ -45,7 +45,6 @@ class MultiselectClient(IMultiselectClient): :param stream: stream to communicate with multiselect over :return: selected protocol """ - # Create a communicator to handle all communication across the stream communicator = MultiselectCommunicator(stream) diff --git a/libp2p/security/insecure_security.py b/libp2p/security/insecure_security.py new file mode 100644 index 00000000..0c5debec --- /dev/null +++ b/libp2p/security/insecure_security.py @@ -0,0 +1,44 @@ +from libp2p.security.secure_transport_interface import ISecureTransport +from libp2p.security.secure_conn_interface import ISecureConn + +class InsecureTransport(ISecureTransport): + + def __init__(self, transport_id): + self.transport_id = transport_id + + async def secure_inbound(self, conn): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are not the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + insecure_conn = InsecureConn(conn, self.transport_id) + return insecure_conn + + async def secure_outbound(self, conn, peer_id): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + insecure_conn = InsecureConn(conn, self.transport_id) + return insecure_conn + +class InsecureConn(ISecureConn): + + def __init__(self, conn, conn_id): + self.conn = conn + self.details = {} + self.details["id"] = conn_id + + def get_conn(self): + """ + :return: connection object that has been made secure + """ + return self.conn + + def get_security_details(self): + """ + :return: map containing details about the connections security + """ + return self.details diff --git a/libp2p/security/security_multistream.py b/libp2p/security/security_multistream.py index 6fe45391..81db4cf2 100644 --- a/libp2p/security/security_multistream.py +++ b/libp2p/security/security_multistream.py @@ -12,7 +12,7 @@ Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interfa """ class SecurityMultistream(ABC): - def __init__(): + def __init__(self): # Map protocol to secure transport self.transports = {} diff --git a/libp2p/stream_muxer/mplex/mplex.py b/libp2p/stream_muxer/mplex/mplex.py index 0d587b54..e660a524 100644 --- a/libp2p/stream_muxer/mplex/mplex.py +++ b/libp2p/stream_muxer/mplex/mplex.py @@ -11,7 +11,7 @@ class Mplex(IMuxedConn): reference: https://github.com/libp2p/go-mplex/blob/master/multiplex.go """ - def __init__(self, conn, generic_protocol_handler, peer_id): + def __init__(self, secured_conn, generic_protocol_handler, peer_id): """ create a new muxed connection :param conn: an instance of raw connection @@ -19,10 +19,11 @@ class Mplex(IMuxedConn): for new muxed streams :param peer_id: peer_id of peer the connection is to """ - super(Mplex, self).__init__(conn, generic_protocol_handler, peer_id) + super(Mplex, self).__init__(secured_conn, generic_protocol_handler, peer_id) - self.raw_conn = conn - self.initiator = conn.initiator + self.secured_conn = secured_conn + self.raw_conn = secured_conn.get_conn() + self.initiator = self.raw_conn.initiator # Store generic protocol handler self.generic_protocol_handler = generic_protocol_handler diff --git a/libp2p/stream_muxer/muxed_connection_interface.py b/libp2p/stream_muxer/muxed_connection_interface.py index 541fd64a..0faf7705 100644 --- a/libp2p/stream_muxer/muxed_connection_interface.py +++ b/libp2p/stream_muxer/muxed_connection_interface.py @@ -10,7 +10,7 @@ class IMuxedConn(ABC): def __init__(self, conn, generic_protocol_handler, peer_id): """ create a new muxed connection - :param conn: an instance of raw connection + :param conn: an instance of secured connection :param generic_protocol_handler: generic protocol handler for new muxed streams :param peer_id: peer_id of peer the connection is to diff --git a/libp2p/transport/tcp/tcp.py b/libp2p/transport/tcp/tcp.py index 21c0574b..f6167cee 100644 --- a/libp2p/transport/tcp/tcp.py +++ b/libp2p/transport/tcp/tcp.py @@ -82,9 +82,10 @@ class TCP(ITransport): await writer.drain() # Await ack for peer id - ack = (await reader.read(1024)).decode() + expected_ack_str = "received peer id" + ack = (await reader.read(len(expected_ack_str))).decode() - if ack != "received peer id": + if ack != expected_ack_str: raise Exception("Receiver did not receive peer id") return RawConnection(host, port, reader, writer, True) diff --git a/libp2p/transport/upgrader.py b/libp2p/transport/upgrader.py index f84fb400..68f3762d 100644 --- a/libp2p/transport/upgrader.py +++ b/libp2p/transport/upgrader.py @@ -1,11 +1,17 @@ from libp2p.stream_muxer.mplex.mplex import Mplex +from libp2p.security.security_multistream import SecurityMultistream class TransportUpgrader: # pylint: disable=no-self-use def __init__(self, secOpt, muxerOpt): - self.sec = secOpt + # Store security option + self.security_multistream = SecurityMultistream() + for key in secOpt: + self.security_multistream.add_transport(key, secOpt[key]) + + # Store muxer option self.muxer = muxerOpt def upgrade_listener(self, transport, listeners): @@ -13,12 +19,14 @@ class TransportUpgrader: Upgrade multiaddr listeners to libp2p-transport listeners """ - def upgrade_security(self, conn, peer_id): + async def upgrade_security(self, raw_conn, peer_id, initiator): """ Upgrade conn to be a secured connection """ - # TODO: Do exchange to determine security module - pass + if initiator: + return await self.security_multistream.secure_outbound(raw_conn, peer_id) + else: + return await self.security_multistream.secure_inbound(raw_conn) def upgrade_connection(self, conn, generic_protocol_handler, peer_id): """ diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index 25176f3e..8b23e16d 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -1,12 +1,28 @@ +import asyncio +import multiaddr import pytest +from libp2p import new_node +from libp2p.peer.peerinfo import info_from_p2p_addr from tests.utils import cleanup, set_up_nodes_by_transport_opt from libp2p.security.security_multistream import SecurityMultistream -from tests.security.insecure_multistream import InsecureConn, InsecureTransport +from libp2p.security.insecure_security import InsecureConn, InsecureTransport # TODO: Add tests for multiple streams being opened on different # protocols through the same connection +def peer_id_for_node(node): + addr = node.get_addrs()[0] + info = info_from_p2p_addr(addr) + return info.peer_id + +async def connect(node1, node2): + """ + Connect node1 to node2 + """ + addr = node2.get_addrs()[0] + info = info_from_p2p_addr(addr) + await node1.connect(info) async def perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator): @@ -15,74 +31,53 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor # TODO: implement -- note we need to introduce the notion of communicating over a raw connection # for testing, we do NOT want to communicate over a stream so we can't just create two nodes # and use their conn because our mplex will internally relay messages to a stream - conn = [] + sec_opt1 = dict((str(i), transport) for i, transport in enumerate(transports_for_initiator)) + sec_opt2 = dict((str(i), transport) for i, transport in enumerate(transports_for_noninitiator)) + + node1 = await new_node(transport_opt=["/ip4/127.0.0.1/tcp/0"], sec_opt=sec_opt1) + node2 = await new_node(transport_opt=["/ip4/127.0.0.1/tcp/0"], sec_opt=sec_opt2) + + await node1.get_network().listen(multiaddr.Multiaddr("/ip4/127.0.0.1/tcp/0")) + await node2.get_network().listen(multiaddr.Multiaddr("/ip4/127.0.0.1/tcp/0")) + + await connect(node1, node2) # Fill initiator - sec_multi_initiator = SecurityMultistream() - for i, transport in enumerate(transports_for_initiator): - sec_multi_initiator.add_transport(str(i), transport) + # sec_multi_initiator = SecurityMultistream() + # for i, transport in enumerate(transports_for_initiator): + # sec_multi_initiator.add_transport(str(i), transport) - # Fill non-initiator - sec_multi_noninitiator = SecurityMultistream() - for i, transport in enumerate(transports_for_noninitiator): - sec_multi_noninitiator.add_transport(str(i), transport) + # # Fill non-initiator + # sec_multi_noninitiator = SecurityMultistream() + # for i, transport in enumerate(transports_for_noninitiator): + # sec_multi_noninitiator.add_transport(str(i), transport) - # Perform negotiation - tasks = [] - tasks.append(asyncio.ensure_future(sec_multi_initiator.secure_inbound(conn))) - tasks.append(asyncio.ensure_future(sec_multi_noninitiator.secure_inbound(conn))) - secured_conns = await asyncio.gather(*tasks) + # # Perform negotiation + # tasks = [] + # tasks.append(asyncio.ensure_future(sec_multi_initiator.secure_inbound(conn))) + # tasks.append(asyncio.ensure_future(sec_multi_noninitiator.secure_inbound(conn))) + # mplex_conns = await asyncio.gather(*tasks) + + # Get conns + node1_conn = node1.get_network().connections[peer_id_for_node(node2)] + node2_conn = node2.get_network().connections[peer_id_for_node(node1)] # Perform assertion - for conn in secured_conns: - assertion_func(conn.get_security_details()) + assertion_func(node1_conn.secured_conn.get_security_details()) + assertion_func(node2_conn.secured_conn.get_security_details()) # Success, terminate pending tasks. await cleanup() @pytest.mark.asyncio -async def test_single_protocol_succeeds(): - expected_selected_protocol = "/echo/1.0.0" - await perform_simple_test(expected_selected_protocol, - ["/echo/1.0.0"], ["/echo/1.0.0"]) +async def test_single_security_transport_succeeds(): + transports_for_initiator = [InsecureTransport("foo")] + transports_for_noninitiator = [InsecureTransport("foo")] + def assertion_func(details): + assert details["id"] == "foo" -@pytest.mark.asyncio -async def test_single_protocol_fails(): - with pytest.raises(MultiselectClientError): - await perform_simple_test("", ["/echo/1.0.0"], - ["/potato/1.0.0"]) + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) - # Cleanup not reached on error - await cleanup() - - -@pytest.mark.asyncio -async def test_multiple_protocol_first_is_valid_succeeds(): - expected_selected_protocol = "/echo/1.0.0" - protocols_for_client = ["/echo/1.0.0", "/potato/1.0.0"] - protocols_for_listener = ["/foo/1.0.0", "/echo/1.0.0"] - await perform_simple_test(expected_selected_protocol, protocols_for_client, - protocols_for_listener) - - -@pytest.mark.asyncio -async def test_multiple_protocol_second_is_valid_succeeds(): - expected_selected_protocol = "/foo/1.0.0" - protocols_for_client = ["/rock/1.0.0", "/foo/1.0.0"] - protocols_for_listener = ["/foo/1.0.0", "/echo/1.0.0"] - await perform_simple_test(expected_selected_protocol, protocols_for_client, - protocols_for_listener) - - -@pytest.mark.asyncio -async def test_multiple_protocol_fails(): - protocols_for_client = ["/rock/1.0.0", "/foo/1.0.0", "/bar/1.0.0"] - protocols_for_listener = ["/aspyn/1.0.0", "/rob/1.0.0", "/zx/1.0.0", "/alex/1.0.0"] - with pytest.raises(MultiselectClientError): - await perform_simple_test("", protocols_for_client, - protocols_for_listener) - - # Cleanup not reached on error - await cleanup() From a0bd6e5eb034a736546c8198ad21f742c167addc Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Tue, 30 Apr 2019 03:27:06 -0400 Subject: [PATCH 06/11] Add simple security with communication test --- tests/security/insecure_security.py | 44 --------------- tests/security/simple_security.py | 61 +++++++++++++++++++++ tests/security/test_security_multistream.py | 33 +++++------ 3 files changed, 78 insertions(+), 60 deletions(-) delete mode 100644 tests/security/insecure_security.py create mode 100644 tests/security/simple_security.py diff --git a/tests/security/insecure_security.py b/tests/security/insecure_security.py deleted file mode 100644 index c7a53afd..00000000 --- a/tests/security/insecure_security.py +++ /dev/null @@ -1,44 +0,0 @@ -from libp2p.security.security_transport_interface import ISecureTransport -from libp2p.security.security_conn_interface import ISecureConn - -class InsecureTransport(ISecureTransport): - - def __init__(self, transport_id): - self.transport_id = transport_id - - async def secure_inbound(self, conn): - """ - Secure the connection, either locally or by communicating with opposing node via conn, - for an inbound connection (i.e. we are not the initiator) - :return: secure connection object (that implements secure_conn_interface) - """ - insecure_conn = InsecureConn(conn, self.transport_id) - return insecure_conn - - async def secure_outbound(self, conn, peer_id): - """ - Secure the connection, either locally or by communicating with opposing node via conn, - for an inbound connection (i.e. we are the initiator) - :return: secure connection object (that implements secure_conn_interface) - """ - insecure_conn = InsecureConn(conn, self.transport_id) - return insecure_conn - -class InsecureConn(ISecureConn): - - def __init__(self, conn, conn_id): - self.conn = conn - self.details = {} - self.details["conn_id"] = conn_id - - def get_conn(self): - """ - :return: connection object that has been made secure - """ - return self.conn - - def get_security_details(self): - """ - :return: map containing details about the connections security - """ - return self.details diff --git a/tests/security/simple_security.py b/tests/security/simple_security.py new file mode 100644 index 00000000..5df58113 --- /dev/null +++ b/tests/security/simple_security.py @@ -0,0 +1,61 @@ +import asyncio +from libp2p.security.secure_transport_interface import ISecureTransport +from libp2p.security.secure_conn_interface import ISecureConn + +class SimpleSecurityTransport(ISecureTransport): + + def __init__(self, key_phrase): + self.key_phrase = key_phrase + + async def secure_inbound(self, conn): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are not the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + await conn.write(self.key_phrase.encode()) + incoming = (await conn.read()).decode() + + if incoming != self.key_phrase: + raise Exception("Key phrase differed between nodes. Expected " + self.key_phrase) + + secure_conn = SimpleSecureConn(conn, self.key_phrase) + return secure_conn + + async def secure_outbound(self, conn, peer_id): + """ + Secure the connection, either locally or by communicating with opposing node via conn, + for an inbound connection (i.e. we are the initiator) + :return: secure connection object (that implements secure_conn_interface) + """ + await conn.write(self.key_phrase.encode()) + incoming = (await conn.read()).decode() + + # Force context switch, as this security transport is built for testing locally + # in a single event loop + await asyncio.sleep(0) + + if incoming != self.key_phrase: + raise Exception("Key phrase differed between nodes. Expected " + self.key_phrase) + + secure_conn = SimpleSecureConn(conn, self.key_phrase) + return secure_conn + +class SimpleSecureConn(ISecureConn): + + def __init__(self, conn, key_phrase): + self.conn = conn + self.details = {} + self.details["key_phrase"] = key_phrase + + def get_conn(self): + """ + :return: connection object that has been made secure + """ + return self.conn + + def get_security_details(self): + """ + :return: map containing details about the connections security + """ + return self.details diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index 8b23e16d..aae8ce3a 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -7,6 +7,7 @@ from libp2p.peer.peerinfo import info_from_p2p_addr from tests.utils import cleanup, set_up_nodes_by_transport_opt from libp2p.security.security_multistream import SecurityMultistream from libp2p.security.insecure_security import InsecureConn, InsecureTransport +from simple_security import SimpleSecurityTransport # TODO: Add tests for multiple streams being opened on different # protocols through the same connection @@ -42,21 +43,10 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor await connect(node1, node2) - # Fill initiator - # sec_multi_initiator = SecurityMultistream() - # for i, transport in enumerate(transports_for_initiator): - # sec_multi_initiator.add_transport(str(i), transport) - - # # Fill non-initiator - # sec_multi_noninitiator = SecurityMultistream() - # for i, transport in enumerate(transports_for_noninitiator): - # sec_multi_noninitiator.add_transport(str(i), transport) - - # # Perform negotiation - # tasks = [] - # tasks.append(asyncio.ensure_future(sec_multi_initiator.secure_inbound(conn))) - # tasks.append(asyncio.ensure_future(sec_multi_noninitiator.secure_inbound(conn))) - # mplex_conns = await asyncio.gather(*tasks) + # Wait a very short period to allow conns to be stored (since the functions + # storing the conns are async, they may happen at slightly different times + # on each node) + await asyncio.sleep(0.1) # Get conns node1_conn = node1.get_network().connections[peer_id_for_node(node2)] @@ -71,7 +61,7 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor @pytest.mark.asyncio -async def test_single_security_transport_succeeds(): +async def test_single_insecure_security_transport_succeeds(): transports_for_initiator = [InsecureTransport("foo")] transports_for_noninitiator = [InsecureTransport("foo")] @@ -81,3 +71,14 @@ async def test_single_security_transport_succeeds(): await perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator) +@pytest.mark.asyncio +async def test_single_simple_test_security_transport_succeeds(): + transports_for_initiator = [SimpleSecurityTransport("tacos")] + transports_for_noninitiator = [SimpleSecurityTransport("tacos")] + + def assertion_func(details): + assert details["key_phrase"] == "tacos" + + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) + From e555f17a7b6321a8893e91c03ed52d6209f7c190 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Tue, 30 Apr 2019 16:07:26 -0400 Subject: [PATCH 07/11] Fix bug in security multistream --- libp2p/security/security_multistream.py | 5 ++--- tests/security/test_security_multistream.py | 24 +++++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/libp2p/security/security_multistream.py b/libp2p/security/security_multistream.py index 81db4cf2..94d29348 100644 --- a/libp2p/security/security_multistream.py +++ b/libp2p/security/security_multistream.py @@ -40,7 +40,7 @@ class SecurityMultistream(ABC): """ # Select a secure transport - transport = await self.select_transport(conn, True) + transport = await self.select_transport(conn, False) # Create secured connection secure_conn = await transport.secure_inbound(conn) @@ -81,7 +81,6 @@ class SecurityMultistream(ABC): protocol = await self.multiselect_client.select_one_of(list(self.transports.keys()), conn) else: # Select protocol if non-initiator - protocol = await self.multiselect.negotiate(conn) - + protocol, _ = await self.multiselect.negotiate(conn) # Return transport from protocol return self.transports[protocol] diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index aae8ce3a..1fcc8bca 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -32,8 +32,8 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor # TODO: implement -- note we need to introduce the notion of communicating over a raw connection # for testing, we do NOT want to communicate over a stream so we can't just create two nodes # and use their conn because our mplex will internally relay messages to a stream - sec_opt1 = dict((str(i), transport) for i, transport in enumerate(transports_for_initiator)) - sec_opt2 = dict((str(i), transport) for i, transport in enumerate(transports_for_noninitiator)) + sec_opt1 = transports_for_initiator + sec_opt2 = transports_for_noninitiator node1 = await new_node(transport_opt=["/ip4/127.0.0.1/tcp/0"], sec_opt=sec_opt1) node2 = await new_node(transport_opt=["/ip4/127.0.0.1/tcp/0"], sec_opt=sec_opt2) @@ -62,8 +62,8 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor @pytest.mark.asyncio async def test_single_insecure_security_transport_succeeds(): - transports_for_initiator = [InsecureTransport("foo")] - transports_for_noninitiator = [InsecureTransport("foo")] + transports_for_initiator = {"foo": InsecureTransport("foo")} + transports_for_noninitiator = {"foo": InsecureTransport("foo")} def assertion_func(details): assert details["id"] == "foo" @@ -73,8 +73,8 @@ async def test_single_insecure_security_transport_succeeds(): @pytest.mark.asyncio async def test_single_simple_test_security_transport_succeeds(): - transports_for_initiator = [SimpleSecurityTransport("tacos")] - transports_for_noninitiator = [SimpleSecurityTransport("tacos")] + transports_for_initiator = {"tacos": SimpleSecurityTransport("tacos")} + transports_for_noninitiator = {"tacos": SimpleSecurityTransport("tacos")} def assertion_func(details): assert details["key_phrase"] == "tacos" @@ -82,3 +82,15 @@ async def test_single_simple_test_security_transport_succeeds(): await perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator) +@pytest.mark.asyncio +async def test_two_simple_test_security_transport_for_initiator_succeeds(): + transports_for_initiator = {"tacos": SimpleSecurityTransport("tacos"), + "shleep": SimpleSecurityTransport("shleep")} + transports_for_noninitiator = {"shleep": SimpleSecurityTransport("shleep")} + + def assertion_func(details): + assert details["key_phrase"] == "shleep" + + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) + From 4333c2d061765ba459b526a972e4ff88e1c04bb9 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Wed, 1 May 2019 13:54:19 -0400 Subject: [PATCH 08/11] Fix linting issues' --- libp2p/__init__.py | 2 +- libp2p/network/connection/raw_connection.py | 2 -- libp2p/network/swarm.py | 2 +- libp2p/security/insecure_security.py | 2 +- libp2p/security/secure_conn_interface.py | 3 ++- libp2p/security/secure_transport_interface.py | 6 ++--- libp2p/security/security_multistream.py | 22 ++++++++++--------- libp2p/transport/upgrader.py | 4 ++-- tests/security/simple_security.py | 2 +- tests/security/test_security_multistream.py | 17 +++++++------- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/libp2p/__init__.py b/libp2p/__init__.py index 0c660e75..5924bcce 100644 --- a/libp2p/__init__.py +++ b/libp2p/__init__.py @@ -2,6 +2,7 @@ import asyncio import multiaddr from Crypto.PublicKey import RSA +from libp2p.security.insecure_security import InsecureTransport from .peer.peerstore import PeerStore from .peer.id import id_from_public_key from .network.swarm import Swarm @@ -10,7 +11,6 @@ from .kademlia.routed_host import RoutedHost from .transport.upgrader import TransportUpgrader from .transport.tcp.tcp import TCP from .kademlia.network import KademliaServer -from libp2p.security.insecure_security import InsecureTransport async def cleanup_done_tasks(): diff --git a/libp2p/network/connection/raw_connection.py b/libp2p/network/connection/raw_connection.py index 66ef1f0c..23cb5516 100644 --- a/libp2p/network/connection/raw_connection.py +++ b/libp2p/network/connection/raw_connection.py @@ -1,7 +1,5 @@ -import asyncio from .raw_connection_interface import IRawConnection - class RawConnection(IRawConnection): def __init__(self, ip, port, reader, writer, initiator): diff --git a/libp2p/network/swarm.py b/libp2p/network/swarm.py index 644554c9..80c21d92 100644 --- a/libp2p/network/swarm.py +++ b/libp2p/network/swarm.py @@ -147,7 +147,7 @@ class Swarm(INetwork): # to appropriate stream handler (using multiaddr) raw_conn = RawConnection(multiaddr.value_for_protocol('ip4'), multiaddr.value_for_protocol('tcp'), reader, writer, False) - + # Per, https://discuss.libp2p.io/t/multistream-security/130, we first secure # the conn and then mux the conn secured_conn = await self.upgrader.upgrade_security(raw_conn, peer_id, False) diff --git a/libp2p/security/insecure_security.py b/libp2p/security/insecure_security.py index 0c5debec..dfa80a7e 100644 --- a/libp2p/security/insecure_security.py +++ b/libp2p/security/insecure_security.py @@ -5,7 +5,7 @@ class InsecureTransport(ISecureTransport): def __init__(self, transport_id): self.transport_id = transport_id - + async def secure_inbound(self, conn): """ Secure the connection, either locally or by communicating with opposing node via conn, diff --git a/libp2p/security/secure_conn_interface.py b/libp2p/security/secure_conn_interface.py index 3b5bb5c5..e8433a29 100644 --- a/libp2p/security/secure_conn_interface.py +++ b/libp2p/security/secure_conn_interface.py @@ -1,5 +1,7 @@ from abc import ABC, abstractmethod +# pylint: disable=W0105 + """ Represents a secured connection object, which includes a connection and details about the security involved in the secured connection @@ -19,4 +21,3 @@ class ISecureConn(ABC): """ :return: map containing details about the connections security """ - diff --git a/libp2p/security/secure_transport_interface.py b/libp2p/security/secure_transport_interface.py index 3e497190..54ca8b17 100644 --- a/libp2p/security/secure_transport_interface.py +++ b/libp2p/security/secure_transport_interface.py @@ -1,9 +1,9 @@ -import asyncio - from abc import ABC, abstractmethod +# pylint: disable=W0105 + """ -Transport that is used to secure a connection. This transport is +Transport that is used to secure a connection. This transport is chosen by a security transport multistream module. Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go diff --git a/libp2p/security/security_multistream.py b/libp2p/security/security_multistream.py index 94d29348..c8d2f884 100644 --- a/libp2p/security/security_multistream.py +++ b/libp2p/security/security_multistream.py @@ -1,9 +1,9 @@ -import asyncio - -from abc import ABC, abstractmethod +from abc import ABC from libp2p.protocol_muxer.multiselect_client import MultiselectClient from libp2p.protocol_muxer.multiselect import Multiselect +# pylint: disable=W0105 + """ Represents a secured connection object, which includes a connection and details about the security involved in the secured connection @@ -13,7 +13,7 @@ Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interfa class SecurityMultistream(ABC): def __init__(self): - # Map protocol to secure transport + # Map protocol to secure transport self.transports = {} # Create multiselect @@ -31,7 +31,7 @@ class SecurityMultistream(ABC): # we only care about selecting the protocol, not any handler function self.multiselect.add_handler(protocol, None) - + async def secure_inbound(self, conn): """ Secure the connection, either locally or by communicating with opposing node via conn, @@ -66,19 +66,21 @@ class SecurityMultistream(ABC): async def select_transport(self, conn, initiator): """ - Select a transport that both us and the node on the + Select a transport that both us and the node on the other end of conn support and agree on :param conn: conn to choose a transport over :param initiator: true if we are the initiator, false otherwise :return: selected secure transport """ - # TODO: Is conn acceptable to multiselect/multiselect_client instead of stream? In go repo, - # they pass in a raw conn (https://raw.githubusercontent.com/libp2p/go-conn-security-multistream/master/ssms.go) - + # TODO: Is conn acceptable to multiselect/multiselect_client + # instead of stream? In go repo, they pass in a raw conn + # (https://raw.githubusercontent.com/libp2p/go-conn-security-multistream/master/ssms.go) + protocol = None if initiator: # Select protocol if initiator - protocol = await self.multiselect_client.select_one_of(list(self.transports.keys()), conn) + protocol = \ + await self.multiselect_client.select_one_of(list(self.transports.keys()), conn) else: # Select protocol if non-initiator protocol, _ = await self.multiselect.negotiate(conn) diff --git a/libp2p/transport/upgrader.py b/libp2p/transport/upgrader.py index 68f3762d..2ccce244 100644 --- a/libp2p/transport/upgrader.py +++ b/libp2p/transport/upgrader.py @@ -25,8 +25,8 @@ class TransportUpgrader: """ if initiator: return await self.security_multistream.secure_outbound(raw_conn, peer_id) - else: - return await self.security_multistream.secure_inbound(raw_conn) + + return await self.security_multistream.secure_inbound(raw_conn) def upgrade_connection(self, conn, generic_protocol_handler, peer_id): """ diff --git a/tests/security/simple_security.py b/tests/security/simple_security.py index 5df58113..62f56668 100644 --- a/tests/security/simple_security.py +++ b/tests/security/simple_security.py @@ -6,7 +6,7 @@ class SimpleSecurityTransport(ISecureTransport): def __init__(self, key_phrase): self.key_phrase = key_phrase - + async def secure_inbound(self, conn): """ Secure the connection, either locally or by communicating with opposing node via conn, diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index 1fcc8bca..ed2ccde7 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -4,9 +4,8 @@ import pytest from libp2p import new_node from libp2p.peer.peerinfo import info_from_p2p_addr -from tests.utils import cleanup, set_up_nodes_by_transport_opt -from libp2p.security.security_multistream import SecurityMultistream -from libp2p.security.insecure_security import InsecureConn, InsecureTransport +from libp2p.security.insecure_security import InsecureTransport +from tests.utils import cleanup from simple_security import SimpleSecurityTransport # TODO: Add tests for multiple streams being opened on different @@ -25,8 +24,9 @@ async def connect(node1, node2): info = info_from_p2p_addr(addr) await node1.connect(info) -async def perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator): - +async def perform_simple_test(assertion_func, \ + transports_for_initiator, transports_for_noninitiator): + # Create libp2p nodes and connect them, then secure the connection, then check # the proper security was chosen # TODO: implement -- note we need to introduce the notion of communicating over a raw connection @@ -43,9 +43,9 @@ async def perform_simple_test(assertion_func, transports_for_initiator, transpor await connect(node1, node2) - # Wait a very short period to allow conns to be stored (since the functions + # Wait a very short period to allow conns to be stored (since the functions # storing the conns are async, they may happen at slightly different times - # on each node) + # on each node) await asyncio.sleep(0.1) # Get conns @@ -84,7 +84,7 @@ async def test_single_simple_test_security_transport_succeeds(): @pytest.mark.asyncio async def test_two_simple_test_security_transport_for_initiator_succeeds(): - transports_for_initiator = {"tacos": SimpleSecurityTransport("tacos"), + transports_for_initiator = {"tacos": SimpleSecurityTransport("tacos"), "shleep": SimpleSecurityTransport("shleep")} transports_for_noninitiator = {"shleep": SimpleSecurityTransport("shleep")} @@ -93,4 +93,3 @@ async def test_two_simple_test_security_transport_for_initiator_succeeds(): await perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator) - From 515a461172ecd5617c98d1f45922cbb9c24144ca Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Wed, 1 May 2019 17:13:01 -0400 Subject: [PATCH 09/11] Add more security tests --- tests/security/test_security_multistream.py | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index ed2ccde7..f849bb82 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -4,6 +4,7 @@ import pytest from libp2p import new_node from libp2p.peer.peerinfo import info_from_p2p_addr +from libp2p.protocol_muxer.multiselect_client import MultiselectClientError from libp2p.security.insecure_security import InsecureTransport from tests.utils import cleanup from simple_security import SimpleSecurityTransport @@ -93,3 +94,66 @@ async def test_two_simple_test_security_transport_for_initiator_succeeds(): await perform_simple_test(assertion_func, transports_for_initiator, transports_for_noninitiator) + +@pytest.mark.asyncio +async def test_two_simple_test_security_transport_for_noninitiator_succeeds(): + transports_for_initiator = {"tacos": SimpleSecurityTransport("tacos")} + transports_for_noninitiator = {"shleep": SimpleSecurityTransport("shleep"), + "tacos": SimpleSecurityTransport("tacos")} + + def assertion_func(details): + assert details["key_phrase"] == "tacos" + + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) + + +@pytest.mark.asyncio +async def test_two_simple_test_security_transport_for_both_succeeds(): + transports_for_initiator = {"a": SimpleSecurityTransport("a"), + "b": SimpleSecurityTransport("b")} + transports_for_noninitiator = {"c": SimpleSecurityTransport("c"), + "b": SimpleSecurityTransport("b")} + + def assertion_func(details): + assert details["key_phrase"] == "b" + + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) + +@pytest.mark.asyncio +async def test_multiple_security_none_the_same_fails(): + transports_for_initiator = {"a": SimpleSecurityTransport("a"), + "b": SimpleSecurityTransport("b")} + transports_for_noninitiator = {"c": SimpleSecurityTransport("c"), + "d": SimpleSecurityTransport("d")} + + def assertion_func(_): + assert False + + with pytest.raises(MultiselectClientError): + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) + + await cleanup() + +@pytest.mark.asyncio +async def test_default_insecure_security(): + transports_for_initiator = None + transports_for_noninitiator = None + + details1 = None + details2 = None + + def assertion_func(details): + nonlocal details1 + nonlocal details2 + if not details1: + details1 = details + elif not details2: + details2 = details + else: + assert details1 == details2 + + await perform_simple_test(assertion_func, + transports_for_initiator, transports_for_noninitiator) From 144dc8d8540bfd008986dae4e949605a4dae9236 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Wed, 1 May 2019 17:21:11 -0400 Subject: [PATCH 10/11] Move simple security to libp2p/security --- {tests => libp2p}/security/simple_security.py | 0 tests/security/test_security_multistream.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {tests => libp2p}/security/simple_security.py (100%) diff --git a/tests/security/simple_security.py b/libp2p/security/simple_security.py similarity index 100% rename from tests/security/simple_security.py rename to libp2p/security/simple_security.py diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index f849bb82..ed1ad299 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -6,8 +6,8 @@ from libp2p import new_node from libp2p.peer.peerinfo import info_from_p2p_addr from libp2p.protocol_muxer.multiselect_client import MultiselectClientError from libp2p.security.insecure_security import InsecureTransport +from libp2p.security.simple_security import SimpleSecurityTransport from tests.utils import cleanup -from simple_security import SimpleSecurityTransport # TODO: Add tests for multiple streams being opened on different # protocols through the same connection From 867f3a70f674011c4d223a36ee2e1e8c19ed9506 Mon Sep 17 00:00:00 2001 From: Stuckinaboot Date: Tue, 7 May 2019 01:31:54 -0400 Subject: [PATCH 11/11] Modify stream to be reader_writer --- .../protocol_muxer/multiselect_communicator.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/libp2p/protocol_muxer/multiselect_communicator.py b/libp2p/protocol_muxer/multiselect_communicator.py index d7e0dd50..0c5b3fa0 100644 --- a/libp2p/protocol_muxer/multiselect_communicator.py +++ b/libp2p/protocol_muxer/multiselect_communicator.py @@ -8,19 +8,24 @@ class MultiselectCommunicator(IMultiselectCommunicator): which is necessary for them to work """ - def __init__(self, stream): - self.stream = stream + def __init__(self, reader_writer): + """ + MultistreamCommunicator expects a reader_writer object that has + an async read and an async write function (this could be a stream, + raw connection, or other object implementing those functions) + """ + self.reader_writer = reader_writer async def write(self, msg_str): """ - Write message to stream + Write message to reader_writer :param msg_str: message to write """ - await self.stream.write(msg_str.encode()) + await self.reader_writer.write(msg_str.encode()) async def read_stream_until_eof(self): """ - Reads message from stream until EOF + Reads message from reader_writer until EOF """ - read_str = (await self.stream.read()).decode() + read_str = (await self.reader_writer.read()).decode() return read_str