resolve conflicts

This commit is contained in:
Cayo Puigdefabregas 2024-02-13 09:46:00 +01:00
commit b7e64586f8
4 changed files with 71 additions and 8 deletions

View File

@ -440,6 +440,7 @@ class DID(models.Model):
related_name='dids',
null=True,
)
# JSON-serialized DID document
didweb_document = models.TextField()
def get_key_material(self, password):
@ -589,6 +590,7 @@ class VerificableCredential(models.Model):
on_delete=models.CASCADE,
related_name='vcredentials',
)
revocationBitmapIndex = models.AutoField()
def get_data(self, password):
if not self.data:

View File

@ -1,6 +1,10 @@
import base64
import json
import uuid
import logging
import zlib
import pyroaring
from django.conf import settings
from django.core.cache import cache
from django.urls import reverse_lazy
@ -12,7 +16,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect, HttpResponse, Http404
from idhub.models import DID
from idhub.models import DID, VerificableCredential
from idhub.email.views import NotifyActivateUserByEmail
from trustchain_idhub import settings
@ -99,9 +103,29 @@ class PasswordResetView(auth_views.PasswordResetView):
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)
document = did.didweb_document
# 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.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 = [{ # This is an object within a list.
"id": f"{id_did}#revocation",
"type": "RevocationBitmap2022",
"serviceEndpoint": f"data:application/octet-stream;base64,{encoded_revocation_bitmap}"
}]
document["service"] = revocation_service
# Serialize the DID + Revocation list in preparation for sending
document = json.dumps(document)
retval = HttpResponse(document)
retval.headers["Content-Type"] = "application/json"
return retval

View File

@ -30,3 +30,4 @@ ujson==5.9.0
openpyxl==3.1.2
jsonpath_ng==1.6.1
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
pyroaring==0.4.5

View File

@ -1,11 +1,16 @@
import asyncio
import base64
import datetime
import zlib
from ast import literal_eval
import didkit
import json
import urllib
import jinja2
from django.template.backends.django import Template
from django.template.loader import get_template
from pyroaring import BitMap
from trustchain_idhub import settings
@ -18,9 +23,12 @@ def keydid_from_controller_key(key):
return didkit.key_to_did("key", key)
async def resolve_keydid(keydid):
def resolve_did(keydid):
async def inner():
return await didkit.resolve_did(keydid, "{}")
return asyncio.run(inner())
def webdid_from_controller_key(key):
"""
@ -29,7 +37,7 @@ def webdid_from_controller_key(key):
"""
keydid = keydid_from_controller_key(key) # "did:key:<...>"
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:]
webdid_url = f"did:web:{domain}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
webdid_url_owner = webdid_url + "#owner"
@ -99,9 +107,37 @@ 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
return asyncio.run(inner())
valid, reason = asyncio.run(inner())
if not valid:
return valid, reason
# 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)
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"
# Fallthrough means all is good.
return True, "Credential passes all checks"
def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str: