Complete support for revocation

This commit is contained in:
Daniel Armengod 2024-02-05 19:44:54 +01:00
parent 19183b9f86
commit 9f40c8c88d
2 changed files with 26 additions and 20 deletions

View File

@ -81,20 +81,22 @@ class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
def serve_did(request, did_id): 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) did = get_object_or_404(DID, did=id_did)
# Deserialize the base DID from JSON storage # Deserialize the base DID from JSON storage
document = json.loads(did.didweb_document) document = json.loads(did.didweb_document)
# Has this DID issued any Verifiable Credentials? If so, we need to add a Revocation List "service" # Has this DID issued any Verifiable Credentials? If so, we need to add a Revocation List "service"
# entry to the DID document. # 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 = [] revoked_credential_indexes = []
for credential in revoked_credentials: for credential in revoked_credentials:
revoked_credential_indexes.append(credential.revocationBitmapIndex) revoked_credential_indexes.append(credential.revocationBitmapIndex)
# TODO: Conditionally add "service" to DID document only if the DID has issued any VC # TODO: Conditionally add "service" to DID document only if the DID has issued any VC
revocation_bitmap = pyroaring.BitMap(revoked_credential_indexes) revocation_bitmap = pyroaring.BitMap(revoked_credential_indexes)
encoded_revocation_bitmap = base64.b64encode(zlib.compress(revocation_bitmap.serialize())) 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", "id": f"{id_did}#revocation",
"type": "RevocationBitmap2022", "type": "RevocationBitmap2022",
"serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}" "serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}"

View File

@ -2,6 +2,7 @@ import asyncio
import base64 import base64
import datetime import datetime
import zlib import zlib
from ast import literal_eval
import didkit import didkit
import json 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. If it is false, the VC is invalid and the second argument contains a JSON object with further information.
""" """
async def inner(): 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()) valid, reason = asyncio.run(inner())
if not valid: if not valid:
@ -116,24 +120,24 @@ def verify_credential(vc):
pass pass
# Credential verifies against its schema. Now check revocation status. # Credential verifies against its schema. Now check revocation status.
vc = json.loads(vc) 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. if "credentialStatus" in vc:
vc_issuer = vc["issuer"]["id"] # This is a DID 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.
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. vc_issuer = vc["issuer"]["id"] # This is a DID
issuer_revocation_list = issuer_did_document["service"][0] if vc_issuer[:7] == "did:web": # Only DID:WEB can revoke
assert issuer_revocation_list["type"] == "RevocationBitmap2022" 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.
revocation_bitmap = BitMap.deserialize( issuer_revocation_list = issuer_did_document["service"][0]
zlib.decompress( assert issuer_revocation_list["type"] == "RevocationBitmap2022"
base64.b64decode( revocation_bitmap = BitMap.deserialize(
issuer_revocation_list["serviceEndpoint"].rsplit(",")[1] 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. # 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: def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str: