Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: enabled handling VPs (request, creation, verification) with different VCs #1956

Merged
merged 17 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions aries_cloudagent/messaging/decorators/attach_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from ..valid import (
BASE64,
BASE64URL_NO_PAD,
DictOrDictListField,
INDY_ISO8601_DATETIME,
JWS_HEADER_KID,
SHA256,
Expand Down Expand Up @@ -228,7 +229,7 @@ def __init__(
sha256_: str = None,
links_: Union[Sequence[str], str] = None,
base64_: str = None,
json_: dict = None,
json_: Union[Sequence[dict], dict] = None,
):
"""
Initialize decorator data.
Expand Down Expand Up @@ -492,7 +493,7 @@ def validate_data_spec(self, data: Mapping, **kwargs):
required=False,
data_key="jws",
)
json_ = fields.Dict(
json_ = DictOrDictListField(
description="JSON-serialized data",
required=False,
example='{"sample": "content"}',
Expand Down Expand Up @@ -619,7 +620,7 @@ def data_base64(
@classmethod
def data_json(
cls,
mapping: dict,
mapping: Union[Sequence[dict], dict],
*,
ident: str = None,
description: str = None,
Expand Down
157 changes: 97 additions & 60 deletions aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,7 +1230,7 @@ async def create_vp(
challenge: str = None,
domain: str = None,
records_filter: dict = None,
) -> dict:
) -> Union[Sequence[dict], dict]:
"""
Create VerifiablePresentation.

Expand All @@ -1244,78 +1244,99 @@ async def create_vp(
req = await self.make_requirement(
srs=pd.submission_requirements, descriptors=pd.input_descriptors
)
result = await self.apply_requirements(
req=req, credentials=credentials, records_filter=records_filter
)
applicable_creds, descriptor_maps = await self.merge(result)
applicable_creds_list = []
for credential in applicable_creds:
applicable_creds_list.append(credential.cred_value)
if (
not self.profile.settings.get("debug.auto_respond_presentation_request")
and not records_filter
and len(applicable_creds_list) > 1
):
raise DIFPresExchError(
"Multiple credentials are applicable for presentation_definition "
f"{pd.id} and --auto-respond-presentation-request setting is not "
"enabled. Please specify which credentials should be applied to "
"which input_descriptors using record_ids filter."
result = []
if req.nested_req:
for nested_req in req.nested_req:
res = await self.apply_requirements(
req=nested_req,
credentials=credentials,
records_filter=records_filter,
)
result.append(res)
else:
res = await self.apply_requirements(
req=req, credentials=credentials, records_filter=records_filter
)
# submission_property
submission_property = PresentationSubmission(
id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
)
if self.is_holder:
(
issuer_id,
filtered_creds_list,
) = await self.get_sign_key_credential_subject_id(
applicable_creds=applicable_creds
result.append(res)

result_vp = []
for res in result:
applicable_creds, descriptor_maps = await self.merge(res)
applicable_creds_list = []
for credential in applicable_creds:
applicable_creds_list.append(credential.cred_value)
if (
not self.profile.settings.get("debug.auto_respond_presentation_request")
and not records_filter
and len(applicable_creds_list) > 1
):
raise DIFPresExchError(
"Multiple credentials are applicable for presentation_definition "
f"{pd.id} and --auto-respond-presentation-request setting is not "
"enabled. Please specify which credentials should be applied to "
"which input_descriptors using record_ids filter."
)
# submission_property
submission_property = PresentationSubmission(
id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
)
if not issuer_id and len(filtered_creds_list) == 0:
vp = await create_presentation(credentials=applicable_creds_list)
vp["presentation_submission"] = submission_property.serialize()
if self.proof_type is BbsBlsSignature2020.signature_type:
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
return vp
else:
vp = await create_presentation(credentials=filtered_creds_list)
else:
if not self.pres_signing_did:
if self.is_holder:
(
issuer_id,
filtered_creds_list,
) = await self.get_sign_key_credential_subject_id(
applicable_creds=applicable_creds
)
if not issuer_id:
if not issuer_id and len(filtered_creds_list) == 0:
vp = await create_presentation(credentials=applicable_creds_list)
vp["presentation_submission"] = submission_property.serialize()
if self.proof_type is BbsBlsSignature2020.signature_type:
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
return vp
result_vp.append(vp)
continue
else:
vp = await create_presentation(credentials=filtered_creds_list)
else:
issuer_id = self.pres_signing_did
vp = await create_presentation(credentials=applicable_creds_list)
vp["presentation_submission"] = submission_property.serialize()
if self.proof_type is BbsBlsSignature2020.signature_type:
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
async with self.profile.session() as session:
wallet = session.inject(BaseWallet)
issue_suite = await self._get_issue_suite(
wallet=wallet,
issuer_id=issuer_id,
)
signed_vp = await sign_presentation(
presentation=vp,
suite=issue_suite,
challenge=challenge,
document_loader=document_loader,
)
return signed_vp
if not self.pres_signing_did:
(
issuer_id,
filtered_creds_list,
) = await self.get_sign_key_credential_subject_id(
applicable_creds=applicable_creds
)
if not issuer_id:
vp = await create_presentation(
credentials=applicable_creds_list
)
vp["presentation_submission"] = submission_property.serialize()
if self.proof_type is BbsBlsSignature2020.signature_type:
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
result_vp.append(vp)
continue
else:
vp = await create_presentation(credentials=filtered_creds_list)
else:
issuer_id = self.pres_signing_did
vp = await create_presentation(credentials=applicable_creds_list)
vp["presentation_submission"] = submission_property.serialize()
if self.proof_type is BbsBlsSignature2020.signature_type:
vp["@context"].append(SECURITY_CONTEXT_BBS_URL)
async with self.profile.session() as session:
wallet = session.inject(BaseWallet)
issue_suite = await self._get_issue_suite(
wallet=wallet,
issuer_id=issuer_id,
)
signed_vp = await sign_presentation(
presentation=vp,
suite=issue_suite,
challenge=challenge,
document_loader=document_loader,
)
result_vp.append(signed_vp)
if len(result_vp) == 1:
return result_vp[0]
return result_vp

def check_if_cred_id_derived(self, id: str) -> bool:
"""Check if credential or credentialSubjet id is derived."""
Expand Down Expand Up @@ -1367,7 +1388,7 @@ async def merge(
async def verify_received_pres(
self,
pd: PresentationDefinition,
pres: dict,
pres: Union[Sequence[dict], dict],
):
"""
Verify credentials received in presentation.
Expand All @@ -1376,8 +1397,24 @@ async def verify_received_pres(
pres: received VerifiablePresentation
pd: PresentationDefinition
"""
descriptor_map_list = pres["presentation_submission"].get("descriptor_map")
input_descriptors = pd.input_descriptors
if isinstance(pres, Sequence):
for pr in pres:
descriptor_map_list = pr["presentation_submission"].get(
"descriptor_map"
)
await self.__verify_desc_map_list(
descriptor_map_list, pr, input_descriptors
)
else:
descriptor_map_list = pres["presentation_submission"].get("descriptor_map")
await self.__verify_desc_map_list(
descriptor_map_list, pres, input_descriptors
)

async def __verify_desc_map_list(
self, descriptor_map_list, pres, input_descriptors
):
inp_desc_id_contraint_map = {}
inp_desc_id_schema_one_of_filter = set()
inp_desc_id_schemas_map = {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
from copy import deepcopy
from datetime import datetime
from typing import Sequence
from uuid import uuid4

import mock as async_mock
Expand Down Expand Up @@ -103,7 +104,17 @@ async def test_load_cred_json_a(self, setup_tuple, profile):
pd=tmp_pd[0],
challenge="1f44d55f-f161-4938-a659-f8026467f126",
)
assert len(tmp_vp.get("verifiableCredential")) == tmp_pd[1]

if isinstance(tmp_vp, Sequence):
cred_count_list = []
for tmp_vp_single in tmp_vp:
cred_count_list.append(
len(tmp_vp_single.get("verifiableCredential"))
)

assert min(cred_count_list) == tmp_pd[1]
else:
assert len(tmp_vp.get("verifiableCredential")) == tmp_pd[1]

@pytest.mark.asyncio
@pytest.mark.ursa_bbs_signatures
Expand All @@ -120,7 +131,17 @@ async def test_load_cred_json_b(self, setup_tuple, profile):
pd=tmp_pd[0],
challenge="1f44d55f-f161-4938-a659-f8026467f126",
)
assert len(tmp_vp.get("verifiableCredential")) == tmp_pd[1]

if isinstance(tmp_vp, Sequence):
cred_count_list = []
for tmp_vp_single in tmp_vp:
cred_count_list.append(
len(tmp_vp_single.get("verifiableCredential"))
)

assert min(cred_count_list) == tmp_pd[1]
else:
assert len(tmp_vp.get("verifiableCredential")) == tmp_pd[1]

@pytest.mark.asyncio
async def test_to_requirement_catch_errors(self, profile):
Expand Down Expand Up @@ -2264,11 +2285,12 @@ async def test_create_vp_no_issuer(self, profile, setup_tuple):
pd=pd_list[0][0],
challenge="3fa85f64-5717-4562-b3fc-2c963f66afa7",
)
assert vp["test"] == "1"
assert (
vp["presentation_submission"]["definition_id"]
== "32f54163-7166-48f1-93d8-ff217bdb0653"
)
for vp_single in vp:
assert vp_single["test"] == "1"
assert (
vp_single["presentation_submission"]["definition_id"]
== "32f54163-7166-48f1-93d8-ff217bdb0653"
)

@pytest.mark.asyncio
@pytest.mark.ursa_bbs_signatures
Expand Down Expand Up @@ -2324,8 +2346,9 @@ async def test_create_vp_with_bbs_suite(self, profile, setup_tuple):
pd=pd_list[0][0],
challenge="3fa85f64-5717-4562-b3fc-2c963f66afa7",
)
assert vp["test"] == "1"
assert SECURITY_CONTEXT_BBS_URL in vp["@context"]
for vp_single in vp:
assert vp_single["test"] == "1"
assert SECURITY_CONTEXT_BBS_URL in vp_single["@context"]

@pytest.mark.asyncio
@pytest.mark.ursa_bbs_signatures
Expand Down Expand Up @@ -2378,8 +2401,10 @@ async def test_create_vp_no_issuer_with_bbs_suite(self, profile, setup_tuple):
pd=pd_list[0][0],
challenge="3fa85f64-5717-4562-b3fc-2c963f66afa7",
)
assert vp["test"] == "1"
assert SECURITY_CONTEXT_BBS_URL in vp["@context"]
# 2 sub_reqs, vp is a sequence
for vp_single in vp:
assert vp_single["test"] == "1"
assert SECURITY_CONTEXT_BBS_URL in vp_single["@context"]

@pytest.mark.asyncio
@pytest.mark.ursa_bbs_signatures
Expand Down Expand Up @@ -3055,6 +3080,8 @@ async def test_multiple_applicable_creds_with_no_id(self, profile, setup_tuple):
pd=tmp_pd[0],
challenge="1f44d55f-f161-4938-a659-f8026467f126",
)
# only 1 sub_req
assert isinstance(tmp_vp, dict)
assert len(tmp_vp["verifiableCredential"]) == 2
assert (
tmp_vp.get("verifiableCredential")[0]
Expand All @@ -3075,19 +3102,22 @@ async def test_multiple_applicable_creds_with_no_id(self, profile, setup_tuple):
pd=tmp_pd[0],
challenge="1f44d55f-f161-4938-a659-f8026467f126",
)
assert len(tmp_vp["verifiableCredential"]) == 2
assert (
tmp_vp.get("verifiableCredential")[0]
.get("credentialSubject")
.get("givenName")
== "TEST"
)
assert (
tmp_vp.get("verifiableCredential")[1]
.get("credentialSubject")
.get("givenName")
== "TEST"
)
assert isinstance(tmp_vp, Sequence)
# 1 for each submission requirement group
assert len(tmp_vp) == 3
for tmp_vp_single in tmp_vp:
assert (
tmp_vp_single.get("verifiableCredential")[0]
.get("credentialSubject")
.get("givenName")
== "TEST"
)
assert (
tmp_vp_single.get("verifiableCredential")[1]
.get("credentialSubject")
.get("givenName")
== "TEST"
)

@pytest.mark.asyncio
@pytest.mark.ursa_bbs_signatures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,22 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord:
challenge = pres_request["options"].get("challenge", str(uuid4()))
if not challenge:
challenge = str(uuid4())
pres_ver_result = await verify_presentation(
presentation=dif_proof,
suites=await self._get_all_suites(wallet=wallet),
document_loader=self._profile.inject(DocumentLoader),
challenge=challenge,
)
if isinstance(dif_proof, Sequence):
for proof in dif_proof:
pres_ver_result = await verify_presentation(
presentation=proof,
suites=await self._get_all_suites(wallet=wallet),
document_loader=self._profile.inject(DocumentLoader),
challenge=challenge,
)
if not pres_ver_result.verified:
break
else:
pres_ver_result = await verify_presentation(
presentation=dif_proof,
suites=await self._get_all_suites(wallet=wallet),
document_loader=self._profile.inject(DocumentLoader),
challenge=challenge,
)
pres_ex_record.verified = json.dumps(pres_ver_result.verified)
return pres_ex_record
Loading