From 897e66b7e1f3ba1c537b543935b61b73c2fb1b1f Mon Sep 17 00:00:00 2001 From: mhchia Date: Fri, 7 Feb 2020 17:47:50 +0800 Subject: [PATCH] Add the skeletons of noise transport and conn --- libp2p/security/insecure/transport.py | 3 +- libp2p/security/noise/__init__.py | 0 libp2p/security/noise/transport.py | 69 +++++++++++++++++++++++++++ libp2p/tools/factories.py | 57 ++++++++++++++++++++-- tests/security/test_noise.py | 15 ++++++ 5 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 libp2p/security/noise/__init__.py create mode 100644 libp2p/security/noise/transport.py create mode 100644 tests/security/test_noise.py 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/transport.py b/libp2p/security/noise/transport.py new file mode 100644 index 0000000..291fb81 --- /dev/null +++ b/libp2p/security/noise/transport.py @@ -0,0 +1,69 @@ +from libp2p.crypto.keys import KeyPair, PrivateKey +from libp2p.io.msgio import ReadWriteCloser +from libp2p.network.connection.raw_connection_interface import IRawConnection +from libp2p.peer.id import ID +from libp2p.security.base_session import BaseSession +from libp2p.security.secure_conn_interface import ISecureConn +from libp2p.security.secure_transport_interface import ISecureTransport +from libp2p.typing import TProtocol + +PROTOCOL_ID = TProtocol("/") + + +class NoiseConnection(BaseSession): + conn: ReadWriteCloser + + def __init__( + self, + local_peer: ID, + local_private_key: PrivateKey, + remote_peer: ID, + conn: ReadWriteCloser, + 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: + return await self.conn.read(n) + + async def write(self, data: bytes) -> int: + return await self.conn.write(data) + + async def close(self) -> None: + await self.conn.close() + + +class Transport(ISecureTransport): + libp2p_privkey: PrivateKey + noise_privkey: PrivateKey + local_peer: ID + early_data: bytes + with_noise_pipes: bool + + 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 + + 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. + return NoiseConnection(self.local_peer, self.libp2p_privkey, None, conn, False) + + 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. + return NoiseConnection( + self.local_peer, self.libp2p_privkey, peer_id, conn, False + ) diff --git a/libp2p/tools/factories.py b/libp2p/tools/factories.py index 67e2651..40d644e 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,12 @@ 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 +import libp2p.security.noise.transport as noise +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 +63,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 noise.Transport( + 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 +106,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/tests/security/test_noise.py b/tests/security/test_noise.py new file mode 100644 index 0000000..b912f98 --- /dev/null +++ b/tests/security/test_noise.py @@ -0,0 +1,15 @@ +import pytest + +from libp2p.tools.factories import noise_conn_factory + + +@pytest.mark.trio +async def test_noise_transport(nursery): + async with noise_conn_factory(nursery): + pass + + +@pytest.mark.trio +async def test_noise_connection(): + async with noise_conn_factory() as conns: + local_conn, remote_conn = conns