Esempio n. 1
0
    async def send_response(self, msg: Message) -> Message:
        """ Send response to request.

            send_response message format:

                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/admin_connections/1.0/send_response",
                  "did": <did of request sender>
                }

            Response format:
                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/response",
                  "did":"A.did@A:B",
                  "did_doc": {
                      //did doc
                  }
                }
        """

        their_did = msg['did']

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

        my_did = pairwise_info['my_did']
        label = pairwise_meta['label']
        my_vk = await did.key_for_local_did(self.agent.wallet_handle, my_did)

        response_msg = Message({
            '@type': Connection.RESPONSE,
            '~thread': { Message.THREAD_ID: pairwise_meta['req_id'], Message.SENDER_ORDER: 0 },
            'connection': {
                'did': my_did,
                'did_doc': {
                    "@context": "https://w3id.org/did/v1",
                    "id": my_did,
                    "publicKey": [{
                        "id": my_did + "#keys-1",
                        "type": "Ed25519VerificationKey2018",
                        "controller": my_did,
                        "publicKeyBase58": my_vk
                    }],
                    "service": [{
                        "id": my_did + ";indy",
                        "type": "IndyAgent",
                        "recipientKeys": [my_vk],
                        #"routingKeys": ["<example-agency-verkey>"],
                        "serviceEndpoint": self.agent.endpoint,
                    }],
                }
            }
        })

        # Apply signature to connection field, sign it with the key used in the invitation and request
        response_msg['connection~sig'] = await self.agent.sign_agent_message_field(response_msg['connection'], pairwise_meta["connection_key"])
        del response_msg['connection']

        pending_connection = Serializer.unpack(
            json.loads(
                await non_secrets.get_wallet_record(self.agent.wallet_handle,
                                                    'invitations',
                                                    pairwise_meta['connection_key'],
                                                    '{}')
            )['value']
        )
        pending_connection['status'] = "Response Sent"
        pending_connection['@type'] = AdminConnection.RESPONSE_SENT
        pending_connection['history'].append({
            'date': str(datetime.datetime.now()),
            'msg': msg.to_dict()})

        await self.agent.send_message_to_agent(their_did, response_msg)

        await self.agent.send_admin_message(pending_connection)

        await non_secrets.delete_wallet_record(self.agent.wallet_handle,
                                               'invitations',
                                               pairwise_meta['connection_key'])
Esempio n. 2
0
    async def receive_invite(self, msg: Message) -> Message:
        """ Receive and save invite.

            This interaction represents an out-of-band communication channel. In the future and in
            practice, these sort of invitations will be received over any number of channels such as
            SMS, Email, QR Code, NFC, etc.

            In this iteration, invite messages are received from the admin interface as a URL
            after being copied and pasted from another agent instance.

            The URL is formatted as follows:

                https://<domain>/<path>?c_i=<invitationstring>

            The invitation string is a base64 url encoded json string.

            Structure of an invite message:

                {
                    "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation",
                    "label": "Alice",
                    "did": "did:sov:QmWbsNYhMrjHiqZDTUTEJs"
                }

            Or, in the case of a peer DID:

                {
                    "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation",
                    "label": "Alice",
                    "key": "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K",
                    "endpoint": "https://example.com/endpoint"
                }

            Currently, only peer DID format is supported.
        """

        # Parse invite string
        matches = re.match("(.+)?c_i=(.+)", msg['invite'])
        if not matches:
            raise BadInviteException("Invite string is improperly formatted")

        invite_msg = Serializer.unpack(
            base64.urlsafe_b64decode(matches.group(2)).decode('utf-8')
        )

        pending_connection = Message({
            '@type': AdminConnection.INVITE_RECEIVED,
            'label': invite_msg['label'],
            'connection_key': invite_msg['recipientKeys'][0],
            'endpoint': invite_msg['serviceEndpoint'],
            'history': [{
                'date': str(datetime.datetime.now()),
                'msg': invite_msg.to_dict()
            }],
            'status': "Invite Received"
        })
        await self.agent.send_admin_message(pending_connection)

        await non_secrets.add_wallet_record(
            self.agent.wallet_handle,
            'invitations',
            invite_msg['recipientKeys'][0],
            Serializer.pack(pending_connection),
            '{}'
        )
