Complete support for revocation
This commit is contained in:
parent
19183b9f86
commit
9f40c8c88d
|
@ -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}"
|
||||||
|
|
|
@ -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,8 +120,10 @@ 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)
|
||||||
|
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.
|
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
|
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_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]
|
issuer_revocation_list = issuer_did_document["service"][0]
|
||||||
assert issuer_revocation_list["type"] == "RevocationBitmap2022"
|
assert issuer_revocation_list["type"] == "RevocationBitmap2022"
|
||||||
|
@ -131,9 +137,7 @@ def verify_credential(vc):
|
||||||
if revocation_index in revocation_bitmap:
|
if revocation_index in revocation_bitmap:
|
||||||
return False, "Credential has been revoked by the issuer"
|
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:
|
||||||
|
|
Loading…
Reference in New Issue