mirror of
https://github.com/varun-r-mallya/py-libp2p.git
synced 2026-03-24 06:01:26 +00:00
@ -2,6 +2,7 @@ import asyncio
|
|||||||
import multiaddr
|
import multiaddr
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
from libp2p.security.insecure_security import InsecureTransport
|
||||||
from .peer.peerstore import PeerStore
|
from .peer.peerstore import PeerStore
|
||||||
from .peer.id import id_from_public_key
|
from .peer.id import id_from_public_key
|
||||||
from .network.swarm import Swarm
|
from .network.swarm import Swarm
|
||||||
@ -75,7 +76,7 @@ def initialize_default_swarm(
|
|||||||
# TODO TransportUpgrader is not doing anything really
|
# TODO TransportUpgrader is not doing anything really
|
||||||
# TODO parse muxer and sec to pass into TransportUpgrader
|
# TODO parse muxer and sec to pass into TransportUpgrader
|
||||||
muxer = muxer_opt or ["mplex/6.7.0"]
|
muxer = muxer_opt or ["mplex/6.7.0"]
|
||||||
sec = sec_opt or ["secio"]
|
sec = sec_opt or {"insecure/1.0.0": InsecureTransport("insecure")}
|
||||||
upgrader = TransportUpgrader(sec, muxer)
|
upgrader = TransportUpgrader(sec, muxer)
|
||||||
|
|
||||||
peerstore = peerstore_opt or PeerStore()
|
peerstore = peerstore_opt or PeerStore()
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from .raw_connection_interface import IRawConnection
|
from .raw_connection_interface import IRawConnection
|
||||||
|
|
||||||
|
|
||||||
class RawConnection(IRawConnection):
|
class RawConnection(IRawConnection):
|
||||||
|
|
||||||
def __init__(self, ip, port, reader, writer, initiator):
|
def __init__(self, ip, port, reader, writer, initiator):
|
||||||
@ -12,6 +11,19 @@ class RawConnection(IRawConnection):
|
|||||||
self._next_id = 0 if initiator else 1
|
self._next_id = 0 if initiator else 1
|
||||||
self.initiator = initiator
|
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):
|
def close(self):
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
|
|
||||||
|
|||||||
@ -72,8 +72,10 @@ class Swarm(INetwork):
|
|||||||
# Transport dials peer (gets back a raw conn)
|
# Transport dials peer (gets back a raw conn)
|
||||||
raw_conn = await self.transport.dial(multiaddr, self.self_id)
|
raw_conn = await self.transport.dial(multiaddr, self.self_id)
|
||||||
|
|
||||||
# Use upgrader to upgrade raw conn to muxed conn
|
# Per, https://discuss.libp2p.io/t/multistream-security/130, we first secure
|
||||||
muxed_conn = self.upgrader.upgrade_connection(raw_conn, \
|
# 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)
|
self.generic_protocol_handler, peer_id)
|
||||||
|
|
||||||
# Store muxed connection in connections
|
# Store muxed connection in connections
|
||||||
@ -148,7 +150,11 @@ class Swarm(INetwork):
|
|||||||
# to appropriate stream handler (using multiaddr)
|
# to appropriate stream handler (using multiaddr)
|
||||||
raw_conn = RawConnection(multiaddr.value_for_protocol('ip4'),
|
raw_conn = RawConnection(multiaddr.value_for_protocol('ip4'),
|
||||||
multiaddr.value_for_protocol('tcp'), reader, writer, False)
|
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)
|
self.generic_protocol_handler, peer_id)
|
||||||
|
|
||||||
# Store muxed_conn with peer id
|
# Store muxed_conn with peer id
|
||||||
|
|||||||
@ -45,7 +45,6 @@ class MultiselectClient(IMultiselectClient):
|
|||||||
:param stream: stream to communicate with multiselect over
|
:param stream: stream to communicate with multiselect over
|
||||||
:return: selected protocol
|
:return: selected protocol
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create a communicator to handle all communication across the stream
|
# Create a communicator to handle all communication across the stream
|
||||||
communicator = MultiselectCommunicator(stream)
|
communicator = MultiselectCommunicator(stream)
|
||||||
|
|
||||||
|
|||||||
@ -8,19 +8,24 @@ class MultiselectCommunicator(IMultiselectCommunicator):
|
|||||||
which is necessary for them to work
|
which is necessary for them to work
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, stream):
|
def __init__(self, reader_writer):
|
||||||
self.stream = stream
|
"""
|
||||||
|
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):
|
async def write(self, msg_str):
|
||||||
"""
|
"""
|
||||||
Write message to stream
|
Write message to reader_writer
|
||||||
:param msg_str: message to write
|
: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):
|
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
|
return read_str
|
||||||
|
|||||||
44
libp2p/security/insecure_security.py
Normal file
44
libp2p/security/insecure_security.py
Normal file
@ -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
|
||||||
23
libp2p/security/secure_conn_interface.py
Normal file
23
libp2p/security/secure_conn_interface.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_security_details(self):
|
||||||
|
"""
|
||||||
|
:return: map containing details about the connections security
|
||||||
|
"""
|
||||||
27
libp2p/security/secure_transport_interface.py
Normal file
27
libp2p/security/secure_transport_interface.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
# pylint: disable=W0105
|
||||||
|
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
"""
|
||||||
88
libp2p/security/security_multistream.py
Normal file
88
libp2p/security/security_multistream.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
Relevant go repo: https://github.com/libp2p/go-conn-security/blob/master/interface.go
|
||||||
|
"""
|
||||||
|
class SecurityMultistream(ABC):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 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, False)
|
||||||
|
|
||||||
|
# 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]
|
||||||
61
libp2p/security/simple_security.py
Normal file
61
libp2p/security/simple_security.py
Normal file
@ -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
|
||||||
@ -11,7 +11,7 @@ class Mplex(IMuxedConn):
|
|||||||
reference: https://github.com/libp2p/go-mplex/blob/master/multiplex.go
|
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
|
create a new muxed connection
|
||||||
:param conn: an instance of raw connection
|
:param conn: an instance of raw connection
|
||||||
@ -19,10 +19,11 @@ class Mplex(IMuxedConn):
|
|||||||
for new muxed streams
|
for new muxed streams
|
||||||
:param peer_id: peer_id of peer the connection is to
|
: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.secured_conn = secured_conn
|
||||||
self.initiator = conn.initiator
|
self.raw_conn = secured_conn.get_conn()
|
||||||
|
self.initiator = self.raw_conn.initiator
|
||||||
|
|
||||||
# Store generic protocol handler
|
# Store generic protocol handler
|
||||||
self.generic_protocol_handler = generic_protocol_handler
|
self.generic_protocol_handler = generic_protocol_handler
|
||||||
|
|||||||
@ -10,7 +10,7 @@ class IMuxedConn(ABC):
|
|||||||
def __init__(self, conn, generic_protocol_handler, peer_id):
|
def __init__(self, conn, generic_protocol_handler, peer_id):
|
||||||
"""
|
"""
|
||||||
create a new muxed connection
|
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
|
:param generic_protocol_handler: generic protocol handler
|
||||||
for new muxed streams
|
for new muxed streams
|
||||||
:param peer_id: peer_id of peer the connection is to
|
:param peer_id: peer_id of peer the connection is to
|
||||||
|
|||||||
@ -82,9 +82,10 @@ class TCP(ITransport):
|
|||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
# Await ack for peer id
|
# 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")
|
raise Exception("Receiver did not receive peer id")
|
||||||
|
|
||||||
return RawConnection(host, port, reader, writer, True)
|
return RawConnection(host, port, reader, writer, True)
|
||||||
|
|||||||
@ -1,25 +1,36 @@
|
|||||||
from libp2p.stream_muxer.mplex.mplex import Mplex
|
from libp2p.stream_muxer.mplex.mplex import Mplex
|
||||||
|
from libp2p.security.security_multistream import SecurityMultistream
|
||||||
|
|
||||||
|
|
||||||
class TransportUpgrader:
|
class TransportUpgrader:
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
|
|
||||||
def __init__(self, secOpt, muxerOpt):
|
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
|
self.muxer = muxerOpt
|
||||||
|
|
||||||
def upgrade_listener(self, transport, listeners):
|
def upgrade_listener(self, transport, listeners):
|
||||||
"""
|
"""
|
||||||
upgrade multiaddr listeners to libp2p-transport listeners
|
Upgrade multiaddr listeners to libp2p-transport listeners
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def upgrade_security(self):
|
async def upgrade_security(self, raw_conn, peer_id, initiator):
|
||||||
pass
|
"""
|
||||||
|
Upgrade conn to be a secured connection
|
||||||
|
"""
|
||||||
|
if initiator:
|
||||||
|
return await self.security_multistream.secure_outbound(raw_conn, peer_id)
|
||||||
|
|
||||||
|
return await self.security_multistream.secure_inbound(raw_conn)
|
||||||
|
|
||||||
def upgrade_connection(self, conn, generic_protocol_handler, peer_id):
|
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
|
# For PoC, no security, default to mplex
|
||||||
|
|||||||
159
tests/security/test_security_multistream.py
Normal file
159
tests/security/test_security_multistream.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import asyncio
|
||||||
|
import multiaddr
|
||||||
|
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 libp2p.security.simple_security import SimpleSecurityTransport
|
||||||
|
from tests.utils import cleanup
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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)]
|
||||||
|
node2_conn = node2.get_network().connections[peer_id_for_node(node1)]
|
||||||
|
|
||||||
|
# Perform assertion
|
||||||
|
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_insecure_security_transport_succeeds():
|
||||||
|
transports_for_initiator = {"foo": InsecureTransport("foo")}
|
||||||
|
transports_for_noninitiator = {"foo": InsecureTransport("foo")}
|
||||||
|
|
||||||
|
def assertion_func(details):
|
||||||
|
assert details["id"] == "foo"
|
||||||
|
|
||||||
|
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 = {"tacos": SimpleSecurityTransport("tacos")}
|
||||||
|
transports_for_noninitiator = {"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_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)
|
||||||
|
|
||||||
|
@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)
|
||||||
Reference in New Issue
Block a user