First iteration of the ssi routines

This commit is contained in:
Daniel Armengod 2023-11-10 06:45:12 +01:00
parent 440ea7ebbe
commit 9608add9dd
10 changed files with 207 additions and 21 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

View File

@ -0,0 +1,73 @@
# 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
)
```

58
idhub_ssikit/__init__.py Normal file
View File

@ -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_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())

Binary file not shown.

25
main.py
View File

@ -2,6 +2,7 @@ import asyncio
import didkit import didkit
import json import json
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
import idhub_ssikit
jwk_issuer = didkit.generate_ed25519_key() jwk_issuer = didkit.generate_ed25519_key()
jwk_subject = didkit.generate_ed25519_key() jwk_subject = didkit.generate_ed25519_key()
@ -10,24 +11,22 @@ did_issuer = didkit.key_to_did("key", jwk_issuer)
did_subject = didkit.key_to_did("key", jwk_subject) did_subject = didkit.key_to_did("key", jwk_subject)
async def main(): env = Environment(
env = Environment(
loader=FileSystemLoader("vc_templates"), loader=FileSystemLoader("vc_templates"),
autoescape=select_autoescape() autoescape=select_autoescape()
) )
unsigned_vc_template = env.get_template("affiliation.jsonld.j2") unsigned_vc_template = env.get_template("member-credential.json")
data = { data = {
"vc_id": "http://example.org/credentials/3731", "vc_id": "http://example.org/credentials/3731",
"issuer_did": did_issuer, "issuer_did": did_issuer,
"subject_did": did_subject, "subject_did": did_subject,
"issuance_date": "2020-08-19T21:41:50Z", "issuance_date": "2020-08-19T21:41:50Z",
"subject_is_member_of": "Pangea" "subject_is_member_of": "Pangea"
} }
unsigned_vc = unsigned_vc_template.render(data) signed_credential = idhub_ssikit.render_and_sign_credential(
signed_credential = await didkit.issue_credential( unsigned_vc_template,
unsigned_vc, jwk_issuer,
json.dumps({"proofFormat": "ldp"}), data
jwk_issuer) )
print(signed_credential)
asyncio.run(main()) print(signed_credential)

View File

@ -4,7 +4,7 @@
{ {
"title" : "trustchain:title", "title" : "trustchain:title",
"name": "trustchain:name", "name": "trustchain:name",
"grade": "trustchain:grade" "grade": "trustchain:grade",
"date_earned": "trustchain:dateearned" "date_earned": "trustchain:dateearned"
} }
], ],

View File

@ -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"
}
}
}

View File

@ -0,0 +1,23 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "0892f680-6aeb-11eb-9bcf-f10d8993fde7",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"issuer": {
"id": "{{ issuer_did }}",
"name": "Acme University"
},
"issuanceDate": "2021-05-11T23:09:06.803Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science"
}
}
}