Example #1
0
    def __init__(
        self,
        domains: Set,
        certificate: Certificate,
        certificate_filepath: str,
        interface_signature=NOT_SIGNED.bool_value(False),
        timestamp=NOT_SIGNED,
        identity_evidence=NOT_SIGNED,
        substantiate_immediately=False,
        passphrase=None,
    ) -> None:

        self.serving_domains = domains
        self.certificate = certificate
        self.certificate_filepath = certificate_filepath
        self._interface_signature_object = interface_signature
        self._timestamp = timestamp
        self.last_seen = NEVER_SEEN("Haven't connected to this node yet.")
        self.fleet_state_checksum = None
        self.fleet_state_updated = None
        self._evidence_of_decentralized_identity = constant_or_bytes(
            identity_evidence)

        if substantiate_immediately:
            self.substantiate_stamp(
                password=passphrase)  # TODO: Derive from keyring
Example #2
0
def test_constant_can_be_used_as_hashable_object():
    from constant_sorrow.constants import DAPPER_DAN
    pomades = {DAPPER_DAN, "fop"}

    assert DAPPER_DAN in pomades
    dapper_dan_bytes = bytes(DAPPER_DAN)

    IN_A_COUPLE_WEEKS = constant_or_bytes(dapper_dan_bytes)
    assert DAPPER_DAN is IN_A_COUPLE_WEEKS

    assert DAPPER_DAN in pomades
    assert IN_A_COUPLE_WEEKS in pomades
Example #3
0
    def from_bytes(
        cls,
        ursula_as_bytes: bytes,
        version: int = INCLUDED_IN_BYTESTRING,
        federated_only: bool = False,
    ) -> 'Ursula':
        if version is INCLUDED_IN_BYTESTRING:
            version, payload = cls.version_splitter(ursula_as_bytes,
                                                    return_remainder=True)
        else:
            payload = ursula_as_bytes

        # Check version and raise IsFromTheFuture if this node is... you guessed it...
        if version > cls.LEARNER_VERSION:
            # TODO: Some auto-updater logic?
            try:
                canonical_address, _ = BytestringSplitter(
                    PUBLIC_ADDRESS_LENGTH)(payload, return_remainder=True)
                checksum_address = to_checksum_address(canonical_address)
                nickname, _ = nickname_from_seed(checksum_address)
                display_name = "⇀{}↽ ({})".format(nickname, checksum_address)
                message = cls.unknown_version_message.format(
                    display_name, version, cls.LEARNER_VERSION)
            except BytestringSplittingError:
                message = cls.really_unknown_version_message.format(
                    version, cls.LEARNER_VERSION)

            raise cls.IsFromTheFuture(message)

        # Version stuff checked out.  Moving on.
        node_info = cls.internal_splitter(payload)

        powers_and_material = {
            SigningPower: node_info.pop("verifying_key"),
            DecryptingPower: node_info.pop("encrypting_key")
        }

        interface_info = node_info.pop("rest_interface")
        node_info['rest_host'] = interface_info.host
        node_info['rest_port'] = interface_info.port

        node_info['timestamp'] = maya.MayaDT(node_info.pop("timestamp"))
        node_info['checksum_public_address'] = to_checksum_address(
            node_info.pop("public_address"))

        domains_vbytes = VariableLengthBytestring.dispense(
            node_info['domains'])
        node_info['domains'] = [constant_or_bytes(d) for d in domains_vbytes]

        ursula = cls.from_public_keys(powers_and_material,
                                      federated_only=federated_only,
                                      **node_info)
        return ursula
Example #4
0
    def __init__(
        self,
        domains: Set,
        certificate: Certificate,
        certificate_filepath: str,
        interface_signature=NOT_SIGNED.bool_value(False),
        timestamp=NOT_SIGNED,
        decentralized_identity_evidence=NOT_SIGNED,
        substantiate_immediately=False,
        password=None,
    ) -> None:

        #
        # Fleet
        #

        self.serving_domains = domains
        self.fleet_state_checksum = None
        self.fleet_state_updated = None
        self.last_seen = NEVER_SEEN("No Connection to Node")

        self.fleet_state_icon = UNKNOWN_FLEET_STATE
        self.fleet_state_nickname = UNKNOWN_FLEET_STATE
        self.fleet_state_nickname_metadata = UNKNOWN_FLEET_STATE

        #
        # Identity
        #

        self._timestamp = timestamp
        self.certificate = certificate
        self.certificate_filepath = certificate_filepath
        self.__interface_signature = interface_signature
        self.__decentralized_identity_evidence = constant_or_bytes(
            decentralized_identity_evidence)

        # Assume unverified
        self.verified_stamp = False
        self.verified_worker = False
        self.verified_interface = False
        self.verified_node = False

        if substantiate_immediately:
            self.substantiate_stamp(client_password=password)
