From 61039671cb7b137ddc240810204ff0abdf01f528 Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Thu, 13 Jan 2022 15:20:42 -0800 Subject: [PATCH 1/8] update pthid Signed-off-by: Shaanjot Gill --- .../protocols/didexchange/v1_0/manager.py | 27 +++++++++++---- .../didexchange/v1_0/tests/test_manager.py | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 282bbb8335..0a7eadde7f 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -2,6 +2,9 @@ import json import logging +import pydid + +from pydid import BaseDIDDocument as ResolvedDocument from ....connections.models.conn_record import ConnRecord from ....connections.models.diddoc import DIDDoc @@ -12,6 +15,8 @@ from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder from ....multitenant.base import BaseMultitenantManager +from ....resolver.base import ResolverError +from ....resolver.did_resolver import DIDResolver from ....storage.error import StorageNotFoundError from ....transport.inbound.receipt import MessageReceipt from ....wallet.base import BaseWallet @@ -296,14 +301,22 @@ async def create_request( filter(None, [base_mediation_record, mediation_record]) ), ) - if ( - conn_rec.their_public_did is not None - and conn_rec.their_public_did.startswith("did:") - ): + if conn_rec.their_public_did is not None: qualified_did = conn_rec.their_public_did - else: - qualified_did = f"did:sov:{conn_rec.their_public_did}" - pthid = conn_rec.invitation_msg_id or qualified_did + if not conn_rec.their_public_did.startswith("did:"): + qualified_did = f"did:sov:{conn_rec.their_public_did}" + resolver = self._profile.inject(DIDResolver) + try: + doc_dict: dict = await resolver.resolve(self._profile, qualified_did) + doc: ResolvedDocument = pydid.deserialize_document( + doc_dict, strict=True + ) + did_url = doc.service[0].id + except ResolverError as error: + raise DIDXManagerError( + "Failed to resolve public DID in invitation" + ) from error + pthid = conn_rec.invitation_msg_id or did_url attach = AttachDecorator.data_base64(did_doc.serialize()) async with self.profile.session() as session: wallet = session.inject(BaseWallet) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index aee31fa30b..41f18473f8 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -1,6 +1,7 @@ import json from asynctest import mock as async_mock, TestCase as AsyncTestCase +from pydid import DIDDocument from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache @@ -18,6 +19,9 @@ from .....messaging.decorators.attach_decorator import AttachDecorator from .....multitenant.base import BaseMultitenantManager from .....multitenant.manager import MultitenantManager +from .....resolver.base import ResolverError +from .....resolver.did_resolver import DIDResolver +from .....resolver.tests import DOC from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.did_info import DIDInfo @@ -102,6 +106,10 @@ async def setUp(self): return_value=TestConfig.test_endpoint ) self.context.injector.bind_instance(BaseLedger, self.ledger) + self.resolver = async_mock.MagicMock() + did_doc = DIDDocument.deserialize(DOC) + self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.context.injector.bind_instance(DIDResolver, self.resolver) self.multitenant_mgr = async_mock.MagicMock(MultitenantManager, autospec=True) self.context.injector.bind_instance( @@ -266,6 +274,29 @@ async def test_create_request_implicit_use_public_did(self): assert info_public.did == conn_rec.my_did + async def test_create_request_implicit_resolver_error(self): + async with self.profile.session() as session: + await session.wallet.create_public_did( + DIDMethod.SOV, + KeyType.ED25519, + ) + with async_mock.patch.object( + self.resolver, + "resolve", + async_mock.CoroutineMock(side_effect=ResolverError()), + ): + with self.assertRaises(DIDXManagerError) as ctx: + conn_rec = await self.manager.create_request_implicit( + their_public_did=TestConfig.test_target_did, + my_label=None, + my_endpoint=None, + use_public_did=True, + alias="Tester", + ) + assert "Failed to resolve public DID in invitation" in str( + ctx.exception + ) + async def test_create_request_implicit_no_public_did(self): with self.assertRaises(WalletError) as context: await self.manager.create_request_implicit( @@ -292,6 +323,7 @@ async def test_create_request(self): ) ), save=async_mock.CoroutineMock(), + their_public_did=None, ) with async_mock.patch.object( @@ -345,6 +377,7 @@ async def test_create_request_multitenant(self): ) ), save=async_mock.CoroutineMock(), + their_public_did=None, ) ) @@ -419,6 +452,7 @@ async def test_create_request_my_endpoint(self): ) ), save=async_mock.CoroutineMock(), + their_public_did=None, ) with async_mock.patch.object( From dcb5cfdc4515d7b2133f9c5304f4124ed6e8e4de Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Thu, 13 Jan 2022 15:26:18 -0800 Subject: [PATCH 2/8] format fix Signed-off-by: Shaanjot Gill --- .../protocols/didexchange/v1_0/tests/test_manager.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 41f18473f8..3f74a37339 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -293,9 +293,7 @@ async def test_create_request_implicit_resolver_error(self): use_public_did=True, alias="Tester", ) - assert "Failed to resolve public DID in invitation" in str( - ctx.exception - ) + assert "Failed to resolve public DID in invitation" in str(ctx.exception) async def test_create_request_implicit_no_public_did(self): with self.assertRaises(WalletError) as context: From a142c9de3359bc45845fd4463fe91bf03d7694be Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Mon, 17 Jan 2022 16:08:15 -0800 Subject: [PATCH 3/8] updates Signed-off-by: Shaanjot Gill --- .../messaging/decorators/attach_decorator.py | 7 +- .../decorators/tests/test_attach_decorator.py | 1 + .../protocols/connections/v1_0/manager.py | 11 ++ .../connections/v1_0/tests/test_manager.py | 45 ++++++- .../protocols/didexchange/v1_0/manager.py | 76 ++++++++--- .../didexchange/v1_0/tests/test_manager.py | 125 ++++++++++++------ 6 files changed, 202 insertions(+), 63 deletions(-) diff --git a/aries_cloudagent/messaging/decorators/attach_decorator.py b/aries_cloudagent/messaging/decorators/attach_decorator.py index e86f17734d..b0fb504cf1 100644 --- a/aries_cloudagent/messaging/decorators/attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/attach_decorator.py @@ -414,7 +414,7 @@ def build_protected(verkey: str): ) self.jws_ = AttachDecoratorDataJWS.deserialize(jws) - async def verify(self, wallet: BaseWallet) -> bool: + async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: """ Verify the signature(s). @@ -428,7 +428,7 @@ async def verify(self, wallet: BaseWallet) -> bool: assert self.jws b64_payload = unpad(set_urlsafe_b64(self.base64, True)) - + verkey_list = [] for sig in [self.jws] if self.signatures == 1 else self.jws.signatures: b64_protected = sig.protected b64_sig = sig.signature @@ -438,10 +438,13 @@ async def verify(self, wallet: BaseWallet) -> bool: sign_input = (b64_protected + "." + b64_payload).encode("ascii") b_sig = b64_to_bytes(b64_sig, urlsafe=True) verkey = bytes_to_b58(b64_to_bytes(protected["jwk"]["x"], urlsafe=True)) + verkey_list.append(verkey) if not await wallet.verify_message( sign_input, b_sig, verkey, KeyType.ED25519 ): return False + if signer_verkey and signer_verkey not in verkey_list: + return False return True def __eq__(self, other): diff --git a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py index 6cd9e1d67a..2687c7bf19 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py @@ -475,6 +475,7 @@ async def test_indy_sign(self, wallet, seed): assert deco_indy.data.header_map()["kid"] == did_key(did_info[0].verkey) assert deco_indy.data.header_map()["jwk"]["kid"] == did_key(did_info[0].verkey) assert await deco_indy.data.verify(wallet) + assert await deco_indy.data.verify(wallet, did_info[0].verkey) indy_cred = json.loads(deco_indy.data.signed.decode()) assert indy_cred == INDY_CRED diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index 96e72b8b37..c3e11cd68c 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -833,6 +833,17 @@ async def accept_response( ) if their_did != conn_did_doc.did: raise ConnectionManagerError("Connection DID does not match DIDDoc id") + # Verify connection response using connection field + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + try: + await response.verify_signed_field( + "connection", wallet, connection.invitation_key + ) + except ValueError: + raise ConnectionManagerError( + "connection field verification using invitation_key failed" + ) await self.store_did_document(conn_did_doc) connection.their_did = their_did diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py index c986cb88cf..4fb6efefd3 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -1277,7 +1277,9 @@ async def test_accept_response_find_by_thread_id(self): mock_response.connection.did = self.test_target_did mock_response.connection.did_doc = async_mock.MagicMock() mock_response.connection.did_doc.did = self.test_target_did - + mock_response.verify_signed_field = async_mock.CoroutineMock( + return_value="sig_verkey" + ) receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) with async_mock.patch.object( @@ -1294,6 +1296,7 @@ async def test_accept_response_find_by_thread_id(self): save=async_mock.CoroutineMock(), metadata_get=async_mock.CoroutineMock(), connection_id="test-conn-id", + invitation_key="test-invitation-key", ) conn_rec = await self.manager.accept_response(mock_response, receipt) assert conn_rec.their_did == self.test_target_did @@ -1306,6 +1309,9 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel mock_response.connection.did = self.test_target_did mock_response.connection.did_doc = async_mock.MagicMock() mock_response.connection.did_doc.did = self.test_target_did + mock_response.verify_signed_field = async_mock.CoroutineMock( + return_value="sig_verkey" + ) receipt = MessageReceipt(sender_did=self.test_target_did) @@ -1326,6 +1332,7 @@ async def test_accept_response_not_found_by_thread_id_receipt_has_sender_did(sel save=async_mock.CoroutineMock(), metadata_get=async_mock.CoroutineMock(return_value=False), connection_id="test-conn-id", + invitation_key="test-invitation-id", ) conn_rec = await self.manager.accept_response(mock_response, receipt) @@ -1426,14 +1433,47 @@ async def test_accept_response_find_by_thread_id_did_mismatch(self): with self.assertRaises(ConnectionManagerError): await self.manager.accept_response(mock_response, receipt) - async def test_accept_response_auto_send_mediation_request(self): + async def test_accept_response_verify_invitation_key_sign_failure(self): mock_response = async_mock.MagicMock() mock_response._thread = async_mock.MagicMock() mock_response.connection = async_mock.MagicMock() mock_response.connection.did = self.test_target_did mock_response.connection.did_doc = async_mock.MagicMock() mock_response.connection.did_doc.did = self.test_target_did + mock_response.verify_signed_field = async_mock.CoroutineMock( + side_effect=ValueError + ) + receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) + + with async_mock.patch.object( + ConnRecord, "save", autospec=True + ) as mock_conn_rec_save, async_mock.patch.object( + ConnRecord, "retrieve_by_request_id", async_mock.CoroutineMock() + ) as mock_conn_retrieve_by_req_id, async_mock.patch.object( + MediationManager, "get_default_mediator", async_mock.CoroutineMock() + ): + mock_conn_retrieve_by_req_id.return_value = async_mock.MagicMock( + did=self.test_target_did, + did_doc=async_mock.MagicMock(did=self.test_target_did), + state=ConnRecord.State.RESPONSE.rfc23, + save=async_mock.CoroutineMock(), + metadata_get=async_mock.CoroutineMock(), + connection_id="test-conn-id", + invitation_key="test-invitation-key", + ) + with self.assertRaises(ConnectionManagerError): + await self.manager.accept_response(mock_response, receipt) + async def test_accept_response_auto_send_mediation_request(self): + mock_response = async_mock.MagicMock() + mock_response._thread = async_mock.MagicMock() + mock_response.connection = async_mock.MagicMock() + mock_response.connection.did = self.test_target_did + mock_response.connection.did_doc = async_mock.MagicMock() + mock_response.connection.did_doc.did = self.test_target_did + mock_response.verify_signed_field = async_mock.CoroutineMock( + return_value="sig_verkey" + ) receipt = MessageReceipt(recipient_did=self.test_did, recipient_did_public=True) with async_mock.patch.object( @@ -1450,6 +1490,7 @@ async def test_accept_response_auto_send_mediation_request(self): save=async_mock.CoroutineMock(), metadata_get=async_mock.CoroutineMock(return_value=True), connection_id="test-conn-id", + invitation_key="test-invitation-key", ) conn_rec = await self.manager.accept_response(mock_response, receipt) assert conn_rec.their_did == self.test_target_did diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 0a7eadde7f..845f1ee3f7 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -4,7 +4,7 @@ import logging import pydid -from pydid import BaseDIDDocument as ResolvedDocument +from pydid import BaseDIDDocument as ResolvedDocument, DIDCommService from ....connections.models.conn_record import ConnRecord from ....connections.models.diddoc import DIDDoc @@ -146,7 +146,13 @@ async def receive_invitation( # Save the invitation for later processing await conn_rec.attach_invitation(session, invitation) - + if not conn_rec.invitation_key and conn_rec.their_public_did: + did_document = await self.get_resolved_did_document( + conn_rec.their_public_did + ) + conn_rec.invitation_key = did_document.verification_method[ + 0 + ].public_key_base58 if conn_rec.accept == ConnRecord.ACCEPT_AUTO: request = await self.create_request(conn_rec, mediation_id=mediation_id) responder = self.profile.inject_or(BaseResponder) @@ -303,19 +309,8 @@ async def create_request( ) if conn_rec.their_public_did is not None: qualified_did = conn_rec.their_public_did - if not conn_rec.their_public_did.startswith("did:"): - qualified_did = f"did:sov:{conn_rec.their_public_did}" - resolver = self._profile.inject(DIDResolver) - try: - doc_dict: dict = await resolver.resolve(self._profile, qualified_did) - doc: ResolvedDocument = pydid.deserialize_document( - doc_dict, strict=True - ) - did_url = doc.service[0].id - except ResolverError as error: - raise DIDXManagerError( - "Failed to resolve public DID in invitation" - ) from error + did_document = await self.get_resolved_did_document(qualified_did) + did_url = await self.get_first_applicable_didcomm_service(did_document) pthid = conn_rec.invitation_msg_id or did_url attach = AttachDecorator.data_base64(did_doc.serialize()) async with self.profile.session() as session: @@ -481,9 +476,7 @@ async def receive_request( ) async with self.profile.session() as session: wallet = session.inject(BaseWallet) - if not await request.did_doc_attach.data.verify(wallet): - raise DIDXManagerError("DID Doc signature failed verification") - conn_did_doc = DIDDoc.from_json(request.did_doc_attach.data.signed.decode()) + conn_did_doc = await self.verify_diddoc(wallet, request.did_doc_attach) if request.did != conn_did_doc.did: raise DIDXManagerError( ( @@ -753,7 +746,9 @@ async def accept_response( raise DIDXManagerError("No DIDDoc attached; cannot connect to public DID") async with self.profile.session() as session: wallet = session.inject(BaseWallet) - conn_did_doc = await self.verify_diddoc(wallet, response.did_doc_attach) + conn_did_doc = await self.verify_diddoc( + wallet, response.did_doc_attach, conn_rec.invitation_key + ) if their_did != conn_did_doc.did: raise DIDXManagerError( f"Connection DID {their_did} " @@ -848,12 +843,53 @@ async def verify_diddoc( self, wallet: BaseWallet, attached: AttachDecorator, + invi_key: str = None, ) -> DIDDoc: """Verify DIDDoc attachment and return signed data.""" signed_diddoc_bytes = attached.data.signed if not signed_diddoc_bytes: raise DIDXManagerError("DID doc attachment is not signed.") - if not await attached.data.verify(wallet): + if not await attached.data.verify(wallet, invi_key): raise DIDXManagerError("DID doc attachment signature failed verification") return DIDDoc.deserialize(json.loads(signed_diddoc_bytes.decode())) + + async def get_resolved_did_document(self, qualified_did: str) -> ResolvedDocument: + """Return resolved DID document.""" + resolver = self._profile.inject(DIDResolver) + if not qualified_did.startswith("did:"): + qualified_did = f"did:sov:{qualified_did}" + try: + doc_dict: dict = await resolver.resolve(self._profile, qualified_did) + doc = pydid.deserialize_document(doc_dict, strict=True) + return doc + except ResolverError as error: + raise DIDXManagerError( + "Failed to resolve public DID in invitation" + ) from error + + async def get_first_applicable_didcomm_service( + self, did_doc: ResolvedDocument + ) -> str: + """Return first applicable DIDComm service url with highest priority.""" + if not did_doc.service: + raise DIDXManagerError( + "Cannot connect via public DID that has no associated services" + ) + + didcomm_services = sorted( + [ + service + for service in did_doc.service + if isinstance(service, DIDCommService) + ], + key=lambda service: service.priority, + ) + + if not didcomm_services: + raise DIDXManagerError( + "Cannot connect via public DID that has no associated DIDComm services" + ) + + first_didcomm_service, *_ = didcomm_services + return first_didcomm_service.id diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 3f74a37339..2a99c15af4 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -176,6 +176,37 @@ async def test_receive_invitation(self): invitee_record = await self.manager.receive_invitation(invi_msg) assert invitee_record.state == ConnRecord.State.REQUEST.rfc23 + async def test_receive_invitation_oob_public_did(self): + async with self.profile.session() as session: + self.profile.context.update_settings({"public_invites": True}) + public_did_info = None + await session.wallet.create_public_did( + DIDMethod.SOV, + KeyType.ED25519, + ) + public_did_info = await session.wallet.get_public_did() + with async_mock.patch.object( + test_module, "AttachDecorator", autospec=True + ) as mock_attach_deco, async_mock.patch.object( + self.multitenant_mgr, "get_default_mediator" + ) as mock_get_default_mediator: + mock_get_default_mediator.return_value = None + invi_rec = await self.oob_manager.create_invitation( + my_endpoint="testendpoint", + hs_protos=[HSProto.RFC23], + ) + invi_msg = invi_rec.invitation + invi_msg.services = [public_did_info.did] + mock_attach_deco.data_base64 = async_mock.MagicMock( + return_value=async_mock.MagicMock( + data=async_mock.MagicMock(sign=async_mock.CoroutineMock()) + ) + ) + invitee_record = await self.manager.receive_invitation( + invi_msg, their_public_did=public_did_info.did + ) + assert invitee_record.state == ConnRecord.State.REQUEST.rfc23 + async def test_receive_invitation_no_auto_accept(self): async with self.profile.session() as session: mediation_record = MediationRecord( @@ -274,27 +305,6 @@ async def test_create_request_implicit_use_public_did(self): assert info_public.did == conn_rec.my_did - async def test_create_request_implicit_resolver_error(self): - async with self.profile.session() as session: - await session.wallet.create_public_did( - DIDMethod.SOV, - KeyType.ED25519, - ) - with async_mock.patch.object( - self.resolver, - "resolve", - async_mock.CoroutineMock(side_effect=ResolverError()), - ): - with self.assertRaises(DIDXManagerError) as ctx: - conn_rec = await self.manager.create_request_implicit( - their_public_did=TestConfig.test_target_did, - my_label=None, - my_endpoint=None, - use_public_did=True, - alias="Tester", - ) - assert "Failed to resolve public DID in invitation" in str(ctx.exception) - async def test_create_request_implicit_no_public_did(self): with self.assertRaises(WalletError) as context: await self.manager.create_request_implicit( @@ -321,7 +331,6 @@ async def test_create_request(self): ) ), save=async_mock.CoroutineMock(), - their_public_did=None, ) with async_mock.patch.object( @@ -375,7 +384,6 @@ async def test_create_request_multitenant(self): ) ), save=async_mock.CoroutineMock(), - their_public_did=None, ) ) @@ -450,7 +458,6 @@ async def test_create_request_my_endpoint(self): ) ), save=async_mock.CoroutineMock(), - their_public_did=None, ) with async_mock.patch.object( @@ -472,7 +479,6 @@ async def test_receive_request_explicit_public_did(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -511,6 +517,10 @@ async def test_receive_request_explicit_public_did(self): ) as mock_attach_deco, async_mock.patch.object( test_module, "DIDXResponse", autospec=True ) as mock_response, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ), async_mock.patch.object( self.manager, "create_did_document", async_mock.CoroutineMock() ) as mock_create_did_doc, async_mock.patch.object( MediationManager, "prepare_request", autospec=True @@ -885,7 +895,6 @@ async def test_receive_request_public_did_x_wrong_did(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -909,7 +918,11 @@ async def test_receive_request_public_did_x_wrong_did(self): test_module, "DIDPosture", autospec=True ) as mock_did_posture, async_mock.patch.object( test_module.DIDDoc, "from_json", async_mock.MagicMock() - ) as mock_did_doc_from_json: + ) as mock_did_doc_from_json, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc("LjgpST2rjsoxYegQDRm7EL")), + ): mock_conn_record = async_mock.MagicMock( accept=ConnRecord.ACCEPT_MANUAL, my_did=None, @@ -950,7 +963,9 @@ async def test_receive_request_public_did_x_did_doc_attach_bad_sig(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=False) + signed=async_mock.MagicMock( + decode=async_mock.MagicMock(return_value="dummy-did-doc") + ), ) ), _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), @@ -969,7 +984,11 @@ async def test_receive_request_public_did_x_did_doc_attach_bad_sig(self): test_module, "ConnRecord", async_mock.MagicMock() ) as mock_conn_rec_cls, async_mock.patch.object( test_module, "DIDPosture", autospec=True - ) as mock_did_posture: + ) as mock_did_posture, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(side_effect=DIDXManagerError), + ): mock_conn_record = async_mock.MagicMock( accept=ConnRecord.ACCEPT_MANUAL, my_did=None, @@ -988,7 +1007,7 @@ async def test_receive_request_public_did_x_did_doc_attach_bad_sig(self): return_value=test_module.DIDPosture.PUBLIC ) - with self.assertRaises(DIDXManagerError) as context: + with self.assertRaises(DIDXManagerError): await self.manager.receive_request( request=mock_request, recipient_did=TestConfig.test_did, @@ -998,7 +1017,6 @@ async def test_receive_request_public_did_x_did_doc_attach_bad_sig(self): auto_accept_implicit=False, mediation_id=None, ) - assert "DID Doc signature failed" in str(context.exception) async def test_receive_request_public_did_no_public_invites(self): async with self.profile.session() as session: @@ -1055,7 +1073,6 @@ async def test_receive_request_public_did_no_auto_accept(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -1087,7 +1104,11 @@ async def test_receive_request_public_did_no_auto_accept(self): test_module, "DIDXResponse", autospec=True ) as mock_response, async_mock.patch.object( self.manager, "create_did_document", async_mock.CoroutineMock() - ) as mock_create_did_doc: + ) as mock_create_did_doc, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): mock_conn_record = async_mock.MagicMock( accept=ConnRecord.ACCEPT_MANUAL, my_did=None, @@ -1129,7 +1150,6 @@ async def test_receive_request_peer_did(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -1171,7 +1191,11 @@ async def test_receive_request_peer_did(self): test_module, "AttachDecorator", autospec=True ) as mock_attach_deco, async_mock.patch.object( test_module, "DIDXResponse", autospec=True - ) as mock_response: + ) as mock_response, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( return_value=mock_conn ) @@ -1226,7 +1250,6 @@ async def test_receive_request_multiuse_multitenant(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -1245,7 +1268,11 @@ async def test_receive_request_multiuse_multitenant(self): InMemoryWallet, "create_local_did", autospec=True ) as mock_wallet_create_local_did, async_mock.patch.object( test_module, "DIDDoc", autospec=True - ) as mock_did_doc: + ) as mock_did_doc, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): mock_conn_rec = async_mock.CoroutineMock( connection_id="dummy", accept=ACCEPT_MANUAL, @@ -1295,7 +1322,6 @@ async def test_receive_request_implicit_multitenant(self): did=TestConfig.test_did, did_doc_attach=async_mock.MagicMock( data=async_mock.MagicMock( - verify=async_mock.CoroutineMock(return_value=True), signed=async_mock.MagicMock( decode=async_mock.MagicMock(return_value="dummy-did-doc") ), @@ -1324,7 +1350,11 @@ async def test_receive_request_implicit_multitenant(self): test_module, "DIDPosture", autospec=True ) as mock_did_posture, async_mock.patch.object( test_module, "DIDDoc", autospec=True - ) as mock_did_doc: + ) as mock_did_doc, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): mock_conn_rec = async_mock.CoroutineMock( connection_id="dummy", accept=ACCEPT_MANUAL, @@ -2215,3 +2245,20 @@ async def test_diddoc_connection_targets_diddoc_underspecified(self): x_did_doc._service = {} with self.assertRaises(BaseConnectionManagerError): self.manager.diddoc_connection_targets(x_did_doc, TestConfig.test_verkey) + + async def test_resolve_did_document_error(self): + public_did_info = None + async with self.profile.session() as session: + await session.wallet.create_public_did( + DIDMethod.SOV, + KeyType.ED25519, + ) + public_did_info = await session.wallet.get_public_did() + with async_mock.patch.object( + self.resolver, + "resolve", + async_mock.CoroutineMock(side_effect=ResolverError()), + ): + with self.assertRaises(DIDXManagerError) as ctx: + await self.manager.get_resolved_did_document(public_did_info.did) + assert "Failed to resolve public DID in invitation" in str(ctx.exception) From 0b62eb35c23930e89313d191385305b8139e0b03 Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Thu, 24 Mar 2022 13:14:40 -0700 Subject: [PATCH 4/8] verify signature from kid Signed-off-by: Shaanjot Gill --- .../messaging/decorators/attach_decorator.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/messaging/decorators/attach_decorator.py b/aries_cloudagent/messaging/decorators/attach_decorator.py index b0fb504cf1..9f173e72ff 100644 --- a/aries_cloudagent/messaging/decorators/attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/attach_decorator.py @@ -428,7 +428,7 @@ async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: assert self.jws b64_payload = unpad(set_urlsafe_b64(self.base64, True)) - verkey_list = [] + verkey_to_check = None for sig in [self.jws] if self.signatures == 1 else self.jws.signatures: b64_protected = sig.protected b64_sig = sig.signature @@ -438,12 +438,17 @@ async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: sign_input = (b64_protected + "." + b64_payload).encode("ascii") b_sig = b64_to_bytes(b64_sig, urlsafe=True) verkey = bytes_to_b58(b64_to_bytes(protected["jwk"]["x"], urlsafe=True)) - verkey_list.append(verkey) + encoded_pk = DIDKey.from_did(protected["jwk"]["kid"]).public_key_b58 + verkey_to_check = encoded_pk if not await wallet.verify_message( sign_input, b_sig, verkey, KeyType.ED25519 ): return False - if signer_verkey and signer_verkey not in verkey_list: + if not await wallet.verify_message( + sign_input, b_sig, encoded_pk, KeyType.ED25519 + ): + return False + if signer_verkey and signer_verkey != verkey_to_check: return False return True From 7a9295ece166491f872b8104aaa461201aa45ebd Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Wed, 30 Mar 2022 09:12:53 -0700 Subject: [PATCH 5/8] use ursa-bbs-signatures 1.0.1 Signed-off-by: Shaanjot Gill --- requirements.bbs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.bbs.txt b/requirements.bbs.txt index 972fa90c8a..8debc1f11e 100644 --- a/requirements.bbs.txt +++ b/requirements.bbs.txt @@ -1 +1 @@ -ursa-bbs-signatures~=1.0.1 \ No newline at end of file +ursa-bbs-signatures==1.0.1 \ No newline at end of file From ed19dcce0809e4704a223216820ad6621b01edf0 Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Wed, 30 Mar 2022 13:33:25 -0700 Subject: [PATCH 6/8] revert locking ursa-bbs-signature package - 1.0.2 pypi removed Signed-off-by: Shaanjot Gill --- requirements.bbs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.bbs.txt b/requirements.bbs.txt index 8debc1f11e..972fa90c8a 100644 --- a/requirements.bbs.txt +++ b/requirements.bbs.txt @@ -1 +1 @@ -ursa-bbs-signatures==1.0.1 \ No newline at end of file +ursa-bbs-signatures~=1.0.1 \ No newline at end of file From 91d49be5cce2b16fbce86a2719790f05ce1b246c Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Fri, 1 Apr 2022 11:05:31 -0700 Subject: [PATCH 7/8] retrigger check Signed-off-by: Shaanjot Gill From 9ce96be741540ff259ad28c848b6b3308fca74e5 Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Wed, 6 Apr 2022 11:59:07 -0700 Subject: [PATCH 8/8] update attach_decorator verify method Signed-off-by: Shaanjot Gill --- aries_cloudagent/messaging/decorators/attach_decorator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/messaging/decorators/attach_decorator.py b/aries_cloudagent/messaging/decorators/attach_decorator.py index 9f173e72ff..309ebc6a68 100644 --- a/aries_cloudagent/messaging/decorators/attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/attach_decorator.py @@ -428,7 +428,7 @@ async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: assert self.jws b64_payload = unpad(set_urlsafe_b64(self.base64, True)) - verkey_to_check = None + verkey_to_check = [] for sig in [self.jws] if self.signatures == 1 else self.jws.signatures: b64_protected = sig.protected b64_sig = sig.signature @@ -439,7 +439,7 @@ async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: b_sig = b64_to_bytes(b64_sig, urlsafe=True) verkey = bytes_to_b58(b64_to_bytes(protected["jwk"]["x"], urlsafe=True)) encoded_pk = DIDKey.from_did(protected["jwk"]["kid"]).public_key_b58 - verkey_to_check = encoded_pk + verkey_to_check.append(encoded_pk) if not await wallet.verify_message( sign_input, b_sig, verkey, KeyType.ED25519 ): @@ -448,7 +448,7 @@ async def verify(self, wallet: BaseWallet, signer_verkey: str = None) -> bool: sign_input, b_sig, encoded_pk, KeyType.ED25519 ): return False - if signer_verkey and signer_verkey != verkey_to_check: + if signer_verkey and signer_verkey not in verkey_to_check: return False return True