From 9f40c8c88dc9dca439614943b2548809892c2b2c Mon Sep 17 00:00:00 2001 From: Daniel Armengod Date: Mon, 5 Feb 2024 19:44:54 +0100 Subject: [PATCH] Complete support for revocation --- idhub/views.py | 8 ++++--- utils/idhub_ssikit/__init__.py | 38 +++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/idhub/views.py b/idhub/views.py index dd1a9a4..7807bee 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -81,20 +81,22 @@ class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): def serve_did(request, did_id): - id_did = f'did:web:{settings.DOMAIN}:did-registry:{did_id}' + import urllib.parse + domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:] + id_did = f'did:web:{domain}:did-registry:{did_id}' did = get_object_or_404(DID, did=id_did) # Deserialize the base DID from JSON storage document = json.loads(did.didweb_document) # Has this DID issued any Verifiable Credentials? If so, we need to add a Revocation List "service" # entry to the DID document. - revoked_credentials = did.verificablecredential_set.filter(status=VerificableCredential.Status.REVOKED) + revoked_credentials = did.vcredentials.filter(status=VerificableCredential.Status.REVOKED) revoked_credential_indexes = [] for credential in revoked_credentials: revoked_credential_indexes.append(credential.revocationBitmapIndex) # TODO: Conditionally add "service" to DID document only if the DID has issued any VC revocation_bitmap = pyroaring.BitMap(revoked_credential_indexes) encoded_revocation_bitmap = base64.b64encode(zlib.compress(revocation_bitmap.serialize())) - revocation_service = [{ + revocation_service = [{ # This is an object within a list. "id": f"{id_did}#revocation", "type": "RevocationBitmap2022", "serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}" diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py index fb97158..81a2599 100644 --- a/utils/idhub_ssikit/__init__.py +++ b/utils/idhub_ssikit/__init__.py @@ -2,6 +2,7 @@ import asyncio import base64 import datetime import zlib +from ast import literal_eval import didkit import json @@ -106,7 +107,10 @@ def verify_credential(vc): If it is false, the VC is invalid and the second argument contains a JSON object with further information. """ async def inner(): - return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}') + str_res = await didkit.verify_credential(vc, '{"proofFormat": "ldp"}') + res = literal_eval(str_res) + ok = res["warnings"] == [] and res["errors"] == [] + return ok, str_res valid, reason = asyncio.run(inner()) if not valid: @@ -116,24 +120,24 @@ def verify_credential(vc): pass # Credential verifies against its schema. Now check revocation status. vc = json.loads(vc) - revocation_index = int(vc["credentialStatus"]["revocationBitmapIndex"]) # NOTE: THIS FIELD SHOULD BE SERIALIZED AS AN INTEGER, BUT IOTA DOCUMENTAITON SERIALIZES IT AS A STRING. DEFENSIVE CAST ADDED JUST IN CASE. - vc_issuer = vc["issuer"]["id"] # This is a DID - issuer_did_document = json.loads(resolve_did(vc_issuer)) # TODO: implement a caching layer so we don't have to fetch the DID (and thus the revocation list) every time a VC is validated. - issuer_revocation_list = issuer_did_document["service"][0] - assert issuer_revocation_list["type"] == "RevocationBitmap2022" - revocation_bitmap = BitMap.deserialize( - zlib.decompress( - base64.b64decode( - issuer_revocation_list["serviceEndpoint"].rsplit(",")[1] + if "credentialStatus" in vc: + revocation_index = int(vc["credentialStatus"]["revocationBitmapIndex"]) # NOTE: THIS FIELD SHOULD BE SERIALIZED AS AN INTEGER, BUT IOTA DOCUMENTAITON SERIALIZES IT AS A STRING. DEFENSIVE CAST ADDED JUST IN CASE. + vc_issuer = vc["issuer"]["id"] # This is a DID + if vc_issuer[:7] == "did:web": # Only DID:WEB can revoke + issuer_did_document = json.loads(resolve_did(vc_issuer)) # TODO: implement a caching layer so we don't have to fetch the DID (and thus the revocation list) every time a VC is validated. + issuer_revocation_list = issuer_did_document["service"][0] + assert issuer_revocation_list["type"] == "RevocationBitmap2022" + revocation_bitmap = BitMap.deserialize( + zlib.decompress( + base64.b64decode( + issuer_revocation_list["serviceEndpoint"].rsplit(",")[1] + ) + ) ) - ) - ) - if revocation_index in revocation_bitmap: - return False, "Credential has been revoked by the issuer" + if revocation_index in revocation_bitmap: + return False, "Credential has been revoked by the issuer" # Fallthrough means all is good. - return True, "" - - + return True, "Credential passes all checks" def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str: