Merge pull request 'Pushed ssi functionality to idhub repo' (#75) from ssi into main

Reviewed-on: https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub/pulls/75
This commit is contained in:
cayop 2023-11-15 10:49:07 +00:00
commit b13488fdd4
15 changed files with 227 additions and 154 deletions

View File

@ -114,12 +114,12 @@ class ImportForm(forms.Form):
return user.first() return user.first()
def create_credential(self, user, row): def create_credential(self, user, row):
d = self.json_schema.copy()
d['instance'] = row
return VerificableCredential( return VerificableCredential(
verified=False, verified=False,
user=user, user=user,
data=json.dumps(d) csv_data=json.dumps(row),
issuer_did=self._did,
schema=self._schema,
) )
def exception(self, msg): def exception(self, msg):

View File

@ -19,7 +19,6 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib import messages from django.contrib import messages
from utils.apiregiter import iota
from utils import credtools from utils import credtools
from idhub_auth.models import User from idhub_auth.models import User
from idhub_auth.forms import ProfileForm from idhub_auth.forms import ProfileForm
@ -646,7 +645,7 @@ class DidRegisterView(Credentials, CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.user = self.request.user form.instance.user = self.request.user
form.instance.did = iota.issue_did() form.instance.set_did()
form.save() form.save()
messages.success(self.request, _('DID created successfully')) messages.success(self.request, _('DID created successfully'))
Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance) Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance)

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-11-14 16:32 # Generated by Django 4.2.5 on 2023-11-15 09:58
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -13,6 +13,33 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='DID',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
('created_at', models.DateTimeField(auto_now=True)),
('label', models.CharField(max_length=50)),
('did', models.CharField(max_length=250)),
('key_material', models.CharField(max_length=250)),
(
'user',
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='dids',
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name='File_datas', name='File_datas',
fields=[ fields=[
@ -141,9 +168,9 @@ class Migration(migrations.Migration):
('verified', models.BooleanField()), ('verified', models.BooleanField()),
('created_on', models.DateTimeField(auto_now=True)), ('created_on', models.DateTimeField(auto_now=True)),
('issued_on', models.DateTimeField(null=True)), ('issued_on', models.DateTimeField(null=True)),
('did_issuer', models.CharField(max_length=250)), ('subject_did', models.CharField(max_length=250)),
('did_subject', models.CharField(max_length=250)),
('data', models.TextField()), ('data', models.TextField()),
('csv_data', models.TextField()),
( (
'status', 'status',
models.PositiveSmallIntegerField( models.PositiveSmallIntegerField(
@ -156,6 +183,22 @@ class Migration(migrations.Migration):
default=1, default=1,
), ),
), ),
(
'issuer_did',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='vcredentials',
to='idhub.did',
),
),
(
'schema',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='vcredentials',
to='idhub.schemas',
),
),
( (
'user', 'user',
models.ForeignKey( models.ForeignKey(
@ -275,32 +318,6 @@ class Migration(migrations.Migration):
), ),
], ],
), ),
migrations.CreateModel(
name='DID',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
('created_at', models.DateTimeField(auto_now=True)),
('did', models.CharField(max_length=250, unique=True)),
('label', models.CharField(max_length=50)),
(
'user',
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='dids',
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name='UserRol', name='UserRol',
fields=[ fields=[

View File

@ -2,7 +2,13 @@ import json
import requests import requests
import datetime import datetime
from django.db import models from django.db import models
from django.template.loader import get_template
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from utils.idhub_ssikit import (
generate_did_controller_key,
keydid_from_controller_key,
sign_credential,
)
from idhub_auth.models import User from idhub_auth.models import User
@ -396,15 +402,18 @@ class Event(models.Model):
class DID(models.Model): class DID(models.Model):
created_at = models.DateTimeField(auto_now=True) created_at = models.DateTimeField(auto_now=True)
did = models.CharField(max_length=250, unique=True)
label = models.CharField(max_length=50) label = models.CharField(max_length=50)
did = models.CharField(max_length=250)
# 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)
user = models.ForeignKey( user = models.ForeignKey(
User, User,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='dids', related_name='dids',
null=True, null=True,
) )
# kind = "KEY|WEB"
@property @property
def is_organization_did(self): def is_organization_did(self):
@ -412,6 +421,13 @@ class DID(models.Model):
return True return True
return False return False
def set_did(self):
self.key_material = generate_did_controller_key()
self.did = keydid_from_controller_key(self.key_material)
def get_key(self):
return json.loads(self.key_material)
class Schemas(models.Model): class Schemas(models.Model):
file_schema = models.CharField(max_length=250) file_schema = models.CharField(max_length=250)
@ -445,9 +461,9 @@ class VerificableCredential(models.Model):
verified = models.BooleanField() verified = models.BooleanField()
created_on = models.DateTimeField(auto_now=True) created_on = models.DateTimeField(auto_now=True)
issued_on = models.DateTimeField(null=True) issued_on = models.DateTimeField(null=True)
did_issuer = models.CharField(max_length=250) subject_did = models.CharField(max_length=250)
did_subject = models.CharField(max_length=250)
data = models.TextField() data = models.TextField()
csv_data = models.TextField()
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
choices=Status.choices, choices=Status.choices,
default=Status.ENABLED default=Status.ENABLED
@ -457,6 +473,16 @@ class VerificableCredential(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='vcredentials', related_name='vcredentials',
) )
issuer_did = models.ForeignKey(
DID,
on_delete=models.CASCADE,
related_name='vcredentials',
)
schema = models.ForeignKey(
Schemas,
on_delete=models.CASCADE,
related_name='vcredentials',
)
@property @property
def get_schema(self): def get_schema(self):
@ -474,16 +500,49 @@ class VerificableCredential(models.Model):
return self.Status(self.status).label return self.Status(self.status).label
def get_datas(self): def get_datas(self):
data = json.loads(self.data).get('instance').items() data = json.loads(self.csv_data).items()
return data return data
def issue(self, did): def issue(self, did):
if self.status == self.Status.ISSUED:
return
self.status = self.Status.ISSUED self.status = self.Status.ISSUED
self.did_subject = did self.subject_did = did
self.issued_on = datetime.datetime.now() self.issued_on = datetime.datetime.now()
self.data = sign_credential(
self.render(),
self.issuer_did.key_material
)
def get_context(self):
d = json.loads(self.csv_data)
format = "%Y-%m-%dT%H:%M:%SZ"
issuance_date = self.issued_on.strftime(format)
context = {
'vc_id': self.id,
'issuer_did': self.issuer_did.did,
'subject_did': self.subject_did,
'issuance_date': issuance_date,
}
context.update(d)
return context
def render(self):
context = self.get_context()
template_name = 'credentials/{}'.format(
self.schema.file_schema
)
tmpl = get_template(template_name)
return tmpl.render(context)
def get_issued_on(self): def get_issued_on(self):
return self.issued_on.strftime("%m/%d/%Y") if self.issued_on:
return self.issued_on.strftime("%m/%d/%Y")
return ''
class VCTemplate(models.Model): class VCTemplate(models.Model):
wkit_template_id = models.CharField(max_length=250) wkit_template_id = models.CharField(max_length=250)

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

@ -12,7 +12,6 @@ from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.http import HttpResponse from django.http import HttpResponse
from django.contrib import messages from django.contrib import messages
from utils.apiregiter import iota
from idhub.user.forms import ProfileForm, RequestCredentialForm, CredentialPresentationForm from idhub.user.forms import ProfileForm, RequestCredentialForm, CredentialPresentationForm
from idhub.mixins import UserView from idhub.mixins import UserView
from idhub.models import DID, VerificableCredential, Event from idhub.models import DID, VerificableCredential, Event
@ -190,7 +189,7 @@ class DidRegisterView(MyWallet, CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.user = self.request.user form.instance.user = self.request.user
form.instance.did = iota.issue_did() form.instance.set_did()
form.save() form.save()
messages.success(self.request, _('DID created successfully')) messages.success(self.request, _('DID created successfully'))

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-11-14 16:32 # Generated by Django 4.2.5 on 2023-11-15 09:58
from django.db import migrations, models from django.db import migrations, models

View File

@ -6,5 +6,7 @@ python-decouple==3.8
jsonschema==4.19.1 jsonschema==4.19.1
pandas==2.1.1 pandas==2.1.1
requests==2.31.0 requests==2.31.0
didkit==0.3.2
jinja2==3.1.2
jsonref==1.1.0 jsonref==1.1.0
pyld==2.0.3 pyld==2.0.3

View File

@ -1,21 +0,0 @@
{
"$id": "https://pangea.org/schemas/member-credential-schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"name": "MemberCredential",
"description": "MemberCredential using JsonSchemaCredential",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"membershipType": {
"type": "string",
"enum": ["individual", "organization"]
}
},
"required": ["name", "email", "membershipType"]
}

View File

@ -1,21 +1,4 @@
{ {
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://www.w3.org/ns/credentials/examples/v2"
],
"id": "https://example.com/credentials/3734",
"type": ["VerifiableCredential", "JsonSchemaCredential"],
"issuer": "https://pangea.org/issuers/10",
"issuanceDate": "2023-09-01T19:23:24Z",
"credentialSchema": {
"id": "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json",
"type": "JsonSchema",
"digestSRI": "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW"
},
"credentialSubject": {
"id": "https://pangea.org/schemas/member-credential-schema.json",
"type": "JsonSchema",
"jsonSchema": {
"$id": "https://pangea.org/schemas/member-credential-schema.json", "$id": "https://pangea.org/schemas/member-credential-schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"name": "MemberCredential", "name": "MemberCredential",
@ -36,5 +19,3 @@
}, },
"required": ["name", "email", "membershipType"] "required": ["name", "email", "membershipType"]
} }
}
}

View File

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

View File

@ -1,17 +0,0 @@
import uuid
import hashlib
class Iota:
"""
Framework for simulate the comunication with IOTA DLT
"""
def issue_did(self):
u = str(uuid.uuid4()).encode()
d = hashlib.sha3_256(u).hexdigest()
did = "did:iota:{}".format(d)
return did
iota = Iota()

View File

View File

@ -0,0 +1,74 @@
import asyncio
import datetime
import didkit
import json
import jinja2
from django.template.backends.django import Template
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 sign_credential(unsigned_vc: str, jwk_issuer):
"""
Signs the and unsigned credential with the provided key.
"""
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, 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())