Exemplo n.º 1
0
def test_vladimir_uses_his_own_signing_key(blockchain_alice,
                                           blockchain_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(blockchain_ursulas)[4]

    fraduluent_keys = CryptoPower(power_ups=Ursula._default_crypto_powerups)

    vladimir = Vladimir.from_target_ursula(target_ursula=his_target)

    message = vladimir._signable_interface_info_message()
    signature = vladimir._crypto_power.power_ups(SigningPower).sign(
        vladimir.timestamp_bytes() + message)
    vladimir._interface_signature_object = signature

    vladimir.substantiate_stamp(
        passphrase=TEST_URSULA_INSECURE_DEVELOPMENT_PASSWORD)

    # 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(blockchain_alice.network_middleware)
Exemplo n.º 2
0
    def from_target_ursula(cls,
                           target_ursula: Ursula,
                           claim_signing_key: bool = False,
                           attach_transacting_key: bool = True) -> 'Vladimir':
        """
        Sometimes Vladimir seeks to attack or imitate a *specific* target Ursula.

        TODO: This is probably a more instructive method if it takes a bytes representation instead of the entire Ursula.
        """
        try:
            from tests.utils.middleware import EvilMiddleWare
        except ImportError:
            raise DevelopmentInstallationRequired(
                importable_name='tests.utils.middleware.EvilMiddleWare')
        cls.network_middleware = EvilMiddleWare()

        crypto_power = CryptoPower(
            power_ups=target_ursula._default_crypto_powerups)

        if claim_signing_key:
            crypto_power.consume_power_up(
                SigningPower(
                    public_key=target_ursula.stamp.as_umbral_pubkey()))

        if attach_transacting_key:
            cls.attach_transacting_key(
                blockchain=target_ursula.policy_agent.blockchain)

        vladimir = cls(
            is_me=True,
            crypto_power=crypto_power,
            db_filepath=cls.db_filepath,
            domains=[TEMPORARY_DOMAIN],
            block_until_ready=False,
            start_working_now=False,
            rest_host=target_ursula.rest_interface.host,
            rest_port=target_ursula.rest_interface.port,
            certificate=target_ursula.rest_server_certificate(),
            network_middleware=cls.network_middleware,
            checksum_address=cls.fraud_address,
            worker_address=cls.fraud_address,
            ######### Asshole.
            timestamp=target_ursula._timestamp,
            interface_signature=target_ursula._interface_signature,
            #########
        )
        return vladimir
Exemplo n.º 3
0
    def from_public_keys(cls,
                         powers_and_material: Dict = None,
                         verifying_key: Union[bytes, UmbralPublicKey] = None,
                         encrypting_key: Union[bytes, UmbralPublicKey] = None,
                         federated_only: bool = True,
                         *args,
                         **kwargs) -> 'Character':
        """
        Sometimes we discover a Character and, at the same moment,
        learn the public parts of more of their powers. Here, we take a Dict
        (powers_and_material) in the format {CryptoPowerUp class: material},
        where material can be bytes or UmbralPublicKey.

        Each item in the collection will have the CryptoPowerUp instantiated
        with the given material, and the resulting CryptoPowerUp instance
        consumed by the Character.

        Alternatively, you can pass directly a verifying public key
        (for SigningPower) and/or an encrypting public key (for DecryptionPower).

        # TODO: Need to be federated only until we figure out the best way to get the checksum_address in here.
        """

        crypto_power = CryptoPower()

        if powers_and_material is None:
            powers_and_material = dict()

        if verifying_key:
            powers_and_material[SigningPower] = verifying_key
        if encrypting_key:
            powers_and_material[DecryptingPower] = encrypting_key

        for power_up, public_key in powers_and_material.items():
            try:
                umbral_key = UmbralPublicKey.from_bytes(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(public_key=umbral_key))

        return cls(is_me=False,
                   federated_only=federated_only,
                   crypto_power=crypto_power,
                   *args,
                   **kwargs)
Exemplo n.º 4
0
    def from_public_keys(cls, powers_and_keys: Dict, *args, **kwargs):
        """
        Sometimes we discover a Character and, at the same moment, learn one or
        more of their public keys. Here, we take a Dict
        (powers_and_key_bytes) in the following format:
        {CryptoPowerUp class: public_key_bytes}

        Each item in the collection will have the CryptoPowerUp instantiated
        with the public_key_bytes, and the resulting CryptoPowerUp instance
        consumed by the Character.
        """
        crypto_power = CryptoPower()

        for power_up, public_key in powers_and_keys.items():
            try:
                umbral_key = UmbralPublicKey(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(pubkey=umbral_key))

        return cls(is_me=False, crypto_power=crypto_power, *args, **kwargs)
Exemplo n.º 5
0
    def from_target_ursula(cls,
                           target_ursula: Ursula,
                           claim_signing_key: bool = False,
                           attach_transacting_key: bool = True) -> 'Vladimir':
        """
        Sometimes Vladimir seeks to attack or imitate a *specific* target Ursula.

        TODO: This is probably a more instructive method if it takes a bytes representation instead of the entire Ursula.
        """
        crypto_power = CryptoPower(
            power_ups=target_ursula._default_crypto_powerups)

        if claim_signing_key:
            crypto_power.consume_power_up(
                SigningPower(pubkey=target_ursula.stamp.as_umbral_pubkey()))

        if attach_transacting_key:
            cls.attach_transacting_key(blockchain=target_ursula.blockchain)

        vladimir = cls(
            is_me=True,
            crypto_power=crypto_power,
            db_name=cls.db_name,
            db_filepath=cls.db_name,
            rest_host=target_ursula.rest_information()[0].host,
            rest_port=target_ursula.rest_information()[0].port,
            certificate=target_ursula.rest_server_certificate(),
            network_middleware=cls.network_middleware,
            checksum_address=cls.fraud_address,
            ######### Asshole.
            timestamp=target_ursula._timestamp,
            interface_signature=target_ursula._interface_signature_object,
            #########
        )

        cls.attach_transacting_key(blockchain=target_ursula.blockchain)

        return vladimir
def test_actor_without_signing_power_cannot_sign():
    """
    We can create a Character with no real CryptoPower to speak of.
    This Character can't even sign a message.
    """
    cannot_sign = CryptoPower(power_ups=[])
    non_signer = Character(crypto_power=cannot_sign)

    # The non-signer's stamp doesn't work for signing...
    with pytest.raises(NoSigningPower) as e_info:
        non_signer.stamp("something")

    # ...or as a way to cast the (non-existent) public key to bytes.
    with pytest.raises(NoSigningPower) as e_info:
        bytes(non_signer.stamp)
Exemplo n.º 7
0
    def from_target_ursula(cls, target_ursula, claim_signing_key=False):
        """
        Sometimes Vladimir seeks to attack or imitate a *specific* target Ursula.

        TODO: This is probably a more instructive method if it takes a bytes representation instead of the entire Ursula.
        """
        crypto_power = CryptoPower(power_ups=Ursula._default_crypto_powerups)

        if claim_signing_key:
            crypto_power.consume_power_up(
                SigningPower(pubkey=target_ursula.stamp.as_umbral_pubkey()))

        vladimir = cls(crypto_power=crypto_power,
                       rest_host=target_ursula.rest_information()[0].host,
                       rest_port=target_ursula.rest_information()[0].port,
                       checksum_address=cls.fraud_address,
                       certificate=target_ursula.rest_server_certificate(),
                       is_me=False)

        vladimir._interface_signature_object = target_ursula._interface_signature_object  # Asshole.

        cls.attach_transacting_key(blockchain=target_ursula.blockchain)

        return vladimir
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
    def __init__(self,
                 domains: Set = (GLOBAL_DOMAIN, ),
                 is_me: bool = True,
                 federated_only: bool = False,
                 blockchain: Blockchain = None,
                 checksum_public_address: bytes = NO_BLOCKCHAIN_CONNECTION.
                 bool_value(False),
                 network_middleware: RestMiddleware = None,
                 keyring_dir: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        self.federated_only = federated_only  # type: bool

        #
        # Powers
        #
        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        self._checksum_address = checksum_public_address
        #
        # Self-Character
        #
        if is_me is True:
            if not self.federated_only:
                self.blockchain = blockchain or Blockchain.connect()

            self.keyring_dir = keyring_dir  # type: str
            self.treasure_maps = {}  # type: dict
            self.network_middleware = network_middleware or RestMiddleware()

            #
            # Signing Power
            #
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Learner
            #
            Learner.__init__(self,
                             domains=domains,
                             network_middleware=network_middleware,
                             *args,
                             **kwargs)

        #
        # Stranger-Character
        #
        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stanger-Character."
                )
            self._stamp = StrangerStamp(self.public_keys(SigningPower))
            self.keyring_dir = STRANGER
            self.network_middleware = STRANGER

        #
        # Decentralized
        #
        if not federated_only:
            if not checksum_public_address:
                raise ValueError(
                    "No checksum_public_address provided while running in a non-federated mode."
                )
            else:
                self._checksum_address = checksum_public_address  # TODO: Check that this matches BlockchainPower
        #
        # Federated
        #
        elif federated_only:
            try:
                self._set_checksum_address()  # type: str
            except NoSigningPower:
                self._checksum_address = NO_BLOCKCHAIN_CONNECTION
            if checksum_public_address:
                # We'll take a checksum address, as long as it matches their singing key
                if not checksum_public_address == self.checksum_public_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_public_address))

        #
        # Nicknames
        #
        try:
            self.nickname, self.nickname_metadata = nickname_from_seed(
                self.checksum_public_address)
        except SigningPower.not_found_error:
            if self.federated_only:
                self.nickname = self.nickname_metadata = NO_NICKNAME
            else:
                raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL
