Soporte para revocación

Falta de alguna manera especificar cuando se crean los did:web, que son prerequisito para revocar credenciales
This commit is contained in:
Daniel Armengod 2024-02-01 21:19:07 +01:00
parent e7f6153c62
commit 19183b9f86
3 changed files with 42 additions and 4 deletions

View File

@ -1,6 +1,9 @@
import base64
import json import json
import uuid import uuid
import zlib
import pyroaring
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -82,16 +85,21 @@ def serve_did(request, 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"
# entry to the DID document.
revoked_credentials = did.verificablecredential_set.filter(status=VerificableCredential.Status.REVOKED) revoked_credentials = did.verificablecredential_set.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)
encoded_revocation_bitmap = None # TODO # 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 = [{
"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}"
}] }]
document["service"] = revocation_service
# Serialize the DID + Revocation list in preparation for sending # Serialize the DID + Revocation list in preparation for sending
document = json.dumps(document) document = json.dumps(document)
retval = HttpResponse(document) retval = HttpResponse(document)

View File

@ -28,3 +28,4 @@ fontTools==4.47.0
weasyprint==60.2 weasyprint==60.2
ujson==5.9.0 ujson==5.9.0
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl ./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
pyroaring==0.4.5

View File

@ -1,11 +1,15 @@
import asyncio import asyncio
import base64
import datetime import datetime
import zlib
import didkit import didkit
import json import json
import urllib import urllib
import jinja2 import jinja2
from django.template.backends.django import Template from django.template.backends.django import Template
from django.template.loader import get_template from django.template.loader import get_template
from pyroaring import BitMap
from trustchain_idhub import settings from trustchain_idhub import settings
@ -18,8 +22,11 @@ def keydid_from_controller_key(key):
return didkit.key_to_did("key", key) return didkit.key_to_did("key", key)
async def resolve_keydid(keydid): def resolve_did(keydid):
return await didkit.resolve_did(keydid, "{}") async def inner():
return await didkit.resolve_did(keydid, "{}")
return asyncio.run(inner())
def webdid_from_controller_key(key): def webdid_from_controller_key(key):
@ -29,7 +36,7 @@ def webdid_from_controller_key(key):
""" """
keydid = keydid_from_controller_key(key) # "did:key:<...>" keydid = keydid_from_controller_key(key) # "did:key:<...>"
pubkeyid = keydid.rsplit(":")[-1] # <...> pubkeyid = keydid.rsplit(":")[-1] # <...>
document = json.loads(asyncio.run(resolve_keydid(keydid))) # Documento DID en terminos "key" document = json.loads(resolve_did(keydid)) # Documento DID en terminos "key"
domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:] domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:]
webdid_url = f"did:web:{domain}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>" webdid_url = f"did:web:{domain}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
webdid_url_owner = webdid_url + "#owner" webdid_url_owner = webdid_url + "#owner"
@ -105,6 +112,28 @@ def verify_credential(vc):
if not valid: if not valid:
return valid, reason return valid, reason
# Credential passes basic signature verification. Now check it against its schema. # Credential passes basic signature verification. Now check it against its schema.
# TODO: check agasint schema
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 revocation_index in revocation_bitmap:
return False, "Credential has been revoked by the issuer"
# Fallthrough means all is good.
return True, ""
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: