def test_vladimir_cannot_verify_interface_with_ursulas_signing_key( mining_ursulas): his_target = list(mining_ursulas)[4] # Vladimir has his own ether address; he hopes to publish it along with Ursula's details # so that Alice (or whomever) pays him instead of Ursula, even though Ursula is providing the service. vladimir_ether_address = '0xE57bFE9F44b819898F47BF37E5AF72a0783e1141' # Vladimir imitates Ursula - copying her public keys and interface info, but inserting his ether address. vladimir = Ursula(crypto_power=his_target._crypto_power, rest_host=his_target.rest_interface.host, rest_port=his_target.rest_interface.port, checksum_address=vladimir_ether_address, interface_signature=his_target._interface_signature, is_me=False) # Vladimir can substantiate the stamp using his own ether address... vladimir.substantiate_stamp() vladimir.stamp_is_valid() # ...however, the signature for the interface info isn't valid. with pytest.raises(vladimir.InvalidNode): vladimir.interface_is_valid() # Consequently, the metadata isn't valid. with pytest.raises(vladimir.InvalidNode): vladimir.validate_metadata()
def test_vladimir_uses_his_own_signing_key(alice, mining_ursulas): """ Similar to the attack above, but this time Vladimir makes his own interface signature using his own signing key, which he claims is Ursula's. """ his_target = list(mining_ursulas)[4] vladimir_ether_address = '0xE57bFE9F44b819898F47BF37E5AF72a0783e1141' fraduluent_keys = CryptoPower(power_ups=Ursula._default_crypto_powerups) vladimir = Ursula(crypto_power=fraduluent_keys, rest_host=his_target.rest_interface.host, rest_port=his_target.rest_interface.port, checksum_address=vladimir_ether_address, is_me=False) message = vladimir._signable_interface_info_message() signature = vladimir._crypto_power.power_ups(SigningPower).sign(message) vladimir._interface_signature_object = signature vladimir.substantiate_stamp() # With this slightly more sophisticated attack, his metadata does appear valid. vladimir.validate_metadata() # However, the actual handshake proves him wrong. with pytest.raises(vladimir.InvalidNode): vladimir.verify_node(alice.network_middleware)
def make_ursulas(how_many_ursulas: int, ursula_starting_port: int, config: NucypherConfig) -> list: """ :param how_many_ursulas: How many Ursulas to create. :param ursula_starting_port: The port of the first created Ursula; subsequent Ursulas will increment the port number by 1. :return: A list of created Ursulas """ event_loop = asyncio.get_event_loop() URSULAS = [] for _u in range(how_many_ursulas): port = ursula_starting_port + _u _URSULA = Ursula( dht_port=port, ip_address="127.0.0.1", db_name="test-{}".format(port), rest_port=port + 100, config=config) # TODO: Make ports unstupid and more clear. class MockDatastoreThreadPool(object): def callInThread(self, f, *args, **kwargs): return f(*args, **kwargs) _URSULA.datastore_threadpool = MockDatastoreThreadPool() _URSULA.dht_listen() URSULAS.append(_URSULA) for _counter, ursula in enumerate(URSULAS): event_loop.run_until_complete( ursula.server.bootstrap([("127.0.0.1", ursula_starting_port + _c) for _c in range(how_many_ursulas)])) ursula.publish_dht_information() return URSULAS
def rpc_store(self, sender, nodeid, key, value): source = kademlia.node.Node(nodeid, sender[0], sender[1]) self.welcomeIfNewNode(source) self.log.debug("got a store request from %s" % str(sender)) header, payload = default_constant_splitter(value, return_remainder=True) if header == constants.BYTESTRING_IS_URSULA_IFACE_INFO: from nucypher.characters import Ursula stranger_ursula = Ursula.from_bytes(payload, federated_only=self.sourceNode.federated_only) # TODO: Is federated_only the right thing here? if stranger_ursula.interface_is_valid() and key == digest(stranger_ursula.canonical_public_address): self.sourceNode._node_storage[stranger_ursula.checksum_public_address] = stranger_ursula # TODO: 340 return True else: self.log.warning("Got request to store invalid node: {} / {}".format(key, value)) self.illegal_keys_seen.append(key) return False elif header == constants.BYTESTRING_IS_TREASURE_MAP: from nucypher.policy.models import TreasureMap try: treasure_map = TreasureMap.from_bytes(payload) self.log.info("Storing TreasureMap: {} / {}".format(key, value)) self.sourceNode._treasure_maps[treasure_map.public_id()] = value return True except TreasureMap.InvalidSignature: self.log.warning("Got request to store invalid TreasureMap: {} / {}".format(key, value)) self.illegal_keys_seen.append(key) return False else: self.log.info( "Got request to store bad k/v: {} / {}".format(key, value)) return False
def read_node_metadata(filepath: str, federated_only=False) -> Ursula: """Init one ursula from node storage file""" with open(filepath, "r") as seed_file: seed_file.seek(0) node_bytes = binascii.unhexlify(seed_file.read()) node = Ursula.from_bytes(node_bytes, federated_only=federated_only) return node
def run_ursula(rest_port, dht_port, db_name, checksum_address, federated_only, seed_node, data_dir, config_file) -> None: """ The following procedure is required to "spin-up" an Ursula node. 1. Collect all known known from storages 2. Start the asyncio event loop 3. Initialize Ursula object 4. Start DHT listener 5. Enter the learning loop 6. Run TLS deployment 7. Start the staking daemon Configurable values are first read from the .ini configuration file, but can be overridden (mostly for testing purposes) with inline cli options. """ if not seed_node: other_nodes = collect_stored_nodes( federated_only=federated_only) # 1. Collect known nodes else: other_nodes = tuple() overrides = dict(federated_only=federated_only, known_nodes=other_nodes, rest_port=rest_port, dht_port=dht_port, db_name=db_name, checksum_address=checksum_address) if seed_node: seed_overrides = dict(always_be_learning=False, abort_on_learning_error=False) overrides.update(seed_overrides) asyncio.set_event_loop(asyncio.new_event_loop()) # 2. Init DHT async loop # 3. Initialize Ursula (includes overrides) ursula = Ursula.from_config(filepath=config_file, overrides=overrides) ursula.dht_listen() # 4. Start DHT write_node_metadata(seed_node=seed_node, node=ursula, node_metadata_dir=data_dir) if not seed_node: ursula.start_learning_loop() # 5. Enter learning loop ursula.get_deployer().run() # 6. Run TLS Deployer if not federated_only: ursula.stake() # 7. start staking daemon
def get_seed(): teacher_dht_port = 3500 teacher_rest_port = int(teacher_dht_port) + 100 with open("examples-runtime-cruft/node-metadata-{}".format(teacher_rest_port), "r") as f: f.seek(0) teacher_bytes = binascii.unhexlify(f.read()) URSULA = Ursula.from_bytes(teacher_bytes, federated_only=True) print("Will learn from {}".format(URSULA)) return URSULA
def test_anybody_can_encrypt(): """ Similar to anybody_can_verify() above; we show that anybody can encrypt. """ everyman = Character() ursula = Ursula(is_me=False) cleartext = b"This is Officer Rod Farva. Come in, Ursula! Come in Ursula!" ciphertext, signature = everyman.encrypt_for(ursula, cleartext, sign=False) assert signature == constants.NOT_SIGNED assert ciphertext is not None
def test_alice_can_get_ursulas_keys_via_rest(ursulas): mock_client = TestClient(ursulas[0].rest_app) response = mock_client.get('http://localhost/public_keys') splitter = BytestringSplitter((UmbralPublicKey, PUBLIC_KEY_LENGTH), (UmbralPublicKey, PUBLIC_KEY_LENGTH)) signing_key, encrypting_key = splitter(response.content) stranger_ursula_from_public_keys = Ursula.from_public_keys({ SigningPower: signing_key, EncryptingPower: encrypting_key }) assert stranger_ursula_from_public_keys == ursulas[0]
def consider_arrangement(self, arrangement): ursula = Ursula.from_rest_url( self, host=arrangement.ursula.rest_interface.host, port=arrangement.ursula.rest_interface.port, federated_only=True, ) # TODO: Make this the Ursula to whom we connect. response = requests.post("https://localhost:3601/consider_arrangement", bytes(arrangement), verify=False) if response.status_code == 200: response.was_accepted = True else: raise RuntimeError( "Something went terribly wrong. What'd you do?!") return response
def find_ursula(self, contract=None): ursula = Ursula.as_discovered_on_network( dht_port=None, ip_address="localhost", rest_port=3601, powers_and_keys={ SigningPower: self.ursulas[0].stamp.as_umbral_pubkey(), EncryptingPower: self.ursulas[0].public_key(EncryptingPower) }) response = requests.post("https://localhost:3601/consider_arrangement", bytes(contract), verify=False) if response.status_code == 200: response.was_accepted = True else: raise RuntimeError( "Something went terribly wrong. What'd you do?!") return ursula, response
def __init__(self, teacher_dht_port: int = 3500, teacher_rest_port: int = 3600, node_meta_dir: str = "../examples/examples-runtime-cruft"): self.teacher_dht_port = teacher_dht_port if teacher_rest_port: self.teacher_rest_port = teacher_rest_port else: self.teacher_rest_port = int(self.teacher_dht_port) + 100 with open( "{}/node-metadata-{}".format(node_meta_dir, self.teacher_rest_port), "r") as f: f.seek(0) teacher_bytes = binascii.unhexlify(f.read()) self.ursula = Ursula.from_bytes(teacher_bytes, federated_only=True)
def spin_up_ursula(dht_port, rest_port, db_name, teachers=()): metadata_file = "examples-runtime-cruft/node-metadata-{}".format(rest_port) asyncio.set_event_loop(asyncio.new_event_loop() ) # Ugh. Awful. But needed until we shed the DHT. _URSULA = Ursula( dht_port=dht_port, rest_port=rest_port, rest_host="localhost", dht_host="localhost", db_name=db_name, federated_only=True, known_nodes=teachers, ) _URSULA.dht_listen() try: with open(metadata_file, "w") as f: f.write(bytes(_URSULA).hex()) _URSULA.start_learning_loop() _URSULA.get_deployer().run() finally: os.remove(db_name) os.remove(metadata_file)
def test_alice_refuses_to_make_arrangement_unless_ursula_is_valid( blockchain_alice, idle_blockchain_policy, mining_ursulas): target = list(mining_ursulas)[2] # First, let's imagine that Alice has sampled a Vladimir while making this policy. vladimir = Ursula( crypto_power=CryptoPower(power_ups=Ursula._default_crypto_powerups), rest_host=target.rest_interface.host, rest_port=target.rest_interface.port, checksum_address= '0xE57bFE9F44b819898F47BF37E5AF72a0783e1141', # Fradulent address is_me=False) message = vladimir._signable_interface_info_message() signature = vladimir._crypto_power.power_ups(SigningPower).sign(message) vladimir.substantiate_stamp() vladimir._interface_signature_object = signature class FakeArrangement: federated = False with pytest.raises(vladimir.InvalidNode): idle_blockchain_policy.consider_arrangement( network_middleware=blockchain_alice.network_middleware, arrangement=FakeArrangement(), ursula=vladimir)
def check_node_with_cert(node, cert_file): response = requests.get("https://{}/public_information".format( node.rest_url()), verify=cert_file) ursula = Ursula.from_bytes(response.content, federated_only=True) assert ursula == node
def test_ursula_generates_self_signed_cert(nucypher_test_config): ursula = Ursula(attach_server=False, config=nucypher_test_config) cert, cert_private_key = ursula.generate_self_signed_certificate() public_key_numbers = ursula.public_key( SigningPower).to_cryptography_pubkey().public_numbers() assert cert.public_key().public_numbers() == public_key_numbers
import os from cryptography.hazmat.primitives.asymmetric import ec from hendrix.deploy.tls import HendrixDeployTLS from hendrix.facilities.services import ExistingKeyTLSContextFactory from nucypher.characters import Ursula from OpenSSL.crypto import X509 from OpenSSL.SSL import TLSv1_2_METHOD from nucypher.crypto.api import generate_self_signed_certificate DB_NAME = "non-mining-proxy-node" _URSULA = Ursula(dht_port=3501, rest_port=3601, ip_address="localhost", db_name=DB_NAME) _URSULA.dht_listen() CURVE = ec.SECP256R1 cert, private_key = generate_self_signed_certificate( _URSULA.stamp.fingerprint().decode(), CURVE) deployer = HendrixDeployTLS("start", { "wsgi": _URSULA.rest_app, "https_port": _URSULA.rest_port }, key=private_key, cert=X509.from_cryptography(cert), context_factory=ExistingKeyTLSContextFactory, context_factory_kwargs={
from nucypher.config.metadata import collect_stored_nodes from nucypher.data_sources import DataSource # This is already running in another process. from nucypher.network.middleware import RestMiddleware >>>>>>> 26103ab... Project-wide automated import optimization, followed-up with some hand-tweaking:examples/finnegans-wake-demo.py # This is already running in another process. ############################################## # This is already running in another process. ############################################## BLOCKCHAIN = Blockchain.connect() URSULA = Ursula.from_config() ######### # Alice # ######### ALICE = Alice.from_config() # Here are our Policy details. policy_end_datetime = maya.now() + datetime.timedelta(days=201) m = 2 n = 3 label = b"secret/files/and/stuff" # Alice grants to Bob. BOB = Bob.from_config()
root.setLevel(logging.DEBUG) ch = logging.StreamHandler(sys.stdout) ch.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) root.addHandler(ch) teacher_dht_port = sys.argv[2] teacher_rest_port = int(teacher_dht_port) + 100 with open("examples-runtime-cruft/node-metadata-{}".format(teacher_rest_port), "r") as f: f.seek(0) teacher_bytes = binascii.unhexlify(f.read()) URSULA = Ursula.from_bytes(teacher_bytes, federated_only=True) print("Will learn from {}".format(URSULA)) # network_middleware = SandboxRestMiddleware([URSULA]) ######### # Alice # ######### ALICE = Alice( network_middleware=RestMiddleware(), known_nodes=(URSULA, ), # in lieu of seed nodes federated_only=True, always_be_learning=True) # TODO: 289 # Here are our Policy details.
def make_ursulas(ether_addresses: list, miner_agent=None, miners=False, bare=False, know_each_other=True, **ursula_kwargs) -> Set[Ursula]: """ :param ether_addresses: Ethereum addresses to create ursulas with. :param ursula_starting_port: The port of the first created Ursula; subsequent Ursulas will increment the port number by 1. :param miner_agent: A miner agent instance to use when creating ursulas. :param miners: If True, create staking ursulas on the blockchain from the addresses :param bare: If True, Create an non-learning Ursula without a rest app, dht server or database attached, for testing mining functionality when network transport is not needed. "Just a miner" :return: A list of created Ursulas """ if isinstance(ether_addresses, int): ether_addresses = [to_checksum_address(secure_random(20)) for _ in range(ether_addresses)] event_loop = asyncio.get_event_loop() if not _TEST_KNOWN_URSULAS_CACHE: starting_port = constants.URSULA_PORT_SEED else: starting_port = max(_TEST_KNOWN_URSULAS_CACHE.keys()) + 1 ursulas = set() for port, ether_address in enumerate(ether_addresses, start=starting_port): if bare: ursula = Ursula(is_me=False, # do not attach dht server rest_host="localhost", # TODO: remove rest interface rest_port=port + 100, checksum_address=ether_address, always_be_learning=False, miner_agent=miner_agent, abort_on_learning_error=True, **ursula_kwargs) ursula.is_me = True # Patch to allow execution of transacting methods in tests else: federated_only = not miners if federated_only: ether_address = None ursula = Ursula(is_me=True, checksum_address=ether_address, dht_host="localhost", dht_port=port, db_name="test-{}".format(port), rest_host="localhost", rest_port=port+100, always_be_learning=False, miner_agent=miner_agent, federated_only=federated_only, **ursula_kwargs) ursula.attach_rest_server() class MockDatastoreThreadPool(object): def callInThread(self, f, *args, **kwargs): return f(*args, **kwargs) ursula.datastore_threadpool = MockDatastoreThreadPool() ursula.dht_listen() if miners is True: # TODO: 309 # stake a random amount min_stake, balance = constants.MIN_ALLOWED_LOCKED, ursula.token_balance amount = random.randint(min_stake, balance) # for a random lock duration min_locktime, max_locktime = constants.MIN_LOCKED_PERIODS, constants.MAX_MINTING_PERIODS periods = random.randint(min_locktime, max_locktime) ursula.initialize_stake(amount=amount, lock_periods=periods) else: ursula.federated_only = True ursulas.add(ursula) _TEST_KNOWN_URSULAS_CACHE[ursula.rest_interface.port] = ursula if know_each_other and not bare: for ursula_to_teach in ursulas: # Add other Ursulas as known nodes. for ursula_to_learn_about in ursulas: ursula_to_teach.remember_node(ursula_to_learn_about) event_loop.run_until_complete( ursula.dht_server.bootstrap( [("localhost", starting_port + _c) for _c in range(len(ursulas))])) ursula.publish_dht_information() return ursulas
# This is an example of Alice setting a Policy on the NuCypher network. # In this example, Alice uses n=1, which is almost always a bad idea. Don't do it. # WIP w/ [email protected] import datetime import sys from examples.sandbox_resources import SandboxNetworkyStuff from nucypher.characters import Alice, Bob, Ursula from nucypher.data_sources import DataSource from nucypher.network.node import NetworkyStuff import maya # This is already running in another process. URSULA = Ursula.from_rest_url(NetworkyStuff(), address="localhost", port=3601) network_middleware = SandboxNetworkyStuff([URSULA]) ######### # Alice # ######### ALICE = Alice(network_middleware=network_middleware) # Here are our Policy details. policy_end_datetime = maya.now() + datetime.timedelta(days=5) m = 1 n = 1 label = b"secret/files/and/stuff" # Alice gets on the network and, knowing about at least one Ursula,