From 44438e6a501b5079ad40ee2a57031d2cb75865e4 Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:29:21 -0800 Subject: [PATCH 1/9] Moving publish_treasure_map down to PolicyGroup. --- nkms/characters.py | 14 -------------- nkms/policy/models.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index 115edcb2a..6562e42f7 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -212,20 +212,6 @@ class Alice(Character): alice_privkey, bytes(bob.seal.without_metabytes()), m, n) return (kfrags, eph_key_data) - 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(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 = 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) - return setter, encrypted_treasure_map, dht_value, signature_for_bob, signature_for_ursula - class Bob(Character): _server_class = NuCypherSeedOnlyDHTServer diff --git a/nkms/policy/models.py b/nkms/policy/models.py index 7474386a8..80358de52 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -149,6 +149,22 @@ class PolicyGroup(object): self._id = api.keccak_digest(bytes(self.alice.seal), api.keccak_digest(self.uri)) return self._id + def publish_treasure_map(self): + encrypted_treasure_map, signature_for_bob = self.alice.encrypt_for(self.bob, + self.treasure_map.packed_payload()) + signature_for_ursula = self.alice.seal(self.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 = signature_for_ursula + self.alice.seal + self.hrac() + msgpack.dumps( + encrypted_treasure_map) # TODO: Ideally, this is a Ciphertext object instead of msgpack (see #112) + dht_key = self.treasure_map_dht_key() + + setter = self.alice.server.set(dht_key, b"trmap" + dht_value) + event_loop = asyncio.get_event_loop() + event_loop.run_until_complete(setter) + return encrypted_treasure_map, dht_value, signature_for_bob, signature_for_ursula + class Policy(object): """ From af969c3ff7f94f54715c3b11542bf14859dd2ddd Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:30:08 -0800 Subject: [PATCH 2/9] Bob now gets a TreasureMap from just an Alice and URI (not a PolicyGroup). --- nkms/characters.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nkms/characters.py b/nkms/characters.py index 6562e42f7..d60fc104f 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -249,8 +249,9 @@ class Bob(Character): self._ursulas[ursula_interface_id] = Ursula.as_discovered_on_network(port=port, interface=interface, pubkey_sig_bytes=ursula_pubkey_sig) - def get_treasure_map(self, policy_group): + def get_treasure_map(self, alice, uri): + policy_group = self.reconstitue_policy_group(alice, uri) dht_key = policy_group.treasure_map_dht_key() ursula_coro = self.server.get(dht_key) @@ -295,6 +296,11 @@ class Bob(Character): def get_ursula(self, ursula_id): return self._ursulas[ursula_id] + def reconstitue_policy_group(self, alice, uri): + from nkms.policy.models import PolicyGroup + return PolicyGroup(alice=alice, uri=uri, bob=self) + + class Ursula(Character): _server_class = NuCypherDHTServer From 9541aa8519245ecbe9e267ecde7e966a05c10d26 Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:30:47 -0800 Subject: [PATCH 3/9] Bob can reconstitute a PolicyGroup before he has the PFrag; nothing wrong with that. --- nkms/characters.py | 3 ++- nkms/policy/models.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nkms/characters.py b/nkms/characters.py index d60fc104f..8d9bae9ed 100644 --- a/nkms/characters.py +++ b/nkms/characters.py @@ -336,7 +336,8 @@ class Ursula(Character): *args, **kwargs): if not id: - id = digest(secure_random(32)) # TODO: Network-wide deterministic ID generation (ie, auction or whatever) #136. + id = digest( + secure_random(32)) # TODO: Network-wide deterministic ID generation (ie, auction or whatever) #136. super().attach_server(ksize, alpha, id, storage) diff --git a/nkms/policy/models.py b/nkms/policy/models.py index 80358de52..8db91229a 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -1,6 +1,7 @@ import binascii import msgpack +import asyncio from nkms.characters import Alice, Bob, Ursula from nkms.crypto import api @@ -79,7 +80,7 @@ class PolicyGroup(object): _id = None - def __init__(self, uri: bytes, alice: Alice, bob: Bob, pfrag, policies=None) -> None: + def __init__(self, uri: bytes, alice: Alice, bob: Bob, pfrag=None, policies=None) -> None: self.policies = policies or [] self.alice = alice self.bob = bob From 6554df9c03b560c02d468d2ea8adb8e4d274b2bb Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:31:15 -0800 Subject: [PATCH 4/9] hash convenience method can just be static. --- nkms/policy/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nkms/policy/models.py b/nkms/policy/models.py index 8db91229a..55724d8ef 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -92,7 +92,8 @@ class PolicyGroup(object): def n(self): return len(self.policies) - def hash(self, message): + @staticmethod + def hash(message): return keccak_digest(message) def find_n_ursulas(self, networky_stuff, offer: PolicyOffer): From 38e692a526b2575bfc714c040663d53db8a4b4cd Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:31:31 -0800 Subject: [PATCH 5/9] Making it easier to get an hrac from outside PolicyGroup. --- nkms/policy/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nkms/policy/models.py b/nkms/policy/models.py index 55724d8ef..d4f3578ff 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -110,6 +110,14 @@ class PolicyGroup(object): pass # Tell Alice to either wait or lower the value of n. def hrac(self): + """ + A convenience method for generating an hrac for this instance. + """ + return self.hrac_for(self.alice, self.bob, self.uri) + + @staticmethod + def hrac_for(alice, bob, uri): + """ The "hashed resource authentication code". @@ -121,7 +129,7 @@ class PolicyGroup(object): Alice and Bob have all the information they need to construct this. Ursula does not, so we share it with her. """ - return self.hash(bytes(self.alice.seal) + bytes(self.bob.seal) + self.uri) + return PolicyGroup.hash(bytes(alice.seal) + bytes(bob.seal) + uri) def treasure_map_dht_key(self): """ From ff19d7f27676b5debe5325b0a4ebd49dabd9837a Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:32:10 -0800 Subject: [PATCH 6/9] Payload has always incorrectly used the encrypted TreasureMap instead of ChallengePack - we didn't notice because we aren't doing challenges yet. --- nkms/policy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nkms/policy/models.py b/nkms/policy/models.py index d4f3578ff..d1adfe003 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -262,7 +262,7 @@ class Policy(object): return policy def payload(self): - return bytes(self.kfrag) + msgpack.dumps(self.encrypted_treasure_map) + return bytes(self.kfrag) + msgpack.dumps(self.encrypted_challenge_pack) def activate(self, ursula, negotiation_result): self.ursula = ursula From 267647a07ba31cf433c9a701a3f29cf8b3ee482a Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:32:44 -0800 Subject: [PATCH 7/9] Don't need TreasureMap setter anymore. --- nkms/policy/models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nkms/policy/models.py b/nkms/policy/models.py index d1adfe003..f24992e7d 100644 --- a/nkms/policy/models.py +++ b/nkms/policy/models.py @@ -277,10 +277,6 @@ class Policy(object): self._encrypted_challenge_pack = self.alice.encrypt_for(self.bob, msgpack.dumps(self.challenge_pack)) return self._encrypted_challenge_pack - @encrypted_challenge_pack.setter - def encrypted_treasure_map(self, ecp): - self._encrypted_challenge_pack = ecp - def generate_challenge_pack(self): if self.kfrag == UNKNOWN_KFRAG: # TODO: Test this branch From e891653fde1f86bf56964441ed6a7e18d65b0978 Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:33:01 -0800 Subject: [PATCH 8/9] With PolicyGroup logic simplified for Bob, we can reduce the scope of several fixtures. --- tests/fixtures.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 5cec892b0..057bed855 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -9,7 +9,7 @@ from tests.utilities import NUMBER_OF_URSULAS_IN_NETWORK, MockNetworkyStuff, mak URSULA_PORT, EVENT_LOOP -@pytest.fixture(scope="session") +@pytest.fixture() def alices_policy_group(alice, bob): """ Creates a PolicyGroup, in a manner typical of how Alice might do it, with a unique uri. @@ -27,7 +27,8 @@ def alices_policy_group(alice, bob): ) return policy_group -@pytest.fixture(scope="session") + +@pytest.fixture() def enacted_policy_group(alices_policy_group, ursulas): # Alice has a policy in mind and knows of enough qualifies Ursulas; she crafts an offer for them. deposit = NON_PAYMENT @@ -50,7 +51,7 @@ def alice(): return ALICE -@pytest.fixture(scope="session") +@pytest.fixture() def bob(alice, ursulas): BOB = Bob(alice=alice) BOB.attach_server() @@ -60,7 +61,7 @@ def bob(alice, ursulas): return BOB -@pytest.fixture(scope="session") +@pytest.fixture() def ursulas(): URSULAS = make_ursulas(NUMBER_OF_URSULAS_IN_NETWORK, URSULA_PORT) yield URSULAS From 21c18ea5f864af85e5bb77b3736ce7535754715a Mon Sep 17 00:00:00 2001 From: jMyles Date: Wed, 13 Dec 2017 10:33:34 -0800 Subject: [PATCH 9/9] Tests reflect publish_treasure_map living on PolicyGroup. --- tests/network/test_network_actors.py | 37 +++++++++++++++------------ tests/network/test_network_upgrade.py | 7 ++--- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/tests/network/test_network_actors.py b/tests/network/test_network_actors.py index a7f7a166f..668da12a4 100644 --- a/tests/network/test_network_actors.py +++ b/tests/network/test_network_actors.py @@ -112,9 +112,7 @@ def test_alice_sets_treasure_map_on_network(enacted_policy_group, ursulas): Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and sends it to Ursula via the DHT. """ alice = enacted_policy_group.alice - setter, encrypted_treasure_map, packed_encrypted_treasure_map, signature_for_bob, signature_for_ursula = alice.publish_treasure_map( - enacted_policy_group) - _set_event = EVENT_LOOP.run_until_complete(setter) + _, packed_encrypted_treasure_map, _, _ = enacted_policy_group.publish_treasure_map() treasure_map_as_set_on_network = ursulas[0].server.storage[ digest(enacted_policy_group.treasure_map_dht_key())] @@ -144,34 +142,39 @@ def test_treasure_map_stored_by_ursula_is_the_correct_one_for_bob(alice, bob, ur """ 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 = ursulas[0].server.storage[ - digest(enacted_policy_group.treasure_map_dht_key())] + # treasure_map_as_set_on_network = ursulas[0].server.storage[ + # digest(enacted_policy_group.treasure_map_dht_key())] - _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" + # _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" + + encrypted_treasure_map, _, _, _ = enacted_policy_group.publish_treasure_map() verified, cleartext = treasure_map_as_decrypted_by_bob = bob.verify_from(alice, - encrypted_treasure_map, - decrypt=True, - signature_is_on_cleartext=True, - ) - _alices_signature, treasure_map_as_decrypted_by_bob = BytestringSplitter(Signature)(cleartext, return_remainder=True) + encrypted_treasure_map, + decrypt=True, + signature_is_on_cleartext=True, + ) + _alices_signature, treasure_map_as_decrypted_by_bob = BytestringSplitter(Signature)(cleartext, + return_remainder=True) assert treasure_map_as_decrypted_by_bob == enacted_policy_group.treasure_map.packed_payload() assert verified is True -def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_policy_group, ursulas): +def test_bob_can_retreive_the_treasure_map_and_decrypt_it(bob, ursulas, enacted_policy_group): """ Above, we showed that the TreasureMap saved on the network is the correct one for Bob. Here, we show that Bob can retrieve it with only the information about which he is privy pursuant to the PolicyGroup. """ - bob = enacted_policy_group.bob networky_stuff = MockNetworkyStuff(ursulas) + encrypted_treasure_map, _, _, _ = enacted_policy_group.publish_treasure_map() - # Of course, in the real world, Bob has sufficient information to reconstitute a PolicyGroup, gleaned, we presume, - # through a side-channel with Alice. - treasure_map_from_wire = bob.get_treasure_map(enacted_policy_group) + # Let's imagine that Bob doesn't have access to the instantiated PolicyGroup. + alice, uri = enacted_policy_group.alice, enacted_policy_group.uri + + # However, he can still get the TreasureMap by knowing the alice and uri. + treasure_map_from_wire = bob.get_treasure_map(alice, uri) assert enacted_policy_group.treasure_map == treasure_map_from_wire diff --git a/tests/network/test_network_upgrade.py b/tests/network/test_network_upgrade.py index 49792ee5f..f700a4763 100644 --- a/tests/network/test_network_upgrade.py +++ b/tests/network/test_network_upgrade.py @@ -18,9 +18,7 @@ def test_bob_can_follow_treasure_map(enacted_policy_group, ursulas, alice, bob): """ assert len(bob._ursulas) == 0 - setter, encrypted_treasure_map, packed_encrypted_treasure_map, signature_for_bob, signature_for_ursula = alice.publish_treasure_map( - enacted_policy_group) - _set_event = EVENT_LOOP.run_until_complete(setter) + enacted_policy_group.publish_treasure_map() bob.follow_treasure_map(enacted_policy_group.treasure_map) assert len(bob._ursulas) == len(ursulas) @@ -36,8 +34,11 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_policy_group, a """ # We pick up our story with Bob already having followed the treasure map above, ie: + enacted_policy_group.publish_treasure_map() + bob.follow_treasure_map(enacted_policy_group.treasure_map) assert len(bob._ursulas) == len(ursulas) + # Now we assume Bob has received the pFrag through a side-channel: the_pfrag = enacted_policy_group.pfrag # We'll test against just a single Ursula - here, we made a WorkOrder for just one.