2018-02-19 23:19:43 +00:00
|
|
|
import pytest
|
2018-02-24 05:50:45 +00:00
|
|
|
|
2018-05-08 19:35:34 +00:00
|
|
|
from nucypher.crypto.powers import EncryptingPower
|
2018-02-24 05:50:45 +00:00
|
|
|
from umbral import pre
|
2018-02-14 01:31:35 +00:00
|
|
|
from umbral.fragments import KFrag
|
2018-05-08 19:35:34 +00:00
|
|
|
from nucypher.crypto.constants import CFRAG_LENGTH_WITHOUT_PROOF
|
2017-12-07 08:36:43 +00:00
|
|
|
|
|
|
|
|
2018-04-17 08:46:13 +00:00
|
|
|
def test_bob_cannot_follow_the_treasure_map_in_isolation(enacted_policy, bob):
|
2017-12-12 01:03:47 +00:00
|
|
|
|
2018-04-17 08:46:13 +00:00
|
|
|
# Assume for the moment that Bob has already received a TreasureMap, perhaps via a side channel.
|
2017-12-15 05:20:49 +00:00
|
|
|
hrac, treasure_map = enacted_policy.hrac(), enacted_policy.treasure_map
|
2017-12-12 01:03:47 +00:00
|
|
|
bob.treasure_maps[hrac] = treasure_map
|
|
|
|
|
|
|
|
# Bob knows of no Ursulas.
|
2018-02-25 23:37:14 +00:00
|
|
|
assert len(bob.known_nodes) == 0
|
2017-12-12 01:03:47 +00:00
|
|
|
|
2018-04-17 08:46:13 +00:00
|
|
|
# He can't successfully follow the TreasureMap until he learns of a node to ask.
|
|
|
|
with pytest.raises(bob.NotEnoughUrsulas):
|
|
|
|
bob.follow_treasure_map(hrac)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures("treasure_map_is_set_on_dht")
|
|
|
|
def test_bob_can_follow_treasure_map(enacted_policy, ursulas, bob, alice):
|
|
|
|
"""
|
|
|
|
Similar to above, but this time, we'll show that if Bob can connect to a single node, he can
|
|
|
|
learn enough to follow the TreasureMap.
|
|
|
|
|
|
|
|
Also, we'll get the TreasureMap from the hrac alone (ie, not via a side channel).
|
|
|
|
"""
|
|
|
|
hrac = enacted_policy.hrac()
|
|
|
|
|
|
|
|
# Bob knows of no Ursulas.
|
|
|
|
assert len(bob.known_nodes) == 0
|
|
|
|
|
|
|
|
# Now Bob will ask just a single Ursula to tell him of the nodes about which she's aware.
|
|
|
|
bob.network_bootstrap([("127.0.0.1", ursulas[0].rest_port)])
|
|
|
|
|
|
|
|
# Success! This Ursula knew about all the nodes on the network!
|
|
|
|
assert len(bob.known_nodes) == len(ursulas)
|
|
|
|
|
|
|
|
# Now, Bob can get the TreasureMap all by himself, and doesn't need a side channel.
|
2018-04-18 04:55:25 +00:00
|
|
|
bob.get_treasure_map(alice.stamp, hrac)
|
2018-04-17 08:46:13 +00:00
|
|
|
newly_discovered, total_known = bob.follow_treasure_map(hrac)
|
|
|
|
|
|
|
|
# He finds that he didn't need to discover any new nodes...
|
|
|
|
assert len(newly_discovered) == 0
|
|
|
|
|
|
|
|
# ...because he already knew of all the Ursulas on the map.
|
|
|
|
assert total_known == len(enacted_policy.treasure_map)
|
|
|
|
|
|
|
|
|
2018-04-17 09:44:10 +00:00
|
|
|
def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_policy, bob):
|
2018-04-17 08:46:13 +00:00
|
|
|
# Again, let's assume that he received the TreasureMap via a side channel.
|
|
|
|
hrac, treasure_map = enacted_policy.hrac(), enacted_policy.treasure_map
|
|
|
|
bob.treasure_maps[hrac] = treasure_map
|
|
|
|
|
|
|
|
# Now, let's create a scenario in which Bob knows of only one node.
|
|
|
|
k, v = bob.known_nodes.popitem()
|
|
|
|
bob.known_nodes = {k: v}
|
|
|
|
assert len(bob.known_nodes) == 1
|
|
|
|
|
|
|
|
# This time, when he follows the TreasureMap...
|
|
|
|
newly_discovered, total_known = bob.follow_treasure_map(hrac)
|
|
|
|
|
|
|
|
# The newly discovered nodes are all those in the TreasureMap except the one about which he already knew.
|
|
|
|
assert len(newly_discovered) == len(treasure_map) - 1
|
2017-12-12 01:03:47 +00:00
|
|
|
|
2018-04-17 08:46:13 +00:00
|
|
|
# ...and his total known now matches the length of the TreasureMap.
|
|
|
|
assert total_known == len(treasure_map)
|
2017-12-07 08:36:43 +00:00
|
|
|
|
|
|
|
|
2018-05-07 20:39:30 +00:00
|
|
|
def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_policy, bob,
|
|
|
|
alice, ursulas, capsule_side_channel):
|
2017-12-07 08:36:43 +00:00
|
|
|
"""
|
|
|
|
Now that Bob has his list of Ursulas, he can issue a WorkOrder to one. Upon receiving the WorkOrder, Ursula
|
|
|
|
saves it and responds by re-encrypting and giving Bob a cFrag.
|
|
|
|
|
|
|
|
This is a multipart test; it shows proper relations between the Characters Ursula and Bob and also proper
|
2018-02-19 01:34:38 +00:00
|
|
|
interchange between a KFrag, Capsule, and CFrag object in the context of REST-driven proxy re-encryption.
|
2017-12-07 08:36:43 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
# We pick up our story with Bob already having followed the treasure map above, ie:
|
2017-12-15 05:20:49 +00:00
|
|
|
hrac, treasure_map = enacted_policy.hrac(), enacted_policy.treasure_map
|
|
|
|
bob.treasure_maps[hrac] = treasure_map
|
|
|
|
bob.follow_treasure_map(hrac)
|
2018-02-25 23:37:14 +00:00
|
|
|
assert len(bob.known_nodes) == len(ursulas)
|
2017-12-07 08:36:43 +00:00
|
|
|
|
2017-12-15 05:20:49 +00:00
|
|
|
the_hrac = enacted_policy.hrac()
|
2017-12-07 08:36:43 +00:00
|
|
|
|
2017-12-08 04:41:48 +00:00
|
|
|
# Bob has no saved work orders yet, ever.
|
|
|
|
assert len(bob._saved_work_orders) == 0
|
|
|
|
|
|
|
|
# We'll test against just a single Ursula - here, we make a WorkOrder for just one.
|
2018-02-19 23:19:43 +00:00
|
|
|
# We can pass any number of capsules as args; here we pass just one.
|
2018-04-12 08:41:18 +00:00
|
|
|
work_orders = bob.generate_work_orders(the_hrac, capsule_side_channel[0].capsule, num_ursulas=1)
|
2018-02-19 23:19:43 +00:00
|
|
|
|
|
|
|
# Again: one Ursula, one work_order.
|
2017-12-07 08:36:43 +00:00
|
|
|
assert len(work_orders) == 1
|
|
|
|
|
2018-02-19 23:19:43 +00:00
|
|
|
# Bob saved the WorkOrder.
|
|
|
|
assert len(bob._saved_work_orders) == 1
|
|
|
|
# And the Ursula.
|
2018-02-19 23:16:50 +00:00
|
|
|
assert len(bob._saved_work_orders.ursulas) == 1
|
2017-12-08 04:41:48 +00:00
|
|
|
|
2017-12-07 08:36:43 +00:00
|
|
|
ursula_dht_key, work_order = list(work_orders.items())[0]
|
|
|
|
|
2017-12-08 04:41:48 +00:00
|
|
|
# **** RE-ENCRYPTION HAPPENS HERE! ****
|
2018-04-17 09:44:10 +00:00
|
|
|
cfrags = bob.get_reencrypted_c_frags(work_order)
|
2018-02-19 23:19:43 +00:00
|
|
|
|
|
|
|
# We only gave one Capsule, so we only got one cFrag.
|
|
|
|
assert len(cfrags) == 1
|
|
|
|
the_cfrag = cfrags[0]
|
|
|
|
|
|
|
|
# Attach the CFrag to the Capsule.
|
2018-05-07 20:39:30 +00:00
|
|
|
capsule = capsule_side_channel[0].capsule
|
|
|
|
capsule.attach_cfrag(the_cfrag)
|
2017-12-07 08:36:43 +00:00
|
|
|
|
2017-12-08 04:41:48 +00:00
|
|
|
# Having received the cFrag, Bob also saved the WorkOrder as complete.
|
2018-02-19 23:16:50 +00:00
|
|
|
assert len(bob._saved_work_orders) == 1
|
2017-12-08 04:41:48 +00:00
|
|
|
|
|
|
|
# OK, so cool - Bob has his cFrag! Let's make sure everything went properly. First, we'll show that it is in fact
|
2017-12-07 08:36:43 +00:00
|
|
|
# the correct cFrag (ie, that Ursula performed reencryption properly).
|
2018-05-07 20:39:30 +00:00
|
|
|
for u in ursulas: # ...and to do that, we need to address the right ursula.
|
2018-04-17 09:44:10 +00:00
|
|
|
if u.rest_port == work_order.ursula.rest_port:
|
|
|
|
ursula = u
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise RuntimeError("Somehow we don't know about this Ursula. Major malfunction.")
|
|
|
|
|
2018-04-02 04:16:51 +00:00
|
|
|
kfrag_bytes = ursula.datastore.get_policy_arrangement(
|
2018-02-14 01:31:35 +00:00
|
|
|
work_order.kfrag_hrac.hex().encode()).k_frag
|
|
|
|
the_kfrag = KFrag.from_bytes(kfrag_bytes)
|
2018-05-07 20:39:30 +00:00
|
|
|
the_correct_cfrag = pre.reencrypt(the_kfrag, capsule)
|
|
|
|
|
|
|
|
# The first CFRAG_LENGTH_WITHOUT_PROOF bytes (ie, the cfrag proper, not the proof material), are the same:
|
|
|
|
assert bytes(the_cfrag)[:CFRAG_LENGTH_WITHOUT_PROOF] == bytes(the_correct_cfrag)[:CFRAG_LENGTH_WITHOUT_PROOF] # It's the correct cfrag!
|
|
|
|
|
|
|
|
assert the_correct_cfrag.verify_correctness(capsule,
|
|
|
|
pubkey_a=enacted_policy.public_key,
|
|
|
|
pubkey_b=bob.public_key(EncryptingPower))
|
2017-12-07 08:36:43 +00:00
|
|
|
|
|
|
|
# Now we'll show that Ursula saved the correct WorkOrder.
|
|
|
|
work_orders_from_bob = ursula.work_orders(bob=bob)
|
|
|
|
assert len(work_orders_from_bob) == 1
|
|
|
|
assert work_orders_from_bob[0] == work_order
|
|
|
|
|
|
|
|
|
2018-04-17 09:44:10 +00:00
|
|
|
def test_bob_remembers_that_he_has_cfrags_for_a_particular_capsule(enacted_policy, bob,
|
2018-04-11 15:15:08 +00:00
|
|
|
ursulas, capsule_side_channel):
|
2018-02-19 23:20:26 +00:00
|
|
|
# In our last episode, Bob made a WorkOrder for the capsule...
|
2018-04-12 08:41:18 +00:00
|
|
|
assert len(bob._saved_work_orders.by_capsule(capsule_side_channel[0].capsule)) == 1
|
2018-02-19 23:20:26 +00:00
|
|
|
# ...and he used it to obtain a CFrag from Ursula.
|
2018-04-12 08:41:18 +00:00
|
|
|
assert len(capsule_side_channel[0].capsule._attached_cfrags) == 1
|
2017-12-07 08:36:43 +00:00
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
# He can also get a dict of {Ursula:WorkOrder} by looking them up from the capsule.
|
2018-04-12 08:41:18 +00:00
|
|
|
workorders_by_capsule = bob._saved_work_orders.by_capsule(capsule_side_channel[0].capsule)
|
2017-12-08 04:41:48 +00:00
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
# Bob has just one WorkOrder from that one Ursula.
|
|
|
|
assert len(workorders_by_capsule) == 1
|
|
|
|
saved_work_order = list(workorders_by_capsule.values())[0]
|
2017-12-08 04:41:48 +00:00
|
|
|
|
|
|
|
# The rest of this test will show that if Bob generates another WorkOrder, it's for a *different* Ursula.
|
2018-02-19 23:20:26 +00:00
|
|
|
generated_work_orders = bob.generate_work_orders(enacted_policy.hrac(),
|
2018-04-12 08:41:18 +00:00
|
|
|
capsule_side_channel[0].capsule,
|
2018-02-19 23:20:26 +00:00
|
|
|
num_ursulas=1)
|
|
|
|
id_of_this_new_ursula, new_work_order = list(generated_work_orders.items())[0]
|
2017-12-08 04:41:48 +00:00
|
|
|
|
|
|
|
# This new Ursula isn't the same one to whom we've already issued a WorkOrder.
|
2018-02-19 23:20:26 +00:00
|
|
|
id_of_ursula_from_whom_we_already_have_a_cfrag = list(workorders_by_capsule.keys())[0]
|
2017-12-08 04:41:48 +00:00
|
|
|
assert id_of_ursula_from_whom_we_already_have_a_cfrag != id_of_this_new_ursula
|
|
|
|
|
2018-02-14 01:31:53 +00:00
|
|
|
# ...and, although this WorkOrder has the same capsules as the saved one...
|
2018-02-19 23:20:26 +00:00
|
|
|
assert new_work_order.capsules == saved_work_order.capsules
|
2017-12-08 04:41:48 +00:00
|
|
|
|
|
|
|
# ...it's not the same WorkOrder.
|
2018-02-19 23:20:26 +00:00
|
|
|
assert new_work_order != saved_work_order
|
2017-12-20 03:03:40 +00:00
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
# We can get a new CFrag, just like last time.
|
2018-04-17 09:44:10 +00:00
|
|
|
cfrags = bob.get_reencrypted_c_frags(new_work_order)
|
2018-02-19 23:20:26 +00:00
|
|
|
|
|
|
|
# Again: one Capsule, one cFrag.
|
|
|
|
assert len(cfrags) == 1
|
|
|
|
new_cfrag = cfrags[0]
|
|
|
|
|
|
|
|
# Attach the CFrag to the Capsule.
|
2018-04-12 08:41:18 +00:00
|
|
|
capsule_side_channel[0].capsule.attach_cfrag(new_cfrag)
|
2017-12-20 03:03:40 +00:00
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
|
2018-04-12 04:07:38 +00:00
|
|
|
def test_bob_gathers_and_combines(enacted_policy, bob, ursulas, capsule_side_channel):
|
2018-04-12 08:41:18 +00:00
|
|
|
# The side channel is represented as a single MessageKit, which is all that Bob really needs.
|
|
|
|
the_message_kit, the_data_source = capsule_side_channel
|
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
# Bob has saved two WorkOrders so far.
|
|
|
|
assert len(bob._saved_work_orders) == 2
|
2017-12-21 01:21:00 +00:00
|
|
|
|
|
|
|
# ...but the policy requires us to collect more cfrags.
|
2018-04-17 08:46:13 +00:00
|
|
|
assert len(bob._saved_work_orders) < enacted_policy.treasure_map.m
|
2017-12-21 01:21:00 +00:00
|
|
|
|
2018-02-19 23:20:26 +00:00
|
|
|
# Bob can't decrypt yet with just two CFrags. He needs to gather at least m.
|
2018-02-24 05:50:45 +00:00
|
|
|
with pytest.raises(pre.GenericUmbralError):
|
2018-04-12 08:41:18 +00:00
|
|
|
bob.decrypt(the_message_kit)
|
2018-02-19 23:20:26 +00:00
|
|
|
|
2018-04-17 08:46:13 +00:00
|
|
|
number_left_to_collect = enacted_policy.treasure_map.m - len(bob._saved_work_orders)
|
2018-02-19 23:20:26 +00:00
|
|
|
|
|
|
|
new_work_orders = bob.generate_work_orders(enacted_policy.hrac(),
|
2018-04-12 08:41:18 +00:00
|
|
|
the_message_kit.capsule,
|
2018-02-19 23:20:26 +00:00
|
|
|
num_ursulas=number_left_to_collect)
|
|
|
|
_id_of_yet_another_ursula, new_work_order = list(new_work_orders.items())[0]
|
|
|
|
|
2018-04-17 09:44:10 +00:00
|
|
|
cfrags = bob.get_reencrypted_c_frags(new_work_order)
|
2018-04-12 08:41:18 +00:00
|
|
|
the_message_kit.capsule.attach_cfrag(cfrags[0])
|
2018-02-19 23:20:26 +00:00
|
|
|
|
|
|
|
# Now.
|
|
|
|
# At long last.
|
2018-04-12 08:41:18 +00:00
|
|
|
is_valid, cleartext = bob.verify_from(the_data_source, the_message_kit, decrypt=True)
|
|
|
|
assert cleartext == b'Welcome to the flippering.'
|
|
|
|
assert is_valid
|