mirror of https://github.com/nucypher/nucypher.git
234 lines
10 KiB
Python
234 lines
10 KiB
Python
"""
|
|
This file is part of nucypher.
|
|
|
|
nucypher is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
nucypher is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
|
|
import pytest
|
|
from binascii import unhexlify
|
|
from hendrix.experience import crosstown_traffic
|
|
from hendrix.utils.test_utils import crosstownTaskListDecoratorFactory
|
|
|
|
from nucypher.characters.lawful import Ursula
|
|
from nucypher.characters.unlawful import Vladimir
|
|
from nucypher.crypto.api import keccak_digest
|
|
from nucypher.crypto.powers import SigningPower
|
|
from nucypher.network.nicknames import nickname_from_seed
|
|
from nucypher.network.nodes import FleetStateTracker
|
|
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD
|
|
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
|
|
|
|
|
@pytest.mark.slow()
|
|
def test_all_blockchain_ursulas_know_about_all_other_ursulas(blockchain_ursulas, agency):
|
|
"""
|
|
Once launched, all Ursulas know about - and can help locate - all other Ursulas in the network.
|
|
"""
|
|
token_agent, staking_agent, policy_agent = agency
|
|
for address in staking_agent.swarm():
|
|
for propagating_ursula in blockchain_ursulas[:1]: # Last Ursula is not staking
|
|
if address == propagating_ursula.checksum_address:
|
|
continue
|
|
else:
|
|
assert address in propagating_ursula.known_nodes.addresses(), "{} did not know about {}".\
|
|
format(propagating_ursula, nickname_from_seed(address))
|
|
|
|
|
|
@pytest.mark.slow()
|
|
def test_blockchain_alice_finds_ursula_via_rest(blockchain_alice, blockchain_ursulas):
|
|
|
|
# Imagine alice knows of nobody.
|
|
blockchain_alice._Learner__known_nodes = FleetStateTracker()
|
|
|
|
blockchain_alice.remember_node(blockchain_ursulas[0])
|
|
blockchain_alice.learn_from_teacher_node()
|
|
assert len(blockchain_alice.known_nodes) == len(blockchain_ursulas)
|
|
|
|
for ursula in blockchain_ursulas:
|
|
assert ursula in blockchain_alice.known_nodes
|
|
|
|
|
|
def test_alice_creates_policy_with_correct_hrac(idle_federated_policy):
|
|
"""
|
|
Alice creates a Policy. It has the proper HRAC, unique per her, Bob, and the label
|
|
"""
|
|
alice = idle_federated_policy.alice
|
|
bob = idle_federated_policy.bob
|
|
|
|
assert idle_federated_policy.hrac() == keccak_digest(bytes(alice.stamp)
|
|
+ bytes(bob.stamp)
|
|
+ idle_federated_policy.label)
|
|
|
|
|
|
def test_alice_sets_treasure_map(enacted_federated_policy, federated_ursulas):
|
|
"""
|
|
Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and ...... TODO
|
|
"""
|
|
enacted_federated_policy.publish_treasure_map(network_middleware=MockRestMiddleware())
|
|
treasure_map_index = bytes.fromhex(enacted_federated_policy.treasure_map.public_id())
|
|
treasure_map_as_set_on_network = list(federated_ursulas)[0].treasure_maps[treasure_map_index]
|
|
assert treasure_map_as_set_on_network == enacted_federated_policy.treasure_map
|
|
|
|
|
|
def test_treasure_map_stored_by_ursula_is_the_correct_one_for_bob(federated_alice, federated_bob, federated_ursulas,
|
|
enacted_federated_policy):
|
|
"""
|
|
The TreasureMap given by Alice to Ursula is the correct one for Bob; he can decrypt and read it.
|
|
"""
|
|
|
|
treasure_map_index = bytes.fromhex(enacted_federated_policy.treasure_map.public_id())
|
|
treasure_map_as_set_on_network = list(federated_ursulas)[0].treasure_maps[treasure_map_index]
|
|
|
|
hrac_by_bob = federated_bob.construct_policy_hrac(federated_alice.stamp, enacted_federated_policy.label)
|
|
assert enacted_federated_policy.hrac() == hrac_by_bob
|
|
|
|
hrac, map_id_by_bob = federated_bob.construct_hrac_and_map_id(federated_alice.stamp, enacted_federated_policy.label)
|
|
assert map_id_by_bob == treasure_map_as_set_on_network.public_id()
|
|
|
|
|
|
def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_federated_policy, federated_ursulas):
|
|
"""
|
|
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_federated_policy.bob
|
|
|
|
# Of course, in the real world, Bob has sufficient information to reconstitute a PolicyGroup, gleaned, we presume,
|
|
# through a side-channel with Alice.
|
|
|
|
# If Bob doesn't know about any Ursulas, he can't find the TreasureMap via the REST swarm:
|
|
with pytest.raises(bob.NotEnoughTeachers):
|
|
treasure_map_from_wire = bob.get_treasure_map(enacted_federated_policy.alice.stamp,
|
|
enacted_federated_policy.label)
|
|
|
|
# Bob finds out about one Ursula (in the real world, a seed node)
|
|
bob.remember_node(list(federated_ursulas)[0])
|
|
|
|
# ...and then learns about the rest of the network.
|
|
bob.learn_from_teacher_node(eager=True)
|
|
|
|
# Now he'll have better success finding that map.
|
|
treasure_map_from_wire = bob.get_treasure_map(enacted_federated_policy.alice.stamp,
|
|
enacted_federated_policy.label)
|
|
|
|
assert enacted_federated_policy.treasure_map == treasure_map_from_wire
|
|
|
|
|
|
def test_treasure_map_is_legit(enacted_federated_policy):
|
|
"""
|
|
Sure, the TreasureMap can get to Bob, but we also need to know that each Ursula in the TreasureMap is on the network.
|
|
"""
|
|
for ursula_address, _node_id in enacted_federated_policy.treasure_map:
|
|
assert ursula_address in enacted_federated_policy.bob.known_nodes.addresses()
|
|
|
|
|
|
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075
|
|
def test_vladimir_illegal_interface_key_does_not_propagate(blockchain_ursulas):
|
|
"""
|
|
Although Ursulas propagate each other's interface information, as demonstrated above,
|
|
they do not propagate interface information for Vladimir.
|
|
|
|
Specifically, if Vladimir tries to perform the most obvious imitation attack -
|
|
propagating his own wallet address along with Ursula's information - the validity
|
|
check will catch it and Ursula will refuse to propagate it and also record Vladimir's
|
|
details.
|
|
"""
|
|
ursulas = list(blockchain_ursulas)
|
|
ursula_whom_vladimir_will_imitate, other_ursula = ursulas[0], ursulas[1]
|
|
|
|
# Vladimir sees Ursula on the network and tries to use her public information.
|
|
vladimir = Vladimir.from_target_ursula(ursula_whom_vladimir_will_imitate)
|
|
|
|
# This Ursula is totally legit...
|
|
ursula_whom_vladimir_will_imitate.verify_node(MockRestMiddleware(), accept_federated_only=True)
|
|
|
|
learning_callers = []
|
|
crosstown_traffic.decorator = crosstownTaskListDecoratorFactory(learning_callers)
|
|
|
|
vladimir.network_middleware.propagate_shitty_interface_id(other_ursula, bytes(vladimir))
|
|
|
|
# So far, Ursula hasn't noticed any Vladimirs.
|
|
assert other_ursula.suspicious_activities_witnessed['vladimirs'] == []
|
|
|
|
# ...but now, Ursula will now try to learn about Vladimir on a different thread.
|
|
# We only passed one node (Vladimir)...
|
|
learn_about_vladimir = learning_callers.pop()
|
|
# ...so there was only one learning caller in the queue (now none since we popped it just now).
|
|
assert len(learning_callers) == 0
|
|
|
|
# OK, so cool, let's see what happens when Ursula tries to learn about Vlad.
|
|
learn_about_vladimir()
|
|
|
|
# And indeed, Ursula noticed the situation.
|
|
# She didn't record Vladimir's address.
|
|
assert vladimir not in other_ursula.known_nodes
|
|
|
|
# But she *did* record the actual Ursula's address.
|
|
assert ursula_whom_vladimir_will_imitate in other_ursula.known_nodes
|
|
|
|
# Furthermore, she properly marked Vladimir as suspicious.
|
|
assert vladimir in other_ursula.suspicious_activities_witnessed['vladimirs']
|
|
|
|
|
|
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075
|
|
def test_alice_refuses_to_make_arrangement_unless_ursula_is_valid(blockchain_alice,
|
|
idle_blockchain_policy,
|
|
blockchain_ursulas):
|
|
target = list(blockchain_ursulas)[2]
|
|
# First, let's imagine that Alice has sampled a Vladimir while making this policy.
|
|
vladimir = Vladimir.from_target_ursula(target)
|
|
|
|
message = vladimir._signable_interface_info_message()
|
|
signature = vladimir._crypto_power.power_ups(SigningPower).sign(message)
|
|
|
|
vladimir.substantiate_stamp(client_password=INSECURE_DEVELOPMENT_PASSWORD)
|
|
vladimir._Teacher__interface_signature = signature
|
|
|
|
class FakeArrangement:
|
|
federated = False
|
|
ursula = target
|
|
|
|
vladimir.node_storage.store_node_certificate(certificate=target.certificate)
|
|
|
|
with pytest.raises(vladimir.InvalidNode):
|
|
idle_blockchain_policy.consider_arrangement(network_middleware=blockchain_alice.network_middleware,
|
|
arrangement=FakeArrangement(),
|
|
ursula=vladimir)
|
|
|
|
|
|
def test_alice_does_not_update_with_old_ursula_info(federated_alice, federated_ursulas):
|
|
ursula = list(federated_ursulas)[0]
|
|
old_metadata = bytes(ursula)
|
|
|
|
# Alice has remembered Ursula.
|
|
assert federated_alice.known_nodes[ursula.checksum_address] == ursula
|
|
|
|
# But now, Ursula wants to sign and date her interface info again. This causes a new timestamp.
|
|
ursula._sign_and_date_interface_info()
|
|
|
|
# Indeed, her metadata is not the same now.
|
|
assert bytes(ursula) != old_metadata
|
|
|
|
old_ursula = Ursula.from_bytes(old_metadata, federated_only=True)
|
|
|
|
# Once Alice learns about Ursula's updated info...
|
|
federated_alice.remember_node(ursula)
|
|
|
|
# ...she can't learn about old ursula anymore.
|
|
federated_alice.remember_node(old_ursula)
|
|
|
|
new_metadata = bytes(federated_alice.known_nodes[ursula.checksum_address])
|
|
assert new_metadata != old_metadata
|