From 95e2669b183b4fa6cc4b09e9a82896bc479959aa Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 6 Jun 2024 17:40:08 +0200 Subject: [PATCH] remove idhub_ssikit --- utils/idhub_ssikit/README.md | 73 ---------- utils/idhub_ssikit/TENANT_CFG_TEMPLATE | 15 -- utils/idhub_ssikit/__init__.py | 184 ------------------------- 3 files changed, 272 deletions(-) delete mode 100644 utils/idhub_ssikit/README.md delete mode 100644 utils/idhub_ssikit/TENANT_CFG_TEMPLATE delete mode 100644 utils/idhub_ssikit/__init__.py diff --git a/utils/idhub_ssikit/README.md b/utils/idhub_ssikit/README.md deleted file mode 100644 index 8443717..0000000 --- a/utils/idhub_ssikit/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Helper routines to manage DIDs/VC/VPs - -This module is a wrapper around the functions exported by SpruceID's `DIDKit` framework. - -## DID generation and storage - -For now DIDs are of the kind `did:key`, with planned support for `did:web` in the near future. - -Creation of a DID involves two steps: -* Generate a unique DID controller key -* Derive a `did:key` type from the key - -Both must be stored in the IdHub database and linked to a `User` for later retrieval. - -```python -# Use case: generate and link a new DID for an existing user -user = request.user # ... - -controller_key = idhub_ssikit.generate_did_controller_key() -did_string = idhub_ssikit.keydid_from_controller_key(controller_key) - - -did = idhub.models.DID( - did = did_string, - user = user -) -did_controller_key = idhub.models.DIDControllerKey( - key_material = controller_key, - owner_did = did -) - -did.save() -did_controller_key.save() -``` - -## Verifiable Credential issuance - -Verifiable Credential templates are stored as Jinja2 (TBD) templates in `/schemas` folder. Please examine each template to see what data must be passed to it in order to render. - -The data passed to the template must at a minimum include: -* issuer_did -* subject_did -* vc_id - -For example, in order to render `/schemas/member-credential.json`: - -```python -from jinja2 import Environment, FileSystemLoader, select_autoescape -import idhub_ssikit - -env = Environment( - loader=FileSystemLoader("vc_templates"), - autoescape=select_autoescape() -) -unsigned_vc_template = env.get_template("member-credential.json") - -issuer_user = request.user -issuer_did = user.dids[0] # TODO: Django ORM pseudocode -issuer_did_controller_key = did.keys[0] # TODO: Django ORM pseudocode - -data = { - "vc_id": "http://pangea.org/credentials/3731", - "issuer_did": issuer_did, - "subject_did": "did:web:[...]", - "issuance_date": "2020-08-19T21:41:50Z", - "subject_is_member_of": "Pangea" -} -signed_credential = idhub_ssikit.render_and_sign_credential( - unsigned_vc_template, - issuer_did_controller_key, - data -) -``` \ No newline at end of file diff --git a/utils/idhub_ssikit/TENANT_CFG_TEMPLATE b/utils/idhub_ssikit/TENANT_CFG_TEMPLATE deleted file mode 100644 index 00b3372..0000000 --- a/utils/idhub_ssikit/TENANT_CFG_TEMPLATE +++ /dev/null @@ -1,15 +0,0 @@ -{ - "issuerApiUrl": "http://localhost:8080/issuer-api/default", - "issuerClientName": "PANGEA Issuer Portal", - "issuerDid": null, - "issuerUiUrl": "http://localhost:5000", - "wallets": { - "walt.id": { - "description": "walt.id web wallet", - "id": "walt.id", - "presentPath": "api/siop/initiatePresentation", - "receivePath": "api/siop/initiateIssuance", - "url": "http://localhost:3000" - } - } -} \ No newline at end of file diff --git a/utils/idhub_ssikit/__init__.py b/utils/idhub_ssikit/__init__.py deleted file mode 100644 index 562ef25..0000000 --- a/utils/idhub_ssikit/__init__.py +++ /dev/null @@ -1,184 +0,0 @@ -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 - - -def generate_did_controller_key(): - return didkit.generate_ed25519_key() - - -def keydid_from_controller_key(key): - return didkit.key_to_did("key", key) - - -def resolve_did(keydid): - async def inner(): - return await didkit.resolve_did(keydid, "{}") - - return asyncio.run(inner()) - - -def webdid_from_controller_key(key, domain): - """ - 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(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" - # Reemplazamos los campos del documento DID necesarios: - document["id"] = webdid_url - 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 - document_fixed_serialized = json.dumps(document) - return webdid_url, document_fixed_serialized - - -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()) - - -def sign_credential(unsigned_vc: str, jwk_issuer): - """ - Signs the unsigned credential with the provided key. - The credential template must be rendered with all user data. - """ - async def inner(): - signed_vc = await didkit.issue_credential( - unsigned_vc, - '{"proofFormat": "ldp"}', - jwk_issuer - ) - return signed_vc - - return asyncio.run(inner()) - - -def verify_credential(vc): - """ - 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(): - 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()) - 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].encode('utf-8') - ) - ) - ) - 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: - async def inner(): - unsigned_vp = vp_template.render(data) - 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()) - - -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()) - - -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"}' - return await didkit.verify_presentation(vp, proof_options) - - return asyncio.run(inner()) -