From 0324a69841e77fe6c01f6ef78f9e4db3dacfdb27 Mon Sep 17 00:00:00 2001 From: mhchia Date: Sun, 9 Feb 2020 00:33:26 +0800 Subject: [PATCH] Noise: add `PatternXX` --- libp2p/security/noise/connection.py | 30 +++++++ libp2p/security/noise/patterns.py | 116 +++++++++++++++++++++++++++ libp2p/security/noise/pb/__init__.py | 0 libp2p/security/noise/pb/noise.proto | 5 ++ libp2p/security/noise/transport.py | 48 ++++------- libp2p/tools/factories.py | 3 +- setup.py | 1 + tests/security/test_noise.py | 1 - 8 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 libp2p/security/noise/connection.py create mode 100644 libp2p/security/noise/patterns.py create mode 100644 libp2p/security/noise/pb/__init__.py create mode 100644 libp2p/security/noise/pb/noise.proto 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..1d812b2 --- /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() + msg_0 = noise_state.read_message(msg_0_encrypted) + # TODO: Parse and save the payload from the other side. + + # 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() + msg_2 = noise_state.read_message(msg_2_encrypted) + # TODO: Parse and save another payload from the other side. + + # 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() + msg_1 = noise_state.read_message(msg_1_encrypted) + # TODO: Parse and save the payload from the other side. + + # 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/transport.py b/libp2p/security/noise/transport.py index 906e880..37e15e8 100644 --- a/libp2p/security/noise/transport.py +++ b/libp2p/security/noise/transport.py @@ -1,47 +1,22 @@ 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 +from .patterns import IPattern, PatternXX + PROTOCOL_ID = TProtocol("/noise") -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: - # 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() - - 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, @@ -56,16 +31,25 @@ class Transport(ISecureTransport): 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. - return NoiseConnection(self.local_peer, self.libp2p_privkey, None, conn, False) + 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. - return NoiseConnection( - self.local_peer, self.libp2p_privkey, peer_id, conn, False - ) + pattern = self.get_pattern() + return await pattern.handshake_outbound(conn, peer_id) diff --git a/libp2p/tools/factories.py b/libp2p/tools/factories.py index 40d644e..75a37a3 100644 --- a/libp2p/tools/factories.py +++ b/libp2p/tools/factories.py @@ -29,7 +29,6 @@ from libp2p.pubsub.gossipsub import GossipSub from libp2p.pubsub.pubsub import Pubsub from libp2p.routing.interfaces import IPeerRouting 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 @@ -75,7 +74,7 @@ def noise_static_key_factory() -> PrivateKey: def noise_transport_factory() -> NoiseTransport: - return noise.Transport( + return NoiseTransport( libp2p_keypair=create_secp256k1_key_pair(), noise_privkey=noise_static_key_factory(), early_data=None, 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 index eb34e13..c60f83c 100644 --- a/tests/security/test_noise.py +++ b/tests/security/test_noise.py @@ -2,7 +2,6 @@ import pytest from libp2p.tools.factories import noise_conn_factory - DATA = b"testing_123"