class AdminWalletConnection(Module): FAMILY_NAME = "admin_walletconnection" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION CONNECT = FAMILY + "/connect" DISCONNECT = FAMILY + "/disconnect" USER_ERROR = FAMILY + "/user_error" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminWalletConnection.CONNECT, self.connect) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def connect(self, msg): """ Connect to existing wallet. """ try: await self.agent.connect_wallet(msg['name'], msg['passphrase']) except WalletConnectionException: return Message({ '@type': AdminWalletConnection.USER_ERROR, 'error_code': "invalid_passphrase", 'message': "Invalid Passphrase", 'thread': { 'thid': msg.id } }) # prompt a STATE message. return await self.agent.modules[Admin.FAMILY].state_request(None)
class AdminWalletConnection(Module): def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(ADMIN_WALLETCONNECTION.CONNECT, self.connect) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def connect(self, msg): """ Connect to existing wallet. """ try: await self.agent.connect_wallet(msg['name'], msg['passphrase']) except WalletConnectionException: return Message({ 'type': ADMIN_WALLETCONNECTION.USER_ERROR, 'error_code': "invalid_passphrase", 'message': "Invalid Passphrase", 'thread': { 'thid': msg.id } }) # prompt a STATE message. return await self.agent.modules['ui'].ui_connect(None)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminConnection.SEND_INVITE, self.send_invite) self.router.register(AdminConnection.SEND_REQUEST, self.send_request) self.router.register(AdminConnection.SEND_RESPONSE, self.send_response)
class AdminProtocolDiscovery(Module): FAMILY_NAME = "admin_protocol_discovery" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION SEND_QUERY = FAMILY + "/send_query" QUERY_SENT = FAMILY + "/query_sent" QUERY_RECEIVED = FAMILY + "/query_received" DISCLOSE_SENT = FAMILY + "/disclose_sent" DISCLOSE_RECEIVED = FAMILY + "/disclose_received" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminProtocolDiscovery.SEND_QUERY, self.send_query) async def route(self, msg: Message) -> None: return await self.router.route(msg) async def send_query(self, msg: Message) -> None: query_msg = Message({ '@type': ProtocolDiscovery.QUERY, 'query': msg['query'] }) await self.agent.send_message_to_agent(msg['did'], query_msg) await self.agent.send_admin_message( Message({ '@type': AdminProtocolDiscovery.QUERY_SENT, 'from': msg['did'] }))
class Ui(Module): def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(UI.STATE_REQUEST, self.ui_connect) self.router.register(UI.INITIALIZE, self.initialize_agent) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def ui_connect(self, _) -> Message: return Message({ 'type': UI.STATE, 'content': { 'initialized': self.agent.initialized, 'agent_name': self.agent.owner } }) async def initialize_agent(self, msg): """ Initialize agent. """ if self.agent.initialized is True: return data = msg['content'] agent_name = data['name'] passphrase = data['passphrase'] await self.agent.connect_wallet(data['name'], data['passphrase']) return await self.ui_connect(None)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminConnection.GENERATE_INVITE, self.generate_invite) self.router.register(AdminConnection.RECEIVE_INVITE, self.receive_invite) self.router.register(AdminConnection.SEND_REQUEST, self.send_request) self.router.register(AdminConnection.SEND_RESPONSE, self.send_response)
class BasicMessage(Module): def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(BASICMESSAGE.MESSAGE, self.receive_message) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def receive_message(self, msg: Message) -> Message: their_did_str = msg['from'] pairwise_conn_info_str = await pairwise.get_pairwise(self.agent.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.loads(pairwise_conn_info_json['metadata']) my_did_info_str = await did.get_my_did_with_meta(self.agent.wallet_handle, my_did_str) my_did_info_json = json.loads(my_did_info_str) my_verkey = my_did_info_json['verkey'] message_bytes = str_to_bytes(msg['message']) message_bytes = base64.b64decode(message_bytes) their_key_str, their_data_bytes = await crypto.auth_decrypt( self.agent.wallet_handle, my_verkey, message_bytes) their_data_json = json.loads(bytes_to_str(their_data_bytes)) # store message in the wallet await non_secrets.add_wallet_record( self.agent.wallet_handle, "basicmessage", uuid.uuid4().hex, json.dumps({ 'from': their_did_str, 'timestamp': their_data_json['timestamp'], 'content': their_data_json['content'] }), json.dumps({ "their_did": their_did_str }) ) return Message({ '@type': ADMIN_BASICMESSAGE.MESSAGE_RECEIVED, 'id': self.agent.ui_token, 'with': their_did_str, 'message': { 'from': their_did_str, 'timestamp': their_data_json['timestamp'], 'content': their_data_json['content'] } })
class BasicMessage(Module): """ Class handling messages received from another Indy agent. """ FAMILY_NAME = "basicmessage" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION MESSAGE = FAMILY + "/message" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(BasicMessage.MESSAGE, self.receive_message) async def route(self, msg: Message) -> None: """ Route a message to its registered callback. """ return await self.router.route(msg) async def receive_message(self, msg: Message) -> None: """ Process the reception of a basic message from another Indy agent. :param msg: Basic message is of the following format: { '@type': BasicMessage.MESSAGE, '~l10n': {'locale': 'en'}, 'sent_time': '2019-05-27 08:34:25.105373+00:00', 'content': 'Hello' } :return: None """ is_valid = await self.validate_common_message_blocks( msg, BasicMessage.FAMILY) if not is_valid: return # Store message in the wallet await non_secrets.add_wallet_record( self.agent.wallet_handle, 'basicmessage', uuid.uuid4().hex, json.dumps({ 'from': msg.context['from_did'], 'sent_time': msg['sent_time'], 'content': msg['content'] }), json.dumps({'their_did': msg.context['from_did']})) await self.agent.send_admin_message( Message({ '@type': AdminBasicMessage.MESSAGE_RECEIVED, 'id': self.agent.ui_token, 'with': msg.context['from_did'], 'message': { 'from': msg.context['from_did'], 'sent_time': msg['sent_time'], 'content': msg['content'] } }))
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(CONN.INVITE, self.invite_received) self.router.register(CONN.REQUEST, self.request_received) self.router.register(CONN.RESPONSE, self.response_received) self.router.register(CONN.MESSAGE, self.message_received) self.router.register(CONN_UI.SEND_INVITE, self.send_invite) self.router.register(CONN_UI.SEND_REQUEST, self.send_request) self.router.register(CONN_UI.SEND_RESPONSE, self.send_response) self.router.register(CONN_UI.SEND_MESSAGE, self.send_message)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(CONN.INVITE, self.invite_received) self.router.register(CONN.REQUEST, self.request_received) self.router.register(CONN.RESPONSE, self.response_received) self.router.register(ADMIN_CONNECTIONS.SEND_INVITE, self.send_invite) self.router.register(ADMIN_CONNECTIONS.SEND_REQUEST, self.send_request) self.router.register(ADMIN_CONNECTIONS.SEND_RESPONSE, self.send_response)
class Admin(Module): FAMILY_NAME = "admin" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION STATE = FAMILY + "/state" STATE_REQUEST = FAMILY + "/state_request" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(self.STATE_REQUEST, self.state_request) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def state_request(self, _) -> Message: print("Processing state_request") if self.agent.initialized: invitations = await get_wallet_records(self.agent.wallet_handle, "invitations") # load up pairwise connections pairwise_records = [] agent_pairwises_list_str = await pairwise.list_pairwise( self.agent.wallet_handle) agent_pairwises_list = json.loads(agent_pairwises_list_str) for agent_pairwise_str in agent_pairwises_list: pairwise_record = json.loads(agent_pairwise_str) pairwise_record['metadata'] = json.loads( pairwise_record['metadata']) pairwise_records.append(pairwise_record) await self.agent.send_admin_message( Message({ '@type': self.STATE, 'content': { 'initialized': self.agent.initialized, 'agent_name': self.agent.owner, 'invitations': invitations, 'pairwise_connections': pairwise_records, } })) else: await self.agent.send_admin_message( Message({ '@type': self.STATE, 'content': { 'initialized': self.agent.initialized, } }))
class AdminWalletConnection(Module): """ Class handling messages received from the UI. """ FAMILY_NAME = "admin_walletconnection" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION CONNECT = FAMILY + "/connect" DISCONNECT = FAMILY + "/disconnect" USER_ERROR = FAMILY + "/user_error" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminWalletConnection.CONNECT, self.connect) self.router.register(AdminWalletConnection.DISCONNECT, self.disconnect) async def route(self, msg: Message) -> None: """ Route a message to its registered callback """ return await self.router.route(msg) async def connect(self, msg: Message) -> None: """ Connect to an existing wallet. """ try: await self.agent.connect_wallet(msg['name'], msg['passphrase']) except WalletConnectionException: await self.agent.send_admin_message( Message({ '@type': AdminWalletConnection.USER_ERROR, 'error_code': 'invalid_passphrase', 'message': 'Invalid Passphrase', 'thread': { 'thid': msg['id'] } })) # Prompt a STATE message. return await self.agent.modules[Admin.FAMILY].state_request(None) async def disconnect(self, _) -> None: """ Disconnect from an existing wallet. """ await self.agent.disconnect_wallet() # Prompt a STATE message. return await self.agent.modules[Admin.FAMILY].state_request(None)
class TrustPing(Module): FAMILY_NAME = "trust_ping" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION PING = FAMILY + "/ping" PING_RESPONSE = FAMILY + "/ping_response" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(TrustPing.PING, self.ping) self.router.register(TrustPing.PING_RESPONSE, self.ping_response) async def route(self, msg: Message) -> None: return await self.router.route(msg) async def ping(self, msg: Message) -> None: r = await self.validate_common_message_blocks(msg, TrustPing.FAMILY) if not r: return await self.agent.send_admin_message( Message({ '@type': AdminTrustPing.TRUSTPING_RECEIVED, 'from': msg.context['from_did'], })) await self.agent.send_message_to_agent( msg.context['from_did'], Message({ '@type': TrustPing.PING_RESPONSE, '~thread': { Message.THREAD_ID: msg.id, Message.SENDER_ORDER: 0 } })) async def ping_response(self, msg: Message) -> None: await self.agent.send_admin_message( Message({ '@type': AdminTrustPing.TRUSTPING_RESPONSE_RECEIVED, 'from': msg.context['from_did'], }))
class Admin(Module): def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(ADMIN.STATE_REQUEST, self.state_request) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def state_request(self, _) -> Message: invitations = [] if self.agent.initialized: search_handle = await non_secrets.open_wallet_search(self.agent.wallet_handle, "invitations", json.dumps({}), json.dumps({'retrieveTotalCount': True})) results = await non_secrets.fetch_wallet_search_next_records(self.agent.wallet_handle, search_handle, 100) for r in json.loads(results)["records"] or []: # records is None if empty d = json.loads(r['value']) d["_id"] = r["id"] # include record id for further reference. invitations.append(d) #TODO: fetch in loop till all records are processed await non_secrets.close_wallet_search(search_handle) # load up pairwise connections pairwise_records = [] agent_pairwises_list_str = await pairwise.list_pairwise(self.agent.wallet_handle) agent_pairwises_list = json.loads(agent_pairwises_list_str) for agent_pairwise_str in agent_pairwises_list: pairwise_record = json.loads(agent_pairwise_str) pairwise_record['metadata'] = json.loads(pairwise_record['metadata']) pairwise_records.append(pairwise_record) return Message({ '@type': ADMIN.STATE, 'content': { 'initialized': self.agent.initialized, 'agent_name': self.agent.owner, 'invitations': invitations, 'pairwise_connections': pairwise_records, } })
class BasicMessage(Module): FAMILY_NAME = "basicmessage" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION MESSAGE = FAMILY + "/message" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(BasicMessage.MESSAGE, self.receive_message) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def receive_message(self, msg: Message): r = await self.validate_common_message_blocks(msg, BasicMessage.FAMILY) if not r: return r # store message in the wallet await non_secrets.add_wallet_record( self.agent.wallet_handle, "basicmessage", uuid.uuid4().hex, json.dumps({ 'from': msg.context['from_did'], 'sent_time': msg['sent_time'], 'content': msg['content'] }), json.dumps({"their_did": msg.context['from_did']})) await self.agent.send_admin_message( Message({ '@type': AdminBasicMessage.MESSAGE_RECEIVED, 'id': self.agent.ui_token, 'with': msg.context['from_did'], 'message': { 'from': msg.context['from_did'], 'sent_time': msg['sent_time'], 'content': msg['content'] } }))
class AdminTrustPing(Module): FAMILY_NAME = "admin_trustping" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION + "/" SEND_TRUSTPING = FAMILY + "send_trustping" TRUSTPING_SENT = FAMILY + "trustping_sent" TRUSTPING_RECEIVED = FAMILY + "trustping_received" TRUSTPING_RESPONSE_RECEIVED = FAMILY + "trustping_response_received" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminTrustPing.SEND_TRUSTPING, self.send_trustping) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def trustping_response(self, msg: Message) -> Message: print("trustping_response") print(msg) async def send_trustping(self, msg: Message) -> Message: """ UI activated method. """ their_did_str = msg['to'] message = Message({ '@type': TrustPing.PING }) await self.agent.send_message_to_agent(their_did_str, message) await self.agent.send_admin_message( Message({ '@type': AdminTrustPing.TRUSTPING_SENT, 'to': their_did_str, }) )
class TrustPing(Module): FAMILY_NAME = "trust_ping" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION + "/" PING = FAMILY + "ping" PING_REPONSE = FAMILY + "ping_response" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(TrustPing.PING, self.ping) self.router.register(TrustPing.PING_REPONSE, self.ping_response) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def ping(self, msg: Message) -> Message: await self.agent.send_admin_message( Message({ '@type': AdminTrustPing.TRUSTPING_RECEIVED, 'from': msg.context['from_did'], }) ) await self.agent.send_message_to_agent( msg.context['from_did'], Message({ '@type': TrustPing.PING_REPONSE, '~thread': {'thid': msg.id} }) ) 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'], }) )
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(TrustPing.PING, self.ping) self.router.register(TrustPing.PING_RESPONSE, self.ping_response)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminTrustPing.SEND_TRUSTPING, self.send_trustping)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(Connection.REQUEST, self.request_received) self.router.register(Connection.RESPONSE, self.response_received)
class Connection(Module): FAMILY_NAME = "connections" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION + "/" INVITE = FAMILY + "invitation" REQUEST = FAMILY + "request" RESPONSE = FAMILY + "response" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(Connection.REQUEST, self.request_received) self.router.register(Connection.RESPONSE, self.response_received) async def route(self, msg: Message) -> Message: return await self.router.route(msg) 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", }] } } } """ connection_key = msg.context['to_key'] label = msg['label'] their_did = msg['connection']['did'] # NOTE: these values are pulled based on the minimal connectathon format. Full processing # will require full DIDDoc storage and evaluation. their_vk = msg['connection']['did_doc']['publicKey'][0]['publicKeyBase58'] their_endpoint = msg['connection']['did_doc']['service'][0]['serviceEndpoint'] # 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) 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'] my_vk = await did.key_for_local_did(self.agent.wallet_handle, my_did) #process signed field msg['connection'], sig_verified = await self.agent.unpack_and_verify_signed_agent_message_field(msg['connection~sig']) # connection~sig remains for metadata their_did = msg['connection']['did'] their_vk = msg['connection']['did_doc']['publicKey'][0]['publicKeyBase58'] their_endpoint = msg['connection']['did_doc']['service'][0]['serviceEndpoint'] 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 }) ) 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'])
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminBasicMessage.SEND_MESSAGE, self.send_message) self.router.register(AdminBasicMessage.GET_MESSAGES, self.get_messages)
class AdminBasicMessage(Module): FAMILY_NAME = "admin_basicmessage" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION + "/" MESSAGE_RECEIVED = FAMILY + "message_received" SEND_MESSAGE = FAMILY + "send_message" MESSAGE_SENT = FAMILY + "message_sent" GET_MESSAGES = FAMILY + "get_messages" MESSAGES = FAMILY + "messages" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminBasicMessage.SEND_MESSAGE, self.send_message) self.router.register(AdminBasicMessage.GET_MESSAGES, self.get_messages) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def send_message(self, msg: Message) -> Message: """ UI activated method. """ # This lookup block finds the from address from the to address. This should be fixed, so that the from address # comes in the admin message. their_did_str = msg['to'] pairwise_conn_info_str = await pairwise.get_pairwise( self.agent.wallet_handle, their_did_str) pairwise_conn_info_json = json.loads(pairwise_conn_info_str) my_did_str = pairwise_conn_info_json['my_did'] message_to_send = msg['message'] sent_time = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc).isoformat(' ') # store message in the wallet await non_secrets.add_wallet_record( self.agent.wallet_handle, "basicmessage", uuid.uuid4().hex, json.dumps({ 'from': my_did_str, 'sent_time': sent_time, 'content': message_to_send }), json.dumps({"their_did": their_did_str})) message = Message({ '@type': BasicMessage.MESSAGE, '~l10n': { 'locale': 'en' }, 'sent_time': sent_time, 'content': message_to_send }) await self.agent.send_message_to_agent(their_did_str, message) await self.agent.send_admin_message( Message({ '@type': AdminBasicMessage.MESSAGE_SENT, 'id': self.agent.ui_token, 'with': their_did_str, 'message': { 'from': my_did_str, 'sent_time': sent_time, 'content': message_to_send } })) async def get_messages(self, msg: Message) -> Message: their_did = msg['with'] search_handle = await non_secrets.open_wallet_search( self.agent.wallet_handle, "basicmessage", json.dumps({"their_did": their_did}), json.dumps({})) results = await non_secrets.fetch_wallet_search_next_records( self.agent.wallet_handle, search_handle, 100) messages = [] for r in json.loads( results)["records"] or []: # records is None if empty d = json.loads(r['value']) d["_id"] = r["id"] # include record id for further reference. messages.append(d) #TODO: fetch in loop till all records are processed await non_secrets.close_wallet_search(search_handle) messages = sorted(messages, key=lambda n: n['sent_time'], reverse=True) await self.agent.send_admin_message( Message({ '@type': AdminBasicMessage.MESSAGES, 'with': their_did, 'messages': messages }))
class AdminStaticConnection(Module): FAMILY_NAME = "admin_staticconnections" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION # Message Types in this family CREATE_STATIC_CONNECTION = FAMILY + "/create_static_connection" STATIC_CONNECTION_CREATED = FAMILY + "/static_connection_created" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminStaticConnection.CREATE_STATIC_CONNECTION, self.create_static_connection) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def create_static_connection(self, msg: Message) -> Message: """ Generate new connection invitation. 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. 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", "did": "did:peer:oiSqsNYhMrjHiqZDTUthsw", "key": "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K", "endpoint": "https://example.com/endpoint" } Currently, only peer DID is supported. """ their_did = msg['did'] their_vk = msg['vk'] their_endpoint = msg['endpoint'] label = msg['label'] # 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, 'their_endpoint': their_endpoint, 'their_vk': their_vk, 'my_vk': my_vk, 'static': True })) await self.agent.send_admin_message( Message({ '@type': AdminStaticConnection.STATIC_CONNECTION_CREATED, 'label': label, 'my_did': my_did, 'my_vk': my_vk, 'my_endpoint': self.agent.endpoint }))
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminStaticConnection.CREATE_STATIC_CONNECTION, self.create_static_connection)
class Connection(Module): FAMILY_NAME = "connections" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION INVITE = FAMILY + "/invitation" REQUEST = FAMILY + "/request" RESPONSE = FAMILY + "/response" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(Connection.REQUEST, self.request_received) self.router.register(Connection.RESPONSE, self.response_received) async def route(self, msg: Message) -> None: return await self.router.route(msg) async def request_received(self, msg: Message) -> None: """ 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", }] } } } """ r = await self.validate_common_message_blocks(msg, Connection.FAMILY) if not r: return 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.serialize(pending_connection).decode('utf-8'), '{}' ) 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) async def response_received(self, msg: Message) -> None: """ Process response Response format: { "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/response", "did":"A.did@A:B", "did_doc": { //did doc } } """ r = await self.validate_common_message_blocks(msg, Connection.FAMILY) if not r: return 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) # Verify that their_vk (from did doc) matches msg_vk msg_vk = msg.context['from_key'] if their_vk != msg_vk: err_msg = \ self.build_problem_report_for_connections( Connection.FAMILY, ConnectionMessage.KEY_ERROR, "Key provided in response does not match expected key") verkey, endpoint = ConnectionMessage.extract_verkey_endpoint(msg) await self.agent.send_message_to_endpoint_and_key(verkey, endpoint, err_msg) return # 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.deserialize( 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'])
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(self.STATE_REQUEST, self.state_request)
class AdminConnection(Module): FAMILY_NAME = "admin_connections" VERSION = "1.0" FAMILY = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/" + FAMILY_NAME + "/" + VERSION + "/" # Message Types in this family CONNECTION_LIST = FAMILY + "connection_list" CONNECTION_LIST_REQUEST = FAMILY + "connection_list_request" GENERATE_INVITE = FAMILY + "generate_invite" INVITE_GENERATED = FAMILY + "invite_generated" INVITE_RECEIVED = FAMILY + "invite_received" RECEIVE_INVITE = FAMILY + "receive_invite" SEND_REQUEST = FAMILY + "send_request" REQUEST_SENT = FAMILY + "request_sent" REQUEST_RECEIVED = FAMILY + "request_received" SEND_RESPONSE = FAMILY + "send_response" RESPONSE_SENT = FAMILY + "response_sent" RESPONSE_RECEIVED = FAMILY + "response_received" def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(AdminConnection.GENERATE_INVITE, self.generate_invite) self.router.register(AdminConnection.RECEIVE_INVITE, self.receive_invite) self.router.register(AdminConnection.SEND_REQUEST, self.send_request) self.router.register(AdminConnection.SEND_RESPONSE, self.send_response) async def route(self, msg: Message) -> Message: return await self.router.route(msg) async def generate_invite(self, msg: Message) -> Message: """ Generate new connection invitation. 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. 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", "did": "did:peer:oiSqsNYhMrjHiqZDTUthsw", "key": "8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K", "endpoint": "https://example.com/endpoint" } Currently, only peer DID is supported. """ connection_key = await did.create_key(self.agent.wallet_handle, "{}") # Store connection key await non_secrets.add_wallet_record( self.agent.wallet_handle, 'connection_key', connection_key, connection_key, '{}' ) invite_msg = Message({ '@type': Connection.INVITE, 'label': self.agent.owner, 'recipientKeys': [connection_key], 'serviceEndpoint': self.agent.endpoint, # routingKeys not specified, but here is where they would be put in the invite. }) b64_invite = \ base64.urlsafe_b64encode(bytes(Serializer.pack(invite_msg), 'utf-8')).decode('ascii') await self.agent.send_admin_message( Message({ '@type': AdminConnection.INVITE_GENERATED, 'invite': '{}?c_i={}'.format(self.agent.endpoint, b64_invite) }) ) 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( my_vk, their_connection_key, their_endpoint, request ) 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) 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': { 'thid': pairwise_meta['req_id'] }, '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'])
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(ADMIN_WALLETCONNECTION.CONNECT, self.connect)
def __init__(self, agent): self.agent = agent self.router = SimpleRouter() self.router.register(UI.STATE_REQUEST, self.ui_connect) self.router.register(UI.INITIALIZE, self.initialize_agent)