Esempio n. 3
0
    async def send_request(self, msg: Message):
        """ Recall invite message from wallet and prepare and send request to the inviter.

            send_request message format:

                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/admin_connections/1.0/send_request",
                  "key": <key sent in invite>
                }

            Request format:

                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/request",
                  "label": "Bob",
                  "did": "B.did@B:A",
                  "did_doc": {
                      // did Doc here.
                  }
                }
        """
        pending_connection = Serializer.unpack(
            json.loads(
                await non_secrets.get_wallet_record(
                    self.agent.wallet_handle,
                    'invitations',
                    msg['connection_key'],
                    '{}'
                )
            )['value']
        )

        my_label = self.agent.owner
        label = pending_connection['label']
        their_connection_key = pending_connection['connection_key']
        their_endpoint = pending_connection['endpoint']

        # Create my information for connection
        (my_did, my_vk) = await utils.create_and_store_my_did(self.agent.wallet_handle)

        await did.set_did_metadata(
            self.agent.wallet_handle,
            my_did,
            json.dumps({
                'label': label,
                'their_endpoint': their_endpoint
            })
        )

        # Send Connection Request to inviter
        request = Message({
            '@type': Connection.REQUEST,
            'label': my_label,
            'connection': {
                'did': my_did,
                'did_doc': {
                    "@context": "https://w3id.org/did/v1",
                    "id": my_did,
                    "publicKey": [{
                        "id": my_did + "#keys-1",
                        "type": "Ed25519VerificationKey2018",
                        "controller": my_did,
                        "publicKeyBase58": my_vk
                    }],
                    "service": [{
                        "id": my_did + ";indy",
                        "type": "IndyAgent",
                        "recipientKeys": [my_vk],
                        #"routingKeys": ["<example-agency-verkey>"],
                        "serviceEndpoint": self.agent.endpoint,
                    }],
                }
            }
        })

        await self.agent.send_message_to_endpoint_and_key(
            their_connection_key,
            their_endpoint,
            request,
            my_vk
        )

        pending_connection['@type'] = AdminConnection.REQUEST_SENT
        pending_connection['status'] = "Request Sent"
        pending_connection['history'].append({
            'date': str(datetime.datetime.now()),
            'msg': msg.to_dict()})
        await non_secrets.update_wallet_record_value(self.agent.wallet_handle,
                                                     'invitations',
                                                     pending_connection['connection_key'],
                                                     Serializer.pack(pending_connection))

        await self.agent.send_admin_message(pending_connection)
Esempio n. 4
0
def pack(msg: Message) -> str:
    """
    Serialize from Message to json string or from dictionary to json string.
    """
    return msg.as_json()
Esempio n. 5
0
def unpack_dict(dictionary: dict) -> Message:
    deserialized_msg = Message(dictionary)
    return deserialized_msg
Esempio n. 6
0
    def serialize(msg: Message) -> bytes:
        """ Serialize from Message to json string or from dictionary to json string.
        """

        return msg.as_json().encode('utf-8')
Esempio n. 7
0
    def deserialize(dump: bytes) -> Message:
        """ Deserialize from json string to Message, if it looks like a Message.
            Returns a dictionary otherwise.
        """

        return Message(json.loads(dump))
Esempio n. 8
0
 async def ping_response(self, msg: Message) -> Message:
     await self.agent.send_admin_message(
         Message({
             '@type': AdminTrustPing.TRUSTPING_RESPONSE_RECEIVED,
             'from': msg.context['from_did'],
         }))
Esempio n. 9
0
    async def response_received(self, msg: Message) -> Message:
        """ Process response

            Response format:
                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/response",
                  "did":"A.did@A:B",
                  "did_doc": {
                      //did doc
                  }
                }
        """
        my_did = msg.context['to_did']
        if my_did is None:
            msg[ConnectionMessage.CONNECTION], sig_verified = await self.agent.unpack_and_verify_signed_agent_message_field(
                msg['connection~sig'])
            if not sig_verified:
                print('Encountered error parsing connection response. Connection request not found.')
            else:
                vk, endpoint = ConnectionMessage.extract_verkey_endpoint(msg)
                if None in (vk, endpoint):
                    # Cannot extract verkey and endpoint hence won't send any message back.
                    print('Encountered error parsing connection response. Connection request not found.')
                else:
                    # Sending an error message back to the sender
                    err_msg = self.build_problem_report_for_connections(Connection.FAMILY,
                                                                        ConnectionMessage.RESPONSE_FOR_UNKNOWN_REQUEST,
                                                                        "No corresponding connection request found")
                    await self.agent.send_message_to_endpoint_and_key(vk, endpoint, err_msg)
            return
        # Following should return an error if key not found for given DID
        my_vk = await did.key_for_local_did(self.agent.wallet_handle, my_did)

        #process signed field
        msg[ConnectionMessage.CONNECTION], sig_verified = await self.agent.unpack_and_verify_signed_agent_message_field(msg['connection~sig'])
        # connection~sig remains for metadata

        their_did, their_vk, their_endpoint = ConnectionMessage.extract_their_info(msg)

        msg_vk = msg.context['from_key']
        # TODO: verify their_vk (from did doc) matches msg_vk

        # Retrieve connection information from DID metadata
        my_did_meta = json.loads(
            await did.get_did_metadata(self.agent.wallet_handle, my_did)
        )
        label = my_did_meta['label']

        # Clear DID metadata. This info will be stored in pairwise meta.
        await did.set_did_metadata(self.agent.wallet_handle, my_did, '')

        # In the final implementation, a signature will be provided to verify changes to
        # the keys and DIDs to be used long term in the relationship.
        # Both the signature and signature check are omitted for now until specifics of the
        # signature are decided.

        # Store their information from response
        await utils.store_their_did(self.agent.wallet_handle, their_did, their_vk)

        await did.set_did_metadata(
            self.agent.wallet_handle,
            their_did,
            json.dumps({
                'label': label,
                'endpoint': their_endpoint
            })
        )

        # Create pairwise relationship between my did and their did
        await pairwise.create_pairwise(
            self.agent.wallet_handle,
            their_did,
            my_did,
            json.dumps({
                'label': label,
                'their_endpoint': their_endpoint,
                'their_vk': their_vk,
                'my_vk': my_vk,
                'connection_key': msg.data['connection~sig']['signer']
            })
        )

        pending_connection = Serializer.unpack(
            json.loads(
                await non_secrets.get_wallet_record(self.agent.wallet_handle,
                                                    'invitations',
                                                    msg.data['connection~sig']['signer'],
                                                    '{}')
            )['value']
        )
        pending_connection['status'] = "Response Received"
        pending_connection['@type'] = AdminConnection.RESPONSE_RECEIVED
        pending_connection['history'].append({
            'date': str(datetime.datetime.now()),
            'msg': msg.to_dict()})

        # Pairwise connection between agents is established at this point
        await self.agent.send_admin_message(pending_connection)

        # Delete invitation
        await non_secrets.delete_wallet_record(self.agent.wallet_handle,
                                               'invitations',
                                               msg.data['connection~sig']['signer'])