Exemplo n.º 10
0
class Character(Learner):
    """
    A base-class for any character in our cryptography protocol narrative.
    """

    _default_crypto_powerups = None
    _stamp = None
    _crashed = False

    from nucypher.network.protocols import SuspiciousActivity  # Ship this exception with every Character.
    from nucypher.crypto.signing import InvalidSignature

    def __init__(self,
                 domains: Set = (GLOBAL_DOMAIN, ),
                 is_me: bool = True,
                 federated_only: bool = False,
                 blockchain: Blockchain = None,
                 checksum_public_address: bytes = NO_BLOCKCHAIN_CONNECTION.
                 bool_value(False),
                 network_middleware: RestMiddleware = None,
                 keyring_dir: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        self.federated_only = federated_only  # type: bool

        #
        # Powers
        #
        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        self._checksum_address = checksum_public_address
        #
        # Self-Character
        #
        if is_me is True:
            if not self.federated_only:
                self.blockchain = blockchain or Blockchain.connect()

            self.keyring_dir = keyring_dir  # type: str
            self.treasure_maps = {}  # type: dict
            self.network_middleware = network_middleware or RestMiddleware()

            #
            # Signing Power
            #
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Learner
            #
            Learner.__init__(self,
                             domains=domains,
                             network_middleware=network_middleware,
                             *args,
                             **kwargs)

        #
        # Stranger-Character
        #
        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stanger-Character."
                )
            self._stamp = StrangerStamp(self.public_keys(SigningPower))
            self.keyring_dir = STRANGER
            self.network_middleware = STRANGER

        #
        # Decentralized
        #
        if not federated_only:
            if not checksum_public_address:
                raise ValueError(
                    "No checksum_public_address provided while running in a non-federated mode."
                )
            else:
                self._checksum_address = checksum_public_address  # TODO: Check that this matches BlockchainPower
        #
        # Federated
        #
        elif federated_only:
            try:
                self._set_checksum_address()  # type: str
            except NoSigningPower:
                self._checksum_address = NO_BLOCKCHAIN_CONNECTION
            if checksum_public_address:
                # We'll take a checksum address, as long as it matches their singing key
                if not checksum_public_address == self.checksum_public_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_public_address))

        #
        # Nicknames
        #
        try:
            self.nickname, self.nickname_metadata = nickname_from_seed(
                self.checksum_public_address)
        except SigningPower.not_found_error:
            if self.federated_only:
                self.nickname = self.nickname_metadata = NO_NICKNAME
            else:
                raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL

    def __eq__(self, other) -> bool:
        try:
            other_stamp = other.stamp
        except (AttributeError, NoSigningPower):
            return False
        return bytes(self.stamp) == bytes(other_stamp)

    def __hash__(self):
        return int.from_bytes(bytes(self.stamp), byteorder="big")

    def __repr__(self):
        r = "({})⇀{}↽ ({})"
        try:
            r = r.format(self.__class__.__name__, self.nickname,
                         self.checksum_public_address)
        except NoSigningPower:  # TODO: ....yeah?
            r = r.format(self.__class__.__name__, self.nickname)
        return r

    @property
    def name(self):
        return self.__class__.__name__

    @property
    def rest_interface(self):
        return self.rest_server.rest_url()

    @property
    def stamp(self):
        if self._stamp is NO_SIGNING_POWER:
            raise NoSigningPower
        elif not self._stamp:
            raise AttributeError("SignatureStamp has not been set up yet.")
        else:
            return self._stamp

    @property
    def canonical_public_address(self):
        return to_canonical_address(self.checksum_public_address)

    @canonical_public_address.setter
    def canonical_public_address(self, address_bytes):
        self._checksum_address = to_checksum_address(address_bytes)

    @property
    def checksum_public_address(self):
        if self._checksum_address is NO_BLOCKCHAIN_CONNECTION:
            self._set_checksum_address()
        return self._checksum_address

    @classmethod
    def from_config(cls, config, **overrides) -> 'Character':
        return config.produce(**overrides)

    @classmethod
    def from_public_keys(cls,
                         powers_and_material: Dict,
                         federated_only=True,
                         *args,
                         **kwargs) -> 'Character':
        """
        Sometimes we discover a Character and, at the same moment,
        learn the public parts of more of their powers. Here, we take a Dict
        (powers_and_key_bytes) in the following format:
        {CryptoPowerUp class: public_material_bytes}

        Each item in the collection will have the CryptoPowerUp instantiated
        with the public_material_bytes, and the resulting CryptoPowerUp instance
        consumed by the Character.

        # TODO: Need to be federated only until we figure out the best way to get the checksum_public_address in here.

        """

        crypto_power = CryptoPower()

        for power_up, public_key in powers_and_material.items():
            try:
                umbral_key = UmbralPublicKey(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(pubkey=umbral_key))

        return cls(is_me=False,
                   federated_only=federated_only,
                   crypto_power=crypto_power,
                   *args,
                   **kwargs)

    def store_metadata(self, filepath: str) -> str:
        """
        Save this node to the disk.
        :param filepath: Output filepath to save node metadata.
        :return: Output filepath
        """

        return self.node_storage.store_node_metadata(node=self,
                                                     filepath=filepath)

    def encrypt_for(
        self,
        recipient: 'Character',
        plaintext: bytes,
        sign: bool = True,
        sign_plaintext=True,
    ) -> tuple:
        """
        Encrypts plaintext for recipient actor. Optionally signs the message as well.

        :param recipient: The character whose public key will be used to encrypt
            cleartext.
        :param plaintext: The secret to be encrypted.
        :param sign: Whether or not to sign the message.
        :param sign_plaintext: When signing, the cleartext is signed if this is
            True,  Otherwise, the resulting ciphertext is signed.

        :return: A tuple, (ciphertext, signature).  If sign==False,
            then signature will be NOT_SIGNED.
        """
        signer = self.stamp if sign else DO_NOT_SIGN

        message_kit, signature = encrypt_and_sign(
            recipient_pubkey_enc=recipient.public_keys(DecryptingPower),
            plaintext=plaintext,
            signer=signer,
            sign_plaintext=sign_plaintext)
        return message_kit, signature

    def verify_from(
        self,
        stranger: 'Character',
        message_kit: Union[UmbralMessageKit, bytes],
        signature: Signature = None,
        decrypt=False,
        label=None,
    ) -> bytes:
        """
        Inverse of encrypt_for.

        :param stranger: A Character instance representing
            the actor whom the sender claims to be.  We check the public key
            owned by this Character instance to verify.
        :param message_kit: the message to be (perhaps decrypted and) verified.
        :param signature: The signature to check.
        :param decrypt: Whether or not to decrypt the messages.

        :return: Whether or not the signature is valid, the decrypted plaintext or NO_DECRYPTION_PERFORMED
        """
        sender_pubkey_sig = stranger.stamp.as_umbral_pubkey()
        with suppress(AttributeError):
            if message_kit.sender_pubkey_sig:
                if not message_kit.sender_pubkey_sig == sender_pubkey_sig:
                    raise ValueError(
                        "This MessageKit doesn't appear to have come from {}".
                        format(stranger))

        signature_from_kit = None

        if decrypt:

            # We are decrypting the message; let's do that first and see what the sig header says.
            cleartext_with_sig_header = self.decrypt(message_kit=message_kit,
                                                     label=label)
            sig_header, cleartext = default_constant_splitter(
                cleartext_with_sig_header, return_remainder=True)
            if sig_header == SIGNATURE_IS_ON_CIPHERTEXT:
                # THe ciphertext is what is signed - note that for later.
                message = message_kit.ciphertext
                if not signature:
                    raise ValueError(
                        "Can't check a signature on the ciphertext if don't provide one."
                    )
            elif sig_header == SIGNATURE_TO_FOLLOW:
                # The signature follows in this cleartext - split it off.
                signature_from_kit, cleartext = signature_splitter(
                    cleartext, return_remainder=True)
                message = cleartext
        else:
            # Not decrypting - the message is the object passed in as a message kit.  Cast it.
            message = bytes(message_kit)
            cleartext = NO_DECRYPTION_PERFORMED

        if signature and signature_from_kit:
            if signature != signature_from_kit:
                raise ValueError(
                    "The MessageKit has a Signature, but it's not the same one you provided.  Something's up."
                )

        signature_to_use = signature or signature_from_kit

        if signature_to_use:
            is_valid = signature_to_use.verify(message, sender_pubkey_sig)
            if not is_valid:
                raise stranger.InvalidSignature(
                    "Signature for message isn't valid: {}".format(
                        signature_to_use))
        else:
            raise self.InvalidSignature(
                "No signature provided -- signature presumed invalid.")

        return cleartext

    def decrypt(self,
                message_kit: UmbralMessageKit,
                label: Optional[bytes] = None) -> bytes:
        if label and DelegatingPower in self._default_crypto_powerups:
            delegating_power = self._crypto_power.power_ups(DelegatingPower)
            decrypting_power = delegating_power.get_decrypting_power_from_label(
                label)
        else:
            decrypting_power = self._crypto_power.power_ups(DecryptingPower)
        return decrypting_power.decrypt(message_kit)

    def sign(self, message):
        return self._crypto_power.power_ups(SigningPower).sign(message)

    def public_keys(self, power_up_class: ClassVar):
        """
        Pass a power_up_class, get the public material for this Character which corresponds to that
        class - whatever type of object that may be.

        If the Character doesn't have the power corresponding to that class, raises the
        appropriate PowerUpError (ie, NoSigningPower or NoDecryptingPower).
        """
        power_up = self._crypto_power.power_ups(power_up_class)
        return power_up.public_key()

    def _set_checksum_address(self):

        if self.federated_only:
            verifying_key = self.public_keys(SigningPower)
            uncompressed_bytes = verifying_key.to_bytes(is_compressed=False)
            without_prefix = uncompressed_bytes[1:]
            verifying_key_as_eth_key = EthKeyAPI.PublicKey(without_prefix)
            public_address = verifying_key_as_eth_key.to_checksum_address()
        else:
            try:
                public_address = to_checksum_address(
                    self.canonical_public_address)
            except TypeError:
                raise TypeError(
                    "You can't use a decentralized character without a _checksum_address."
                )
            except NotImplementedError:
                raise TypeError(
                    "You can't use a plain Character in federated mode - you need to implement ether_address."
                )

        self._checksum_address = public_address
Exemplo n.º 11
0
    def __init__(self,
                 domains: Set = None,
                 known_node_class: object = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = NO_BLOCKCHAIN_CONNECTION.bool_value(
                     False),
                 network_middleware: RestMiddleware = None,
                 keyring: NucypherKeyring = None,
                 keyring_root: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 provider_uri: str = None,
                 signer: Signer = None,
                 registry: BaseContractRegistry = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        #
        # Operating Mode
        if hasattr(self, '_interface_class'
                   ):  # TODO: have argument about meaning of 'lawful'
            #                                         and whether maybe only Lawful characters have an interface
            self.interface = self._interface_class(character=self)
        if is_me:
            if not known_node_class:
                # Once in a while, in tests or demos, we init a plain Character who doesn't already know about its node class.
                from nucypher.characters.lawful import Ursula
                known_node_class = Ursula
            # If we're federated only, we assume that all other nodes in our domain are as well.
            known_node_class.set_federated_mode(federated_only)
        else:
            # What an awful hack.  The last convulsions of #466.
            # TODO: Anything else.
            with suppress(AttributeError):
                federated_only = known_node_class._federated_only_instances

        if federated_only:
            if registry or provider_uri:
                raise ValueError(
                    f"Cannot init federated-only character with {registry or provider_uri}."
                )
        self.federated_only = bool(federated_only)  # type: bool

        #
        # Powers
        #

        # Derive powers from keyring
        if keyring_root and keyring:
            if keyring_root != keyring.keyring_root:
                raise ValueError("Inconsistent keyring root directory path")
        if keyring:
            keyring_root, checksum_address = keyring.keyring_root, keyring.checksum_address
            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keyring.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keyring_root = keyring_root
        self.keyring = keyring

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        self._checksum_address = checksum_address

        # Fleet and Blockchain Connection (Everyone)
        if not domains:
            domains = {CharacterConfiguration.DEFAULT_DOMAIN}

        #
        # Self-Character
        #

        if is_me:
            self.treasure_maps = {}  # type: dict

            #
            # Signing Power
            #
            self.signer = signer
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Blockchain
            #
            self.provider_uri = provider_uri
            if not self.federated_only:
                self.registry = registry or InMemoryContractRegistry.from_latest_publication(
                    network=list(domains)[0])  #TODO: #1580
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            # REST
            self.network_middleware = network_middleware or RestMiddleware(
                registry=self.registry)

            #
            # Learner
            #
            Learner.__init__(self,
                             domains=domains,
                             network_middleware=self.network_middleware,
                             node_class=known_node_class,
                             *args,
                             **kwargs)

        #
        # Stranger-Character
        #

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stranger-Character."
                )

            if registry is not None:
                raise TypeError(
                    "Registry cannot be attached to stranger-Characters.")

            verifying_key = self.public_keys(SigningPower)
            self._stamp = StrangerStamp(verifying_key)
            self.keyring_root = STRANGER
            self.network_middleware = STRANGER

        #
        # Decentralized
        #
        if not federated_only:
            self._checksum_address = checksum_address  # TODO: Check that this matches TransactingPower

        #
        # Federated
        #
        elif federated_only:
            try:
                self._set_checksum_address()  # type: str
            except NoSigningPower:
                self._checksum_address = NO_BLOCKCHAIN_CONNECTION
            if checksum_address:
                # We'll take a checksum address, as long as it matches their singing key
                if not checksum_address == self.checksum_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_address))

        #
        # Nicknames
        #
        if self._checksum_address is NO_BLOCKCHAIN_CONNECTION and not self.federated_only and not is_me:
            # Sometimes we don't care about the nickname.  For example, if Alice is granting to Bob, she usually
            # doesn't know or care about his wallet.  Maybe this needs to change?
            # Currently, if this is a stranger and there's no blockchain connection, we assign NO_NICKNAME:
            self.nickname = self.nickname_metadata = NO_NICKNAME
        else:
            try:
                self.nickname, self.nickname_metadata = nickname_from_seed(
                    self.checksum_address)
            except SigningPower.not_found_error:  # TODO: Handle NO_BLOCKCHAIN_CONNECTION more coherently - #1547
                if self.federated_only:
                    self.nickname = self.nickname_metadata = NO_NICKNAME
                else:
                    raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL
