From 5f84157b96368902e58061db3b3f0efb89c0954b Mon Sep 17 00:00:00 2001 From: Kieran Prasch Date: Wed, 10 Oct 2018 20:11:25 -0700 Subject: [PATCH] Derive HostingPower from Keyring --- cli/main.py | 2 +- nucypher/characters/base.py | 2 +- nucypher/characters/lawful.py | 101 ++++++++++++++++------------------ nucypher/config/characters.py | 5 -- nucypher/config/constants.py | 6 +- nucypher/config/keyring.py | 20 ++----- nucypher/config/node.py | 5 +- nucypher/network/server.py | 24 ++++---- tests/fixtures.py | 4 +- 9 files changed, 74 insertions(+), 95 deletions(-) diff --git a/cli/main.py b/cli/main.py index d9a648744..e94e07eb2 100755 --- a/cli/main.py +++ b/cli/main.py @@ -1170,7 +1170,7 @@ def ursula(config, click.secho("Running in {} mode".format(config.operating_mode), fg='blue') # Bootnodes, Seeds, Known Nodes - ursula_config.get_bootnodes() + ursula_config.load_bootnodes() quantity_known_nodes = len(ursula_config.known_nodes) if quantity_known_nodes > 0: click.secho("Loaded {} known nodes from storages".format(quantity_known_nodes, fg='blue')) diff --git a/nucypher/characters/base.py b/nucypher/characters/base.py index edd88a0ff..19ca8a8b9 100644 --- a/nucypher/characters/base.py +++ b/nucypher/characters/base.py @@ -574,7 +574,7 @@ class Character(Learner): @property def rest_interface(self): - return self._crypto_power.power_ups(TLSHostingPower).rest_server.rest_url() + return self.rest_server.rest_url() @property def stamp(self): diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index 6d9ad0dc0..a30b7228d 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -398,7 +398,6 @@ class Ursula(Character, VerifiableNode, Miner): rest_port: int, certificate: Certificate = None, certificate_filepath: str = None, - tls_private_key = None, # TODO: Derive from keyring db_name: str = None, db_filepath: str = None, @@ -442,31 +441,32 @@ class Ursula(Character, VerifiableNode, Miner): if is_me is True: # TODO: 340 self._stored_treasure_maps = dict() + # + # Staking Ursula + # if not federated_only: Miner.__init__(self, is_me=is_me, miner_agent=miner_agent, checksum_address=checksum_address) - # Access staking node via node's transacting keys + # Access staking node via node's transacting keys TODO: Better handle ephemeral staking self ursula blockchain_power = BlockchainPower(blockchain=self.blockchain, account=self.checksum_public_address) self._crypto_power.consume_power_up(blockchain_power) - # Use blockchain power to substantiate stamp + # Use blockchain power to substantiate stamp, instead of signing key self.substantiate_stamp(passphrase=passphrase) # - # ProxyRESTServer and TLSHostingPower + # ProxyRESTServer and TLSHostingPower # TODO: Maybe we want _power_ups to be public after all? # if not crypto_power or (TLSHostingPower not in crypto_power._power_ups): - # TODO: Maybe we want _power_ups to be public after all? - # We'll hook all the TLS stuff up unless the crypto_power was already passed. # - # Self-Ursula + # Ephemeral Self-Ursula # if is_me: self.suspicious_activities_witnessed = {'vladimirs': [], 'bad_treasure_maps': []} # - # REST Server + # REST Server (Ephemeral Self-Ursula) # rest_routes = ProxyRESTRoutes( db_name=db_name, @@ -484,62 +484,41 @@ class Ursula(Character, VerifiableNode, Miner): certificate_dir=self.known_certificates_dir, ) - rest_server = ProxyRESTServer( - rest_host=rest_host, - rest_port=rest_port, - routes=rest_routes, - ) - self.rest_url = rest_server.rest_url - self.datastore = rest_routes.datastore # TODO: Maybe organize this better? - # - # TLSHostingPower + # TLSHostingPower (Ephemeral Self-Ursula) # - tls_hosting_keypair = HostingKeypair( - curve=tls_curve, - host=rest_host, - certificate=certificate, - certificate_filepath=certificate_filepath, - private_key=tls_private_key - ) - - tls_hosting_power = TLSHostingPower(rest_server=rest_server, - keypair=tls_hosting_keypair) + tls_hosting_keypair = HostingKeypair(curve=tls_curve, host=rest_host) + tls_hosting_power = TLSHostingPower(keypair=tls_hosting_keypair, host=rest_host) + self.rest_server = ProxyRESTServer(rest_host=rest_host, rest_port=rest_port, + routes=rest_routes, hosting_power=tls_hosting_power) # # Stranger-Ursula # else: + # TLSHostingPower + if certificate or certificate_filepath: + tls_hosting_power = TLSHostingPower(host=rest_host, + public_certificate_filepath=certificate_filepath, + public_certificate=certificate) + else: + tls_hosting_keypair = HostingKeypair(curve=tls_curve, host=rest_host) + tls_hosting_power = TLSHostingPower(host=rest_host, keypair=tls_hosting_keypair) + # REST Server - # Unless the caller passed a crypto power, - # we'll make our own TLSHostingPower for this stranger. - rest_server = ProxyRESTServer( + # Unless the caller passed a crypto power we'll make our own TLSHostingPower for this stranger. + self.rest_server = ProxyRESTServer( rest_host=rest_host, - rest_port=rest_port + rest_port=rest_port, + hosting_power=tls_hosting_power ) - # - # TLSHostingPower - # - if certificate or certificate_filepath: - tls_hosting_power = TLSHostingPower(rest_server=rest_server, - certificate_filepath=certificate_filepath, - certificate=certificate) - else: - tls_hosting_keypair = HostingKeypair( - curve=tls_curve, - host=rest_host, - certificate_filepath=certificate_filepath) - tls_hosting_power = TLSHostingPower(rest_server=rest_server, - keypair=tls_hosting_keypair) - + # # OK - Now we have a ProxyRestServer and a TLSHostingPower for some Ursula + # self._crypto_power.consume_power_up(tls_hosting_power) # Consume! - else: - self.log.info("Not adhering rest_server; Using the one on crypto_power.") - # # Verifiable Node # @@ -565,7 +544,7 @@ class Ursula(Character, VerifiableNode, Miner): hosting_power = self._crypto_power.power_ups(TLSHostingPower) return ( - hosting_power.rest_server.rest_interface, + self.rest_server.rest_interface, hosting_power.keypair.certificate, hosting_power.keypair.pubkey ) @@ -608,7 +587,7 @@ class Ursula(Character, VerifiableNode, Miner): port: int, federated_only: bool = False) -> 'Ursula': - response = network_middleware.node_information(host, port) # TODO + response = network_middleware.node_information(host, port) # TODO: pre-load certificates here? if not response.status_code == 200: raise RuntimeError("Got a bad response: {}".format(response)) @@ -686,16 +665,30 @@ class Ursula(Character, VerifiableNode, Miner): def from_storage(cls, node_storage: NodeStorage, checksum_adress: str, - federated_only: bool = False, *args, **kwargs) -> 'Ursula': - return node_storage.get(checksum_address=checksum_adress) + federated_only: bool = False) -> 'Ursula': + return node_storage.get(checksum_address=checksum_adress, + federated_only=federated_only) # # Properties # + @property + def datastore(self): + try: + return self.rest_server.routes.datastore + except AttributeError: + raise AttributeError("No rest server attached") + + @property + def rest_url(self): + try: + return self.rest_server.rest_url + except AttributeError: + raise AttributeError("No rest server attached") @property def rest_app(self): - rest_app_on_server = self._crypto_power.power_ups(TLSHostingPower).rest_server.rest_app + rest_app_on_server = self.rest_server.rest_app if not rest_app_on_server: m = "This Ursula doesn't have a REST app attached. If you want one, init with is_me and attach_server." diff --git a/nucypher/config/characters.py b/nucypher/config/characters.py index a42a59716..49c73a8df 100644 --- a/nucypher/config/characters.py +++ b/nucypher/config/characters.py @@ -34,7 +34,6 @@ class UrsulaConfiguration(NodeConfiguration): # TLS tls_curve: EllipticCurve = None, certificate: Certificate = None, - tls_private_key: bytes = None, # TODO: from config here certificate_filepath: str = None, # Ursula @@ -63,7 +62,6 @@ class UrsulaConfiguration(NodeConfiguration): self.tls_curve = tls_curve or self.__DEFAULT_TLS_CURVE self.certificate = certificate self.certificate_filepath = certificate_filepath - self.tls_private_key = tls_private_key # Ursula self.interface_signature = interface_signature @@ -114,9 +112,6 @@ class UrsulaConfiguration(NodeConfiguration): timestamp=None, miner_agent=self.miner_agent ) - if not self.temp: - tls_private_key = self.keyring.tls_private_key - payload.update(dict(tls_private_key=tls_private_key)) return {**super().dynamic_payload, **payload} def produce(self, passphrase: str = None, **overrides): diff --git a/nucypher/config/constants.py b/nucypher/config/constants.py index 0f1d680d0..43201d8c7 100644 --- a/nucypher/config/constants.py +++ b/nucypher/config/constants.py @@ -1,4 +1,3 @@ -import os from collections import namedtuple from os.path import abspath, dirname @@ -12,12 +11,11 @@ PROJECT_ROOT = abspath(dirname(nucypher.__file__)) APP_DIR = AppDirs("nucypher", "NuCypher") DEFAULT_CONFIG_ROOT = APP_DIR.user_data_dir +# # Bootnodes +# Bootnode = namedtuple('Bootnode', ['checksum_address', 'rest_url']) BOOTNODES = ( Bootnode('0xDbf2Bc4b81eB46CdDfa52348Ecf3c142841267E0', 'https://18.223.117.103:9151'), ) -# Test Constants # TODO: Tidy up filepath here -TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts') -NUMBER_OF_URSULAS_IN_MOCK_NETWORK = 10 diff --git a/nucypher/config/keyring.py b/nucypher/config/keyring.py index 6fea38500..1f9ea63af 100644 --- a/nucypher/config/keyring.py +++ b/nucypher/config/keyring.py @@ -25,6 +25,7 @@ from umbral.keys import UmbralPrivateKey, UmbralPublicKey from nucypher.config.constants import DEFAULT_CONFIG_ROOT from nucypher.crypto.api import generate_self_signed_certificate from nucypher.crypto.powers import SigningPower, EncryptingPower, KeyPairBasedPower, DerivedKeyBasedPower +from nucypher.network.server import TLSHostingPower FILE_ENCODING = 'utf-8' @@ -388,19 +389,6 @@ class NucypherKeyring: def __del__(self) -> None: self.lock() - # - # Private Keys - # - @property - @unlock_required - def tls_private_key(self): - """TODO: Deprecate and use self.derive_crypto_power instead""" - key_data = _read_keyfile(keypath=self.__tls_keypath, deserializer=None) - __tls_key = serialization.load_pem_private_key(data=key_data, - password=self.__derived_key_material, - backend=default_backend()) - return __tls_key - # # Public Keys # @@ -520,10 +508,10 @@ class NucypherKeyring: if issubclass(power_class, KeyPairBasedPower): codex = {SigningPower: self.__signing_keypath, - EncryptingPower: self.__root_keypath + EncryptingPower: self.__root_keypath, + TLSHostingPower: self.__tls_keypath, # TODO # BlockchainPower: self.__wallet_path, # TODO - # TLSHostingPower: self.__tls_keypath} # TODO - } + } # Create Power try: diff --git a/nucypher/config/node.py b/nucypher/config/node.py index 4886d30ad..8cc79decb 100644 --- a/nucypher/config/node.py +++ b/nucypher/config/node.py @@ -18,12 +18,11 @@ from cryptography.hazmat.primitives.serialization import Encoding from nucypher.characters.lawful import Ursula from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR, BOOTNODES -from nucypher.config.keyring import NucypherKeyring, _write_tls_certificate, _read_tls_public_certificate +from nucypher.config.keyring import NucypherKeyring, _write_tls_certificate from nucypher.config.storages import NodeStorage, InMemoryNodeStorage from nucypher.crypto.powers import CryptoPowerUp from nucypher.crypto.signing import signature_splitter from nucypher.network.middleware import RestMiddleware -from nucypher.network.nodes import VerifiableNode class NodeConfiguration: @@ -395,7 +394,7 @@ class NodeConfiguration: curve=tls_curve, keyring_root=self.keyring_dir) - # TODO: Operating mode switch + # TODO: Operating mode switch #466 if self.federated_only or not wallet: self.checksum_address = self.keyring.federated_address else: diff --git a/nucypher/network/server.py b/nucypher/network/server.py index c5222a911..6c2dd2cd2 100644 --- a/nucypher/network/server.py +++ b/nucypher/network/server.py @@ -24,8 +24,11 @@ class ProxyRESTServer: def __init__(self, rest_host: str, rest_port: int, + hosting_power = None, routes: 'ProxyRESTRoutes' = None, ) -> None: + + self.routes = routes self.rest_interface = InterfaceInfo(host=rest_host, port=rest_port) if routes: # if is me self.rest_app = routes.rest_app @@ -33,6 +36,8 @@ class ProxyRESTServer: else: self.rest_app = constants.PUBLIC_ONLY + self.__hosting_power = hosting_power + def rest_url(self): return "{}:{}".format(self.rest_interface.host, self.rest_interface.port) @@ -291,18 +296,17 @@ class TLSHostingPower(KeyPairBasedPower): not_found_error = NoHostingPower def __init__(self, - rest_server, - certificate=None, - certificate_filepath=None, + host: str, + public_certificate=None, + public_certificate_filepath=None, *args, **kwargs) -> None: - if certificate and certificate_filepath: + if public_certificate and public_certificate_filepath: # TODO: Design decision here: if they do pass both, and they're identical, do we let that slide? - raise ValueError("Pass either a certificate or a certificate_filepath, not both.") + raise ValueError("Pass either a public_certificate or a public_certificate_filepath, not both.") - if certificate: - kwargs['keypair'] = HostingKeypair(certificate=certificate, host=rest_server.rest_interface.host) - elif certificate_filepath: - kwargs['keypair'] = HostingKeypair(certificate_filepath=certificate_filepath) - self.rest_server = rest_server + if public_certificate: + kwargs['keypair'] = HostingKeypair(certificate=public_certificate, host=host) + elif public_certificate_filepath: + kwargs['keypair'] = HostingKeypair(certificate_filepath=public_certificate_filepath, host=host) super().__init__(*args, **kwargs) diff --git a/tests/fixtures.py b/tests/fixtures.py index b0f20fb71..19c46271b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -16,7 +16,7 @@ from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface from nucypher.blockchain.eth.registry import TemporaryEthereumContractRegistry from nucypher.blockchain.eth.sol.compile import SolidityCompiler from nucypher.config.characters import UrsulaConfiguration, AliceConfiguration, BobConfiguration -from nucypher.config.constants import TEST_CONTRACTS_DIR +from nucypher.config.constants import BASE_DIR from nucypher.config.node import NodeConfiguration from nucypher.data_sources import DataSource from nucypher.keystore import keystore @@ -29,6 +29,8 @@ from nucypher.utilities.sandbox.constants import (DEFAULT_NUMBER_OF_URSULAS_IN_D from nucypher.utilities.sandbox.middleware import MockRestMiddleware from nucypher.utilities.sandbox.ursula import make_federated_ursulas, make_decentralized_ursulas +TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts') + # # Temporary