Esempio n. 10
0
    async def request_received(self, msg: Message) -> Message:
        """ Received connection request.

            Request format:

                {
                  "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/request",
                  "label": "Bob",
                  "connection":{
                      "did": "B.did@B:A",
                      "did_doc": {
                          "@context": "https://w3id.org/did/v1",
                          "publicKey": [{
                            "id": "did:example:123456789abcdefghi#keys-1",
                            "type": "Ed25519VerificationKey2018",
                            "controller": "did:example:123456789abcdefghi",
                            "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
                          }],
                          "service": [{
                            "type": "IndyAgent",
                            "recipientKeys" : [ "<verkey>" ], //pick one
                            "routingKeys": ["<example-agency-verkey>"],
                            "serviceEndpoint": "https://example.agency.com",
                          }]
                      }
                  }
                }
        """
        try:
            ConnectionMessage.Request.validate(msg)
        except Exception as e:
            vk, endpoint = ConnectionMessage.extract_verkey_endpoint(msg)
            if None in (vk, endpoint):
                # Cannot extract verkey and endpoint hence won't send any message back.
                print('Encountered error parsing connection request ', e)
            else:
                # Sending an error message back to the sender
                err_msg = self.build_problem_report_for_connections(Connection.FAMILY, ConnectionMessage.REQUEST_NOT_ACCEPTED, str(e))
                await self.agent.send_message_to_endpoint_and_key(vk, endpoint, err_msg)
            return

        connection_key = msg.context['to_key']

        label = msg['label']

        their_did, their_vk, their_endpoint = ConnectionMessage.extract_their_info(msg)

        # Store their information from request
        await utils.store_their_did(self.agent.wallet_handle, their_did, their_vk)

        await did.set_did_metadata(
            self.agent.wallet_handle,
            their_did,
            json.dumps({
                'label': label,
                'endpoint': their_endpoint,

            })
        )

        # Create my information for connection
        (my_did, my_vk) = await utils.create_and_store_my_did(self.agent.wallet_handle)

        # Create pairwise relationship between my did and their did
        await pairwise.create_pairwise(
            self.agent.wallet_handle,
            their_did,
            my_did,
            json.dumps({
                'label': label,
                'req_id': msg['@id'],
                'their_endpoint': their_endpoint,
                'their_vk': their_vk,
                'my_vk': my_vk,
                'connection_key': connection_key  # used to sign the response
            })
        )

        pending_connection = Message({
            '@type': AdminConnection.REQUEST_RECEIVED,
            'label': label,
            'did': their_did,
            'connection_key': connection_key,
            'endpoint': their_endpoint,
            'history': [{
                'date': str(datetime.datetime.now()),
                'msg': msg.to_dict()}],
            'status': "Request Received"
            # routingKeys not specified, but here is where they would be put in the invite.
        })
        try:
            await non_secrets.add_wallet_record(
                self.agent.wallet_handle,
                'invitations',
                connection_key,
                Serializer.pack(pending_connection),
                '{}'
            )
        except error.IndyError as indy_error:
            if indy_error.error_code == error.ErrorCode.WalletItemAlreadyExists:
                pass
            raise indy_error
        await self.agent.send_admin_message(pending_connection)