Example #5
0
    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.warn("Can't learn right now: {}".format(e.args[0]))
            return

        if Teacher in self.__class__.__bases__:
            announce_nodes = [self]
        else:
            announce_nodes = None

        unresponsive_nodes = set()
        try:
            # TODO: Streamline path generation
            certificate_filepath = self.node_storage.generate_certificate_filepath(
                checksum_address=current_teacher.checksum_public_address)
            response = self.network_middleware.get_nodes_via_rest(
                node=current_teacher,
                nodes_i_need=self._node_ids_to_learn_about_immediately,
                announce_nodes=announce_nodes,
                fleet_checksum=self.known_nodes.checksum)
        except NodeSeemsToBeDown as e:
            unresponsive_nodes.add(current_teacher)
            self.log.info("Bad Response from teacher: {}:{}.".format(
                current_teacher, e))
            return
        finally:
            self.cycle_teacher_node()

        #
        # Before we parse the response, let's handle some edge cases.
        if response.status_code == 204:
            # In this case, this node knows about no other nodes.  Hopefully we've taught it something.
            if response.content == b"":
                return NO_KNOWN_NODES
            # In the other case - where the status code is 204 but the repsonse isn't blank - we'll keep parsing.
            # It's possible that our fleet states match, and we'll check for that later.

        elif response.status_code != 200:
            self.log.info("Bad response from teacher {}: {} - {}".format(
                current_teacher, response, response.content))
            return

        try:
            signature, node_payload = signature_splitter(response.content,
                                                         return_remainder=True)
        except BytestringSplittingError as e:
            self.log.warn(e.args[0])
            return

        try:
            self.verify_from(current_teacher,
                             node_payload,
                             signature=signature)
        except current_teacher.InvalidSignature:
            # TODO: What to do if the teacher improperly signed the node payload?
            raise
        # End edge case handling.
        #

        fleet_state_checksum_bytes, fleet_state_updated_bytes, node_payload = FleetStateTracker.snapshot_splitter(
            node_payload, return_remainder=True)
        current_teacher.last_seen = maya.now()
        # TODO: This is weird - let's get a stranger FleetState going.
        checksum = fleet_state_checksum_bytes.hex()

        # TODO: This doesn't make sense - a decentralized node can still learn about a federated-only node.
        from nucypher.characters.lawful import Ursula
        if constant_or_bytes(node_payload) is FLEET_STATES_MATCH:
            current_teacher.update_snapshot(
                checksum=checksum,
                updated=maya.MayaDT(
                    int.from_bytes(fleet_state_updated_bytes,
                                   byteorder="big")),
                number_of_known_nodes=len(self.known_nodes))
            return FLEET_STATES_MATCH

        node_list = Ursula.batch_from_bytes(
            node_payload, federated_only=self.federated_only)  # TODO: 466

        current_teacher.update_snapshot(checksum=checksum,
                                        updated=maya.MayaDT(
                                            int.from_bytes(
                                                fleet_state_updated_bytes,
                                                byteorder="big")),
                                        number_of_known_nodes=len(node_list))

        new_nodes = []
        for node in node_list:
            if GLOBAL_DOMAIN not in self.learning_domains:
                if not self.learning_domains.intersection(
                        node.serving_domains):
                    continue  # This node is not serving any of our domains.

            # First, determine if this is an outdated representation of an already known node.
            with suppress(KeyError):
                already_known_node = self.known_nodes[
                    node.checksum_public_address]
                if not node.timestamp > already_known_node.timestamp:
                    self.log.debug("Skipping already known node {}".format(
                        already_known_node))
                    # This node is already known.  We can safely continue to the next.
                    continue

            certificate_filepath = self.node_storage.store_node_certificate(
                certificate=node.certificate)

            try:
                if eager:
                    node.verify_node(
                        self.network_middleware,
                        accept_federated_only=self.federated_only,  # TODO: 466
                        certificate_filepath=certificate_filepath)
                    self.log.debug("Verified node: {}".format(
                        node.checksum_public_address))

                else:
                    node.validate_metadata(
                        accept_federated_only=self.federated_only)  # TODO: 466
            # This block is a mess of eagerness.  This can all be done better lazily.
            except NodeSeemsToBeDown as e:
                self.log.info(
                    f"Can't connect to {node} to verify it right now.")
            except node.InvalidNode:
                # TODO: Account for possibility that stamp, rather than interface, was bad.
                self.log.warn(node.invalid_metadata_message.format(node))
            except node.SuspiciousActivity:
                message = "Suspicious Activity: Discovered node with bad signature: {}.  " \
                          "Propagated by: {}".format(current_teacher.checksum_public_address, teacher_uri)
                self.log.warn(message)
            else:
                new = self.remember_node(node, record_fleet_state=False)
                if new:
                    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, len(node_list),
                                              len(new_nodes)), )
        if new_nodes:
            self.known_nodes.record_fleet_state()
            for node in new_nodes:
                self.node_storage.store_node_certificate(
                    certificate=node.certificate)
        return new_nodes