diff --git a/Makefile b/Makefile index 5620e59..203391f 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ PB = libp2p/crypto/pb/crypto.proto \ libp2p/pubsub/pb/rpc.proto \ libp2p/security/insecure/pb/plaintext.proto \ libp2p/security/secio/pb/spipe.proto \ + libp2p/security/noise/pb/noise.proto \ libp2p/identity/identify/pb/identify.proto PY = $(PB:.proto=_pb2.py) PYI = $(PB:.proto=_pb2.pyi) diff --git a/docs/libp2p.security.noise.pb.rst b/docs/libp2p.security.noise.pb.rst new file mode 100644 index 0000000..1088136 --- /dev/null +++ b/docs/libp2p.security.noise.pb.rst @@ -0,0 +1,22 @@ +libp2p.security.noise.pb package +================================ + +Submodules +---------- + +libp2p.security.noise.pb.noise\_pb2 module +------------------------------------------ + +.. automodule:: libp2p.security.noise.pb.noise_pb2 + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: libp2p.security.noise.pb + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/libp2p.security.noise.rst b/docs/libp2p.security.noise.rst new file mode 100644 index 0000000..a6aaa49 --- /dev/null +++ b/docs/libp2p.security.noise.rst @@ -0,0 +1,45 @@ +libp2p.security.noise package +============================= + +Subpackages +----------- + +.. toctree:: + + libp2p.security.noise.pb + +Submodules +---------- + +libp2p.security.noise.connection module +--------------------------------------- + +.. automodule:: libp2p.security.noise.connection + :members: + :undoc-members: + :show-inheritance: + +libp2p.security.noise.patterns module +------------------------------------- + +.. automodule:: libp2p.security.noise.patterns + :members: + :undoc-members: + :show-inheritance: + +libp2p.security.noise.transport module +-------------------------------------- + +.. automodule:: libp2p.security.noise.transport + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: libp2p.security.noise + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/libp2p.security.rst b/docs/libp2p.security.rst index de60977..32dd329 100644 --- a/docs/libp2p.security.rst +++ b/docs/libp2p.security.rst @@ -6,8 +6,9 @@ Subpackages .. toctree:: - libp2p.security.insecure - libp2p.security.secio + libp2p.security.insecure + libp2p.security.noise + libp2p.security.secio Submodules ---------- @@ -16,55 +17,55 @@ libp2p.security.base\_session module ------------------------------------ .. automodule:: libp2p.security.base_session - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: libp2p.security.base\_transport module -------------------------------------- .. automodule:: libp2p.security.base_transport - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: libp2p.security.exceptions module --------------------------------- .. automodule:: libp2p.security.exceptions - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: libp2p.security.secure\_conn\_interface module ---------------------------------------------- .. automodule:: libp2p.security.secure_conn_interface - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: libp2p.security.secure\_transport\_interface module --------------------------------------------------- .. automodule:: libp2p.security.secure_transport_interface - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: libp2p.security.security\_multistream module -------------------------------------------- .. automodule:: libp2p.security.security_multistream - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: Module contents --------------- .. automodule:: libp2p.security - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: diff --git a/libp2p/io/abc.py b/libp2p/io/abc.py index 8f2c758..265663a 100644 --- a/libp2p/io/abc.py +++ b/libp2p/io/abc.py @@ -14,7 +14,7 @@ class Reader(ABC): class Writer(ABC): @abstractmethod - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: ... diff --git a/libp2p/io/msgio.py b/libp2p/io/msgio.py index 32c0d09..82c55ac 100644 --- a/libp2p/io/msgio.py +++ b/libp2p/io/msgio.py @@ -31,9 +31,8 @@ class MsgIOWriter(WriteCloser): def __init__(self, write_closer: WriteCloser) -> None: self.write_closer = write_closer - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: await self.write_msg(data) - return len(data) async def write_msg(self, msg: bytes) -> None: data = encode_msg_with_length(msg) diff --git a/libp2p/network/stream/net_stream.py b/libp2p/network/stream/net_stream.py index 72d5c6a..dab1920 100644 --- a/libp2p/network/stream/net_stream.py +++ b/libp2p/network/stream/net_stream.py @@ -51,14 +51,14 @@ class NetStream(INetStream): except MuxedStreamReset as error: raise StreamReset() from error - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: """ write to stream. :return: number of bytes written """ try: - return await self.muxed_stream.write(data) + await self.muxed_stream.write(data) except MuxedStreamClosed as error: raise StreamClosed() from error diff --git a/libp2p/security/insecure/transport.py b/libp2p/security/insecure/transport.py index 103a580..f452e53 100644 --- a/libp2p/security/insecure/transport.py +++ b/libp2p/security/insecure/transport.py @@ -35,9 +35,8 @@ class InsecureSession(BaseSession): super().__init__(local_peer, local_private_key, is_initiator, peer_id) self.conn = conn - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: await self.conn.write(data) - return len(data) async def read(self, n: int = None) -> bytes: return await self.conn.read(n) diff --git a/libp2p/security/noise/__init__.py b/libp2p/security/noise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libp2p/security/noise/connection.py b/libp2p/security/noise/connection.py new file mode 100644 index 0000000..940d807 --- /dev/null +++ b/libp2p/security/noise/connection.py @@ -0,0 +1,30 @@ +from libp2p.crypto.keys import PrivateKey +from libp2p.network.connection.raw_connection_interface import IRawConnection +from libp2p.peer.id import ID +from libp2p.security.base_session import BaseSession + + +class NoiseConnection(BaseSession): + conn: IRawConnection + + def __init__( + self, + local_peer: ID, + local_private_key: PrivateKey, + remote_peer: ID, + conn: IRawConnection, + is_initiator: bool, + ) -> None: + super().__init__(local_peer, local_private_key, is_initiator, remote_peer) + self.conn = conn + + async def read(self, n: int = None) -> bytes: + # TODO: Add decryption logic here + return await self.conn.read(n) + + async def write(self, data: bytes) -> None: + # TODO: Add encryption logic here + await self.conn.write(data) + + async def close(self) -> None: + await self.conn.close() diff --git a/libp2p/security/noise/patterns.py b/libp2p/security/noise/patterns.py new file mode 100644 index 0000000..dee9a61 --- /dev/null +++ b/libp2p/security/noise/patterns.py @@ -0,0 +1,116 @@ +from abc import ABC, abstractmethod + +from noise.connection import Keypair as NoiseKeypair +from noise.connection import NoiseConnection as NoiseState + +from libp2p.crypto.keys import PrivateKey +from libp2p.network.connection.raw_connection_interface import IRawConnection +from libp2p.peer.id import ID +from libp2p.security.secure_conn_interface import ISecureConn + +from .connection import NoiseConnection + +# FIXME: Choose a serious bound number. +NUM_BYTES_TO_READ = 2048 + + +# TODO: Merged into `BasePattern`? +class PreHandshakeConnection: + conn: IRawConnection + + def __init__(self, conn: IRawConnection) -> None: + self.conn = conn + + async def write_msg(self, data: bytes) -> None: + # TODO: + await self.conn.write(data) + + async def read_msg(self) -> bytes: + return await self.conn.read(NUM_BYTES_TO_READ) + + +class IPattern(ABC): + @abstractmethod + async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: + ... + + @abstractmethod + async def handshake_outbound( + self, conn: IRawConnection, remote_peer: ID + ) -> ISecureConn: + ... + + +class BasePattern(IPattern): + protocol_name: bytes + noise_static_key: PrivateKey + local_peer: ID + libp2p_privkey: PrivateKey + + def create_noise_state(self) -> NoiseState: + noise_state = NoiseState.from_name(self.protocol_name) + noise_state.set_keypair_from_private_bytes( + NoiseKeypair.STATIC, self.noise_static_key.to_bytes() + ) + return noise_state + + +class PatternXX(BasePattern): + def __init__( + self, local_peer: ID, libp2p_privkey: PrivateKey, noise_static_key: PrivateKey + ) -> None: + self.protocol_name = b"Noise_XX_25519_ChaChaPoly_SHA256" + self.local_peer = local_peer + self.libp2p_privkey = libp2p_privkey + self.noise_static_key = noise_static_key + + async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: + noise_state = self.create_noise_state() + handshake_conn = PreHandshakeConnection(conn) + noise_state.set_as_responder() + noise_state.start_handshake() + msg_0_encrypted = await handshake_conn.read_msg() + # TODO: Parse and save the payload from the other side. + _ = noise_state.read_message(msg_0_encrypted) + + # TODO: Send our payload. + our_payload = b"server" + msg_1_encrypted = noise_state.write_message(our_payload) + await handshake_conn.write_msg(msg_1_encrypted) + + msg_2_encrypted = await handshake_conn.read_msg() + # TODO: Parse and save another payload from the other side. + _ = noise_state.read_message(msg_2_encrypted) + + # TODO: Add a specific exception + if not noise_state.handshake_finished: + raise Exception + + # FIXME: `remote_peer` should be derived from the messages. + return NoiseConnection(self.local_peer, self.libp2p_privkey, None, conn, False) + + async def handshake_outbound( + self, conn: IRawConnection, remote_peer: ID + ) -> ISecureConn: + noise_state = self.create_noise_state() + handshake_conn = PreHandshakeConnection(conn) + noise_state.set_as_initiator() + noise_state.start_handshake() + msg_0 = noise_state.write_message() + await handshake_conn.write_msg(msg_0) + msg_1_encrypted = await handshake_conn.read_msg() + # TODO: Parse and save the payload from the other side. + _ = noise_state.read_message(msg_1_encrypted) + + # TODO: Send our payload. + our_payload = b"client" + msg_2_encrypted = noise_state.write_message(our_payload) + await handshake_conn.write_msg(msg_2_encrypted) + + # TODO: Add a specific exception + if not noise_state.handshake_finished: + raise Exception + + return NoiseConnection( + self.local_peer, self.libp2p_privkey, remote_peer, conn, False + ) diff --git a/libp2p/security/noise/pb/__init__.py b/libp2p/security/noise/pb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libp2p/security/noise/pb/noise.proto b/libp2p/security/noise/pb/noise.proto new file mode 100644 index 0000000..54cef92 --- /dev/null +++ b/libp2p/security/noise/pb/noise.proto @@ -0,0 +1,5 @@ +message NoiseHandshakePayload { + optional bytes identity_key = 1; + optional bytes identity_sig = 2; + optional bytes data = 3; +} diff --git a/libp2p/security/noise/pb/noise_pb2.py b/libp2p/security/noise/pb/noise_pb2.py new file mode 100644 index 0000000..d7b92ae --- /dev/null +++ b/libp2p/security/noise/pb/noise_pb2.py @@ -0,0 +1,83 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: libp2p/security/noise/pb/noise.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='libp2p/security/noise/pb/noise.proto', + package='', + syntax='proto2', + serialized_pb=_b('\n$libp2p/security/noise/pb/noise.proto\"Q\n\x15NoiseHandshakePayload\x12\x14\n\x0cidentity_key\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_sig\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c') +) + + + + +_NOISEHANDSHAKEPAYLOAD = _descriptor.Descriptor( + name='NoiseHandshakePayload', + full_name='NoiseHandshakePayload', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='identity_key', full_name='NoiseHandshakePayload.identity_key', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='identity_sig', full_name='NoiseHandshakePayload.identity_sig', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='NoiseHandshakePayload.data', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=40, + serialized_end=121, +) + +DESCRIPTOR.message_types_by_name['NoiseHandshakePayload'] = _NOISEHANDSHAKEPAYLOAD +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +NoiseHandshakePayload = _reflection.GeneratedProtocolMessageType('NoiseHandshakePayload', (_message.Message,), dict( + DESCRIPTOR = _NOISEHANDSHAKEPAYLOAD, + __module__ = 'libp2p.security.noise.pb.noise_pb2' + # @@protoc_insertion_point(class_scope:NoiseHandshakePayload) + )) +_sym_db.RegisterMessage(NoiseHandshakePayload) + + +# @@protoc_insertion_point(module_scope) diff --git a/libp2p/security/noise/pb/noise_pb2.pyi b/libp2p/security/noise/pb/noise_pb2.pyi new file mode 100644 index 0000000..eea8fda --- /dev/null +++ b/libp2p/security/noise/pb/noise_pb2.pyi @@ -0,0 +1,41 @@ +# @generated by generate_proto_mypy_stubs.py. Do not edit! +import sys +from google.protobuf.descriptor import ( + Descriptor as google___protobuf___descriptor___Descriptor, +) + +from google.protobuf.message import ( + Message as google___protobuf___message___Message, +) + +from typing import ( + Optional as typing___Optional, +) + +from typing_extensions import ( + Literal as typing_extensions___Literal, +) + + +class NoiseHandshakePayload(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + identity_key = ... # type: bytes + identity_sig = ... # type: bytes + data = ... # type: bytes + + def __init__(self, + *, + identity_key : typing___Optional[bytes] = None, + identity_sig : typing___Optional[bytes] = None, + data : typing___Optional[bytes] = None, + ) -> None: ... + @classmethod + def FromString(cls, s: bytes) -> NoiseHandshakePayload: ... + def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ... + def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ... + if sys.version_info >= (3,): + def HasField(self, field_name: typing_extensions___Literal[u"data",u"identity_key",u"identity_sig"]) -> bool: ... + def ClearField(self, field_name: typing_extensions___Literal[u"data",u"identity_key",u"identity_sig"]) -> None: ... + else: + def HasField(self, field_name: typing_extensions___Literal[u"data",b"data",u"identity_key",b"identity_key",u"identity_sig",b"identity_sig"]) -> bool: ... + def ClearField(self, field_name: typing_extensions___Literal[u"data",b"data",u"identity_key",b"identity_key",u"identity_sig",b"identity_sig"]) -> None: ... diff --git a/libp2p/security/noise/transport.py b/libp2p/security/noise/transport.py new file mode 100644 index 0000000..37e15e8 --- /dev/null +++ b/libp2p/security/noise/transport.py @@ -0,0 +1,55 @@ +from libp2p.crypto.keys import KeyPair, PrivateKey +from libp2p.network.connection.raw_connection_interface import IRawConnection +from libp2p.peer.id import ID +from libp2p.security.secure_conn_interface import ISecureConn +from libp2p.security.secure_transport_interface import ISecureTransport +from libp2p.typing import TProtocol + +from .patterns import IPattern, PatternXX + +PROTOCOL_ID = TProtocol("/noise") + + +class Transport(ISecureTransport): + libp2p_privkey: PrivateKey + noise_privkey: PrivateKey + local_peer: ID + early_data: bytes + with_noise_pipes: bool + # TODO: A storage of seen noise static keys for pattern IK? + + def __init__( + self, + libp2p_keypair: KeyPair, + noise_privkey: PrivateKey = None, + early_data: bytes = None, + with_noise_pipes: bool = False, + ) -> None: + self.libp2p_privkey = libp2p_keypair.private_key + self.noise_privkey = noise_privkey + self.local_peer = ID.from_pubkey(libp2p_keypair.public_key) + self.early_data = early_data + self.with_noise_pipes = with_noise_pipes + + if self.with_noise_pipes: + raise NotImplementedError + + def get_pattern(self) -> IPattern: + if self.with_noise_pipes: + raise NotImplementedError + else: + return PatternXX(self.local_peer, self.libp2p_privkey, self.noise_privkey) + + async def secure_inbound(self, conn: IRawConnection) -> ISecureConn: + # TODO: SecureInbound attempts to complete a noise-libp2p handshake initiated + # by a remote peer over the given InsecureConnection. + pattern = self.get_pattern() + return await pattern.handshake_inbound(conn) + + async def secure_outbound(self, conn: IRawConnection, peer_id: ID) -> ISecureConn: + # TODO: Validate libp2p pubkey with `peer_id`. Abort if not correct. + # NOTE: Implementations that support Noise Pipes must decide whether to use + # an XX or IK handshake based on whether they possess a cached static + # Noise key for the remote peer. + pattern = self.get_pattern() + return await pattern.handshake_outbound(conn, peer_id) diff --git a/libp2p/security/secio/transport.py b/libp2p/security/secio/transport.py index 9e98873..d8216b8 100644 --- a/libp2p/security/secio/transport.py +++ b/libp2p/security/secio/transport.py @@ -135,9 +135,8 @@ class SecureSession(BaseSession): raise DecryptionFailedException() from e return decrypted_msg - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: await self.write_msg(data) - return len(data) async def write_msg(self, msg: bytes) -> None: encrypted_data = self.local_encrypter.encrypt(msg) diff --git a/libp2p/stream_muxer/mplex/mplex.py b/libp2p/stream_muxer/mplex/mplex.py index 4f62e15..defe0b8 100644 --- a/libp2p/stream_muxer/mplex/mplex.py +++ b/libp2p/stream_muxer/mplex/mplex.py @@ -160,7 +160,7 @@ class Mplex(IMuxedConn): return await self.write_to_stream(_bytes) - async def write_to_stream(self, _bytes: bytes) -> int: + async def write_to_stream(self, _bytes: bytes) -> None: """ writes a byte array to a secured connection. @@ -174,8 +174,6 @@ class Mplex(IMuxedConn): "failed to write message to the underlying connection" ) from e - return len(_bytes) - async def handle_incoming(self) -> None: """Read a message off of the secured connection and add it to the corresponding message buffer.""" diff --git a/libp2p/stream_muxer/mplex/mplex_stream.py b/libp2p/stream_muxer/mplex/mplex_stream.py index 7967574..9c724cc 100644 --- a/libp2p/stream_muxer/mplex/mplex_stream.py +++ b/libp2p/stream_muxer/mplex/mplex_stream.py @@ -134,7 +134,7 @@ class MplexStream(IMuxedStream): self._buf = self._buf[len(payload) :] return bytes(payload) - async def write(self, data: bytes) -> int: + async def write(self, data: bytes) -> None: """ write to stream. @@ -147,7 +147,7 @@ class MplexStream(IMuxedStream): if self.is_initiator else HeaderTags.MessageReceiver ) - return await self.muxed_conn.send_message(flag, data, self.stream_id) + await self.muxed_conn.send_message(flag, data, self.stream_id) async def close(self) -> None: """Closing a stream closes it for writing and closes the remote end for diff --git a/libp2p/tools/factories.py b/libp2p/tools/factories.py index 67e2651..75a37a3 100644 --- a/libp2p/tools/factories.py +++ b/libp2p/tools/factories.py @@ -8,7 +8,9 @@ from multiaddr import Multiaddr import trio from libp2p import generate_new_rsa_identity, generate_peer_id_from -from libp2p.crypto.keys import KeyPair +from libp2p.crypto.ed25519 import create_new_key_pair as create_ed25519_key_pair +from libp2p.crypto.keys import KeyPair, PrivateKey +from libp2p.crypto.secp256k1 import create_new_key_pair as create_secp256k1_key_pair from libp2p.host.basic_host import BasicHost from libp2p.host.host_interface import IHost from libp2p.host.routed_host import RoutedHost @@ -26,9 +28,11 @@ from libp2p.pubsub.floodsub import FloodSub from libp2p.pubsub.gossipsub import GossipSub from libp2p.pubsub.pubsub import Pubsub from libp2p.routing.interfaces import IPeerRouting -from libp2p.security.base_transport import BaseSecureTransport from libp2p.security.insecure.transport import PLAINTEXT_PROTOCOL_ID, InsecureTransport +from libp2p.security.noise.transport import Transport as NoiseTransport import libp2p.security.secio.transport as secio +from libp2p.security.secure_conn_interface import ISecureConn +from libp2p.security.secure_transport_interface import ISecureTransport from libp2p.stream_muxer.mplex.mplex import MPLEX_PROTOCOL_ID, Mplex from libp2p.stream_muxer.mplex.mplex_stream import MplexStream from libp2p.tools.constants import GOSSIPSUB_PARAMS @@ -58,13 +62,26 @@ def initialize_peerstore_with_our_keypair(self_id: ID, key_pair: KeyPair) -> Pee def security_transport_factory( is_secure: bool, key_pair: KeyPair -) -> Dict[TProtocol, BaseSecureTransport]: +) -> Dict[TProtocol, ISecureTransport]: if not is_secure: return {PLAINTEXT_PROTOCOL_ID: InsecureTransport(key_pair)} else: return {secio.ID: secio.Transport(key_pair)} +def noise_static_key_factory() -> PrivateKey: + return create_ed25519_key_pair().private_key + + +def noise_transport_factory() -> NoiseTransport: + return NoiseTransport( + libp2p_keypair=create_secp256k1_key_pair(), + noise_privkey=noise_static_key_factory(), + early_data=None, + with_noise_pipes=False, + ) + + @asynccontextmanager async def raw_conn_factory( nursery: trio.Nursery @@ -88,6 +105,39 @@ async def raw_conn_factory( yield conn_0, conn_1 +@asynccontextmanager +async def noise_conn_factory( + nursery: trio.Nursery +) -> AsyncIterator[Tuple[ISecureConn, ISecureConn]]: + local_transport = noise_transport_factory() + remote_transport = noise_transport_factory() + + local_secure_conn: ISecureConn = None + remote_secure_conn: ISecureConn = None + + async def upgrade_local_conn() -> None: + nonlocal local_secure_conn + local_secure_conn = await local_transport.secure_outbound( + local_conn, local_transport.local_peer + ) + + async def upgrade_remote_conn() -> None: + nonlocal remote_secure_conn + remote_secure_conn = await remote_transport.secure_inbound(remote_conn) + + async with raw_conn_factory(nursery) as conns: + local_conn, remote_conn = conns + async with trio.open_nursery() as nursery: + nursery.start_soon(upgrade_local_conn) + nursery.start_soon(upgrade_remote_conn) + if local_secure_conn is None or remote_secure_conn is None: + raise Exception( + "local or remote secure conn has not been successfully upgraded" + f"local_secure_conn={local_secure_conn}, remote_secure_conn={remote_secure_conn}" + ) + yield local_secure_conn, remote_secure_conn + + class SwarmFactory(factory.Factory): class Meta: model = Swarm diff --git a/setup.py b/setup.py index e808b95..5e5553c 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ install_requires = [ "async-service>=0.1.0a6", "async-exit-stack==1.0.1", "trio-typing>=0.3.0,<0.4.0", + "noiseprotocol>=0.3.0,<0.4.0", ] diff --git a/tests/security/test_noise.py b/tests/security/test_noise.py new file mode 100644 index 0000000..c60f83c --- /dev/null +++ b/tests/security/test_noise.py @@ -0,0 +1,20 @@ +import pytest + +from libp2p.tools.factories import noise_conn_factory + +DATA = b"testing_123" + + +@pytest.mark.trio +async def test_noise_transport(nursery): + async with noise_conn_factory(nursery): + pass + + +@pytest.mark.trio +async def test_noise_connection(nursery): + async with noise_conn_factory(nursery) as conns: + local_conn, remote_conn = conns + await local_conn.write(DATA) + read_data = await remote_conn.read(len(DATA)) + assert read_data == DATA diff --git a/tox.ini b/tox.ini index a03627e..b1e5cdc 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ envlist = combine_as_imports=False force_sort_within_sections=True include_trailing_comma=True -known_third_party=anyio,factory,lru,p2pclient,pytest +known_third_party=anyio,factory,lru,p2pclient,pytest,noise known_first_party=libp2p line_length=88 multi_line_output=3