Skip to content

Commit

Permalink
Merge pull request #1294 from shaangill025/dif-pres-exch
Browse files Browse the repository at this point in the history
DIF PE - is_holder property implementation - compliant with DIF spec
  • Loading branch information
andrewwhitehead authored Jul 8, 2021
2 parents 102bd85 + 8662124 commit 73d218e
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 28 deletions.
3 changes: 3 additions & 0 deletions aries_cloudagent/protocols/present_proof/dif/pres_exch.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ class Meta:
def __init__(
self,
*,
id: str = None,
paths: Sequence[str] = None,
purpose: str = None,
predicate: str = None,
Expand All @@ -394,6 +395,7 @@ def __init__(
self.purpose = purpose
self.predicate = predicate
self._filter = _filter
self.id = id


class DIFFieldSchema(BaseModelSchema):
Expand All @@ -405,6 +407,7 @@ class Meta:
model_class = DIFField
unknown = EXCLUDE

id = fields.Str(description="ID", required=False)
paths = fields.List(
fields.Str(description="Path", required=False),
required=False,
Expand Down
59 changes: 46 additions & 13 deletions aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)
from ....vc.vc_ld.prove import sign_presentation, create_presentation, derive_credential
from ....wallet.base import BaseWallet, DIDInfo
from ....wallet.error import WalletError, WalletNotFoundError
from ....wallet.key_type import KeyType

from .pres_exch import (
Expand Down Expand Up @@ -99,6 +100,7 @@ def __init__(
self.proof_type = Ed25519Signature2018.signature_type
else:
self.proof_type = proof_type
self.is_holder = False

async def _get_issue_suite(
self,
Expand Down Expand Up @@ -191,7 +193,7 @@ async def get_sign_key_credential_subject_id(
else:
reqd_key_type = KeyType.ED25519
for cred in applicable_creds:
if len(cred.subject_ids) > 0:
if cred.subject_ids and len(cred.subject_ids) > 0:
if not issuer_id:
for cred_subject_id in cred.subject_ids:
if not cred_subject_id.startswith("urn:"):
Expand Down Expand Up @@ -373,13 +375,27 @@ async def filter_constraints(
continue

applicable = False
is_holder_field_ids = self.field_ids_for_is_holder(constraints)
for field in constraints._fields:
applicable = await self.filter_by_field(field, credential)
if applicable:
# all fields in the constraint should be satisfied
if not applicable:
break
# is_holder with required directive requested for this field
if applicable and field.id and field.id in is_holder_field_ids:
# Missing credentialSubject.id - cannot verify that holder of claim
# is same as subject
if not credential.subject_ids or len(credential.subject_ids) == 0:
applicable = False
break
# Holder of claim is not same as the subject
if not await self.process_constraint_holders(
subject_ids=credential.subject_ids
):
applicable = False
break
if not applicable:
continue

if constraints.limit_disclosure == "required":
credential_dict = credential.cred_value
new_credential_dict = self.reveal_doc(
Expand All @@ -400,6 +416,32 @@ async def filter_constraints(
result.append(credential)
return result

def field_ids_for_is_holder(self, constraints: Constraints) -> Sequence[str]:
"""Return list of field ids for whose subject holder verification is requested."""
reqd_field_ids = set()
if not constraints.holders:
reqd_field_ids = []
return reqd_field_ids
for holder in constraints.holders:
if holder.directive == "required":
reqd_field_ids = set.union(reqd_field_ids, set(holder.field_ids))
return list(reqd_field_ids)

async def process_constraint_holders(
self,
subject_ids: Sequence[str],
) -> bool:
"""Check if holder or subject of claim still controls the identifier."""
async with self.profile.session() as session:
wallet = session.inject(BaseWallet)
try:
for subject_id in subject_ids:
await wallet.get_local_did(subject_id.replace("did:sov:", ""))
self.is_holder = True
return True
except (WalletError, WalletNotFoundError):
return False

def create_vcrecord(self, cred_dict: dict) -> VCRecord:
"""Return VCRecord from a credential dict."""
proofs = cred_dict.get("proof") or []
Expand Down Expand Up @@ -1146,7 +1188,7 @@ async def create_vp(
submission_property = PresentationSubmission(
id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps
)
if self.check_sign_pres(applicable_creds):
if self.is_holder:
(
issuer_id,
filtered_creds_list,
Expand Down Expand Up @@ -1199,15 +1241,6 @@ async def create_vp(
else:
return vp

def check_sign_pres(self, creds: Sequence[VCRecord]) -> bool:
"""Check if applicable creds have CredentialSubject.id set."""
for cred in creds:
if len(cred.subject_ids) > 0 and not self.check_if_cred_id_derived(
next(iter(cred.subject_ids))
):
return True
return False

def check_if_cred_id_derived(self, id: str) -> bool:
"""Check if credential or credentialSubjet id is derived."""
if id.startswith("urn:bnid:_:c14n"):
Expand Down
147 changes: 147 additions & 0 deletions aries_cloudagent/protocols/present_proof/dif/tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,153 @@ def create_vcrecord(cred_dict: dict, expanded_types: list):
)


is_holder_pd = PresentationDefinition.deserialize(
{
"id": "32f54163-7166-48f1-93d8-ff217bdb0653",
"submission_requirements": [
{
"name": "European Union Citizenship Proofs",
"rule": "all",
"from": "A",
}
],
"input_descriptors": [
{
"id": "citizenship_input_1",
"group": ["A"],
"schema": [
{"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
{"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
],
"constraints": {
"is_holder": [
{
"directive": "required",
"field_id": ["1f44d55f-f161-4938-a659-f8026467f126"],
}
],
"fields": [
{
"id": "1f44d55f-f161-4938-a659-f8026467f126",
"path": ["$.issuanceDate", "$.vc.issuanceDate"],
"filter": {
"type": "string",
"format": "date",
"maximum": "2014-5-16",
},
}
],
},
}
],
}
)

is_holder_pd_multiple_fields_included = PresentationDefinition.deserialize(
{
"id": "32f54163-7166-48f1-93d8-ff217bdb0653",
"submission_requirements": [
{
"name": "European Union Citizenship Proofs",
"rule": "all",
"from": "A",
}
],
"input_descriptors": [
{
"id": "citizenship_input_1",
"group": ["A"],
"schema": [
{"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
{"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
],
"constraints": {
"is_holder": [
{
"directive": "required",
"field_id": [
"1f44d55f-f161-4938-a659-f8026467f126",
"1f44d55f-f161-4938-a659-f8026467f127",
],
}
],
"fields": [
{
"id": "1f44d55f-f161-4938-a659-f8026467f126",
"path": ["$.issuanceDate", "$.vc.issuanceDate"],
"filter": {
"type": "string",
"format": "date",
"maximum": "2014-5-16",
},
},
{
"id": "1f44d55f-f161-4938-a659-f8026467f127",
"path": ["$.issuanceDate", "$.vc.issuanceDate"],
"filter": {
"type": "string",
"format": "date",
"minimum": "2005-5-16",
},
},
],
},
}
],
}
)

is_holder_pd_multiple_fields_excluded = PresentationDefinition.deserialize(
{
"id": "32f54163-7166-48f1-93d8-ff217bdb0653",
"submission_requirements": [
{
"name": "European Union Citizenship Proofs",
"rule": "all",
"from": "A",
}
],
"input_descriptors": [
{
"id": "citizenship_input_1",
"group": ["A"],
"schema": [
{"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"},
{"uri": "https://w3id.org/citizenship#PermanentResidentCard"},
],
"constraints": {
"is_holder": [
{
"directive": "required",
"field_id": ["1f44d55f-f161-4938-a659-f8026467f126"],
}
],
"fields": [
{
"id": "1f44d55f-f161-4938-a659-f8026467f126",
"path": ["$.issuanceDate", "$.vc.issuanceDate"],
"filter": {
"type": "string",
"format": "date",
"maximum": "2014-5-16",
},
},
{
"id": "1f44d55f-f161-4938-a659-f8026467f127",
"path": ["$.issuanceDate", "$.vc.issuanceDate"],
"filter": {
"type": "string",
"format": "date",
"minimum": "2005-5-16",
},
},
],
},
}
],
}
)

creds_with_no_id = [
create_vcrecord(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ def test_submission_requirements_from_both_present(self):
with self.assertRaises(BaseModelError) as cm:
(SubmissionRequirements.deserialize(test_json)).serialize()

def test_submission_requirements_from_both_missing(self):
test_json = """
{
"name": "Citizenship Information",
"rule": "pick",
"count": 1
}
"""
with self.assertRaises(BaseModelError) as cm:
(SubmissionRequirements.deserialize(test_json)).serialize()

def test_is_holder(self):
test_json = """
{
Expand Down
Loading

0 comments on commit 73d218e

Please sign in to comment.