Пример #1
0
class Agent:
    """ Agent class storing all needed elements for agent operation.
    """
    def __init__(self):
        self.owner = None
        self.wallet_handle = None
        self.endpoint = None
        self.endpoint_vk = None
        self.ui_token = None
        self.pool_handle = None
        self.ui_socket = None
        self.initialized = False
        self.modules = {}
        self.family_router = FamilyRouter()
        self.message_queue = asyncio.Queue()
        self.outbound_admin_message_queue = asyncio.Queue()

    def register_module(self, module):
        self.modules[module.FAMILY] = module(self)
        self.family_router.register(module.FAMILY, self.modules[module.FAMILY])

    async def route_message_to_module(self, message):
        return await self.family_router.route(message)

    async def start(self):
        """ Message processing loop task.
        """
        while True:
            try:
                wire_msg_bytes = await self.message_queue.get()

                # Try to unpack message assuming it's not encrypted
                try:
                    msg = Serializer.unpack(wire_msg_bytes)
                except Exception as e:
                    print("Message encrypted, attempting to unpack...")

                # TODO: More graceful checking here
                # (This is an artifact of the provisional wire format and connection protocol)
                if not isinstance(msg, Message) or "@type" not in msg:
                    # Message IS encrypted so unpack it
                    try:
                        msg = await self.unpack_agent_message(wire_msg_bytes)
                    except Exception as e:
                        print(
                            'Failed to unpack message: {}\n\nError: {}'.format(
                                wire_msg_bytes, e))
                        continue  # handle next message in loop

                await self.route_message_to_module(msg)
            except Exception as e:
                print("\n\n--- Message Processing failed --- \n\n")
                traceback.print_exc()

    async def connect_wallet(self, agent_name, passphrase, ephemeral=False):
        """ Create if not already exists and open wallet.
        """

        self.owner = agent_name
        wallet_suffix = "wallet"
        if ephemeral:
            wallet_suffix = "ephemeral_wallet"
        wallet_name = '{}-{}'.format(self.owner, wallet_suffix)

        wallet_config = json.dumps({"id": wallet_name})
        wallet_credentials = json.dumps({"key": passphrase})

        # Handle ephemeral wallets
        if ephemeral:
            try:
                await wallet.delete_wallet(wallet_config, wallet_credentials)
                print("Removing ephemeral wallet.")
            except error.IndyError as e:
                if e.error_code is error.ErrorCode.WalletNotFoundError:
                    pass  # This is ok, and expected.
                else:
                    print("Unexpected Indy Error: {}".format(e))
            except Exception as e:
                print(e)
        # pylint: disable=bare-except

        try:
            await wallet.create_wallet(wallet_config, wallet_credentials)
        except error.IndyError as e:
            if e.error_code is error.ErrorCode.WalletAlreadyExistsError:
                pass  # This is ok, and expected.
            else:
                print("Unexpected Indy Error: {}".format(e))
        except Exception as e:
            print(e)

        try:
            self.wallet_handle = await wallet.open_wallet(
                wallet_config, wallet_credentials)

            (_, self.endpoint_vk) = await did.create_and_store_my_did(
                self.wallet_handle, "{}")

            self.initialized = True

        except Exception as e:
            print(e)
            print("Could not open wallet!")

            raise WalletConnectionException

    async def sign_agent_message_field(self, field_value, my_vk):
        timestamp_bytes = struct.pack(">Q", int(time.time()))

        sig_data_bytes = timestamp_bytes + json.dumps(field_value).encode(
            'ascii')
        sig_data = base64.urlsafe_b64encode(sig_data_bytes).decode('ascii')

        signature_bytes = await crypto.crypto_sign(self.wallet_handle, my_vk,
                                                   sig_data_bytes)
        signature = base64.urlsafe_b64encode(signature_bytes).decode('ascii')

        return {
            "@type":
            "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/signature/1.0/ed25519Sha512_single",
            "signer": my_vk,
            "sig_data": sig_data,
            "signature": signature
        }

    async def unpack_and_verify_signed_agent_message_field(self, signed_field):
        signature_bytes = base64.urlsafe_b64decode(
            signed_field['signature'].encode('ascii'))
        sig_data_bytes = base64.urlsafe_b64decode(
            signed_field['sig_data'].encode('ascii'))
        sig_verified = await crypto.crypto_verify(signed_field['signer'],
                                                  sig_data_bytes,
                                                  signature_bytes)
        data_bytes = base64.urlsafe_b64decode(signed_field['sig_data'])
        timestamp = struct.unpack(">Q", data_bytes[:8])
        fieldjson = data_bytes[8:]
        return json.loads(fieldjson), sig_verified

    async def unpack_agent_message(self, wire_msg_bytes):

        unpacked = json.loads(await
                              crypto.unpack_message(self.wallet_handle,
                                                    wire_msg_bytes))

        from_key = None
        if 'sender_verkey' in unpacked:
            from_key = unpacked['sender_verkey']
            from_did = await utils.did_for_key(self.wallet_handle,
                                               unpacked['sender_verkey'])

        to_key = unpacked['recipient_verkey']
        to_did = await utils.did_for_key(self.wallet_handle,
                                         unpacked['recipient_verkey'])

        msg = Serializer.unpack(unpacked['message'])

        msg.context = {
            'from_did': from_did,  # Could be None
            'to_did': to_did,  # Could be None
            'from_key': from_key,  # Could be None
            'to_key': to_key
        }
        return msg

    async def send_message_to_agent(self, to_did, msg: Message):
        print("Sending:", msg)
        their_did = to_did

        pairwise_info = json.loads(await pairwise.get_pairwise(
            self.wallet_handle, their_did))
        pairwise_meta = json.loads(pairwise_info['metadata'])

        my_did = pairwise_info['my_did']
        their_endpoint = pairwise_meta['their_endpoint']
        their_vk = pairwise_meta['their_vk']

        my_vk = await did.key_for_local_did(self.wallet_handle, my_did)

        await self.send_message_to_endpoint_and_key(my_vk, their_vk,
                                                    their_endpoint, msg)

    # used directly when sending to an endpoint without a known did
    async def send_message_to_endpoint_and_key(self, my_ver_key, their_ver_key,
                                               their_endpoint, msg):
        wire_message = {
            'to':
            their_ver_key,
            'from':
            my_ver_key,
            'payload':
            serialize_bytes_json(await crypto.auth_crypt(
                self.wallet_handle, my_ver_key, their_ver_key,
                str_to_bytes(msg.as_json())))
        }

        wire_message = await crypto.pack_message(self.wallet_handle,
                                                 Serializer.pack(msg),
                                                 [their_ver_key], my_ver_key)

        async with aiohttp.ClientSession() as session:
            headers = {'content-type': 'application/ssi-agent-wire'}
            async with session.post(their_endpoint,
                                    data=wire_message,
                                    headers=headers) as resp:
                if resp.status != 202:
                    print(resp.status)
                    print(await resp.text())

    async def send_admin_message(self, msg: Message):
        await self.outbound_admin_message_queue.put(msg.as_json())
