|
2 | 2 |
|
3 | 3 | import json
|
4 | 4 | import logging
|
| 5 | +import pydid |
| 6 | + |
| 7 | +from pydid import BaseDIDDocument as ResolvedDocument, DIDCommService |
5 | 8 |
|
6 | 9 | from ....connections.models.conn_record import ConnRecord
|
7 | 10 | from ....connections.models.diddoc import DIDDoc
|
|
12 | 15 | from ....messaging.decorators.attach_decorator import AttachDecorator
|
13 | 16 | from ....messaging.responder import BaseResponder
|
14 | 17 | from ....multitenant.base import BaseMultitenantManager
|
| 18 | +from ....resolver.base import ResolverError |
| 19 | +from ....resolver.did_resolver import DIDResolver |
15 | 20 | from ....storage.error import StorageNotFoundError
|
16 | 21 | from ....transport.inbound.receipt import MessageReceipt
|
17 | 22 | from ....wallet.base import BaseWallet
|
@@ -141,7 +146,13 @@ async def receive_invitation(
|
141 | 146 |
|
142 | 147 | # Save the invitation for later processing
|
143 | 148 | await conn_rec.attach_invitation(session, invitation)
|
144 |
| - |
| 149 | + if not conn_rec.invitation_key and conn_rec.their_public_did: |
| 150 | + did_document = await self.get_resolved_did_document( |
| 151 | + conn_rec.their_public_did |
| 152 | + ) |
| 153 | + conn_rec.invitation_key = did_document.verification_method[ |
| 154 | + 0 |
| 155 | + ].public_key_base58 |
145 | 156 | if conn_rec.accept == ConnRecord.ACCEPT_AUTO:
|
146 | 157 | request = await self.create_request(conn_rec, mediation_id=mediation_id)
|
147 | 158 | responder = self.profile.inject_or(BaseResponder)
|
@@ -296,14 +307,11 @@ async def create_request(
|
296 | 307 | filter(None, [base_mediation_record, mediation_record])
|
297 | 308 | ),
|
298 | 309 | )
|
299 |
| - if ( |
300 |
| - conn_rec.their_public_did is not None |
301 |
| - and conn_rec.their_public_did.startswith("did:") |
302 |
| - ): |
| 310 | + if conn_rec.their_public_did is not None: |
303 | 311 | qualified_did = conn_rec.their_public_did
|
304 |
| - else: |
305 |
| - qualified_did = f"did:sov:{conn_rec.their_public_did}" |
306 |
| - pthid = conn_rec.invitation_msg_id or qualified_did |
| 312 | + did_document = await self.get_resolved_did_document(qualified_did) |
| 313 | + did_url = await self.get_first_applicable_didcomm_service(did_document) |
| 314 | + pthid = conn_rec.invitation_msg_id or did_url |
307 | 315 | attach = AttachDecorator.data_base64(did_doc.serialize())
|
308 | 316 | async with self.profile.session() as session:
|
309 | 317 | wallet = session.inject(BaseWallet)
|
@@ -468,9 +476,7 @@ async def receive_request(
|
468 | 476 | )
|
469 | 477 | async with self.profile.session() as session:
|
470 | 478 | wallet = session.inject(BaseWallet)
|
471 |
| - if not await request.did_doc_attach.data.verify(wallet): |
472 |
| - raise DIDXManagerError("DID Doc signature failed verification") |
473 |
| - conn_did_doc = DIDDoc.from_json(request.did_doc_attach.data.signed.decode()) |
| 479 | + conn_did_doc = await self.verify_diddoc(wallet, request.did_doc_attach) |
474 | 480 | if request.did != conn_did_doc.did:
|
475 | 481 | raise DIDXManagerError(
|
476 | 482 | (
|
@@ -751,7 +757,9 @@ async def accept_response(
|
751 | 757 | raise DIDXManagerError("No DIDDoc attached; cannot connect to public DID")
|
752 | 758 | async with self.profile.session() as session:
|
753 | 759 | wallet = session.inject(BaseWallet)
|
754 |
| - conn_did_doc = await self.verify_diddoc(wallet, response.did_doc_attach) |
| 760 | + conn_did_doc = await self.verify_diddoc( |
| 761 | + wallet, response.did_doc_attach, conn_rec.invitation_key |
| 762 | + ) |
755 | 763 | if their_did != conn_did_doc.did:
|
756 | 764 | raise DIDXManagerError(
|
757 | 765 | f"Connection DID {their_did} "
|
@@ -861,12 +869,53 @@ async def verify_diddoc(
|
861 | 869 | self,
|
862 | 870 | wallet: BaseWallet,
|
863 | 871 | attached: AttachDecorator,
|
| 872 | + invi_key: str = None, |
864 | 873 | ) -> DIDDoc:
|
865 | 874 | """Verify DIDDoc attachment and return signed data."""
|
866 | 875 | signed_diddoc_bytes = attached.data.signed
|
867 | 876 | if not signed_diddoc_bytes:
|
868 | 877 | raise DIDXManagerError("DID doc attachment is not signed.")
|
869 |
| - if not await attached.data.verify(wallet): |
| 878 | + if not await attached.data.verify(wallet, invi_key): |
870 | 879 | raise DIDXManagerError("DID doc attachment signature failed verification")
|
871 | 880 |
|
872 | 881 | return DIDDoc.deserialize(json.loads(signed_diddoc_bytes.decode()))
|
| 882 | + |
| 883 | + async def get_resolved_did_document(self, qualified_did: str) -> ResolvedDocument: |
| 884 | + """Return resolved DID document.""" |
| 885 | + resolver = self._profile.inject(DIDResolver) |
| 886 | + if not qualified_did.startswith("did:"): |
| 887 | + qualified_did = f"did:sov:{qualified_did}" |
| 888 | + try: |
| 889 | + doc_dict: dict = await resolver.resolve(self._profile, qualified_did) |
| 890 | + doc = pydid.deserialize_document(doc_dict, strict=True) |
| 891 | + return doc |
| 892 | + except ResolverError as error: |
| 893 | + raise DIDXManagerError( |
| 894 | + "Failed to resolve public DID in invitation" |
| 895 | + ) from error |
| 896 | + |
| 897 | + async def get_first_applicable_didcomm_service( |
| 898 | + self, did_doc: ResolvedDocument |
| 899 | + ) -> str: |
| 900 | + """Return first applicable DIDComm service url with highest priority.""" |
| 901 | + if not did_doc.service: |
| 902 | + raise DIDXManagerError( |
| 903 | + "Cannot connect via public DID that has no associated services" |
| 904 | + ) |
| 905 | + |
| 906 | + didcomm_services = sorted( |
| 907 | + [ |
| 908 | + service |
| 909 | + for service in did_doc.service |
| 910 | + if isinstance(service, DIDCommService) |
| 911 | + ], |
| 912 | + key=lambda service: service.priority, |
| 913 | + ) |
| 914 | + |
| 915 | + if not didcomm_services: |
| 916 | + raise DIDXManagerError( |
| 917 | + "Cannot connect via public DID that has no associated DIDComm services" |
| 918 | + ) |
| 919 | + |
| 920 | + first_didcomm_service, *_ = didcomm_services |
| 921 | + return first_didcomm_service.id |
0 commit comments