Exemplo n.º 12
0
class Character(Learner):
    """
    A base-class for any character in our cryptography protocol narrative.
    """

    _display_name_template = "({})⇀{}↽ ({})"  # Used in __repr__ and in cls.from_bytes
    _default_crypto_powerups = None
    _stamp = None
    _crashed = False

    from nucypher.network.protocols import SuspiciousActivity  # Ship this exception with every Character.

    def __init__(self,
                 domains: Set = None,
                 known_node_class: object = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = NO_BLOCKCHAIN_CONNECTION.bool_value(
                     False),
                 network_middleware: RestMiddleware = None,
                 keyring: NucypherKeyring = None,
                 keyring_root: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 provider_uri: str = None,
                 signer: Signer = None,
                 registry: BaseContractRegistry = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        #
        # Operating Mode
        if hasattr(self, '_interface_class'
                   ):  # TODO: have argument about meaning of 'lawful'
            #                                         and whether maybe only Lawful characters have an interface
            self.interface = self._interface_class(character=self)
        if is_me:
            if not known_node_class:
                # Once in a while, in tests or demos, we init a plain Character who doesn't already know about its node class.
                from nucypher.characters.lawful import Ursula
                known_node_class = Ursula
            # If we're federated only, we assume that all other nodes in our domain are as well.
            known_node_class.set_federated_mode(federated_only)
        else:
            # What an awful hack.  The last convulsions of #466.
            # TODO: Anything else.
            with suppress(AttributeError):
                federated_only = known_node_class._federated_only_instances

        if federated_only:
            if registry or provider_uri:
                raise ValueError(
                    f"Cannot init federated-only character with {registry or provider_uri}."
                )
        self.federated_only = bool(federated_only)  # type: bool

        #
        # Powers
        #

        # Derive powers from keyring
        if keyring_root and keyring:
            if keyring_root != keyring.keyring_root:
                raise ValueError("Inconsistent keyring root directory path")
        if keyring:
            keyring_root, checksum_address = keyring.keyring_root, keyring.checksum_address
            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keyring.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keyring_root = keyring_root
        self.keyring = keyring

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        self._checksum_address = checksum_address

        # Fleet and Blockchain Connection (Everyone)
        if not domains:
            domains = {CharacterConfiguration.DEFAULT_DOMAIN}

        #
        # Self-Character
        #

        if is_me:
            self.treasure_maps = {}  # type: dict

            #
            # Signing Power
            #
            self.signer = signer
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Blockchain
            #
            self.provider_uri = provider_uri
            if not self.federated_only:
                self.registry = registry or InMemoryContractRegistry.from_latest_publication(
                    network=list(domains)[0])  #TODO: #1580
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            # REST
            self.network_middleware = network_middleware or RestMiddleware(
                registry=self.registry)

            #
            # Learner
            #
            Learner.__init__(self,
                             domains=domains,
                             network_middleware=self.network_middleware,
                             node_class=known_node_class,
                             *args,
                             **kwargs)

        #
        # Stranger-Character
        #

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stranger-Character."
                )

            if registry is not None:
                raise TypeError(
                    "Registry cannot be attached to stranger-Characters.")

            verifying_key = self.public_keys(SigningPower)
            self._stamp = StrangerStamp(verifying_key)
            self.keyring_root = STRANGER
            self.network_middleware = STRANGER

        #
        # Decentralized
        #
        if not federated_only:
            self._checksum_address = checksum_address  # TODO: Check that this matches TransactingPower

        #
        # Federated
        #
        elif federated_only:
            try:
                self._set_checksum_address()  # type: str
            except NoSigningPower:
                self._checksum_address = NO_BLOCKCHAIN_CONNECTION
            if checksum_address:
                # We'll take a checksum address, as long as it matches their singing key
                if not checksum_address == self.checksum_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_address))

        #
        # Nicknames
        #
        if self._checksum_address is NO_BLOCKCHAIN_CONNECTION and not self.federated_only and not is_me:
            # Sometimes we don't care about the nickname.  For example, if Alice is granting to Bob, she usually
            # doesn't know or care about his wallet.  Maybe this needs to change?
            # Currently, if this is a stranger and there's no blockchain connection, we assign NO_NICKNAME:
            self.nickname = self.nickname_metadata = NO_NICKNAME
        else:
            try:
                self.nickname, self.nickname_metadata = nickname_from_seed(
                    self.checksum_address)
            except SigningPower.not_found_error:  # TODO: Handle NO_BLOCKCHAIN_CONNECTION more coherently - #1547
                if self.federated_only:
                    self.nickname = self.nickname_metadata = NO_NICKNAME
                else:
                    raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL

    def __eq__(self, other) -> bool:
        try:
            other_stamp = other.stamp
        except (AttributeError, NoSigningPower):
            return False
        return bytes(self.stamp) == bytes(other_stamp)

    def __hash__(self):
        return int.from_bytes(bytes(self.stamp), byteorder="big")

    def __repr__(self):
        r = self._display_name_template
        try:
            r = r.format(self.__class__.__name__, self.nickname,
                         self.checksum_address)
        except (
                NoSigningPower, TypeError
        ):  # TODO: ....yeah?  We can probably do better for a repr here.
            r = f"({self.__class__.__name__})⇀{self.nickname}↽"
        return r

    @property
    def name(self):
        return self.__class__.__name__

    @property
    def stamp(self):
        if self._stamp is NO_SIGNING_POWER:
            raise NoSigningPower
        elif not self._stamp:
            raise AttributeError("SignatureStamp has not been set up yet.")
        else:
            return self._stamp

    @property
    def canonical_public_address(self):
        return to_canonical_address(self._checksum_address)

    @canonical_public_address.setter
    def canonical_public_address(self, address_bytes):
        self._checksum_address = to_checksum_address(address_bytes)

    @property
    def checksum_address(self):
        if self._checksum_address is NO_BLOCKCHAIN_CONNECTION:
            self._set_checksum_address()
        return self._checksum_address

    @classmethod
    def from_config(cls, config, **overrides) -> 'Character':
        return config.produce(**overrides)

    @classmethod
    def from_public_keys(cls,
                         powers_and_material: Dict = None,
                         verifying_key: Union[bytes, UmbralPublicKey] = None,
                         encrypting_key: Union[bytes, UmbralPublicKey] = None,
                         *args,
                         **kwargs) -> 'Character':
        """
        Sometimes we discover a Character and, at the same moment,
        learn the public parts of more of their powers. Here, we take a Dict
        (powers_and_material) in the format {CryptoPowerUp class: material},
        where material can be bytes or UmbralPublicKey.

        Each item in the collection will have the CryptoPowerUp instantiated
        with the given material, and the resulting CryptoPowerUp instance
        consumed by the Character.

        Alternatively, you can pass directly a verifying public key
        (for SigningPower) and/or an encrypting public key (for DecryptionPower).
        """
        crypto_power = CryptoPower()

        if powers_and_material is None:
            powers_and_material = dict()

        if verifying_key:
            powers_and_material[SigningPower] = verifying_key
        if encrypting_key:
            powers_and_material[DecryptingPower] = encrypting_key

        for power_up, public_key in powers_and_material.items():
            try:
                umbral_key = UmbralPublicKey.from_bytes(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(public_key=umbral_key))

        return cls(is_me=False, crypto_power=crypto_power, *args, **kwargs)

    def store_metadata(self, filepath: str) -> str:
        """
        Save this node to the disk.
        :param filepath: Output filepath to save node metadata.
        :return: Output filepath
        """

        return self.node_storage.store_node_metadata(node=self,
                                                     filepath=filepath)

    def encrypt_for(
        self,
        recipient: 'Character',
        plaintext: bytes,
        sign: bool = True,
        sign_plaintext=True,
    ) -> tuple:
        """
        Encrypts plaintext for recipient actor. Optionally signs the message as well.

        :param recipient: The character whose public key will be used to encrypt
            cleartext.
        :param plaintext: The secret to be encrypted.
        :param sign: Whether or not to sign the message.
        :param sign_plaintext: When signing, the cleartext is signed if this is
            True,  Otherwise, the resulting ciphertext is signed.

        :return: A tuple, (ciphertext, signature).  If sign==False,
            then signature will be NOT_SIGNED.
        """
        signer = self.stamp if sign else DO_NOT_SIGN

        message_kit, signature = encrypt_and_sign(
            recipient_pubkey_enc=recipient.public_keys(DecryptingPower),
            plaintext=plaintext,
            signer=signer,
            sign_plaintext=sign_plaintext)
        return message_kit, signature

    def verify_from(
        self,
        stranger: 'Character',
        message_kit: Union[UmbralMessageKit, bytes],
        signature: Signature = None,
        decrypt=False,
        label=None,
    ) -> bytes:
        """
        Inverse of encrypt_for.

        :param stranger: A Character instance representing
            the actor whom the sender claims to be.  We check the public key
            owned by this Character instance to verify.
        :param message_kit: the message to be (perhaps decrypted and) verified.
        :param signature: The signature to check.
        :param decrypt: Whether or not to decrypt the messages.
        :param label: A label used for decrypting messages encrypted under its associated policy encrypting key

        :return: Whether or not the signature is valid, the decrypted plaintext or NO_DECRYPTION_PERFORMED
        """

        #
        # Optional Sanity Check
        #

        # In the spirit of duck-typing, we want to accept a message kit object, or bytes
        # If the higher-order object MessageKit is passed, we can perform an additional
        # eager sanity check before performing decryption.

        with contextlib.suppress(AttributeError):
            sender_verifying_key = stranger.stamp.as_umbral_pubkey()
            if message_kit.sender_verifying_key:
                if not message_kit.sender_verifying_key == sender_verifying_key:
                    raise ValueError(
                        "This MessageKit doesn't appear to have come from {}".
                        format(stranger))

        #
        # Decrypt
        #

        signature_from_kit = None
        if decrypt:

            # We are decrypting the message; let's do that first and see what the sig header says.
            cleartext_with_sig_header = self.decrypt(message_kit=message_kit,
                                                     label=label)
            sig_header, cleartext = default_constant_splitter(
                cleartext_with_sig_header, return_remainder=True)

            if sig_header == SIGNATURE_IS_ON_CIPHERTEXT:
                # The ciphertext is what is signed - note that for later.
                message = message_kit.ciphertext
                if not signature:
                    raise ValueError(
                        "Can't check a signature on the ciphertext if don't provide one."
                    )

            elif sig_header == SIGNATURE_TO_FOLLOW:
                # The signature follows in this cleartext - split it off.
                signature_from_kit, cleartext = signature_splitter(
                    cleartext, return_remainder=True)
                message = cleartext

        else:
            # Not decrypting - the message is the object passed in as a message kit.  Cast it.
            message = bytes(message_kit)
            cleartext = NO_DECRYPTION_PERFORMED

        #
        # Verify Signature
        #

        if signature and signature_from_kit:
            if signature != signature_from_kit:
                raise ValueError(
                    "The MessageKit has a Signature, but it's not the same one you provided.  Something's up."
                )

        signature_to_use = signature or signature_from_kit
        if signature_to_use:
            is_valid = signature_to_use.verify(
                message,
                sender_verifying_key)  # FIXME: Message is undefined here
            if not is_valid:
                raise InvalidSignature(
                    "Signature for message isn't valid: {}".format(
                        signature_to_use))
        else:
            raise InvalidSignature(
                "No signature provided -- signature presumed invalid.")

        return cleartext

    def decrypt(self,
                message_kit: UmbralMessageKit,
                label: Optional[bytes] = None) -> bytes:
        if label and DelegatingPower in self._default_crypto_powerups:
            delegating_power = self._crypto_power.power_ups(DelegatingPower)
            decrypting_power = delegating_power.get_decrypting_power_from_label(
                label)
        else:
            decrypting_power = self._crypto_power.power_ups(DecryptingPower)
        return decrypting_power.decrypt(message_kit)

    def sign(self, message):
        return self._crypto_power.power_ups(SigningPower).sign(message)

    def public_keys(self, power_up_class: ClassVar):
        """
        Pass a power_up_class, get the public material for this Character which corresponds to that
        class - whatever type of object that may be.

        If the Character doesn't have the power corresponding to that class, raises the
        appropriate PowerUpError (ie, NoSigningPower or NoDecryptingPower).
        """
        power_up = self._crypto_power.power_ups(power_up_class)
        return power_up.public_key()

    def _set_checksum_address(self):

        if self.federated_only:
            verifying_key = self.public_keys(SigningPower)
            uncompressed_bytes = verifying_key.to_bytes(is_compressed=False)
            without_prefix = uncompressed_bytes[1:]
            verifying_key_as_eth_key = EthKeyAPI.PublicKey(without_prefix)
            public_address = verifying_key_as_eth_key.to_checksum_address()
        else:
            try:
                public_address = to_checksum_address(
                    self.canonical_public_address)
            except TypeError:
                raise TypeError(
                    "You can't use a decentralized character without a _checksum_address."
                )
            except NotImplementedError:
                raise TypeError(
                    "You can't use a plain Character in federated mode - you need to implement ether_address."
                )

        self._checksum_address = public_address

    def make_rpc_controller(self, crash_on_error: bool = False):
        app_name = bytes(self.stamp).hex()[:6]
        controller = JSONRPCController(app_name=app_name,
                                       crash_on_error=crash_on_error,
                                       interface=self.interface)

        self.controller = controller
        return controller

    def make_cli_controller(self, crash_on_error: bool = False):
        app_name = bytes(self.stamp).hex()[:6]
        controller = CLIController(app_name=app_name,
                                   crash_on_error=crash_on_error,
                                   interface=self.interface)

        self.controller = controller
        return controller
