Skip to content

Commit

Permalink
Add crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
drew2a committed Oct 6, 2021
1 parent 2f815f5 commit efde2e4
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import random
from binascii import unhexlify

from cryptography.exceptions import InvalidSignature
from pony.orm import TransactionIntegrityError, db_session

from ipv8.lazy_community import lazy_wrapper
from tribler_core.components.tag.community.tag_crypto import TagCrypto
from tribler_core.components.tag.community.tag_payload import TagOperationMessage
from tribler_core.components.tag.community.tag_validator import TagValidator
from tribler_core.components.tag.db.tag_db import TagDatabase
Expand All @@ -21,11 +23,12 @@ class TagCommunity(TriblerCommunity):

community_id = unhexlify('042020c5e5e2ee0727fe99d704b430698e308d98')

def __init__(self, *args, db: TagDatabase, validator: TagValidator,
gossip_interval_for_tags_in_sec=DEFAULT_GOSSIP_INTERVAL_FOR_TAGS_IN_SEC, **kwargs):
def __init__(self, *args, db: TagDatabase, gossip_interval_for_tags_in_sec=DEFAULT_GOSSIP_INTERVAL_FOR_TAGS_IN_SEC,
**kwargs):
super().__init__(*args, **kwargs)
self.db = db
self.validator = validator
self.validator = TagValidator()
self.crypto = TagCrypto()

