From 3e04480d622ab652f915d86ea31f04ba88445d6b Mon Sep 17 00:00:00 2001 From: mhchia Date: Wed, 21 Aug 2019 15:12:35 +0800 Subject: [PATCH] Raise `HandshakeFailure` in transport Change the exception handling flow. Raise `SecurityUpgradeFailure` in security_multistream. --- libp2p/crypto/utils.py | 1 + libp2p/exceptions.py | 8 +++-- libp2p/security/insecure/transport.py | 17 +++++----- libp2p/security/security_multistream.py | 36 +++++++++++++++++---- libp2p/transport/exceptions.py | 9 +++++- tests/security/test_security_multistream.py | 4 +-- 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/libp2p/crypto/utils.py b/libp2p/crypto/utils.py index 78b8b7b6..8519598b 100644 --- a/libp2p/crypto/utils.py +++ b/libp2p/crypto/utils.py @@ -10,6 +10,7 @@ def pubkey_from_protobuf(pubkey_pb: protobuf.PublicKey) -> PublicKey: # TODO: Test against secp256k1 keys elif pubkey_pb.key_type == protobuf.Secp256k1: return Secp256k1PublicKey.from_bytes(pubkey_pb.data) + # TODO: Support `Ed25519` and `ECDSA` in the future? else: raise ValueError( f"unsupported key_type={pubkey_pb.key_type}, data={pubkey_pb.data!r}" diff --git a/libp2p/exceptions.py b/libp2p/exceptions.py index 8d8af447..0ea00781 100644 --- a/libp2p/exceptions.py +++ b/libp2p/exceptions.py @@ -1,6 +1,8 @@ -class ValidationError(Exception): +class BaseLibp2pError(Exception): + pass + + +class ValidationError(BaseLibp2pError): """ Raised when something does not pass a validation check. """ - - pass diff --git a/libp2p/security/insecure/transport.py b/libp2p/security/insecure/transport.py index 850bdf36..8ce6f41f 100644 --- a/libp2p/security/insecure/transport.py +++ b/libp2p/security/insecure/transport.py @@ -6,7 +6,7 @@ from libp2p.peer.id import ID from libp2p.security.base_session import BaseSession from libp2p.security.base_transport import BaseSecureTransport from libp2p.security.secure_conn_interface import ISecureConn -from libp2p.transport.exceptions import SecurityUpgradeFailure +from libp2p.transport.exceptions import HandshakeFailure from libp2p.typing import TProtocol from libp2p.utils import encode_fixedint_prefixed, read_fixedint_prefixed @@ -32,14 +32,14 @@ class InsecureSession(BaseSession): # Verify if the given `pubkey` matches the given `peer_id` try: remote_pubkey = pubkey_from_protobuf(remote_msg.pubkey) - except ValueError as error: - raise SecurityUpgradeFailure( - f"unknown protocol of remote_msg.pubkey={remote_msg.pubkey}" - ) from error + except ValueError: + raise HandshakeFailure( + f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}" + ) remote_peer_id = ID(remote_msg.id) remote_peer_id_from_pubkey = ID.from_pubkey(remote_pubkey) if remote_peer_id_from_pubkey != remote_peer_id: - raise SecurityUpgradeFailure( + raise HandshakeFailure( "peer id and pubkey from the remote mismatch: " f"remote_peer_id={remote_peer_id}, remote_pubkey={remote_pubkey}, " f"remote_peer_id_from_pubkey={remote_peer_id_from_pubkey}" @@ -76,10 +76,9 @@ class InsecureTransport(BaseSecureTransport): """ session = InsecureSession(self, conn, peer_id) await session.run_handshake() - # TODO: Check if `remote_public_key is not None`. If so, check if `session.remote_peer` received_peer_id = session.get_remote_peer() - if received_peer_id != peer_id: - raise SecurityUpgradeFailure( + if session.remote_permanent_pubkey is not None and received_peer_id != peer_id: + raise HandshakeFailure( "remote peer sent unexpected peer ID. " f"expected={peer_id} received={received_peer_id}" ) diff --git a/libp2p/security/security_multistream.py b/libp2p/security/security_multistream.py index 6e69d7a0..f52e54a1 100644 --- a/libp2p/security/security_multistream.py +++ b/libp2p/security/security_multistream.py @@ -4,11 +4,15 @@ from typing import Mapping from libp2p.network.connection.raw_connection_interface import IRawConnection from libp2p.peer.id import ID -from libp2p.protocol_muxer.multiselect import Multiselect -from libp2p.protocol_muxer.multiselect_client import MultiselectClient +from libp2p.protocol_muxer.multiselect import Multiselect, MultiselectError +from libp2p.protocol_muxer.multiselect_client import ( + MultiselectClient, + MultiselectClientError, +) from libp2p.protocol_muxer.multiselect_communicator import RawConnectionCommunicator from libp2p.security.secure_conn_interface import ISecureConn from libp2p.security.secure_transport_interface import ISecureTransport +from libp2p.transport.exceptions import HandshakeFailure, SecurityUpgradeFailure from libp2p.typing import TProtocol @@ -63,8 +67,18 @@ class SecurityMultistream(ABC): for an inbound connection (i.e. we are not the initiator) :return: secure connection object (that implements secure_conn_interface) """ - transport = await self.select_transport(conn, False) - secure_conn = await transport.secure_inbound(conn) + try: + transport = await self.select_transport(conn, False) + except MultiselectError as error: + raise SecurityUpgradeFailure( + "failed to negotiate the secure protocol" + ) from error + try: + secure_conn = await transport.secure_inbound(conn) + except HandshakeFailure as error: + raise SecurityUpgradeFailure( + "failed to secure the inbound transport" + ) from error return secure_conn async def secure_outbound(self, conn: IRawConnection, peer_id: ID) -> ISecureConn: @@ -73,8 +87,18 @@ class SecurityMultistream(ABC): for an inbound connection (i.e. we are the initiator) :return: secure connection object (that implements secure_conn_interface) """ - transport = await self.select_transport(conn, True) - secure_conn = await transport.secure_outbound(conn, peer_id) + try: + transport = await self.select_transport(conn, True) + except MultiselectClientError as error: + raise SecurityUpgradeFailure( + "failed to negotiate the secure protocol" + ) from error + try: + secure_conn = await transport.secure_outbound(conn, peer_id) + except HandshakeFailure as error: + raise SecurityUpgradeFailure( + "failed to secure the outbound transport" + ) from error return secure_conn async def select_transport( diff --git a/libp2p/transport/exceptions.py b/libp2p/transport/exceptions.py index 5826f83c..2a85becb 100644 --- a/libp2p/transport/exceptions.py +++ b/libp2p/transport/exceptions.py @@ -1,7 +1,14 @@ +from libp2p.exceptions import BaseLibp2pError + + # TODO: Add `BaseLibp2pError` and `UpgradeFailure` can inherit from it? -class UpgradeFailure(Exception): +class UpgradeFailure(BaseLibp2pError): pass class SecurityUpgradeFailure(UpgradeFailure): pass + + +class HandshakeFailure(BaseLibp2pError): + pass diff --git a/tests/security/test_security_multistream.py b/tests/security/test_security_multistream.py index 9a380b1f..ef32b3bf 100644 --- a/tests/security/test_security_multistream.py +++ b/tests/security/test_security_multistream.py @@ -4,9 +4,9 @@ import pytest from libp2p import new_node from libp2p.crypto.rsa import create_new_key_pair -from libp2p.protocol_muxer.multiselect_client import MultiselectClientError from libp2p.security.insecure.transport import InsecureSession, InsecureTransport from libp2p.security.simple.transport import SimpleSecurityTransport +from libp2p.transport.exceptions import SecurityUpgradeFailure from tests.configs import LISTEN_MADDR from tests.utils import cleanup, connect @@ -161,7 +161,7 @@ async def test_multiple_security_none_the_same_fails(): def assertion_func(_): assert False - with pytest.raises(MultiselectClientError): + with pytest.raises(SecurityUpgradeFailure): await perform_simple_test( assertion_func, transports_for_initiator, transports_for_noninitiator )