Exemplo n.º 13
0
    def __init__(self,
                 domain: str = None,
                 known_node_class: object = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = None,
                 network_middleware: RestMiddleware = None,
                 keyring: NucypherKeyring = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 provider_uri: str = None,
                 signer: Signer = None,
                 registry: BaseContractRegistry = None,
                 include_self_in_the_state: bool = False,
                 *args,
                 **kwargs) -> None:
        """

        A participant in the cryptological drama (a screenplay, if you like) of NuCypher.

        Characters can represent users, nodes, wallets, offline devices, or other objects of varying levels of abstraction.

        The Named Characters use this class as a Base, and achieve their individuality from additional methods and PowerUps.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        #
        # Prologue of the federation
        #

        # FIXME: excuse me... can I speak to the manager?
        if is_me:
            # If this is a federated-is_me-character, assume everyone else is too.
            self._set_known_node_class(known_node_class, federated_only)
        else:
            # What an awful hack.  The last convulsions of #466.  # TODO: Anything else.
            with suppress(AttributeError):
                federated_only = known_node_class._federated_only_instances

        if federated_only:
            if registry or provider_uri:
                raise ValueError(
                    f"Cannot init federated-only character with {registry or provider_uri}."
                )
        self.federated_only: bool = federated_only

        ##########################################

        #
        # Keys & Powers
        #

        if keyring:
            keyring_root, keyring_checksum_address = keyring.keyring_root, keyring.checksum_address
            if checksum_address and (keyring_checksum_address !=
                                     checksum_address):
                raise ValueError(
                    f"Provided checksum address {checksum_address} "
                    f"does not match character's keyring checksum address {keyring_checksum_address}"
                )
            checksum_address = keyring_checksum_address

            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keyring.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keyring = keyring

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        #
        # Self
        #

        if is_me:

            # Signing Power
            self.signer = signer
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            # Blockchainy
            if not self.federated_only:
                self.provider_uri = provider_uri

                # TODO: Implicit / lazy blockchain connection here?
                # if not BlockchainInterfaceFactory.is_interface_initialized(provider_uri=provider_uri):
                #     BlockchainInterfaceFactory.initialize_interface(provider_uri=provider_uri)

                self.registry = registry or InMemoryContractRegistry.from_latest_publication(
                    network=domain)  # See #1580
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            # REST
            self.network_middleware = network_middleware or RestMiddleware(
                registry=self.registry)

            # Learner
            Learner.__init__(
                self,
                domain=domain,
                network_middleware=self.network_middleware,
                node_class=known_node_class,
                include_self_in_the_state=include_self_in_the_state,
                *args,
                **kwargs)

            if self.federated_only:
                try:
                    derived_federated_address = self.derive_federated_address()
                except NoSigningPower:
                    derived_federated_address = NO_SIGNING_POWER.bool_value(
                        False)

                if checksum_address and (checksum_address !=
                                         derived_federated_address):
                    raise ValueError(
                        f"Provided checksum address {checksum_address} "
                        f"does not match federated character's verifying key {derived_federated_address}"
                    )
                checksum_address = derived_federated_address

            self.checksum_address = checksum_address

        #
        # Stranger
        #

        else:
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stranger-Character."
                )

            if registry is not None:
                raise TypeError(
                    "Registry cannot be attached to stranger-Characters.")

            verifying_key = self.public_keys(SigningPower)
            self._stamp = StrangerStamp(verifying_key)
            self.keyring_root = STRANGER
            self.network_middleware = STRANGER
            self.checksum_address = checksum_address

        self.__setup_nickname(is_me=is_me)

        # Character Control
        # TODO: have argument about meaning of 'lawful' and whether maybe only Lawful characters have an interface
        if hasattr(self, '_interface_class'):
            # Controller Interface
            self.interface = self._interface_class(character=self)
        self.controller = NO_CONTROL_PROTOCOL
Exemplo n.º 14
0
    def __init__(self,
                 domains: Set = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
                 network_middleware: RestMiddleware = None,
                 keyring: NucypherKeyring = None,
                 keyring_root: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 provider_uri: str = None,
                 registry: BaseContractRegistry = None,
                 *args, **kwargs
                 ) -> None:

        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        #
        # Operating Mode
        #
        if federated_only:
            if registry or provider_uri:
                raise ValueError(f"Cannot init federated-only character with {registry or provider_uri}.")
        self.federated_only = federated_only  # type: bool

        #
        # Powers
        #

        # Derive powers from keyring
        if keyring_root and keyring:
            if keyring_root != keyring.keyring_root:
                raise ValueError("Inconsistent keyring root directory path")
        if keyring:
            keyring_root, checksum_address = keyring.keyring_root, keyring.checksum_address
            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keyring.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keyring_root = keyring_root
        self.keyring = keyring

        if crypto_power and crypto_power_ups:
            raise ValueError("Pass crypto_power or crypto_power_ups (or neither), but not both.")
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(power_ups=self._default_crypto_powerups)

        self._checksum_address = checksum_address

        # Fleet and Blockchain Connection (Everyone)
        if not domains:
            domains = (CharacterConfiguration.DEFAULT_DOMAIN,)

        #
        # Self-Character
        #

        if is_me:
            if not bool(federated_only) ^ bool(registry):
                raise ValueError(f"Pass either federated only or registry for is_me Characters.  \
                                 Got '{federated_only}' and '{registry}'.")

            self.treasure_maps = {}  # type: dict
            self.network_middleware = network_middleware or RestMiddleware()

            #
            # Signing Power
            #
            try:
                signing_power = self._crypto_power.power_ups(SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp()  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Blockchain
            #
            self.provider_uri = provider_uri
            if not self.federated_only:
                self.registry = registry or InMemoryContractRegistry.from_latest_publication()
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            #
            # Learner
            #
            Learner.__init__(self,
                             domains=domains,
                             network_middleware=self.network_middleware,
                             *args, **kwargs)

        #
        # Stranger-Character
        #

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError("Network middleware cannot be attached to a Stranger-Character.")

            if registry is not None:
                raise TypeError("Registry cannot be attached to stranger-Characters.")

            self._stamp = StrangerStamp(self.public_keys(SigningPower))
            self.network_middleware = STRANGER

        #
        # Decentralized
        #
        if not federated_only:
            self._checksum_address = checksum_address  # TODO: Check that this matches TransactingPower

        #
        # Federated
        #
        elif federated_only:
            try:
                self._set_checksum_address()  # type: str
            except NoSigningPower:
                self._checksum_address = NO_BLOCKCHAIN_CONNECTION
            if checksum_address:
                # We'll take a checksum address, as long as it matches their singing key
                if not checksum_address == self.checksum_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(error.format(checksum_address))

        #
        # Nicknames
        #
        try:
            self.nickname, self.nickname_metadata = nickname_from_seed(self.checksum_address)
        except SigningPower.not_found_error:
            if self.federated_only:
                self.nickname = self.nickname_metadata = NO_NICKNAME
            else:
                raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL
Exemplo n.º 15
0
    def from_target_ursula(
        cls,
        target_ursula: Ursula,
        substitute_verifying_key: bool = False,
        sign_metadata: bool = False,
    ) -> 'Vladimir':
        """
        Sometimes Vladimir seeks to attack or imitate a *specific* target Ursula.

        TODO: This is probably a more instructive method if it takes a bytes representation instead of the entire Ursula.
        """
        try:
            from tests.utils.middleware import EvilMiddleWare
        except ImportError:
            raise DevelopmentInstallationRequired(
                importable_name='tests.utils.middleware.EvilMiddleWare')
        cls.network_middleware = EvilMiddleWare()

        crypto_power = CryptoPower(
            power_ups=target_ursula._default_crypto_powerups)

        blockchain = target_ursula.policy_agent.blockchain
        cls.attach_transacting_key(blockchain=blockchain)

        db_filepath = tempfile.mkdtemp(prefix='Vladimir')

        vladimir = cls(
            is_me=True,
            crypto_power=crypto_power,
            db_filepath=db_filepath,
            domain=TEMPORARY_DOMAIN,
            rest_host=target_ursula.rest_interface.host,
            rest_port=target_ursula.rest_interface.port,
            certificate=target_ursula.certificate,
            network_middleware=cls.network_middleware,
            checksum_address=cls.fraud_address,
            worker_address=cls.fraud_address,
            signer=Web3Signer(blockchain.client),
            provider_uri=blockchain.provider_uri,
        )

        # Let's use the target's public info, and try to make some changes.

        metadata = target_ursula.metadata()
        metadata_bytes = bytes(metadata)

        # Since it is an object from a Rust extension, we cannot directly modify it,
        # so we have to replace stuff in the byte representation and then deserialize.
        # We are replacinig objects with constant size,
        # so it should work regardless of the binary format.

        # Our basic replacement. We want to impersonate the target Ursula.
        metadata_bytes = metadata_bytes.replace(
            metadata.payload.canonical_address, vladimir.canonical_address)

        # Use our own verifying key
        if substitute_verifying_key:
            metadata_bytes = metadata_bytes.replace(
                bytes(metadata.payload.verifying_key),
                bytes(vladimir.stamp.as_umbral_pubkey()))

        fake_metadata = NodeMetadata.from_bytes(metadata_bytes)

        # Re-generate metadata signature using our signing key
        if sign_metadata:
            fake_metadata = NodeMetadata(vladimir.stamp.as_umbral_signer(),
                                         fake_metadata.payload)

        # Put metadata back
        vladimir._metadata = fake_metadata

        return vladimir
Exemplo n.º 16
0
class Character(Learner):
    """
    A base-class for any character in our cryptography protocol narrative.
    """

    _default_crypto_powerups = None
    _stamp = None
    _crashed = False

    from nucypher.network.protocols import SuspiciousActivity  # Ship this exception with every Character.

    class InvalidSignature(Exception):
        """
        Raised when a signature doesn't pass validation/verification.
        """

    def __init__(self,
                 is_me: bool = True,
                 network_middleware: RestMiddleware = None,
                 known_certificates_dir: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 federated_only: bool = False,
                 checksum_address: bytes = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """
        super().__init__(*args, **kwargs)

        self.federated_only = federated_only  # type: bool
        self.known_certificates_dir = known_certificates_dir

        #
        # Power-ups and Powers
        #
        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )

        crypto_power_ups = crypto_power_ups or []  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        #
        # Identity and Network
        #

        if is_me is True:

            self.treasure_maps = {}  # type: dict
            self.network_middleware = network_middleware or RestMiddleware()

            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = constants.NO_SIGNING_POWER

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Can't attach network middleware to a Character who isn't me.  What are you even trying to do?"
                )
            self._stamp = StrangerStamp(self.public_keys(SigningPower))

        # Decentralized
        if not federated_only:
            if not checksum_address:
                raise ValueError(
                    "No checksum_address provided while running in a non-federated mode."
                )
            else:
                self._checksum_address = checksum_address  # type: str

        # Federated
        elif federated_only:
            self._checksum_address = constants.NO_BLOCKCHAIN_CONNECTION

            if checksum_address:
                # We'll take a checksum address, as long as it matches their singing key
                self._set_checksum_address()  # type: str
                if not checksum_address == self.checksum_public_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_address))

    def __eq__(self, other) -> bool:
        return bytes(self.stamp) == bytes(other.stamp)

    def __hash__(self):
        return int.from_bytes(bytes(self.stamp), byteorder="big")

    def __repr__(self):
        class_name = self.__class__.__name__
        r = "{} {}"
        r = r.format(class_name, self.canonical_public_address)
        return r

    @property
    def name(self):
        return self.__class__.__name__

    @property
    def rest_interface(self):
        return self._crypto_power.power_ups(
            TLSHostingPower).rest_server.rest_url()

    @property
    def stamp(self):
        if self._stamp is constants.NO_SIGNING_POWER:
            raise NoSigningPower
        elif not self._stamp:
            raise AttributeError("SignatureStamp has not been set up yet.")
        else:
            return self._stamp

    @property
    def canonical_public_address(self):
        return to_canonical_address(self.checksum_public_address)

    @canonical_public_address.setter
    def canonical_public_address(self, address_bytes):
        self._checksum_address = to_checksum_address(address_bytes)

    @property
    def ether_address(self):
        raise NotImplementedError

    @property
    def checksum_public_address(self):
        if self._checksum_address is constants.NO_BLOCKCHAIN_CONNECTION:
            self._set_checksum_address()
        return self._checksum_address

    @classmethod
    def from_config(cls, config, **overrides) -> 'Character':
        return config.produce(**overrides)

    @classmethod
    def from_public_keys(cls,
                         powers_and_material: Dict,
                         federated_only=True,
                         *args,
                         **kwargs) -> 'Character':
        # TODO: Need to be federated only until we figure out the best way to get the checksum_address in here.
        """
        Sometimes we discover a Character and, at the same moment,
        learn the public parts of more of their powers. Here, we take a Dict
        (powers_and_key_bytes) in the following format:
        {CryptoPowerUp class: public_material_bytes}

        Each item in the collection will have the CryptoPowerUp instantiated
        with the public_material_bytes, and the resulting CryptoPowerUp instance
        consumed by the Character.
        """
        crypto_power = CryptoPower()

        for power_up, public_key in powers_and_material.items():
            try:
                umbral_key = UmbralPublicKey(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(pubkey=umbral_key))

        return cls(is_me=False,
                   federated_only=federated_only,
                   crypto_power=crypto_power,
                   *args,
                   **kwargs)

    def learn_from_teacher_node(self, eager=True):
        """
        Sends a request to node_url to find out about known nodes.
        """
        self._learning_round += 1

        try:
            current_teacher = self.current_teacher_node()
        except self.NotEnoughTeachers as e:
            self.log.warning("Can't learn right now: {}".format(e.args[0]))
            return

        rest_url = current_teacher.rest_interface  # TODO: Name this..?

        # TODO: Do we really want to try to learn about all these nodes instantly?
        # Hearing this traffic might give insight to an attacker.
        if VerifiableNode in self.__class__.__bases__:
            announce_nodes = [self]
        else:
            announce_nodes = None

        unresponsive_nodes = set()
        try:

            # TODO: Streamline path generation
            certificate_filepath = os.path.join(
                self.known_certificates_dir,
                current_teacher.certificate_filename)
            response = self.network_middleware.get_nodes_via_rest(
                url=rest_url,
                nodes_i_need=self._node_ids_to_learn_about_immediately,
                announce_nodes=announce_nodes,
                certificate_filepath=certificate_filepath)
        except requests.exceptions.ConnectionError as e:
            unresponsive_nodes.add(current_teacher)
            teacher_rest_info = current_teacher.rest_information()[0]

            # TODO: This error isn't necessarily "no repsonse" - let's maybe pass on the text of the exception here.
            self.log.info("No Response from teacher: {}:{}.".format(
                teacher_rest_info.host, teacher_rest_info.port))
            self.cycle_teacher_node()
            return

        if response.status_code != 200:
            raise RuntimeError("Bad response from teacher: {} - {}".format(
                response, response.content))

        signature, nodes = signature_splitter(response.content,
                                              return_remainder=True)

        # TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node.
        from nucypher.characters.lawful import Ursula
        node_list = Ursula.batch_from_bytes(nodes,
                                            federated_only=self.federated_only)

        new_nodes = []
        for node in node_list:

            if node.checksum_public_address in self.known_nodes or node.checksum_public_address == self.checksum_public_address:
                continue  # TODO: 168 Check version and update if required.

            try:
                if eager:
                    node.verify_node(self.network_middleware,
                                     accept_federated_only=self.federated_only)
                else:
                    node.validate_metadata(
                        accept_federated_only=self.federated_only)
            except node.SuspiciousActivity:
                # TODO: Account for possibility that stamp, rather than interface, was bad.
                message = "Suspicious Activity: Discovered node with bad signature: {}.  " \
                          "Propagated by: {}".format(current_teacher.checksum_public_address, rest_url)
                self.log.warning(message)
            self.log.info("Previously unknown node: {}".format(
                node.checksum_public_address))

            self.log.info("Previously unknown node: {}".format(
                node.checksum_public_address))
            self.remember_node(node)
            new_nodes.append(node)

        self._adjust_learning(new_nodes)

        learning_round_log_message = "Learning round {}.  Teacher: {} knew about {} nodes, {} were new."
        self.log.info(
            learning_round_log_message.format(
                self._learning_round, current_teacher.checksum_public_address,
                len(node_list), len(new_nodes)), )
        if new_nodes and self.known_certificates_dir:
            for node in new_nodes:
                node.save_certificate_to_disk(self.known_certificates_dir)

        return new_nodes

    def encrypt_for(
        self,
        recipient: 'Character',
        plaintext: bytes,
        sign: bool = True,
        sign_plaintext=True,
    ) -> tuple:
        """
        Encrypts plaintext for recipient actor. Optionally signs the message as well.

        :param recipient: The character whose public key will be used to encrypt
            cleartext.
        :param plaintext: The secret to be encrypted.
        :param sign: Whether or not to sign the message.
        :param sign_plaintext: When signing, the cleartext is signed if this is
            True,  Otherwise, the resulting ciphertext is signed.

        :return: A tuple, (ciphertext, signature).  If sign==False,
            then signature will be NOT_SIGNED.
        """
        signer = self.stamp if sign else constants.DO_NOT_SIGN

        message_kit, signature = encrypt_and_sign(
            recipient_pubkey_enc=recipient.public_keys(EncryptingPower),
            plaintext=plaintext,
            signer=signer,
            sign_plaintext=sign_plaintext)
        return message_kit, signature

    def verify_from(
        self,
        stranger: 'Character',
        message_kit: Union[UmbralMessageKit, bytes],
        signature: Signature = None,
        decrypt=False,
        delegator_signing_key: UmbralPublicKey = None,
    ) -> tuple:
        """
        Inverse of encrypt_for.

        :param actor_that_sender_claims_to_be: A Character instance representing
            the actor whom the sender claims to be.  We check the public key
            owned by this Character instance to verify.
        :param message_kit: the message to be (perhaps decrypted and) verified.
        :param signature: The signature to check.
        :param decrypt: Whether or not to decrypt the messages.
        :param delegator_signing_key: A signing key from the original delegator.
            This is used only when decrypting a MessageKit with an activated Capsule
            to check that the KFrag used to create each attached CFrag is the
            authentic KFrag initially created by the delegator.

        :return: Whether or not the signature is valid, the decrypted plaintext
            or NO_DECRYPTION_PERFORMED
        """
        sender_pubkey_sig = stranger.stamp.as_umbral_pubkey()
        with suppress(AttributeError):
            if message_kit.sender_pubkey_sig:
                if not message_kit.sender_pubkey_sig == sender_pubkey_sig:
                    raise ValueError(
                        "This MessageKit doesn't appear to have come from {}".
                        format(stranger))

        signature_from_kit = None

        if decrypt:
            # We are decrypting the message; let's do that first and see what the sig header says.
            cleartext_with_sig_header = self.decrypt(
                message_kit, verifying_key=delegator_signing_key)
            sig_header, cleartext = default_constant_splitter(
                cleartext_with_sig_header, return_remainder=True)
            if sig_header == constants.SIGNATURE_IS_ON_CIPHERTEXT:
                # THe ciphertext is what is signed - note that for later.
                message = message_kit.ciphertext
                if not signature:
                    raise ValueError(
                        "Can't check a signature on the ciphertext if don't provide one."
                    )
            elif sig_header == constants.SIGNATURE_TO_FOLLOW:
                # The signature follows in this cleartext - split it off.
                signature_from_kit, cleartext = signature_splitter(
                    cleartext, return_remainder=True)
                message = cleartext
        else:
            # Not decrypting - the message is the object passed in as a message kit.  Cast it.
            message = bytes(message_kit)
            cleartext = constants.NO_DECRYPTION_PERFORMED

        if signature and signature_from_kit:
            if signature != signature_from_kit:
                raise ValueError(
                    "The MessageKit has a Signature, but it's not the same one you provided.  Something's up."
                )

        signature_to_use = signature or signature_from_kit

        if signature_to_use:
            is_valid = signature_to_use.verify(message, sender_pubkey_sig)
            if not is_valid:
                raise stranger.InvalidSignature(
                    "Signature for message isn't valid: {}".format(
                        signature_to_use))
        else:
            raise self.InvalidSignature(
                "No signature provided -- signature presumed invalid.")

        #
        # Next we have decrypt() and sign() - these use the private
        # keys of their respective powers; any character who has these powers can use these functions.
        #
        # If they don't have the correct Power, the appropriate PowerUpError is raised.
        #
        return cleartext

    def decrypt(self, message_kit, verifying_key: UmbralPublicKey = None):
        return self._crypto_power.power_ups(EncryptingPower).decrypt(
            message_kit, verifying_key)

    def sign(self, message):
        return self._crypto_power.power_ups(SigningPower).sign(message)

    def public_keys(self,
                    power_up_class: ClassVar) -> Union[Tuple, UmbralPublicKey]:
        """
        Pass a power_up_class, get the public material for this Character which corresponds to that
        class - whatever type of object that may be.

        If the Character doesn't have the power corresponding to that class, raises the
        appropriate PowerUpError (ie, NoSigningPower or NoEncryptingPower).
        """
        power_up = self._crypto_power.power_ups(power_up_class)
        return power_up.public_key()

    def _set_checksum_address(self):

        if self.federated_only:
            verifying_key = self.public_keys(SigningPower)
            uncompressed_bytes = verifying_key.to_bytes(is_compressed=False)
            without_prefix = uncompressed_bytes[1:]
            verifying_key_as_eth_key = EthKeyAPI.PublicKey(without_prefix)
            public_address = verifying_key_as_eth_key.to_checksum_address()
        else:
            try:
                public_address = to_checksum_address(
                    self.canonical_public_address)
            except TypeError:
                raise TypeError(
                    "You can't use a decentralized character without a _checksum_address."
                )
            except NotImplementedError:
                raise TypeError(
                    "You can't use a plain Character in federated mode - you need to implement ether_address."
                )

        self._checksum_address = public_address
