2023-11-10 05:48:52 +00:00
|
|
|
import asyncio
|
|
|
|
import datetime
|
|
|
|
import didkit
|
|
|
|
import json
|
2024-01-20 10:36:45 +00:00
|
|
|
import urllib
|
2023-11-10 05:48:52 +00:00
|
|
|
import jinja2
|
2023-11-15 10:43:13 +00:00
|
|
|
from django.template.backends.django import Template
|
2023-12-01 18:31:09 +00:00
|
|
|
from django.template.loader import get_template
|
2023-11-10 05:48:52 +00:00
|
|
|
|
2024-01-15 09:34:42 +00:00
|
|
|
from trustchain_idhub import settings
|
|
|
|
|
2023-11-10 05:48:52 +00:00
|
|
|
|
|
|
|
def generate_did_controller_key():
|
|
|
|
return didkit.generate_ed25519_key()
|
|
|
|
|
|
|
|
|
|
|
|
def keydid_from_controller_key(key):
|
|
|
|
return didkit.key_to_did("key", key)
|
|
|
|
|
|
|
|
|
2024-01-15 09:34:42 +00:00
|
|
|
async def resolve_keydid(keydid):
|
|
|
|
return await didkit.resolve_did(keydid, "{}")
|
|
|
|
|
|
|
|
|
|
|
|
def webdid_from_controller_key(key):
|
|
|
|
"""
|
|
|
|
Se siguen los pasos para generar un webdid a partir de un keydid.
|
|
|
|
Documentado en la docu de spruceid.
|
|
|
|
"""
|
|
|
|
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"
|
2024-01-20 10:36:45 +00:00
|
|
|
domain = urllib.parse.urlencode({"domain": settings.DOMAIN})[7:]
|
|
|
|
webdid_url = f"did:web:{domain}:did-registry:{pubkeyid}" # nueva URL: "did:web:idhub.pangea.org:<...>"
|
2024-01-15 09:34:42 +00:00
|
|
|
webdid_url_owner = webdid_url + "#owner"
|
|
|
|
# Reemplazamos los campos del documento DID necesarios:
|
|
|
|
document["id"] = webdid_url
|
2024-01-16 13:01:15 +00:00
|
|
|
document["verificationMethod"][0]["id"] = webdid_url_owner
|
|
|
|
document["verificationMethod"][0]["controller"] = webdid_url
|
|
|
|
document["authentication"][0] = webdid_url_owner
|
|
|
|
document["assertionMethod"][0] = webdid_url_owner
|
2024-01-15 09:34:42 +00:00
|
|
|
document_fixed_serialized = json.dumps(document)
|
|
|
|
return webdid_url, document_fixed_serialized
|
|
|
|
|
|
|
|
|
2023-11-10 05:48:52 +00:00
|
|
|
def generate_generic_vc_id():
|
|
|
|
# TODO agree on a system for Verifiable Credential IDs
|
|
|
|
return "https://pangea.org/credentials/42"
|
|
|
|
|
|
|
|
|
|
|
|
def render_and_sign_credential(vc_template: jinja2.Template, jwk_issuer, vc_data: dict[str, str]):
|
|
|
|
"""
|
|
|
|
Populates a VC template with data for issuance, and signs the result with the provided key.
|
|
|
|
|
|
|
|
The `vc_data` parameter must at a minimum include:
|
|
|
|
* issuer_did
|
|
|
|
* subject_did
|
|
|
|
* vc_id
|
|
|
|
and must include whatever other fields are relevant for the vc_template to be instantiated.
|
|
|
|
|
|
|
|
The following field(s) will be auto-generated if not passed in `vc_data`:
|
|
|
|
* issuance_date (to `datetime.datetime.now()`)
|
|
|
|
"""
|
|
|
|
async def inner():
|
|
|
|
unsigned_vc = vc_template.render(vc_data)
|
|
|
|
signed_vc = await didkit.issue_credential(
|
|
|
|
unsigned_vc,
|
|
|
|
'{"proofFormat": "ldp"}',
|
|
|
|
jwk_issuer
|
|
|
|
)
|
|
|
|
return signed_vc
|
|
|
|
|
|
|
|
if vc_data.get("issuance_date") is None:
|
|
|
|
vc_data["issuance_date"] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
|
|
|
|
|
|
|
return asyncio.run(inner())
|
|
|
|
|
2023-11-15 10:43:13 +00:00
|
|
|
|
|
|
|
def sign_credential(unsigned_vc: str, jwk_issuer):
|
|
|
|
"""
|
2023-12-01 18:31:09 +00:00
|
|
|
Signs the unsigned credential with the provided key.
|
|
|
|
The credential template must be rendered with all user data.
|
2023-11-15 10:43:13 +00:00
|
|
|
"""
|
|
|
|
async def inner():
|
2023-12-01 18:31:09 +00:00
|
|
|
signed_vc = await didkit.issue_credential(
|
|
|
|
unsigned_vc,
|
|
|
|
'{"proofFormat": "ldp"}',
|
|
|
|
jwk_issuer
|
|
|
|
)
|
|
|
|
return signed_vc
|
2023-11-15 10:43:13 +00:00
|
|
|
|
|
|
|
return asyncio.run(inner())
|
|
|
|
|
2023-11-10 05:48:52 +00:00
|
|
|
|
2023-12-01 18:31:09 +00:00
|
|
|
def verify_credential(vc):
|
2023-11-10 05:48:52 +00:00
|
|
|
"""
|
|
|
|
Returns a (bool, str) tuple indicating whether the credential is valid.
|
|
|
|
If the boolean is true, the credential is valid and the second argument can be ignored.
|
|
|
|
If it is false, the VC is invalid and the second argument contains a JSON object with further information.
|
|
|
|
"""
|
|
|
|
async def inner():
|
2023-12-01 18:31:09 +00:00
|
|
|
return await didkit.verify_credential(vc, '{"proofFormat": "ldp"}')
|
2023-11-10 05:48:52 +00:00
|
|
|
|
2024-01-31 09:54:40 +00:00
|
|
|
valid, reason = asyncio.run(inner())
|
|
|
|
if not valid:
|
|
|
|
return valid, reason
|
|
|
|
# Credential passes basic signature verification. Now check it against its schema.
|
2023-11-27 06:42:12 +00:00
|
|
|
|
|
|
|
|
2023-12-01 18:31:09 +00:00
|
|
|
def issue_verifiable_presentation(vp_template: Template, vc_list: list[str], jwk_holder: str, holder_did: str) -> str:
|
2023-11-27 06:42:12 +00:00
|
|
|
async def inner():
|
2023-12-01 18:31:09 +00:00
|
|
|
unsigned_vp = vp_template.render(data)
|
2023-11-27 06:42:12 +00:00
|
|
|
signed_vp = await didkit.issue_presentation(
|
|
|
|
unsigned_vp,
|
|
|
|
'{"proofFormat": "ldp"}',
|
|
|
|
jwk_holder
|
|
|
|
)
|
|
|
|
return signed_vp
|
|
|
|
|
|
|
|
data = {
|
|
|
|
"holder_did": holder_did,
|
|
|
|
"verifiable_credential_list": "[" + ",".join(vc_list) + "]"
|
|
|
|
}
|
|
|
|
|
|
|
|
return asyncio.run(inner())
|
|
|
|
|
|
|
|
|
2023-12-04 09:56:22 +00:00
|
|
|
def create_verifiable_presentation(jwk_holder: str, unsigned_vp: str) -> str:
|
|
|
|
async def inner():
|
|
|
|
signed_vp = await didkit.issue_presentation(
|
|
|
|
unsigned_vp,
|
|
|
|
'{"proofFormat": "ldp"}',
|
|
|
|
jwk_holder
|
|
|
|
)
|
|
|
|
return signed_vp
|
|
|
|
|
|
|
|
return asyncio.run(inner())
|
|
|
|
|
|
|
|
|
2023-11-27 06:42:12 +00:00
|
|
|
def verify_presentation(vp):
|
|
|
|
"""
|
|
|
|
Returns a (bool, str) tuple indicating whether the credential is valid.
|
|
|
|
If the boolean is true, the credential is valid and the second argument can be ignored.
|
|
|
|
If it is false, the VC is invalid and the second argument contains a JSON object with further information.
|
|
|
|
"""
|
|
|
|
async def inner():
|
|
|
|
proof_options = '{"proofFormat": "ldp"}'
|
2023-12-01 18:31:09 +00:00
|
|
|
return await didkit.verify_presentation(vp, proof_options)
|
2023-11-27 06:42:12 +00:00
|
|
|
|
|
|
|
return asyncio.run(inner())
|
2023-12-01 18:31:09 +00:00
|
|
|
|