self.add_message_handler(TagOperationMessage, self.on_message)
self.register_task("gossip_random_tags", self.gossip_random_tags, interval=gossip_interval_for_tags_in_sec)
Expand All @@ -35,16 +38,18 @@ def __init__(self, *args, db: TagDatabase, validator: TagValidator,
@lazy_wrapper(TagOperationMessage)
def on_message(self, peer, payload):
self.logger.debug(f'Message received: {payload}')
# TODO: check is signature valid
try:
self.validator.validate_mesage(payload)
self.crypto.validate_signature(payload)
with db_session():
self.db.add_tag_operation(payload.infohash, payload.tag.decode(), payload.operation, payload.time,
payload.creator_public_key, payload.signature)
except ValueError as e:
except TransactionIntegrityError: # db error
pass # ignore all duplicates
except ValueError as e: # validation error
self.logger.warning(e)
except TransactionIntegrityError: # ignore all duplicates
pass
except InvalidSignature as e: # signature verification error
self.logger.error(e)

def gossip_random_tags(self):
if not self.get_peers():
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import copy

from cryptography.exceptions import InvalidSignature

from ipv8.keyvault.crypto import default_eccrypto
from ipv8.messaging.serialization import default_serializer
from ipv8.types import Key
from tribler_core.components.tag.community.tag_payload import TagOperationMessage


class TagCrypto:
def __init__(self):
pass

def _pack(self, message: TagOperationMessage) -> bytes:
to_pack = copy.copy(message)
to_pack.signature = b'' # this field is excluded from signing
return default_serializer.pack_serializable(to_pack)

def sign(self, message: TagOperationMessage, key: Key) -> bytes:
return default_eccrypto.create_signature(key, self._pack(message))

def validate_signature(self, message: TagOperationMessage):
assert message.creator_public_key
key = default_eccrypto.key_from_public_bin(message.creator_public_key)
if not default_eccrypto.is_valid_signature(key, self._pack(message), message.signature):
raise InvalidSignature()
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ def __init__(self):
pass

def validate_mesage(self, message: VariablePayload):
if not message.tag or len(message.tag) > 50 or len(message.tag) < 3:
if not message.tag:
raise ValueError("Tag can't be empty")

tag_length = len(message.tag.decode())
if not 3 <= tag_length <= 50:
raise ValueError('Tag length should be in range [3..50]')

# try co convert operation into Enum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
from ipv8.test.base import TestBase
from ipv8.test.mocking.ipv8 import MockIPv8
from tribler_core.components.tag.community.tag_community import TagCommunity
from tribler_core.components.tag.community.tag_payload import TagPayloadValidator
from tribler_core.components.tag.db.tag_db import Operation, TagDatabase
from tribler_core.components.tag.community.tests.test_tag_validator import create_message
from tribler_core.components.tag.db.tag_db import TagDatabase

GOSSIP_INTERVAL_FOR_RANDOM_TAGS = 0.1 # in seconds


class TestTagCommunity(TestBase):

def setUp(self):
super().setUp()
self.initialize(TagCommunity, 2)
Expand All @@ -19,28 +18,38 @@ async def tearDown(self):
await super().tearDown()

def create_node(self, *args, **kwargs):
return MockIPv8("curve25519", TagCommunity, db=TagDatabase(), validator=TagPayloadValidator(),
return MockIPv8("curve25519", TagCommunity, db=TagDatabase(),
gossip_interval_for_tags_in_sec=GOSSIP_INTERVAL_FOR_RANDOM_TAGS)

async def test_gossip(self):
node0_db = self.overlay(0).db
node1_db = self.overlay(1).db
with db_session:
# add 5 messages that signed correctly
for i in range(5):
node0_db.add_tag_operation(infohash=f'{i}'.encode() * 20,
tag=f'{i}' * 3,
operation=Operation.ADD,
time=1,
creator_public_key=f'{i}'.encode() * 74,
signature=f'{i}'.encode() * 64)
message = create_message(infohash=f'{i}'.encode() * 20, tag=f'{i}' * 3)
message.creator_public_key = self.overlay(0).my_peer.public_key.key_to_bin()
message.signature = self.overlay(0).crypto.sign(message, self.overlay(0).my_peer.key)

node0_db.add_tag_operation(message.infohash, message.tag.decode(), message.operation, message.time,
message.creator_public_key, message.signature)

# add 5 messages that signed incorrectly
for i in range(5, 10):
message = create_message(infohash=f'{i}'.encode() * 20, tag=f'{i}' * 3)
message.signature = self.overlay(0).crypto.sign(message, self.overlay(0).my_peer.key)
message.creator_public_key = self.overlay(0).my_peer.public_key.key_to_bin()

node0_db.add_tag_operation(message.infohash, message.tag.decode(), message.operation, message.time,
message.creator_public_key, message.signature)

with db_session:
assert node0_db.instance.TorrentTagOp.select().count() == 5
assert node0_db.instance.TorrentTagOp.select().count() == 10
assert node1_db.instance.TorrentTagOp.select().count() == 0

await self.introduce_nodes()
await self.deliver_messages(timeout=GOSSIP_INTERVAL_FOR_RANDOM_TAGS * 2)

with db_session:
assert node0_db.instance.TorrentTagOp.select().count() == 5
assert node0_db.instance.TorrentTagOp.select().count() == 10
assert node1_db.instance.TorrentTagOp.select().count() == 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest
from cryptography.exceptions import InvalidSignature

from ipv8.keyvault.crypto import ECCrypto
from ipv8.types import Key
from tribler_core.components.tag.community.tag_crypto import TagCrypto
from tribler_core.components.tag.community.tag_payload import TagOperationMessage
from tribler_core.components.tag.db.tag_db import Operation


# pylint: disable=protected-access

@pytest.fixture(name="tag_crypto") # this workaround implemented only for pylint
def fixture_tag_crypto():
return TagCrypto()


@pytest.fixture(name="random_message") # this workaround implemented only for pylint
def fixture_random_message():
return TagOperationMessage(f'{1}'.encode() * 20, Operation.ADD, 1, f'{1}'.encode() * 74, f'{1}'.encode() * 64,
''.encode())


@pytest.fixture(name="key") # this workaround implemented only for pylint
def fixture_key():
return ECCrypto().generate_key(u"curve25519")


pytestmark = pytest.mark.asyncio


async def test_pack(tag_crypto: TagCrypto, random_message: TagOperationMessage):
# ensure that signature field doesn't erase
assert random_message.signature

assert tag_crypto._pack(random_message)
assert random_message.signature


async def test_sign(tag_crypto: TagCrypto, random_message: TagOperationMessage, key: Key):
assert tag_crypto.sign(random_message, key)


async def test_is_signature_valid(tag_crypto: TagCrypto, random_message: TagOperationMessage, key: Key):
random_message.creator_public_key = key.pub().key_to_bin()
random_message.signature = tag_crypto.sign(random_message, key)
tag_crypto.validate_signature(random_message)


async def test_is_signature_invalid(tag_crypto: TagCrypto, random_message: TagOperationMessage, key: Key):
random_message.creator_public_key = key.pub().key_to_bin()
random_message.signature = tag_crypto.sign(random_message, key)
with pytest.raises(InvalidSignature):
random_message.tag = 'changed_tag'.encode()
tag_crypto.validate_signature(random_message)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ def fixture_validator():
pytestmark = pytest.mark.asyncio


def create_message(operation: int = Operation.ADD, tag: str = 'tag') -> VariablePayload:
return TagOperationMessage('infohash', operation, 'time', 'creator_public_key', 'signature', tag)
def create_message(infohash=b'infohash', operation: int = Operation.ADD, time: int = 0,
creator_public_key=b'creator_public_key', signature=b'signature',
tag: str = 'tag') -> VariablePayload:
return TagOperationMessage(infohash, operation, time, creator_public_key, signature, tag.encode())


async def test_correct_tag_size(validator: TagValidator):
Expand Down
4 changes: 2 additions & 2 deletions src/tribler-core/tribler_core/components/tag/tag_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from tribler_core.components.base import Component
from tribler_core.components.ipv8 import Ipv8Component
from tribler_core.components.tag.community.tag_community import TagCommunity
from tribler_core.components.tag.community.tag_crypto import TagCrypto
from tribler_core.components.tag.community.tag_validator import TagValidator

INFINITE = -1
Expand All @@ -22,8 +23,7 @@ async def run(self):
self._ipv8_component.peer,
self._ipv8_component.ipv8.endpoint,
self._ipv8_component.ipv8.network,
db=database_path,
validator=TagValidator(),
db=database_path
)

self._ipv8_component.initialise_community_by_default(self.community)
Expand Down

0 comments on commit efde2e4

Please sign in to comment.