Exemplo n.º 17
0
    def __init__(self,
                 attach_server=True,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups=None,
                 is_me=True,
                 network_middleware=None,
                 config: "NucypherConfig" = None,
                 *args,
                 **kwargs):
        """
        :param attach_server:  Whether to attach a Server when this Character is
            born.
        :param crypto_power: A CryptoPower object; if provided, this will be the
            character's CryptoPower.
        :param crypto_power_ups:  If crypto_power is not provided, a new
            CryptoPower will be made and will consume all of the CryptoPowerUps
            in this list.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.
        """
        self.config = config if config is not None else NucypherConfig.get(
        )  # default
        self.known_nodes = {}
        self.log = getLogger("characters")

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )

        if not crypto_power_ups:
            crypto_power_ups = []

        if crypto_power:
            self._crypto_power = crypto_power
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(self._default_crypto_powerups)
        if is_me:
            self.network_middleware = network_middleware or NetworkyStuff()
            try:
                signing_power = self._crypto_power.power_ups(SigningPower)
                self._stamp = signing_power.get_signature_stamp()
            except NoSigningPower:
                self._stamp = constants.NO_SIGNING_POWER

            if attach_server:
                self.attach_server()
        else:
            if network_middleware is not None:
                raise TypeError(
                    "Can't attach network middleware to a Character who isn't me.  What are you even trying to do?"
                )
            self._stamp = StrangerStamp(self.public_key(SigningPower))
