diff --git a/idhub/models.py b/idhub/models.py index 8a805d4..16de09c 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -31,6 +31,18 @@ class DID(models.Model): return False +class DIDControllerKey(models.Model): + # In JWK format. Must be stored as-is and passed whole to library functions. + # Example key material: + # '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}' + key_material = models.CharField(max_length=250) + owner_did = models.ForeignKey( + DID, + on_delete=models.CASCADE, + related_name="keys" + ) + + class Schemas(models.Model): file_schema = models.CharField(max_length=250) data = models.TextField() diff --git a/idhub_ssikit/README.md b/idhub_ssikit/README.md new file mode 100644 index 0000000..e69de29 diff --git a/trustchain_walletkit/TENANT_CFG_TEMPLATE b/idhub_ssikit/TENANT_CFG_TEMPLATE similarity index 100% rename from trustchain_walletkit/TENANT_CFG_TEMPLATE rename to idhub_ssikit/TENANT_CFG_TEMPLATE diff --git a/idhub_ssikit/__init__.py b/idhub_ssikit/__init__.py new file mode 100644 index 0000000..dced08e --- /dev/null +++ b/idhub_ssikit/__init__.py @@ -0,0 +1,58 @@ +import asyncio +import datetime +import didkit +import json +import jinja2 + + +def generate_did_controller_key(): + return didkit.generate_ed25519_key() + + +def keydid_from_controller_key(key): + return didkit.key_to_did("key", key) + + +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 verify_credential(vc, proof_options): + """ + 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(): + return didkit.verify_credential(vc, proof_options) + + return asyncio.run(inner()) diff --git a/requirements.txt b/requirements.txt index 4281ff1..34bad77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ python-decouple==3.8 jsonschema==4.19.1 pandas==2.1.1 requests==2.31.0 - +didkit==0.3.2 +jinja2==3.1.2 \ No newline at end of file diff --git a/schemas/member-credential.json b/schemas/member-credential.json new file mode 100644 index 0000000..e98bef2 --- /dev/null +++ b/schemas/member-credential.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "name": "https://schema.org/name", + "email": "https://schema.org/email", + "membershipType": "https://schema.org/memberOf", + "individual": "https://schema.org/Person", + "organization": "https://schema.org/Organization", + "Member": "https://schema.org/Member", + "startDate": "https://schema.org/startDate", + "jsonSchema": "https://schema.org/jsonSchema", + "$ref": "https://schema.org/jsonSchemaRef" + } + ], + "type": ["VerifiableCredential", "VerifiableAttestation"], + "id": "{{ vc_id }}", + "issuer": "{{ issuer_did }}", + "issuanceDate": "{{ issuance_date }}", + "credentialSubject": { + "id": "{{ subject_did }}", + "Member": { + "name": "{{ name }}", + "email": "{{ email }}", + "membershipType": "{{ membershipType }}", + "startDate": "{{ startDate }}" + }, + "jsonSchema": { + "$ref": "https://gitea.pangea.org/trustchain-oc1-orchestral/schemas/member-schema.json" + } + } +} \ No newline at end of file diff --git a/trustchain_walletkit/__init__.py b/trustchain_walletkit/__init__.py deleted file mode 100644 index 02d0144..0000000 --- a/trustchain_walletkit/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -from pathlib import Path - -import requests -import json - -WALLETKITD = 'http://localhost:8080/' -ISSUER = f'{WALLETKITD}issuer-api/default/' -VERIFIER = f'{WALLETKITD}verifier-api/default/' - -default_ctype_header = { - 'Content-Type': 'application/json', # specify the type of data you're sending - 'Accept': 'application/json', # specify the type of data you can accept -} - - -def include_str(path): - with open(path, "r") as f: - return f.read().strip() - - -# Create DID for tenant -# Valid methods: 'key'|'web' -def user_create_did(did_method): - url = f'{ISSUER}config/did/create' - data = { - 'method': did_method - } - response = requests.post(url, json=data, headers=default_ctype_header) - response.raise_for_status() - return response.text - - -def admin_create_template(template_name, template_body): - url = f'{ISSUER}config/templates/{template_name}' - body = template_body - response = requests.post(url, data=body, headers=default_ctype_header) - response.raise_for_status() - return - - -def user_issue_vc(vc_name, vc_params): - url = f'{ISSUER}credentials/issuance/request' - # ... - # TODO examine cross-device issuance workflow - pass - - - - - -TENANT_CFG_TMEPLATE = include_str("./TENANT_CFG_TEMPLATE") -