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'])
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), '{}' )
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)
def pack(msg: Message) -> str: """ Serialize from Message to json string or from dictionary to json string. """ return msg.as_json()
def unpack_dict(dictionary: dict) -> Message: deserialized_msg = Message(dictionary) return deserialized_msg
def serialize(msg: Message) -> bytes: """ Serialize from Message to json string or from dictionary to json string. """ return msg.as_json().encode('utf-8')
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))
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'], }))
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'])
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)