Exemplo n.º 18
0
class Character(object):
    """
    A base-class for any character in our cryptography protocol narrative.
    """
    _server = None
    _server_class = Server
    _default_crypto_powerups = None
    _stamp = None

    address = "This is a fake address."  # TODO: #192

    def __init__(self,
                 attach_server=True,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups=None,
                 is_me=True,
                 network_middleware=None,
                 config: "NucypherConfig" = None,
                 *args,
                 **kwargs):
        """
        :param attach_server:  Whether to attach a Server when this Character is
            born.
        :param crypto_power: A CryptoPower object; if provided, this will be the
            character's CryptoPower.
        :param crypto_power_ups:  If crypto_power is not provided, a new
            CryptoPower will be made and will consume all of the CryptoPowerUps
            in this list.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.
        """
        self.config = config if config is not None else NucypherConfig.get(
        )  # default
        self.known_nodes = {}
        self.log = getLogger("characters")

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )

        if not crypto_power_ups:
            crypto_power_ups = []

        if crypto_power:
            self._crypto_power = crypto_power
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(self._default_crypto_powerups)
        if is_me:
            self.network_middleware = network_middleware or NetworkyStuff()
            try:
                signing_power = self._crypto_power.power_ups(SigningPower)
                self._stamp = signing_power.get_signature_stamp()
            except NoSigningPower:
                self._stamp = constants.NO_SIGNING_POWER

            if attach_server:
                self.attach_server()
        else:
            if network_middleware is not None:
                raise TypeError(
                    "Can't attach network middleware to a Character who isn't me.  What are you even trying to do?"
                )
            self._stamp = StrangerStamp(self.public_key(SigningPower))

    def __eq__(self, other):
        return bytes(self.stamp) == bytes(other.stamp)

    def __hash__(self):
        return int.from_bytes(self.stamp, byteorder="big")

    class NotEnoughUrsulas(RuntimeError):
        """
        All Characters depend on knowing about enough Ursulas to perform their role.
        This exception is raised when a piece of logic can't proceed without more Ursulas.
        """

    class SuspiciousActivity(RuntimeError):
        """raised when an action appears to amount to malicious conduct."""

    @classmethod
    def from_public_keys(cls, powers_and_keys: Dict, *args, **kwargs):
        """
        Sometimes we discover a Character and, at the same moment, learn one or
        more of their public keys. Here, we take a Dict
        (powers_and_key_bytes) in the following format:
        {CryptoPowerUp class: public_key_bytes}

        Each item in the collection will have the CryptoPowerUp instantiated
        with the public_key_bytes, and the resulting CryptoPowerUp instance
        consumed by the Character.
        """
        crypto_power = CryptoPower()

        for power_up, public_key in powers_and_keys.items():
            try:
                umbral_key = UmbralPublicKey(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(pubkey=umbral_key))

        return cls(is_me=False, crypto_power=crypto_power, *args, **kwargs)

    def attach_server(self,
                      ksize=20,
                      alpha=3,
                      id=None,
                      storage=None,
                      *args,
                      **kwargs) -> None:
        if self._server:
            raise RuntimeError(
                "Attaching the server twice is almost certainly a bad idea.")
        self._server = self._server_class(ksize, alpha, id, storage, *args,
                                          **kwargs)

    @property
    def stamp(self):
        if self._stamp is constants.NO_SIGNING_POWER:
            raise NoSigningPower
        elif not self._stamp:
            raise AttributeError("SignatureStamp has not been set up yet.")
        else:
            return self._stamp

    @property
    def server(self) -> Server:
        if self._server:
            return self._server
        else:
            raise RuntimeError("Server hasn't been attached.")

    @property
    def name(self):
        return self.__class__.__name__

    def encrypt_for(
        self,
        recipient: "Character",
        plaintext: bytes,
        sign: bool = True,
        sign_plaintext=True,
    ) -> tuple:
        """
        Encrypts plaintext for recipient actor. Optionally signs the message as well.

        :param recipient: The character whose public key will be used to encrypt
            cleartext.
        :param plaintext: The secret to be encrypted.
        :param sign: Whether or not to sign the message.
        :param sign_plaintext: When signing, the cleartext is signed if this is
            True,  Otherwise, the resulting ciphertext is signed.

        :return: A tuple, (ciphertext, signature).  If sign==False,
            then signature will be NOT_SIGNED.
        """
        signer = self.stamp if sign else constants.DO_NOT_SIGN

        message_kit, signature = encrypt_and_sign(
            recipient_pubkey_enc=recipient.public_key(EncryptingPower),
            plaintext=plaintext,
            signer=signer,
            sign_plaintext=sign_plaintext)
        return message_kit, signature

    def verify_from(
        self,
        actor_whom_sender_claims_to_be: "Character",
        message_kit: Union[UmbralMessageKit, bytes],
        signature: Signature = None,
        decrypt=False,
        delegator_signing_key: UmbralPublicKey = None,
    ) -> tuple:
        """
        Inverse of encrypt_for.

        :param actor_that_sender_claims_to_be: A Character instance representing
            the actor whom the sender claims to be.  We check the public key
            owned by this Character instance to verify.
        :param message_kit: the message to be (perhaps decrypted and) verified.
        :param signature: The signature to check.
        :param decrypt: Whether or not to decrypt the messages.
        :param delegator_signing_key: A signing key from the original delegator.
            This is used only when decrypting a MessageKit with an activated Capsule
            to check that the KFrag used to create each attached CFrag is the
            authentic KFrag initially created by the delegator.

        :return: Whether or not the signature is valid, the decrypted plaintext
            or NO_DECRYPTION_PERFORMED
        """
        sender_pubkey_sig = actor_whom_sender_claims_to_be.stamp.as_umbral_pubkey(
        )
        with suppress(AttributeError):
            if message_kit.sender_pubkey_sig:
                if not message_kit.sender_pubkey_sig == sender_pubkey_sig:
                    raise ValueError(
                        "This MessageKit doesn't appear to have come from {}".
                        format(actor_whom_sender_claims_to_be))

        signature_from_kit = None

        if decrypt:
            # We are decrypting the message; let's do that first and see what the sig header says.
            cleartext_with_sig_header = self.decrypt(
                message_kit, verifying_key=delegator_signing_key)
            sig_header, cleartext = default_constant_splitter(
                cleartext_with_sig_header, return_remainder=True)
            if sig_header == constants.SIGNATURE_IS_ON_CIPHERTEXT:
                # THe ciphertext is what is signed - note that for later.
                message = message_kit.ciphertext
                if not signature:
                    raise ValueError(
                        "Can't check a signature on the ciphertext if don't provide one."
                    )
            elif sig_header == constants.SIGNATURE_TO_FOLLOW:
                # The signature follows in this cleartext - split it off.
                signature_from_kit, cleartext = signature_splitter(
                    cleartext, return_remainder=True)
                message = cleartext
        else:
            # Not decrypting - the message is the object passed in as a message kit.  Cast it.
            message = bytes(message_kit)
            cleartext = constants.NO_DECRYPTION_PERFORMED

        if signature and signature_from_kit:
            if signature != signature_from_kit:
                raise ValueError(
                    "The MessageKit has a Signature, but it's not the same one you provided.  Something's up."
                )

        signature_to_use = signature or signature_from_kit

        if signature_to_use:
            is_valid = signature_to_use.verify(message, sender_pubkey_sig)
        else:
            # Meh, we didn't even get a signature.  Not much we can do.
            is_valid = False

        return is_valid, cleartext

    """
    Next we have decrypt(), sign(), and generate_self_signed_certificate() - these use the private 
    keys of their respective powers; any character who has these powers can use these functions.

    If they don't have the correct Power, the appropriate PowerUpError is raised.
    """

    def decrypt(self, message_kit, verifying_key: UmbralPublicKey = None):
        return self._crypto_power.power_ups(EncryptingPower).decrypt(
            message_kit, verifying_key)

    def sign(self, message):
        return self._crypto_power.power_ups(SigningPower).sign(message)

    def generate_self_signed_certificate(self):
        signing_power = self._crypto_power.power_ups(SigningPower)
        return signing_power.generate_self_signed_cert(
            self.stamp.fingerprint().decode())

    """
    And finally, some miscellaneous but generally-applicable abilities:
    """

    def public_key(self, power_up_class: ClassVar):
        """
        Pass a power_up_class, get the public key for this Character which corresponds to that
        class.

        If the Character doesn't have the power corresponding to that class, raises the
        appropriate PowerUpError (ie, NoSigningPower or NoEncryptingPower).
        """
        power_up = self._crypto_power.power_ups(power_up_class)
        return power_up.public_key()

    def learn_about_nodes(self, address, port):
        """
        Sends a request to node_url to find out about known nodes.
        """
        response = self.network_middleware.get_nodes_via_rest(address, port)
        signature, nodes = signature_splitter(response.content,
                                              return_remainder=True)
        # TODO: Although not treasure map-related, this has a whiff of #172.
        ursula_interface_splitter = dht_value_splitter + BytestringSplitter(
            (bytes, 17))
        split_nodes = ursula_interface_splitter.repeat(nodes)
        new_nodes = {}
        for node_meta in split_nodes:
            header, sig, pubkey, interface_info = node_meta
            if not pubkey in self.known_nodes:
                if sig.verify(keccak_digest(interface_info), pubkey):
                    address, dht_port, rest_port = msgpack.loads(
                        interface_info)
                    new_nodes[pubkey] = \
                        Ursula.as_discovered_on_network(
                            rest_port=rest_port,
                            dht_port=dht_port,
                            ip_address=address.decode("utf-8"),
                            powers_and_keys=({SigningPower: pubkey})
                        )
                else:
                    message = "Suspicious Activity: Discovered node with bad signature: {}.  Propagated by: {}:{}".format(
                        node_meta, address, port)
                    self.log.warn(message)
        return new_nodes

    def network_bootstrap(self, node_list):
        for node_addr, port in node_list:
            new_nodes = self.learn_about_nodes(node_addr, port)
        self.known_nodes.update(new_nodes)
