From 7e43f3caadf3075d2b4bd0af4d54c0fad15694a4 Mon Sep 17 00:00:00 2001 From: jMyles Date: Fri, 10 Nov 2017 23:36:21 -0800 Subject: [PATCH 01/14] More thorough implementation of Signature throughout. --- nkms/characters.py | 4 ++-- nkms/crypto/powers.py | 2 +- nkms/crypto/signature.py | 13 +++++++++---- nkms/network/protocols.py | 8 +++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index 84471513c..74dffd49e 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -8,7 +8,7 @@ from nkms.crypto import api as API from nkms.crypto.api import secure_random, keccak_digest from nkms.crypto.constants import NOT_SIGNED, NO_DECRYPTION_PERFORMED from nkms.crypto.powers import CryptoPower, SigningPower, EncryptingPower -from nkms.crypto.utils import verify + from nkms.keystore.keypairs import Keypair from nkms.network import blockchain_client from nkms.network.blockchain_client import list_all_ursulas @@ -136,7 +136,7 @@ class Character(object): actor = self._lookup_actor(actor_whom_sender_claims_to_be) - return verify(signature, message, actor.seal), cleartext + return signature.verify(message, actor.seal), cleartext def _lookup_actor(self, actor: "Character"): try: diff --git a/nkms/crypto/powers.py b/nkms/crypto/powers.py index 99c8911a8..2a20a5783 100644 --- a/nkms/crypto/powers.py +++ b/nkms/crypto/powers.py @@ -77,7 +77,7 @@ class CryptoPower(object): raise NoSigningPower(e) msg_digest = b"".join(API.keccak_digest(m) for m in messages) - return Signature(sig=sig_keypair.sign(msg_digest)) + return Signature(sig_keypair.sign(msg_digest)) def decrypt(self, ciphertext): try: diff --git a/nkms/crypto/signature.py b/nkms/crypto/signature.py index 2bb48dd64..0ba41da38 100644 --- a/nkms/crypto/signature.py +++ b/nkms/crypto/signature.py @@ -5,9 +5,9 @@ class Signature(object): """ The Signature object allows signatures to be made and verified. """ - LENGTH = 32 + _EXPECTED_LENGTH = 65 - def __init__(self, v: int=None, r: int=None, s: int=None, sig: bytes=None): + def __init__(self, sig_as_bytes: bytes=None, v: int=None, r: int=None, s: int=None): """ Initializes a Signature object. @@ -17,8 +17,13 @@ class Signature(object): :return: Signature object """ - if sig: - v, r, s = API.ecdsa_load_sig(sig) + if sig_as_bytes and any((v, r, s)): + raise ValueError("Pass *either* sig_as_bytes *or* v, r, and s - don't pass both.") + if not any ((sig_as_bytes, v, r, s)): + raise ValueError("Pass either sig_as_bytes or v, r, and s.") + + if sig_as_bytes: + v, r, s = API.ecdsa_load_sig(sig_as_bytes) self._v = v self._r = r diff --git a/nkms/network/protocols.py b/nkms/network/protocols.py index 18e33e467..a67a4858b 100644 --- a/nkms/network/protocols.py +++ b/nkms/network/protocols.py @@ -5,6 +5,7 @@ from kademlia.protocol import KademliaProtocol from kademlia.utils import digest from nkms.crypto import utils from nkms.crypto.api import keccak_digest +from nkms.crypto.signature import Signature from nkms.network.constants import NODE_HAS_NO_STORAGE from nkms.network.node import NuCypherNode from nkms.network.routing import NuCypherRoutingTable @@ -45,10 +46,10 @@ class NuCypherHashProtocol(KademliaProtocol): # TODO: This try block is not the right approach - a Ciphertext class can resolve this instead. try: # Ursula uaddr scenario - verified = utils.verify(signature, message, sender_pubkey_sig) + verified = signature.verify(message, sender_pubkey_sig) except Exception as e: # trmap scenario - verified = utils.verify(signature, msgpack.dumps(message), sender_pubkey_sig) + verified = signature.verify(msgpack.dumps(message), sender_pubkey_sig) if not verified or not proper_key == dht_key: self.log.warning("Got request to store illegal k/v: {} / {}".format(dht_key, dht_value)) @@ -63,7 +64,8 @@ class NuCypherHashProtocol(KademliaProtocol): self.log.debug("got a store request from %s" % str(sender)) if value.startswith(b"uaddr") or value.startswith(b"trmap"): - signature, sender_pubkey_sig, extra_info, message = msgpack.loads(value[5::]) # TODO: #114 + sig_bytes, sender_pubkey_sig, extra_info, message = msgpack.loads(value[5::]) # TODO: #114 + signature = Signature(sig_bytes) # extra_info is a hash of the policy_group.id in the case of a treasure map, or a TTL in the case # of an Ursula interface. TODO: Decide whether to keep this notion and, if so, use the TTL. do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig, message, extra_info, key, value) From 4f803ba2dff3b8f02c870f5f057ca67f012f7987 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 01:10:29 -0800 Subject: [PATCH 02/14] BytestringSplitter for #114. --- nkms/crypto/signature.py | 5 ++++- nkms/crypto/utils.py | 30 ++++++++++++++++++++++----- tests/crypto/test_bytestring_types.py | 13 ++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/crypto/test_bytestring_types.py diff --git a/nkms/crypto/signature.py b/nkms/crypto/signature.py index 0ba41da38..4c429ca4b 100644 --- a/nkms/crypto/signature.py +++ b/nkms/crypto/signature.py @@ -1,7 +1,7 @@ from nkms.crypto import api as API -class Signature(object): +class Signature(bytes): """ The Signature object allows signatures to be made and verified. """ @@ -29,6 +29,9 @@ class Signature(object): self._r = r self._s = s + def __repr__(self): + return "{} v{}: {} - {}".format(__class__.__name__, self._v, self._r, self._s) + def verify(self, message: bytes, pubkey: bytes) -> bool: """ Verifies that a message's signature was valid. diff --git a/nkms/crypto/utils.py b/nkms/crypto/utils.py index afa98f4c6..e7228d2ab 100644 --- a/nkms/crypto/utils.py +++ b/nkms/crypto/utils.py @@ -1,7 +1,27 @@ -from nkms.crypto import api -def verify(signature: bytes, message: bytes, pubkey: bytes) -> bool: - msg_digest = api.keccak_digest(message) - ecdsa_sig = api.ecdsa_load_sig(bytes(signature)) - return api.ecdsa_verify(*ecdsa_sig, msg_digest, pubkey) +class BytestringSplitter(object): + + def __init__(self, *message_types): + """ + :param message_types: A collection of types of messages to parse. + """ + self.message_types = message_types + + def __call__(self, splittable): + if self.total_expected_length() != len(splittable): + raise ValueError("Wrong number of bytes to constitute message types {} - need {}, got {}".format(self.message_types, self.total_expected_length(), len(splittable))) + + cursor = 0 + message_objects = [] + + for message_type in self.message_types: + expected_end_of_object_bytes = cursor + message_type._EXPECTED_LENGTH + bytes_for_this_object = splittable[cursor:cursor + expected_end_of_object_bytes] + message_objects.append(message_type(bytes_for_this_object)) + cursor = expected_end_of_object_bytes + + return message_objects + + def total_expected_length(self): + return sum(m._EXPECTED_LENGTH for m in self.message_types) diff --git a/tests/crypto/test_bytestring_types.py b/tests/crypto/test_bytestring_types.py new file mode 100644 index 000000000..240eab6a1 --- /dev/null +++ b/tests/crypto/test_bytestring_types.py @@ -0,0 +1,13 @@ +from nkms.crypto.api import secure_random +from nkms.crypto.signature import Signature +from nkms.crypto.utils import BytestringSplitter + + +def test_split_two_signatures(): + """ + We make two random Signatures and concat them. Then split them and show that we got the proper result. + """ + sig1, sig2 = Signature(secure_random(65)), Signature(secure_random(65)) + two_signature_splitter = BytestringSplitter(Signature, Signature) + rebuilt_sig1, rebuilt_sig2 = two_signature_splitter(sig1 + sig2) + assert (sig1, sig2) == (rebuilt_sig1, rebuilt_sig2) From 1ed6e6d30b799fa58c2264bbefab26e448fe4a2d Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 13:21:24 -0800 Subject: [PATCH 03/14] Test showing BytestringSplitter taking arbitrary bytes. --- tests/crypto/test_bytestring_types.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/crypto/test_bytestring_types.py b/tests/crypto/test_bytestring_types.py index 240eab6a1..df211d134 100644 --- a/tests/crypto/test_bytestring_types.py +++ b/tests/crypto/test_bytestring_types.py @@ -8,6 +8,16 @@ def test_split_two_signatures(): We make two random Signatures and concat them. Then split them and show that we got the proper result. """ sig1, sig2 = Signature(secure_random(65)), Signature(secure_random(65)) + sigs_concatted = sig1 + sig2 two_signature_splitter = BytestringSplitter(Signature, Signature) - rebuilt_sig1, rebuilt_sig2 = two_signature_splitter(sig1 + sig2) + rebuilt_sig1, rebuilt_sig2 = two_signature_splitter(sigs_concatted) assert (sig1, sig2) == (rebuilt_sig1, rebuilt_sig2) + + +def test_split_signature_from_arbitrary_bytes(): + how_many_bytes = 10 + signature = Signature(secure_random(65)) + some_bytes = secure_random(how_many_bytes) + splitter = BytestringSplitter(Signature, how_many_bytes) + rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes) + From 1f28a671fa63901997b934571408674e9a4423aa Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 13:50:51 -0800 Subject: [PATCH 04/14] BytestringSplitter now works with arbitrary types. --- nkms/crypto/utils.py | 18 +++++++++++++----- tests/crypto/test_bytestring_types.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nkms/crypto/utils.py b/nkms/crypto/utils.py index e7228d2ab..a65590888 100644 --- a/nkms/crypto/utils.py +++ b/nkms/crypto/utils.py @@ -9,19 +9,27 @@ class BytestringSplitter(object): self.message_types = message_types def __call__(self, splittable): - if self.total_expected_length() != len(splittable): + if len(self) != len(splittable): raise ValueError("Wrong number of bytes to constitute message types {} - need {}, got {}".format(self.message_types, self.total_expected_length(), len(splittable))) cursor = 0 message_objects = [] for message_type in self.message_types: - expected_end_of_object_bytes = cursor + message_type._EXPECTED_LENGTH + message_class, message_length = self.get_message_meta(message_type) + expected_end_of_object_bytes = cursor + message_length bytes_for_this_object = splittable[cursor:cursor + expected_end_of_object_bytes] - message_objects.append(message_type(bytes_for_this_object)) + message = message_class(bytes_for_this_object) + + message_objects.append(message) cursor = expected_end_of_object_bytes return message_objects - def total_expected_length(self): - return sum(m._EXPECTED_LENGTH for m in self.message_types) + def __len__(self): + return sum(self.get_message_meta(m)[1] for m in self.message_types) + + @staticmethod + def get_message_meta(message_type): + return message_type if isinstance(message_type, tuple) else (message_type, message_type._EXPECTED_LENGTH) + diff --git a/tests/crypto/test_bytestring_types.py b/tests/crypto/test_bytestring_types.py index df211d134..8e83644f8 100644 --- a/tests/crypto/test_bytestring_types.py +++ b/tests/crypto/test_bytestring_types.py @@ -18,6 +18,6 @@ def test_split_signature_from_arbitrary_bytes(): how_many_bytes = 10 signature = Signature(secure_random(65)) some_bytes = secure_random(how_many_bytes) - splitter = BytestringSplitter(Signature, how_many_bytes) + splitter = BytestringSplitter(Signature, (bytes, how_many_bytes)) rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes) From 3901f13750f3d46cca42eec95740d53aa3137a46 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 13:55:51 -0800 Subject: [PATCH 05/14] Test showing that trying to split too many bytes raises ValueError. --- nkms/crypto/utils.py | 2 +- tests/crypto/test_bytestring_types.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nkms/crypto/utils.py b/nkms/crypto/utils.py index a65590888..8d001e755 100644 --- a/nkms/crypto/utils.py +++ b/nkms/crypto/utils.py @@ -10,7 +10,7 @@ class BytestringSplitter(object): def __call__(self, splittable): if len(self) != len(splittable): - raise ValueError("Wrong number of bytes to constitute message types {} - need {}, got {}".format(self.message_types, self.total_expected_length(), len(splittable))) + raise ValueError("Wrong number of bytes to constitute message types {} - need {}, got {}".format(self.message_types, len(self), len(splittable))) cursor = 0 message_objects = [] diff --git a/tests/crypto/test_bytestring_types.py b/tests/crypto/test_bytestring_types.py index 8e83644f8..0ea61f66c 100644 --- a/tests/crypto/test_bytestring_types.py +++ b/tests/crypto/test_bytestring_types.py @@ -1,3 +1,5 @@ +import pytest + from nkms.crypto.api import secure_random from nkms.crypto.signature import Signature from nkms.crypto.utils import BytestringSplitter @@ -19,5 +21,17 @@ def test_split_signature_from_arbitrary_bytes(): signature = Signature(secure_random(65)) some_bytes = secure_random(how_many_bytes) splitter = BytestringSplitter(Signature, (bytes, how_many_bytes)) + + rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes) + +def test_trying_to_extract_too_many_bytes_raises_typeerror(): + how_many_bytes = 10 + too_many_bytes = 11 + signature = Signature(secure_random(65)) + some_bytes = secure_random(how_many_bytes) + splitter = BytestringSplitter(Signature, (bytes, too_many_bytes)) + + with pytest.raises(ValueError): + rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes) From 53a0450bf55e737286dcac35ee8f866b911a7bee Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 15:49:15 -0800 Subject: [PATCH 06/14] Added dunders to Seal for concat'ing. --- nkms/characters.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nkms/characters.py b/nkms/characters.py index 74dffd49e..49ecca271 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -322,6 +322,15 @@ class Seal(object): def __eq__(self, other): return other == self._as_tuple() or other == bytes(self) + def __add__(self, other): + return bytes(self) + other + + def __radd__(self, other): + return other + bytes(self) + + def __len__(self): + return len(bytes(self)) + class StrangerSeal(Seal): """ From 3dda083b7152d031e55c0b5b61a682255eede8d6 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:39:47 -0800 Subject: [PATCH 07/14] New constants for use with BytestringSplitter. --- nkms/crypto/constants.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nkms/crypto/constants.py b/nkms/crypto/constants.py index dffcfa393..e27c616cc 100644 --- a/nkms/crypto/constants.py +++ b/nkms/crypto/constants.py @@ -1,2 +1,6 @@ +# TODO: Turn these into classes? +PUBKEY_SIG_LENGTH = 64 +HASH_DIGEST_LENGTH = 32 + NOT_SIGNED = 445 NO_DECRYPTION_PERFORMED = 455 From ee3170de329befdbe1858ca392c4cfad95bdbe40 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:43:06 -0800 Subject: [PATCH 08/14] Imlement HRAC in protocols. --- nkms/network/protocols.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nkms/network/protocols.py b/nkms/network/protocols.py index a67a4858b..855c61380 100644 --- a/nkms/network/protocols.py +++ b/nkms/network/protocols.py @@ -40,13 +40,13 @@ class NuCypherHashProtocol(KademliaProtocol): else: return NODE_HAS_NO_STORAGE, False - def determine_legality_of_dht_key(self, signature, sender_pubkey_sig, message, extra_info, dht_key, dht_value): - proper_key = digest(keccak_digest(bytes(sender_pubkey_sig) + bytes(extra_info))) + def determine_legality_of_dht_key(self, signature, sender_pubkey_sig, message, hrac, dht_key, dht_value): + proper_key = digest(keccak_digest(bytes(sender_pubkey_sig) + bytes(hrac))) # TODO: This try block is not the right approach - a Ciphertext class can resolve this instead. try: # Ursula uaddr scenario - verified = signature.verify(message, sender_pubkey_sig) + verified = signature.verify(hrac, sender_pubkey_sig) except Exception as e: # trmap scenario verified = signature.verify(msgpack.dumps(message), sender_pubkey_sig) From 49015c468279b76db8cc94936668f022bcf6acc9 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:43:33 -0800 Subject: [PATCH 09/14] dht_value_splitter for reuse. --- nkms/network/protocols.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nkms/network/protocols.py b/nkms/network/protocols.py index 855c61380..b9bba811e 100644 --- a/nkms/network/protocols.py +++ b/nkms/network/protocols.py @@ -3,13 +3,17 @@ import msgpack from kademlia.node import Node from kademlia.protocol import KademliaProtocol from kademlia.utils import digest -from nkms.crypto import utils from nkms.crypto.api import keccak_digest +from nkms.crypto.constants import PUBKEY_SIG_LENGTH, HASH_DIGEST_LENGTH from nkms.crypto.signature import Signature +from nkms.crypto.utils import BytestringSplitter from nkms.network.constants import NODE_HAS_NO_STORAGE from nkms.network.node import NuCypherNode from nkms.network.routing import NuCypherRoutingTable +dht_value_splitter = BytestringSplitter(Signature, (bytes, PUBKEY_SIG_LENGTH), (bytes, HASH_DIGEST_LENGTH), + return_remainder=True) + class NuCypherHashProtocol(KademliaProtocol): def __init__(self, sourceNode, storage, ksize, *args, **kwargs): @@ -64,11 +68,11 @@ class NuCypherHashProtocol(KademliaProtocol): self.log.debug("got a store request from %s" % str(sender)) if value.startswith(b"uaddr") or value.startswith(b"trmap"): - sig_bytes, sender_pubkey_sig, extra_info, message = msgpack.loads(value[5::]) # TODO: #114 - signature = Signature(sig_bytes) + signature, sender_pubkey_sig, hrac, message = dht_value_splitter(value[5::]) + # extra_info is a hash of the policy_group.id in the case of a treasure map, or a TTL in the case # of an Ursula interface. TODO: Decide whether to keep this notion and, if so, use the TTL. - do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig, message, extra_info, key, value) + do_store = self.determine_legality_of_dht_key(signature, sender_pubkey_sig, message, hrac, key, value) else: self.log.info("Got request to store bad k/v: {} / {}".format(key, value)) do_store = False From f19a581602cfccdd9dd6dce5a71b5f2115f39a9f Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:43:55 -0800 Subject: [PATCH 10/14] Raise error if Signature is wrong length. --- nkms/crypto/signature.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nkms/crypto/signature.py b/nkms/crypto/signature.py index 4c429ca4b..9cb417ee6 100644 --- a/nkms/crypto/signature.py +++ b/nkms/crypto/signature.py @@ -23,6 +23,8 @@ class Signature(bytes): raise ValueError("Pass either sig_as_bytes or v, r, and s.") if sig_as_bytes: + if not len(sig_as_bytes) == self._EXPECTED_LENGTH: + raise ValueError("{} must be {} bytes.".format(self.__class__.__name__, self._EXPECTED_LENGTH)) v, r, s = API.ecdsa_load_sig(sig_as_bytes) self._v = v From d23f10bfe7d678772dd378ba78449923e4a930aa Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:45:33 -0800 Subject: [PATCH 11/14] Using HRAC and dht_value_splitter in tests. --- tests/network/test_network_actors.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/network/test_network_actors.py b/tests/network/test_network_actors.py index 81de9735f..20557ba5d 100644 --- a/tests/network/test_network_actors.py +++ b/tests/network/test_network_actors.py @@ -6,7 +6,11 @@ import pytest from kademlia.utils import digest from nkms.characters import Ursula, Alice, Character, Bob, congregate +from nkms.crypto.constants import PUBKEY_SIG_LENGTH, HASH_DIGEST_LENGTH +from nkms.crypto.signature import Signature +from nkms.crypto.utils import BytestringSplitter from nkms.network.blockchain_client import list_all_ursulas +from nkms.network.protocols import dht_value_splitter from nkms.policy.constants import NON_PAYMENT from nkms.policy.models import PolicyManagerForAlice, PolicyOffer, Policy from tests.test_utilities import make_fake_ursulas, MockNetworkyStuff @@ -57,9 +61,7 @@ def test_vladimir_illegal_interface_key_does_not_propagate(): assert ursula.server.protocol.illegal_keys_seen == [] # Vladimir does almost everything right.... - interface_info = msgpack.dumps((vladimir.port, vladimir.interface)) - signature = vladimir.seal(interface_info) - value = b"uaddr" + msgpack.dumps([bytes(signature), bytes(vladimir.seal), 0, interface_info]) # TODO: #114 + value = vladimir.interface_dht_value() # Except he sets an illegal key for his interface. illegal_key = "Not allowed to set arbitrary key for this." @@ -111,8 +113,8 @@ def test_alice_finds_ursula(): getter = ALICE.server.get(all_ursulas[ursula_index]) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - signature, ursula_pubkey_sig, ttl, interface_info = msgpack.loads(value.lstrip(b"uaddr-")) - port, interface = msgpack.loads(interface_info) + _signature, _ursula_pubkey_sig, _hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-")) + port = msgpack.loads(interface_info)[0] assert port == URSULA_PORT + ursula_index @@ -185,9 +187,11 @@ def test_treasure_map_stored_by_ursula_is_the_correct_one_for_bob(): """ The TreasureMap given by Alice to Ursula is the correct one for Bob; he can decrypt and read it. """ + treasure_map_as_set_on_network, signature, policy_group = test_alice_sets_treasure_map_on_network() - _signature_for_ursula, pubkey_sig_alice, uri_hash, encrypted_treasure_map = msgpack.loads( + _signature_for_ursula, pubkey_sig_alice, hrac, packed_encrypted_treasure_map = dht_value_splitter( treasure_map_as_set_on_network[5::]) # 5:: to account for prepended "trmap" + encrypted_treasure_map = msgpack.loads(packed_encrypted_treasure_map) # TOOD: Make this a Ciphertext #112 verified, treasure_map_as_decrypted_by_bob = BOB.verify_from(ALICE, signature, encrypted_treasure_map, decrypt=True, @@ -222,6 +226,6 @@ def test_treaure_map_is_legit(): getter = ALICE.server.get(ursula_interface_id) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - signature, ursula_pubkey_sig, ttl, interface_info = msgpack.loads(value.lstrip(b"uaddr-")) - port, _interface = msgpack.loads(interface_info) + signature, ursula_pubkey_sig, hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-")) + port = msgpack.loads(interface_info)[0] assert port in URSULA_PORTS From 72ffa361a4756a9e0b2163baa02b8b929f8d5d09 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:46:00 -0800 Subject: [PATCH 12/14] BytestringSplitter is now allowed to give back the remainder. --- nkms/crypto/utils.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/nkms/crypto/utils.py b/nkms/crypto/utils.py index 8d001e755..9e8189d54 100644 --- a/nkms/crypto/utils.py +++ b/nkms/crypto/utils.py @@ -1,29 +1,44 @@ +import msgpack class BytestringSplitter(object): - - def __init__(self, *message_types): + def __init__(self, *message_types, return_remainder=False, msgpack_remainder=False): """ :param message_types: A collection of types of messages to parse. """ self.message_types = message_types + self.return_remainder = return_remainder + self.msgpack_remainder = msgpack_remainder def __call__(self, splittable): - if len(self) != len(splittable): - raise ValueError("Wrong number of bytes to constitute message types {} - need {}, got {}".format(self.message_types, len(self), len(splittable))) - + if not any((self.return_remainder, self.msgpack_remainder)) and len(self) != len(splittable): + raise ValueError( + "Wrong number of bytes to constitute message types {} - need {}, got {} \n Did you mean to return the remainder?".format( + self.message_types, len(self), len(splittable))) + if len(self) > len(splittable): + raise ValueError( + "Not enough bytes to constitute message types {} - need {}, got {}".format(self.message_types, + len(self), + len(splittable))) cursor = 0 message_objects = [] for message_type in self.message_types: message_class, message_length = self.get_message_meta(message_type) expected_end_of_object_bytes = cursor + message_length - bytes_for_this_object = splittable[cursor:cursor + expected_end_of_object_bytes] + bytes_for_this_object = splittable[cursor:expected_end_of_object_bytes] message = message_class(bytes_for_this_object) message_objects.append(message) cursor = expected_end_of_object_bytes + remainder = splittable[cursor:] + + if self.msgpack_remainder: + message_objects.append(msgpack.loads(remainder)) + elif self.return_remainder: + message_objects.append(remainder) + return message_objects def __len__(self): @@ -32,4 +47,3 @@ class BytestringSplitter(object): @staticmethod def get_message_meta(message_type): return message_type if isinstance(message_type, tuple) else (message_type, message_type._EXPECTED_LENGTH) - From f643e0cd2b9d0ab5b52d6d7a8db2b682dec92d53 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 17:46:27 -0800 Subject: [PATCH 13/14] Using HRAC and dht_value_splitter for characters. --- nkms/characters.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index 49ecca271..0f13e8554 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -1,17 +1,17 @@ import asyncio import msgpack + from kademlia.network import Server from kademlia.utils import digest - from nkms.crypto import api as API from nkms.crypto.api import secure_random, keccak_digest from nkms.crypto.constants import NOT_SIGNED, NO_DECRYPTION_PERFORMED from nkms.crypto.powers import CryptoPower, SigningPower, EncryptingPower - from nkms.keystore.keypairs import Keypair from nkms.network import blockchain_client from nkms.network.blockchain_client import list_all_ursulas +from nkms.network.protocols import dht_value_splitter from nkms.network.server import NuCypherDHTServer, NuCypherSeedOnlyDHTServer from nkms.policy.constants import NOT_FROM_ALICE @@ -181,14 +181,12 @@ class Alice(Character): def publish_treasure_map(self, policy_group): encrypted_treasure_map, signature_for_bob = self.encrypt_for(policy_group.bob, policy_group.treasure_map.packed_payload()) - signature_for_ursula = self.seal( - msgpack.dumps(encrypted_treasure_map)) # TODO: Great use-case for Ciphertext class + signature_for_ursula = self.seal(policy_group.hrac()) # TODO: Great use-case for Ciphertext class # In order to know this is safe to propagate, Ursula needs to see a signature, our public key, # and, reasons explained in treasure_map_dht_key above, the uri_hash. - dht_value = msgpack.dumps( - (bytes(signature_for_ursula), bytes(self.seal), policy_group.hrac(), - encrypted_treasure_map)) # TODO: #114 + dht_value = signature_for_ursula + self.seal + policy_group.hrac() + msgpack.dumps( + encrypted_treasure_map) # TODO: Ideally, this is a Ciphertext object instead of msgpack (see #112) dht_key = policy_group.treasure_map_dht_key() setter = self.server.set(dht_key, b"trmap" + dht_value) @@ -223,9 +221,9 @@ class Bob(Character): getter = self.server.get(ursula_interface_id) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - signature, ursula_pubkey_sig, ttl, interface_info = msgpack.loads( + signature, ursula_pubkey_sig, hrac, interface_info = dht_value_splitter( value.lstrip(b"uaddr")) # TODO: If we're going to implement TTL, it'll be here. - port, interface = msgpack.loads(interface_info) + port, interface, ttl = msgpack.loads(interface_info) self._ursulas[ursula_interface_id] = Ursula.as_discovered_on_network(port=port, interface=interface, pubkey_sig_bytes=ursula_pubkey_sig) @@ -236,8 +234,9 @@ class Bob(Character): ursula_coro = self.server.get(dht_key) event_loop = asyncio.get_event_loop() packed_encrypted_treasure_map = event_loop.run_until_complete(ursula_coro) - _signature_for_ursula, pubkey_sig_alice, uri_hash, encrypted_treasure_map = msgpack.loads( + _signature_for_ursula, pubkey_sig_alice, hrac, packed_encrypted_treasure_map = dht_value_splitter( packed_encrypted_treasure_map[5::]) + encrypted_treasure_map = msgpack.loads(packed_encrypted_treasure_map) verified, packed_node_list = self.verify_from(self.alice, signature, encrypted_treasure_map, signature_is_on_cleartext=True, decrypt=True) if not verified: @@ -276,16 +275,17 @@ class Ursula(Character): return self.server.listen(port, interface) def interface_info(self): - return msgpack.dumps((self.port, self.interface)) + return self.port, self.interface, self.interface_ttl def interface_dht_key(self): - return keccak_digest(bytes(self.seal) + bytes(self.interface_ttl)) + return self.hash(self.seal + self.interface_hrac()) def interface_dht_value(self): - signature = self.seal(self.interface_info()) - ttl = 0 # TODO: We don't actually need this - and it's not currently implemented in a meaningful way, - # but it matches the schema for a shared TreasureMap. Maybe we use it to indicate a TTL? - return b"uaddr" + msgpack.dumps((bytes(signature), bytes(self.seal), ttl, self.interface_info())) + signature = self.seal(self.interface_hrac()) + return b"uaddr" + signature + self.seal + self.interface_hrac() + msgpack.dumps(self.interface_info()) + + def interface_hrac(self): + return self.hash(msgpack.dumps(self.interface_info())) def publish_interface_information(self): if not self.port and self.interface: From ec8c225f7d5f9c3ff6b4e5bf2e4bd12421f0d282 Mon Sep 17 00:00:00 2001 From: jMyles Date: Sat, 11 Nov 2017 18:00:32 -0800 Subject: [PATCH 14/14] Moved BytestringSplitter remainder logic to __call__; implemented throughout. --- nkms/characters.py | 12 ++++++------ nkms/crypto/utils.py | 12 +++++------- nkms/network/protocols.py | 5 ++--- tests/crypto/test_bytestring_types.py | 2 +- tests/network/test_network_actors.py | 10 +++++----- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index 0f13e8554..0b60beff5 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -221,9 +221,10 @@ class Bob(Character): getter = self.server.get(ursula_interface_id) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - signature, ursula_pubkey_sig, hrac, interface_info = dht_value_splitter( - value.lstrip(b"uaddr")) # TODO: If we're going to implement TTL, it'll be here. - port, interface, ttl = msgpack.loads(interface_info) + signature, ursula_pubkey_sig, hrac, (port, interface, ttl) = dht_value_splitter(value.lstrip(b"uaddr"), + msgpack_remainder=True) + + # TODO: If we're going to implement TTL, it will be here. self._ursulas[ursula_interface_id] = Ursula.as_discovered_on_network(port=port, interface=interface, pubkey_sig_bytes=ursula_pubkey_sig) @@ -234,9 +235,8 @@ class Bob(Character): ursula_coro = self.server.get(dht_key) event_loop = asyncio.get_event_loop() packed_encrypted_treasure_map = event_loop.run_until_complete(ursula_coro) - _signature_for_ursula, pubkey_sig_alice, hrac, packed_encrypted_treasure_map = dht_value_splitter( - packed_encrypted_treasure_map[5::]) - encrypted_treasure_map = msgpack.loads(packed_encrypted_treasure_map) + _signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map = dht_value_splitter( + packed_encrypted_treasure_map[5::], msgpack_remainder=True) verified, packed_node_list = self.verify_from(self.alice, signature, encrypted_treasure_map, signature_is_on_cleartext=True, decrypt=True) if not verified: diff --git a/nkms/crypto/utils.py b/nkms/crypto/utils.py index 9e8189d54..be62a94a8 100644 --- a/nkms/crypto/utils.py +++ b/nkms/crypto/utils.py @@ -2,16 +2,14 @@ import msgpack class BytestringSplitter(object): - def __init__(self, *message_types, return_remainder=False, msgpack_remainder=False): + def __init__(self, *message_types): """ :param message_types: A collection of types of messages to parse. """ self.message_types = message_types - self.return_remainder = return_remainder - self.msgpack_remainder = msgpack_remainder - def __call__(self, splittable): - if not any((self.return_remainder, self.msgpack_remainder)) and len(self) != len(splittable): + def __call__(self, splittable, return_remainder=False, msgpack_remainder=False): + if not any((return_remainder, msgpack_remainder)) and len(self) != len(splittable): raise ValueError( "Wrong number of bytes to constitute message types {} - need {}, got {} \n Did you mean to return the remainder?".format( self.message_types, len(self), len(splittable))) @@ -34,9 +32,9 @@ class BytestringSplitter(object): remainder = splittable[cursor:] - if self.msgpack_remainder: + if msgpack_remainder: message_objects.append(msgpack.loads(remainder)) - elif self.return_remainder: + elif return_remainder: message_objects.append(remainder) return message_objects diff --git a/nkms/network/protocols.py b/nkms/network/protocols.py index b9bba811e..715721a65 100644 --- a/nkms/network/protocols.py +++ b/nkms/network/protocols.py @@ -11,8 +11,7 @@ from nkms.network.constants import NODE_HAS_NO_STORAGE from nkms.network.node import NuCypherNode from nkms.network.routing import NuCypherRoutingTable -dht_value_splitter = BytestringSplitter(Signature, (bytes, PUBKEY_SIG_LENGTH), (bytes, HASH_DIGEST_LENGTH), - return_remainder=True) +dht_value_splitter = BytestringSplitter(Signature, (bytes, PUBKEY_SIG_LENGTH), (bytes, HASH_DIGEST_LENGTH)) class NuCypherHashProtocol(KademliaProtocol): @@ -68,7 +67,7 @@ class NuCypherHashProtocol(KademliaProtocol): self.log.debug("got a store request from %s" % str(sender)) if value.startswith(b"uaddr") or value.startswith(b"trmap"): - signature, sender_pubkey_sig, hrac, message = dht_value_splitter(value[5::]) + signature, sender_pubkey_sig, hrac, message = dht_value_splitter(value[5::], return_remainder=True) # extra_info is a hash of the policy_group.id in the case of a treasure map, or a TTL in the case # of an Ursula interface. TODO: Decide whether to keep this notion and, if so, use the TTL. diff --git a/tests/crypto/test_bytestring_types.py b/tests/crypto/test_bytestring_types.py index 0ea61f66c..45452e1fd 100644 --- a/tests/crypto/test_bytestring_types.py +++ b/tests/crypto/test_bytestring_types.py @@ -34,4 +34,4 @@ def test_trying_to_extract_too_many_bytes_raises_typeerror(): splitter = BytestringSplitter(Signature, (bytes, too_many_bytes)) with pytest.raises(ValueError): - rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes) + rebuilt_signature, rebuilt_bytes = splitter(signature + some_bytes, return_remainder=True) diff --git a/tests/network/test_network_actors.py b/tests/network/test_network_actors.py index 20557ba5d..60573b22d 100644 --- a/tests/network/test_network_actors.py +++ b/tests/network/test_network_actors.py @@ -113,7 +113,7 @@ def test_alice_finds_ursula(): getter = ALICE.server.get(all_ursulas[ursula_index]) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - _signature, _ursula_pubkey_sig, _hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-")) + _signature, _ursula_pubkey_sig, _hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-"), return_remainder=True) port = msgpack.loads(interface_info)[0] assert port == URSULA_PORT + ursula_index @@ -189,9 +189,9 @@ def test_treasure_map_stored_by_ursula_is_the_correct_one_for_bob(): """ treasure_map_as_set_on_network, signature, policy_group = test_alice_sets_treasure_map_on_network() - _signature_for_ursula, pubkey_sig_alice, hrac, packed_encrypted_treasure_map = dht_value_splitter( - treasure_map_as_set_on_network[5::]) # 5:: to account for prepended "trmap" - encrypted_treasure_map = msgpack.loads(packed_encrypted_treasure_map) # TOOD: Make this a Ciphertext #112 + _signature_for_ursula, pubkey_sig_alice, hrac, encrypted_treasure_map = dht_value_splitter( + treasure_map_as_set_on_network[5::], msgpack_remainder=True) # 5:: to account for prepended "trmap" + verified, treasure_map_as_decrypted_by_bob = BOB.verify_from(ALICE, signature, encrypted_treasure_map, decrypt=True, @@ -226,6 +226,6 @@ def test_treaure_map_is_legit(): getter = ALICE.server.get(ursula_interface_id) loop = asyncio.get_event_loop() value = loop.run_until_complete(getter) - signature, ursula_pubkey_sig, hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-")) + signature, ursula_pubkey_sig, hrac, interface_info = dht_value_splitter(value.lstrip(b"uaddr-"), return_remainder=True) port = msgpack.loads(interface_info)[0] assert port in URSULA_PORTS