Пример #2
0
class Agent:
    """ Agent class storing all needed elements for agent operation.
    """
    def __init__(self):
        self.owner = None
        self.wallet_handle = None
        self.endpoint = None
        self.endpoint_vk = None
        self.ui_token = None
        self.pool_handle = None
        self.ui_socket = None
        self.initialized = False
        self.modules = {}
        self.family_router = FamilyRouter()
        self.message_queue = asyncio.Queue()
        self.outbound_admin_message_queue = asyncio.Queue()

    def register_module(self, module):
        self.modules[module.FAMILY] = module(self)
        self.family_router.register(module.FAMILY, self.modules[module.FAMILY])

    async def route_message_to_module(self, message):
        return await self.family_router.route(message)

    async def start(self):
        """ Message processing loop task.
        """
        while True:
            try:
                wire_msg_bytes = await self.message_queue.get()

                # Try to unpack message assuming it's not encrypted
                try:
                    msg = Serializer.unpack(wire_msg_bytes)
                except Exception as e:
                    print("Message encryped, attempting to unpack...")

                # TODO: More graceful checking here
                # (This is an artifact of the provisional wire format and connection protocol)
                if not isinstance(msg, Message) or "@type" not in msg:
                    # Message IS encrypted so unpack it
                    try:
                        msg = await self.unpack_agent_message(wire_msg_bytes)
                    except Exception as e:
                        print(
                            'Failed to unpack message: {}\n\nError: {}'.format(
                                wire_msg_bytes, e))
                        continue  # handle next message in loop

                await self.route_message_to_module(msg)
            except Exception as e:
                print("\n\nMessage Processing failed!!! \n\n{}".format(e))

    async def connect_wallet(self, agent_name, passphrase, ephemeral=False):
        """ Create if not already exists and open wallet.
        """

        self.owner = agent_name
        wallet_suffix = "wallet"
        if ephemeral:
            wallet_suffix = "ephemeral_wallet"
        wallet_name = '{}-{}'.format(self.owner, wallet_suffix)

        wallet_config = json.dumps({"id": wallet_name})
        wallet_credentials = json.dumps({"key": passphrase})

        # Handle ephemeral wallets
        if ephemeral:
            try:
                await wallet.delete_wallet(wallet_config, wallet_credentials)
                print("Removing ephemeral wallet.")
            except error.IndyError as e:
                if e.error_code is error.ErrorCode.WalletNotFoundError:
                    pass  # This is ok, and expected.
                else:
                    print("Unexpected Indy Error: {}".format(e))
            except Exception as e:
                print(e)
        # pylint: disable=bare-except

        try:
            await wallet.create_wallet(wallet_config, wallet_credentials)
        except error.IndyError as e:
            if e.error_code is error.ErrorCode.WalletAlreadyExistsError:
                pass  # This is ok, and expected.
            else:
                print("Unexpected Indy Error: {}".format(e))
        except Exception as e:
            print(e)

        try:
            self.wallet_handle = await wallet.open_wallet(
                wallet_config, wallet_credentials)

            (_, self.endpoint_vk) = await did.create_and_store_my_did(
                self.wallet_handle, "{}")

            self.initialized = True

        except Exception as e:
            print(e)
            print("Could not open wallet!")

            raise WalletConnectionException

    async def unpack_agent_message(self, wire_msg_bytes):
        wire_msg = json.loads(wire_msg_bytes)

        # get key from wallet from to: did
        # TODO: add error reporting if DID not found

        #load my key list
        did_list_str = await did.list_my_dids_with_meta(self.wallet_handle)
        did_list = json.loads(did_list_str)
        my_verkey_to_did = {}
        for d in did_list:
            my_verkey_to_did[d['verkey']] = d['did']

        if wire_msg['to'] not in my_verkey_to_did:
            raise Exception("Unknown recipient key")

        #load pairwise
        pairwise_list_str = await pairwise.list_pairwise(self.wallet_handle)
        pairwise_list = json.loads(pairwise_list_str)
        their_verkey_to_did = {}
        for p_str in pairwise_list:
            p = json.loads(p_str)
            p_meta = json.loads(p['metadata'])
            their_verkey_to_did[p_meta['their_verkey']] = p['their_did']

        from_did = None
        if wire_msg['from'] in their_verkey_to_did:
            from_did = their_verkey_to_did[wire_msg['from']]

        # now process payload
        message_bytes = base64.b64decode(wire_msg['payload'])

        their_key_str, their_data_bytes = await crypto.auth_decrypt(
            self.wallet_handle, wire_msg['to'], message_bytes)

        msg_str = bytes_to_str(their_data_bytes)
        msg_json = json.loads(msg_str)

        msg = Message(msg_json)

        msg.context = {
            'from_did': from_did,  # will be none if unknown sender verkey
            'from_key': their_key_str,
            'to_key': wire_msg['to'],
            'to_did': my_verkey_to_did[wire_msg['to']]
        }
        return msg

    async def send_message_to_agent(self, to_did, msg: Message):

        their_did_str = to_did

        pairwise_conn_info_str = await pairwise.get_pairwise(
            self.wallet_handle, their_did_str)
        pairwise_conn_info_json = json.loads(pairwise_conn_info_str)

        my_did_str = pairwise_conn_info_json['my_did']

        metadata_json = json.loads(pairwise_conn_info_json['metadata'])
        # conn_name = metadata_json['conn_name']
        their_endpoint = metadata_json['their_endpoint']
        their_verkey_str = metadata_json['their_verkey']

        my_did_info_str = await did.get_my_did_with_meta(
            self.wallet_handle, my_did_str)
        my_did_info_json = json.loads(my_did_info_str)
        my_verkey_str = my_did_info_json['verkey']

        await self.send_message_to_endpoint_and_key(my_verkey_str,
                                                    their_verkey_str,
                                                    their_endpoint, msg)

    # used directly when sending to an endpoint without a known did
    async def send_message_to_endpoint_and_key(self, my_ver_key, their_ver_key,
                                               their_endpoint, msg):
        wire_message = {
            'to':
            their_ver_key,
            'from':
            my_ver_key,
            'payload':
            serialize_bytes_json(await crypto.auth_crypt(
                self.wallet_handle, my_ver_key, their_ver_key,
                str_to_bytes(msg.as_json())))
        }

        async with aiohttp.ClientSession() as session:
            async with session.post(their_endpoint,
                                    data=json.dumps(wire_message)) as resp:
                print(resp.status)
                print(await resp.text())

    async def send_admin_message(self, msg: Message):
        await self.outbound_admin_message_queue.put(msg.as_json())