Exemplo n.º 19
0
class Character(Learner):
    """A base-class for any character in our cryptography protocol narrative."""

    _display_name_template = "({})⇀{}↽ ({})"  # Used in __repr__ and in cls.from_bytes
    _default_crypto_powerups = None
    _stamp = None

    def __init__(self,
                 domain: str = None,
                 known_node_class: object = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = None,
                 network_middleware: RestMiddleware = None,
                 keystore: Keystore = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 eth_provider_uri: str = None,
                 signer: Signer = None,
                 registry: BaseContractRegistry = None,
                 include_self_in_the_state: bool = False,
                 *args,
                 **kwargs) -> None:
        """

        A participant in the cryptological drama (a screenplay, if you like) of NuCypher.

        Characters can represent users, nodes, wallets, offline devices, or other objects of varying levels of abstraction.

        The Named Characters use this class as a Base, and achieve their individuality from additional methods and PowerUps.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        #
        # Prologue of the federation
        #

        # FIXME: excuse me... can I speak to the manager?
        if is_me:
            # If this is a federated-is_me-character, assume everyone else is too.
            self._set_known_node_class(known_node_class, federated_only)
        else:
            # What an awful hack.  The last convulsions of #466.  # TODO: Anything else.
            with suppress(AttributeError):
                federated_only = known_node_class._federated_only_instances

        if federated_only:
            if registry or eth_provider_uri:
                raise ValueError(
                    f"Cannot init federated-only character with {registry or eth_provider_uri}."
                )
        self.federated_only: bool = federated_only

        ##########################################

        #
        # Keys & Powers
        #

        if keystore:
            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keystore.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keystore = keystore

        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        #
        # Self
        #

        if is_me:

            # Signing Power
            self.signer = signer
            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            # Blockchainy
            if not self.federated_only:
                self.eth_provider_uri = eth_provider_uri
                self.registry = registry or InMemoryContractRegistry.from_latest_publication(
                    network=domain)  # See #1580
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            # REST
            self.network_middleware = network_middleware or RestMiddleware(
                registry=self.registry, eth_provider_uri=eth_provider_uri)

            # Learner
            Learner.__init__(
                self,
                domain=domain,
                network_middleware=self.network_middleware,
                node_class=known_node_class,
                include_self_in_the_state=include_self_in_the_state,
                *args,
                **kwargs)

            if self.federated_only:
                try:
                    derived_federated_address = self.derive_federated_address()
                except NoSigningPower:
                    # TODO: Why allow such a character (without signing power) to be created at all?
                    derived_federated_address = NO_SIGNING_POWER.bool_value(
                        False)

                if checksum_address and (checksum_address !=
                                         derived_federated_address):
                    raise ValueError(
                        f"Provided checksum address {checksum_address} "
                        f"does not match federated character's verifying key {derived_federated_address}"
                    )
                checksum_address = derived_federated_address

            self.checksum_address = checksum_address

        #
        # Stranger
        #

        else:
            if network_middleware is not None:
                raise TypeError(
                    "Network middleware cannot be attached to a Stranger-Character."
                )

            if registry is not None:
                raise TypeError(
                    "Registry cannot be attached to stranger-Characters.")

            verifying_key = self.public_keys(SigningPower)
            self._stamp = StrangerStamp(verifying_key)
            self.keystore_dir = STRANGER
            self.network_middleware = STRANGER
            self.checksum_address = checksum_address

        self.__setup_nickname(is_me=is_me)

        # Character Control
        # TODO: have argument about meaning of 'lawful' and whether maybe only Lawful characters have an interface
        if hasattr(self, '_interface_class'):
            # Controller Interface
            self.interface = self._interface_class(character=self)
        self.controller = NO_CONTROL_PROTOCOL

    def __eq__(self, other) -> bool:
        try:
            other_stamp = other.stamp
        except (AttributeError, NoSigningPower):
            return False
        return bytes(self.stamp) == bytes(other_stamp)

    def __hash__(self):
        return int.from_bytes(bytes(self.stamp), byteorder="big")

    def __repr__(self):
        r = self._display_name_template
        try:
            r = r.format(self.__class__.__name__, self.nickname,
                         self.checksum_address)
        except (
                NoSigningPower, TypeError
        ):  # TODO: ....yeah?  We can probably do better for a repr here.
            r = f"({self.__class__.__name__})⇀{self.nickname}↽"
        return r

    def __setup_nickname(self, is_me: bool):
        if not self.checksum_address and not self.federated_only and not is_me:
            # Sometimes we don't care about the nickname.  For example, if Alice is granting to Bob, she usually
            # doesn't know or care about his wallet.  Maybe this needs to change?
            # Currently, if this is a stranger and there's no blockchain connection, we assign NO_NICKNAME:
            self.nickname = NO_NICKNAME
        else:
            try:
                if not self.checksum_address:
                    self.nickname = NO_NICKNAME
                else:
                    # This can call _set_checksum_address.
                    self.nickname = Nickname.from_seed(self.checksum_address)
            except SigningPower.not_found_error:
                if self.federated_only:
                    self.nickname = NO_NICKNAME
                else:
                    raise

    @property
    def name(self):
        return self.__class__.__name__

    @property
    def stamp(self):
        if self._stamp is NO_SIGNING_POWER:
            raise NoSigningPower
        elif not self._stamp:
            raise AttributeError("SignatureStamp has not been set up yet.")
        else:
            return self._stamp

    @property
    def canonical_address(self):
        # TODO: This is wasteful.  #1995
        return to_canonical_address(self.checksum_address)

    @classmethod
    def from_config(cls, config, **overrides) -> 'Character':
        return config.produce(**overrides)

    @classmethod
    def from_public_keys(cls,
                         powers_and_material: Dict = None,
                         verifying_key: Optional[PublicKey] = None,
                         encrypting_key: Optional[PublicKey] = None,
                         *args,
                         **kwargs) -> 'Character':
        """
        Sometimes we discover a Character and, at the same moment,
        learn the public parts of more of their powers. Here, we take a Dict
        (powers_and_material) in the format {CryptoPowerUp class: material},
        where material can be bytes or umbral.PublicKey.

        Each item in the collection will have the CryptoPowerUp instantiated
        with the given material, and the resulting CryptoPowerUp instance
        consumed by the Character.

        Alternatively, you can pass directly a verifying public key
        (for SigningPower) and/or an encrypting public key (for DecryptionPower).
        """
        crypto_power = CryptoPower()

        if powers_and_material is None:
            powers_and_material = dict()

        if verifying_key:
            powers_and_material[SigningPower] = verifying_key
        if encrypting_key:
            powers_and_material[DecryptingPower] = encrypting_key

        for power_up, public_key in powers_and_material.items():
            try:
                umbral_key = PublicKey.from_bytes(public_key)
            except TypeError:
                umbral_key = public_key

            crypto_power.consume_power_up(power_up(public_key=umbral_key))

        return cls(is_me=False, crypto_power=crypto_power, *args, **kwargs)

    def _set_known_node_class(self, known_node_class, federated_only):
        if not known_node_class:
            # Once in a while, in tests or demos, we init a plain Character who doesn't already know about its node class.
            from nucypher.characters.lawful import Ursula
            known_node_class = Ursula
        self.known_node_class = known_node_class
        # If we're federated only, we assume that all other nodes in our domain are as well.
        known_node_class.set_federated_mode(federated_only)

    # TODO: Unused
    def store_metadata(self, filepath: Path) -> Path:
        """
        Save this node to the disk.
        :param filepath: Output filepath to save node metadata.
        :return: Output filepath
        """

        return self.node_storage.store_node_metadata(node=self,
                                                     filepath=filepath)

    def encrypt_for(
        self,
        recipient: 'Character',
        plaintext: bytes,
    ) -> MessageKit:
        """
        Encrypts plaintext for recipient actor. Optionally signs the message as well.

        :param recipient: The character whose public key will be used to encrypt
            cleartext.
        :param plaintext: The secret to be encrypted.
        :param sign_plaintext: the cleartext is signed if this is
            True,  Otherwise, the resulting ciphertext is signed.

        :return: the message kit.
        """

        # TODO: who even uses this method except for tests?

        message_kit = MessageKit(
            policy_encrypting_key=recipient.public_keys(DecryptingPower),
            plaintext=plaintext)
        return message_kit

    def public_keys(self, power_up_class: ClassVar):
        """
        Pass a power_up_class, get the public material for this Character which corresponds to that
        class - whatever type of object that may be.

        If the Character doesn't have the power corresponding to that class, raises the
        appropriate PowerUpError (ie, NoSigningPower or NoDecryptingPower).
        """
        power_up = self._crypto_power.power_ups(power_up_class)
        return power_up.public_key()

    def derive_federated_address(self):
        if self.federated_only:
            verifying_key = self.public_keys(SigningPower)
            verifying_key_as_eth_key = EthKeyAPI.PublicKey.from_compressed_bytes(
                bytes(verifying_key))
            federated_address = verifying_key_as_eth_key.to_checksum_address()
        else:
            raise RuntimeError(
                'Federated address can only be derived for federated characters.'
            )
        return federated_address

    def make_rpc_controller(self, crash_on_error: bool = False):
        app_name = bytes(self.stamp).hex()[:6]
        controller = JSONRPCController(app_name=app_name,
                                       crash_on_error=crash_on_error,
                                       interface=self.interface)

        self.controller = controller
        return controller

    def make_cli_controller(self, crash_on_error: bool = False):
        app_name = bytes(self.stamp).hex()[:6]
        controller = CharacterCLIController(app_name=app_name,
                                            crash_on_error=crash_on_error,
                                            interface=self.interface)

        self.controller = controller
        return controller

    def disenchant(self):
        self.log.debug(f"Disenchanting {self}")
        Learner.stop_learning_loop(self)
Exemplo n.º 20
0
    def __init__(self,
                 is_me: bool = True,
                 network_middleware: RestMiddleware = None,
                 known_certificates_dir: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 federated_only: bool = False,
                 checksum_address: bytes = None,
                 *args,
                 **kwargs) -> None:
        """

        Base class for Nucypher protocol actors.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """
        super().__init__(*args, **kwargs)

        self.federated_only = federated_only  # type: bool
        self.known_certificates_dir = known_certificates_dir

        #
        # Power-ups and Powers
        #
        if crypto_power and crypto_power_ups:
            raise ValueError(
                "Pass crypto_power or crypto_power_ups (or neither), but not both."
            )

        crypto_power_ups = crypto_power_ups or []  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(
                power_ups=self._default_crypto_powerups)

        #
        # Identity and Network
        #

        if is_me is True:

            self.treasure_maps = {}  # type: dict
            self.network_middleware = network_middleware or RestMiddleware()

            try:
                signing_power = self._crypto_power.power_ups(
                    SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp(
                )  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = constants.NO_SIGNING_POWER

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError(
                    "Can't attach network middleware to a Character who isn't me.  What are you even trying to do?"
                )
            self._stamp = StrangerStamp(self.public_keys(SigningPower))

        # Decentralized
        if not federated_only:
            if not checksum_address:
                raise ValueError(
                    "No checksum_address provided while running in a non-federated mode."
                )
            else:
                self._checksum_address = checksum_address  # type: str

        # Federated
        elif federated_only:
            self._checksum_address = constants.NO_BLOCKCHAIN_CONNECTION

            if checksum_address:
                # We'll take a checksum address, as long as it matches their singing key
                self._set_checksum_address()  # type: str
                if not checksum_address == self.checksum_public_address:
                    error = "Federated-only Characters derive their address from their Signing key; got {} instead."
                    raise self.SuspiciousActivity(
                        error.format(checksum_address))
Exemplo n.º 21
0
    def __init__(self,
                 domain: str = None,
                 known_node_class: object = None,
                 is_me: bool = True,
                 federated_only: bool = False,
                 checksum_address: str = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
                 network_middleware: RestMiddleware = None,
                 keyring: NucypherKeyring = None,
                 keyring_root: str = None,
                 crypto_power: CryptoPower = None,
                 crypto_power_ups: List[CryptoPowerUp] = None,
                 provider_uri: str = None,
                 signer: Signer = None,
                 registry: BaseContractRegistry = None,
                 *args, **kwargs
                 ) -> None:

        """

        A participant in the cryptological drama (a screenplay, if you like) of NuCypher.

        Characters can represent users, nodes, wallets, offline devices, or other objects of varying levels of abstraction.

        The Named Characters use this class as a Base, and achieve their individuality from additional methods and PowerUps.


        PowerUps
        ========
        :param crypto_power: A CryptoPower object; if provided, this will be the character's CryptoPower.
        :param crypto_power_ups: If crypto_power is not provided, a new one will be made to consume all CryptoPowerUps.

        If neither crypto_power nor crypto_power_ups are provided, we give this
        Character all CryptoPowerUps listed in their _default_crypto_powerups
        attribute.

        :param is_me: Set this to True when you want this Character to represent
            the owner of the configuration under which the program is being run.
            A Character who is_me can do things that other Characters can't,
            like run servers, sign messages, and decrypt messages which are
            encrypted for them.  Typically this will be True for exactly one
            Character, but there are scenarios in which its imaginable to be
            represented by zero Characters or by more than one Character.

        """

        if provider_uri:
            if not BlockchainInterfaceFactory.is_interface_initialized(provider_uri=provider_uri):
                BlockchainInterfaceFactory.initialize_interface(provider_uri=provider_uri)

        #
        # Operating Mode
        #

        if hasattr(self, '_interface_class'):  # TODO: have argument about meaning of 'lawful'
            #                                         and whether maybe only Lawful characters have an interface
            self.interface = self._interface_class(character=self)

        if is_me:
            self._set_known_node_class(known_node_class, federated_only)
        else:
            # What an awful hack.  The last convulsions of #466.
            # TODO: Anything else.
            with suppress(AttributeError):
                federated_only = known_node_class._federated_only_instances

        if federated_only:
            if registry or provider_uri:
                raise ValueError(f"Cannot init federated-only character with {registry or provider_uri}.")
        self.federated_only: bool = federated_only

        #
        # Powers
        #

        # Derive powers from keyring
        if keyring_root and keyring:
            if keyring_root != keyring.keyring_root:
                raise ValueError("Inconsistent keyring root directory path")
        if keyring:
            keyring_root, checksum_address = keyring.keyring_root, keyring.checksum_address
            crypto_power_ups = list()
            for power_up in self._default_crypto_powerups:
                power = keyring.derive_crypto_power(power_class=power_up)
                crypto_power_ups.append(power)
        self.keyring_root = keyring_root
        self.keyring = keyring

        if crypto_power and crypto_power_ups:
            raise ValueError("Pass crypto_power or crypto_power_ups (or neither), but not both.")
        crypto_power_ups = crypto_power_ups or list()  # type: list

        if crypto_power:
            self._crypto_power = crypto_power  # type: CryptoPower
        elif crypto_power_ups:
            self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
        else:
            self._crypto_power = CryptoPower(power_ups=self._default_crypto_powerups)

        #
        # Self-Character
        #

        if is_me:
            #
            # Signing Power
            #
            self.signer = signer
            try:
                signing_power = self._crypto_power.power_ups(SigningPower)  # type: SigningPower
                self._stamp = signing_power.get_signature_stamp()  # type: SignatureStamp
            except NoSigningPower:
                self._stamp = NO_SIGNING_POWER

            #
            # Blockchain
            #
            self.provider_uri = provider_uri
            if not self.federated_only:
                self.registry = registry or InMemoryContractRegistry.from_latest_publication(network=domain)  # See #1580
            else:
                self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)

            # REST
            self.network_middleware = network_middleware or RestMiddleware(registry=self.registry)

            #
            # Learner
            #
            Learner.__init__(self,
                             domain=domain,
                             network_middleware=self.network_middleware,
                             node_class=known_node_class,
                             *args, **kwargs)

        #
        # Stranger-Character
        #

        else:  # Feel like a stranger
            if network_middleware is not None:
                raise TypeError("Network middleware cannot be attached to a Stranger-Character.")

            if registry is not None:
                raise TypeError("Registry cannot be attached to stranger-Characters.")

            verifying_key = self.public_keys(SigningPower)
            self._stamp = StrangerStamp(verifying_key)
            self.keyring_root = STRANGER
            self.network_middleware = STRANGER

        # TODO: Figure out when to do this.
        try:
            _transacting_power = self._crypto_power.power_ups(TransactingPower)
        except NoTransactingPower:
            self._checksum_address = checksum_address
        else:
            self._set_checksum_address(checksum_address)

        #
        # Nicknames
        #
        if self._checksum_address is NO_BLOCKCHAIN_CONNECTION and not self.federated_only and not is_me:
            # Sometimes we don't care about the nickname.  For example, if Alice is granting to Bob, she usually
            # doesn't know or care about his wallet.  Maybe this needs to change?
            # Currently, if this is a stranger and there's no blockchain connection, we assign NO_NICKNAME:
            self.nickname = NO_NICKNAME
        else:
            try:
                # TODO: It's possible that this is NO_BLOCKCHAIN_CONNECTION.
                if self.checksum_address is NO_BLOCKCHAIN_CONNECTION:
                    self.nickname = NO_NICKNAME
                else:
                    # This can call _set_checksum_address.
                    self.nickname = Nickname.from_seed(self.checksum_address)
            except SigningPower.not_found_error:  # TODO: Handle NO_BLOCKCHAIN_CONNECTION more coherently - #1547
                if self.federated_only:
                    self.nickname = NO_NICKNAME
                else:
                    raise

        #
        # Fleet state
        #
        if is_me is True:
            self.known_nodes.record_fleet_state()

        #
        # Character Control
        #
        self.controller = NO_CONTROL_PROTOCOL