From 683710573e0216c073648c20d226ab871e3279e8 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Tue, 26 Nov 2019 16:09:46 +0800 Subject: [PATCH 1/9] Add `strict_signing: bool and sign_key` to Pubsub --- libp2p/pubsub/pubsub.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index 493a303..076e60c 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -16,6 +16,7 @@ from typing import ( import base58 from lru import LRU +from libp2p.crypto.keys import PrivateKey from libp2p.exceptions import ParseError, ValidationError from libp2p.host.host_interface import IHost from libp2p.io.exceptions import IncompleteReadError @@ -82,8 +83,17 @@ class Pubsub: _tasks: List["asyncio.Future[Any]"] + # Indicate if we should enforce signature verification + strict_signing: bool + sign_key: PrivateKey + def __init__( - self, host: IHost, router: "IPubsubRouter", my_id: ID, cache_size: int = None + self, + host: IHost, + router: "IPubsubRouter", + my_id: ID, + cache_size: int = None, + strict_signing: bool = True, ) -> None: """ Construct a new Pubsub object, which is responsible for handling all @@ -147,6 +157,12 @@ class Pubsub: self._tasks.append(asyncio.ensure_future(self.handle_peer_queue())) self._tasks.append(asyncio.ensure_future(self.handle_dead_peer_queue())) + self.strict_signing = strict_signing + if strict_signing: + self.sign_key = self.host.get_private_key() + else: + self.sign_key = None + def get_hello_packet(self) -> rpc_pb2.RPC: """Generate subscription message with all topics we are subscribed to only send hello packet if we have subscribed topics.""" @@ -456,8 +472,6 @@ class Pubsub: seqno=self._next_seqno(), ) - # TODO: Sign with our signing key - await self.push_msg(self.host.get_id(), msg) logger.debug("successfully published message %s", msg) From f3c997215919c512cade5ea427e80b8207e3365c Mon Sep 17 00:00:00 2001 From: NIC619 Date: Tue, 26 Nov 2019 16:10:20 +0800 Subject: [PATCH 2/9] Implement Pubsub signature validator --- libp2p/pubsub/validators.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libp2p/pubsub/validators.py b/libp2p/pubsub/validators.py index d197207..0f6e55e 100644 --- a/libp2p/pubsub/validators.py +++ b/libp2p/pubsub/validators.py @@ -1,10 +1,17 @@ -# FIXME: Replace the type of `pubkey` with a custom type `Pubkey` -def signature_validator(pubkey: bytes, msg: bytes) -> bool: +from libp2p.crypto.keys import PublicKey + +from .pb import rpc_pb2 + + +def signature_validator(pubkey: PublicKey, msg: rpc_pb2.Message) -> bool: """ Verify the message against the given public key. :param pubkey: the public key which signs the message. :param msg: the message signed. """ - # TODO: Implement the signature validation + try: + pubkey.verify(msg.SerializeToString(), msg.signature) + except Exception: + return False return True From 0fd400fdf837654836818d904468c6a5e667ce36 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Tue, 26 Nov 2019 16:10:58 +0800 Subject: [PATCH 3/9] Sign and verify in Pubsub --- libp2p/pubsub/pubsub.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index 076e60c..e49d0e8 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -17,6 +17,7 @@ import base58 from lru import LRU from libp2p.crypto.keys import PrivateKey +from libp2p.crypto.serialization import deserialize_public_key from libp2p.exceptions import ParseError, ValidationError from libp2p.host.host_interface import IHost from libp2p.io.exceptions import IncompleteReadError @@ -472,6 +473,12 @@ class Pubsub: seqno=self._next_seqno(), ) + if self.strict_signing: + priv_key = self.sign_key + signature = priv_key.sign(msg.SerializeToString()) + msg.key = self.host.get_public_key().serialize() + msg.signature = signature + await self.push_msg(self.host.get_id(), msg) logger.debug("successfully published message %s", msg) @@ -519,18 +526,20 @@ class Pubsub: # TODO: Check if the `from` is in the blacklist. If yes, reject. - # TODO: Check if signing is required and if so signature should be attached. - # If the message is processed before, return(i.e., don't further process the message). if self._is_msg_seen(msg): return - # TODO: - Validate the message. If failed, reject it. - # Validate the signature of the message - # FIXME: `signature_validator` is currently a stub. - if not signature_validator(msg.key, msg.SerializeToString()): - logger.debug("Signature validation failed for msg: %s", msg) - return + # Check if signing is required and if so signature should be attached. + if self.strict_signing: + if msg.signature == b'': + logger.debug("Reject because no signature attached for msg: %s", msg) + return + # Validate the signature of the message + if not signature_validator(deserialize_public_key(msg.key), msg): + logger.debug("Signature validation failed for msg: %s", msg) + return + # Validate the message with registered topic validators. # If the validation failed, return(i.e., don't further process the message). try: From d5d6962dce9b8e72b0309771ab451c3ef8b79c7b Mon Sep 17 00:00:00 2001 From: NIC619 Date: Tue, 26 Nov 2019 16:12:50 +0800 Subject: [PATCH 4/9] Update Pubsub fixture and test --- libp2p/pubsub/pubsub.py | 2 +- libp2p/tools/factories.py | 1 + tests/pubsub/conftest.py | 28 ++++++++++++++++++++++------ tests/pubsub/test_pubsub.py | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index e49d0e8..0fea734 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -532,7 +532,7 @@ class Pubsub: # Check if signing is required and if so signature should be attached. if self.strict_signing: - if msg.signature == b'': + if msg.signature == b"": logger.debug("Reject because no signature attached for msg: %s", msg) return # Validate the signature of the message diff --git a/libp2p/tools/factories.py b/libp2p/tools/factories.py index b5c16b8..b189cfa 100644 --- a/libp2p/tools/factories.py +++ b/libp2p/tools/factories.py @@ -153,6 +153,7 @@ class PubsubFactory(factory.Factory): router = None my_id = factory.LazyAttribute(lambda obj: obj.host.get_id()) cache_size = None + strict_signing = False async def swarm_pair_factory( diff --git a/tests/pubsub/conftest.py b/tests/pubsub/conftest.py index 9dbe90b..520fdf4 100644 --- a/tests/pubsub/conftest.py +++ b/tests/pubsub/conftest.py @@ -4,14 +4,24 @@ from libp2p.tools.constants import GOSSIPSUB_PARAMS from libp2p.tools.factories import FloodsubFactory, GossipsubFactory, PubsubFactory -def _make_pubsubs(hosts, pubsub_routers, cache_size): +@pytest.fixture +def is_strict_signing(): + return False + + +def _make_pubsubs(hosts, pubsub_routers, cache_size, is_strict_signing): if len(pubsub_routers) != len(hosts): raise ValueError( f"lenght of pubsub_routers={pubsub_routers} should be equaled to the " f"length of hosts={len(hosts)}" ) return tuple( - PubsubFactory(host=host, router=router, cache_size=cache_size) + PubsubFactory( + host=host, + router=router, + cache_size=cache_size, + strict_signing=is_strict_signing, + ) for host, router in zip(hosts, pubsub_routers) ) @@ -27,16 +37,22 @@ def gossipsub_params(): @pytest.fixture -def pubsubs_fsub(num_hosts, hosts, pubsub_cache_size): +def pubsubs_fsub(num_hosts, hosts, pubsub_cache_size, is_strict_signing): floodsubs = FloodsubFactory.create_batch(num_hosts) - _pubsubs_fsub = _make_pubsubs(hosts, floodsubs, pubsub_cache_size) + _pubsubs_fsub = _make_pubsubs( + hosts, floodsubs, pubsub_cache_size, is_strict_signing + ) yield _pubsubs_fsub # TODO: Clean up @pytest.fixture -def pubsubs_gsub(num_hosts, hosts, pubsub_cache_size, gossipsub_params): +def pubsubs_gsub( + num_hosts, hosts, pubsub_cache_size, gossipsub_params, is_strict_signing +): gossipsubs = GossipsubFactory.create_batch(num_hosts, **gossipsub_params._asdict()) - _pubsubs_gsub = _make_pubsubs(hosts, gossipsubs, pubsub_cache_size) + _pubsubs_gsub = _make_pubsubs( + hosts, gossipsubs, pubsub_cache_size, is_strict_signing + ) yield _pubsubs_gsub # TODO: Clean up diff --git a/tests/pubsub/test_pubsub.py b/tests/pubsub/test_pubsub.py index ebe2003..48ef52b 100644 --- a/tests/pubsub/test_pubsub.py +++ b/tests/pubsub/test_pubsub.py @@ -510,3 +510,18 @@ async def test_push_msg(pubsubs_fsub, monkeypatch): await pubsubs_fsub[0].push_msg(pubsubs_fsub[0].my_id, msg_2) await asyncio.sleep(0.01) assert not event.is_set() + + +@pytest.mark.parametrize("num_hosts, is_strict_signing", ((2, True),)) +@pytest.mark.asyncio +async def test_strict_signing(pubsubs_fsub, hosts, monkeypatch): + await connect(hosts[0], hosts[1]) + await pubsubs_fsub[0].subscribe(TESTING_TOPIC) + await pubsubs_fsub[1].subscribe(TESTING_TOPIC) + await asyncio.sleep(1) + + await pubsubs_fsub[0].publish(TESTING_TOPIC, TESTING_DATA) + await asyncio.sleep(1) + + assert len(pubsubs_fsub[0].seen_messages) == 1 + assert len(pubsubs_fsub[1].seen_messages) == 1 From 064c109b647b2f1452ad1e96ceb74503c9261793 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Thu, 28 Nov 2019 18:45:00 +0800 Subject: [PATCH 5/9] Fix signature validator: Add prefix and return verify result --- libp2p/pubsub/pubsub.py | 20 ++++++++++++++++++-- libp2p/pubsub/validators.py | 7 ++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index 0fea734..69f873a 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -49,6 +49,8 @@ SyncValidatorFn = Callable[[ID, rpc_pb2.Message], bool] AsyncValidatorFn = Callable[[ID, rpc_pb2.Message], Awaitable[bool]] ValidatorFn = Union[SyncValidatorFn, AsyncValidatorFn] +PUBSUB_SIGNING_PREFIX = "libp2p-pubsub:" + class TopicValidator(NamedTuple): validator: ValidatorFn @@ -475,7 +477,9 @@ class Pubsub: if self.strict_signing: priv_key = self.sign_key - signature = priv_key.sign(msg.SerializeToString()) + signature = priv_key.sign( + PUBSUB_SIGNING_PREFIX.encode() + msg.SerializeToString() + ) msg.key = self.host.get_public_key().serialize() msg.signature = signature @@ -536,7 +540,19 @@ class Pubsub: logger.debug("Reject because no signature attached for msg: %s", msg) return # Validate the signature of the message - if not signature_validator(deserialize_public_key(msg.key), msg): + # First, construct the original payload that's signed by 'msg.key' + msg_without_key_sig = rpc_pb2.Message( + data=msg.data, + topicIDs=msg.topicIDs, + from_id=msg.from_id, + seqno=msg.seqno, + ) + payload = ( + PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() + ) + if not signature_validator( + deserialize_public_key(msg.key), payload, msg.signature + ): logger.debug("Signature validation failed for msg: %s", msg) return diff --git a/libp2p/pubsub/validators.py b/libp2p/pubsub/validators.py index 0f6e55e..2683c0e 100644 --- a/libp2p/pubsub/validators.py +++ b/libp2p/pubsub/validators.py @@ -1,9 +1,7 @@ from libp2p.crypto.keys import PublicKey -from .pb import rpc_pb2 - -def signature_validator(pubkey: PublicKey, msg: rpc_pb2.Message) -> bool: +def signature_validator(pubkey: PublicKey, payload: bytes, signature: bytes) -> bool: """ Verify the message against the given public key. @@ -11,7 +9,6 @@ def signature_validator(pubkey: PublicKey, msg: rpc_pb2.Message) -> bool: :param msg: the message signed. """ try: - pubkey.verify(msg.SerializeToString(), msg.signature) + return pubkey.verify(payload, signature) except Exception: return False - return True From a262b94836c9f5d8f51ecc92239e17d4dae4bbf8 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Fri, 29 Nov 2019 14:12:42 +0800 Subject: [PATCH 6/9] Apply PR feedback: check if signing key and ID match --- libp2p/pubsub/pubsub.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index 69f873a..ac08b41 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -539,6 +539,12 @@ class Pubsub: if msg.signature == b"": logger.debug("Reject because no signature attached for msg: %s", msg) return + # Validate if message sender matches message signer, + # i.e., check if `msg.key` matches `msg.from_id` + msg_pubkey = deserialize_public_key(msg.key) + if ID.from_pubkey(msg_pubkey) != msg.from_id: + logger.debug("Reject because signing key does not match sender ID for msg: %s", msg) + return # Validate the signature of the message # First, construct the original payload that's signed by 'msg.key' msg_without_key_sig = rpc_pb2.Message( @@ -551,7 +557,7 @@ class Pubsub: PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() ) if not signature_validator( - deserialize_public_key(msg.key), payload, msg.signature + msg_pubkey, payload, msg.signature ): logger.debug("Signature validation failed for msg: %s", msg) return From f4e86b117259366e679a81a118d0adb35ca41d68 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Fri, 29 Nov 2019 14:13:07 +0800 Subject: [PATCH 7/9] Add tests for failed signature validation cases --- tests/pubsub/test_pubsub.py | 57 ++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/pubsub/test_pubsub.py b/tests/pubsub/test_pubsub.py index 48ef52b..8052926 100644 --- a/tests/pubsub/test_pubsub.py +++ b/tests/pubsub/test_pubsub.py @@ -5,6 +5,7 @@ import pytest from libp2p.exceptions import ValidationError from libp2p.peer.id import ID +from libp2p.pubsub.pubsub import PUBSUB_SIGNING_PREFIX from libp2p.pubsub.pb import rpc_pb2 from libp2p.tools.pubsub.utils import make_pubsub_msg from libp2p.tools.utils import connect @@ -514,7 +515,7 @@ async def test_push_msg(pubsubs_fsub, monkeypatch): @pytest.mark.parametrize("num_hosts, is_strict_signing", ((2, True),)) @pytest.mark.asyncio -async def test_strict_signing(pubsubs_fsub, hosts, monkeypatch): +async def test_strict_signing(pubsubs_fsub, hosts): await connect(hosts[0], hosts[1]) await pubsubs_fsub[0].subscribe(TESTING_TOPIC) await pubsubs_fsub[1].subscribe(TESTING_TOPIC) @@ -525,3 +526,57 @@ async def test_strict_signing(pubsubs_fsub, hosts, monkeypatch): assert len(pubsubs_fsub[0].seen_messages) == 1 assert len(pubsubs_fsub[1].seen_messages) == 1 + + +@pytest.mark.parametrize("num_hosts, is_strict_signing", ((2, True),)) +@pytest.mark.asyncio +async def test_strict_signing_failed_validation(pubsubs_fsub, hosts, monkeypatch): + msg = make_pubsub_msg( + origin_id=pubsubs_fsub[0].my_id, + topic_ids=[TESTING_TOPIC], + data=TESTING_DATA, + seqno=b"\x00" * 8, + ) + priv_key = pubsubs_fsub[0].sign_key + signature = priv_key.sign( + PUBSUB_SIGNING_PREFIX.encode() + msg.SerializeToString() + ) + + event = asyncio.Event() + + def _is_msg_seen(msg): + return False + + # Use router publish to check if `push_msg` succeed. + async def router_publish(*args, **kwargs): + # The event will only be set if `push_msg` succeed. + event.set() + + monkeypatch.setattr(pubsubs_fsub[0], "_is_msg_seen", _is_msg_seen) + monkeypatch.setattr(pubsubs_fsub[0].router, "publish", router_publish) + + # Test: no signature attached in `msg` + await pubsubs_fsub[0].push_msg(pubsubs_fsub[0].my_id, msg) + await asyncio.sleep(0.01) + assert not event.is_set() + + # Test: `msg.key` does not match `msg.from_id` + msg.key = hosts[1].get_public_key().serialize() + msg.signature = signature + await pubsubs_fsub[0].push_msg(pubsubs_fsub[0].my_id, msg) + await asyncio.sleep(0.01) + assert not event.is_set() + + # Test: invalid signature + msg.key = hosts[0].get_public_key().serialize() + msg.signature = b"\x12" * 100 + await pubsubs_fsub[0].push_msg(pubsubs_fsub[0].my_id, msg) + await asyncio.sleep(0.01) + assert not event.is_set() + + # Finally, assert the signature indeed will pass validation + msg.key = hosts[0].get_public_key().serialize() + msg.signature = signature + await pubsubs_fsub[0].push_msg(pubsubs_fsub[0].my_id, msg) + await asyncio.sleep(0.01) + assert event.is_set() From 1c54c38ca7a6ceb569687329cd81171f0bad47ee Mon Sep 17 00:00:00 2001 From: NIC619 Date: Fri, 29 Nov 2019 17:24:40 +0800 Subject: [PATCH 8/9] Fix lint and add `signing_strict` to interop tests --- libp2p/pubsub/pubsub.py | 9 +++++---- tests/pubsub/test_pubsub.py | 6 ++---- tests_interop/conftest.py | 26 +++++++++++++++++++++++--- tests_interop/test_pubsub.py | 3 +++ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index ac08b41..0b4d605 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -543,7 +543,10 @@ class Pubsub: # i.e., check if `msg.key` matches `msg.from_id` msg_pubkey = deserialize_public_key(msg.key) if ID.from_pubkey(msg_pubkey) != msg.from_id: - logger.debug("Reject because signing key does not match sender ID for msg: %s", msg) + logger.debug( + "Reject because signing key does not match sender ID for msg: %s", + msg, + ) return # Validate the signature of the message # First, construct the original payload that's signed by 'msg.key' @@ -556,9 +559,7 @@ class Pubsub: payload = ( PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() ) - if not signature_validator( - msg_pubkey, payload, msg.signature - ): + if not signature_validator(msg_pubkey, payload, msg.signature): logger.debug("Signature validation failed for msg: %s", msg) return diff --git a/tests/pubsub/test_pubsub.py b/tests/pubsub/test_pubsub.py index 8052926..01d8ba7 100644 --- a/tests/pubsub/test_pubsub.py +++ b/tests/pubsub/test_pubsub.py @@ -5,8 +5,8 @@ import pytest from libp2p.exceptions import ValidationError from libp2p.peer.id import ID -from libp2p.pubsub.pubsub import PUBSUB_SIGNING_PREFIX from libp2p.pubsub.pb import rpc_pb2 +from libp2p.pubsub.pubsub import PUBSUB_SIGNING_PREFIX from libp2p.tools.pubsub.utils import make_pubsub_msg from libp2p.tools.utils import connect from libp2p.utils import encode_varint_prefixed @@ -538,9 +538,7 @@ async def test_strict_signing_failed_validation(pubsubs_fsub, hosts, monkeypatch seqno=b"\x00" * 8, ) priv_key = pubsubs_fsub[0].sign_key - signature = priv_key.sign( - PUBSUB_SIGNING_PREFIX.encode() + msg.SerializeToString() - ) + signature = priv_key.sign(PUBSUB_SIGNING_PREFIX.encode() + msg.SerializeToString()) event = asyncio.Event() diff --git a/tests_interop/conftest.py b/tests_interop/conftest.py index e75e519..8ae9769 100644 --- a/tests_interop/conftest.py +++ b/tests_interop/conftest.py @@ -76,7 +76,24 @@ def is_gossipsub(): @pytest.fixture -async def p2pds(num_p2pds, is_host_secure, is_gossipsub, unused_tcp_port_factory): +def is_pubsub_signing(): + return True + + +@pytest.fixture +def is_pubsub_signing_strict(): + return True + + +@pytest.fixture +async def p2pds( + num_p2pds, + is_host_secure, + is_gossipsub, + unused_tcp_port_factory, + is_pubsub_signing, + is_pubsub_signing_strict, +): p2pds: Union[Daemon, Exception] = await asyncio.gather( *[ make_p2pd( @@ -84,6 +101,8 @@ async def p2pds(num_p2pds, is_host_secure, is_gossipsub, unused_tcp_port_factory unused_tcp_port_factory(), is_host_secure, is_gossipsub=is_gossipsub, + is_pubsub_signing=is_pubsub_signing, + is_pubsub_signing_strict=is_pubsub_signing_strict, ) for _ in range(num_p2pds) ], @@ -102,13 +121,14 @@ async def p2pds(num_p2pds, is_host_secure, is_gossipsub, unused_tcp_port_factory @pytest.fixture -def pubsubs(num_hosts, hosts, is_gossipsub): +def pubsubs(num_hosts, hosts, is_gossipsub, is_pubsub_signing_strict): if is_gossipsub: routers = GossipsubFactory.create_batch(num_hosts, **GOSSIPSUB_PARAMS._asdict()) else: routers = FloodsubFactory.create_batch(num_hosts) _pubsubs = tuple( - PubsubFactory(host=host, router=router) for host, router in zip(hosts, routers) + PubsubFactory(host=host, router=router, strict_signing=is_pubsub_signing_strict) + for host, router in zip(hosts, routers) ) yield _pubsubs # TODO: Clean up diff --git a/tests_interop/test_pubsub.py b/tests_interop/test_pubsub.py index 4e845d7..f67b47a 100644 --- a/tests_interop/test_pubsub.py +++ b/tests_interop/test_pubsub.py @@ -55,6 +55,9 @@ def validate_pubsub_msg(msg: rpc_pb2.Message, data: bytes, from_peer_id: ID) -> assert msg.data == data and msg.from_id == from_peer_id +@pytest.mark.parametrize( + "is_pubsub_signing, is_pubsub_signing_strict", ((True, True), (False, False)) +) @pytest.mark.parametrize("is_gossipsub", (True, False)) @pytest.mark.parametrize("num_hosts, num_p2pds", ((1, 2),)) @pytest.mark.asyncio From 658a0ae156454c61f7aff6c98459448c7ce4a639 Mon Sep 17 00:00:00 2001 From: NIC619 Date: Fri, 29 Nov 2019 19:37:48 +0800 Subject: [PATCH 9/9] Apply PR feedback: move signature validation logic into signature validator --- libp2p/pubsub/pubsub.py | 31 +++---------------------------- libp2p/pubsub/validators.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/libp2p/pubsub/pubsub.py b/libp2p/pubsub/pubsub.py index 0b4d605..fab8024 100644 --- a/libp2p/pubsub/pubsub.py +++ b/libp2p/pubsub/pubsub.py @@ -17,7 +17,6 @@ import base58 from lru import LRU from libp2p.crypto.keys import PrivateKey -from libp2p.crypto.serialization import deserialize_public_key from libp2p.exceptions import ParseError, ValidationError from libp2p.host.host_interface import IHost from libp2p.io.exceptions import IncompleteReadError @@ -30,7 +29,7 @@ from libp2p.utils import encode_varint_prefixed, read_varint_prefixed_bytes from .pb import rpc_pb2 from .pubsub_notifee import PubsubNotifee -from .validators import signature_validator +from .validators import PUBSUB_SIGNING_PREFIX, signature_validator if TYPE_CHECKING: from .pubsub_router_interface import IPubsubRouter # noqa: F401 @@ -49,8 +48,6 @@ SyncValidatorFn = Callable[[ID, rpc_pb2.Message], bool] AsyncValidatorFn = Callable[[ID, rpc_pb2.Message], Awaitable[bool]] ValidatorFn = Union[SyncValidatorFn, AsyncValidatorFn] -PUBSUB_SIGNING_PREFIX = "libp2p-pubsub:" - class TopicValidator(NamedTuple): validator: ValidatorFn @@ -534,32 +531,10 @@ class Pubsub: if self._is_msg_seen(msg): return - # Check if signing is required and if so signature should be attached. + # Check if signing is required and if so validate the signature if self.strict_signing: - if msg.signature == b"": - logger.debug("Reject because no signature attached for msg: %s", msg) - return - # Validate if message sender matches message signer, - # i.e., check if `msg.key` matches `msg.from_id` - msg_pubkey = deserialize_public_key(msg.key) - if ID.from_pubkey(msg_pubkey) != msg.from_id: - logger.debug( - "Reject because signing key does not match sender ID for msg: %s", - msg, - ) - return # Validate the signature of the message - # First, construct the original payload that's signed by 'msg.key' - msg_without_key_sig = rpc_pb2.Message( - data=msg.data, - topicIDs=msg.topicIDs, - from_id=msg.from_id, - seqno=msg.seqno, - ) - payload = ( - PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() - ) - if not signature_validator(msg_pubkey, payload, msg.signature): + if not signature_validator(msg): logger.debug("Signature validation failed for msg: %s", msg) return diff --git a/libp2p/pubsub/validators.py b/libp2p/pubsub/validators.py index 2683c0e..22f6579 100644 --- a/libp2p/pubsub/validators.py +++ b/libp2p/pubsub/validators.py @@ -1,14 +1,41 @@ -from libp2p.crypto.keys import PublicKey +import logging + +from libp2p.crypto.serialization import deserialize_public_key +from libp2p.peer.id import ID + +from .pb import rpc_pb2 + +logger = logging.getLogger("libp2p.pubsub") + +PUBSUB_SIGNING_PREFIX = "libp2p-pubsub:" -def signature_validator(pubkey: PublicKey, payload: bytes, signature: bytes) -> bool: +def signature_validator(msg: rpc_pb2.Message) -> bool: """ Verify the message against the given public key. :param pubkey: the public key which signs the message. :param msg: the message signed. """ + # Check if signature is attached + if msg.signature == b"": + logger.debug("Reject because no signature attached for msg: %s", msg) + return False + + # Validate if message sender matches message signer, + # i.e., check if `msg.key` matches `msg.from_id` + msg_pubkey = deserialize_public_key(msg.key) + if ID.from_pubkey(msg_pubkey) != msg.from_id: + logger.debug( + "Reject because signing key does not match sender ID for msg: %s", msg + ) + return False + # First, construct the original payload that's signed by 'msg.key' + msg_without_key_sig = rpc_pb2.Message( + data=msg.data, topicIDs=msg.topicIDs, from_id=msg.from_id, seqno=msg.seqno + ) + payload = PUBSUB_SIGNING_PREFIX.encode() + msg_without_key_sig.SerializeToString() try: - return pubkey.verify(payload, signature) + return msg_pubkey.verify(payload, msg.signature) except Exception: return False