pyvckit #1
12
README.md
12
README.md
|
@ -31,23 +31,19 @@ The application's backend is responsible for issuing credentials upun user reque
|
|||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
3. Install the DIDKit wheel
|
||||
```
|
||||
wget https://gitea.pangea.org/trustchain-oc1-orchestral/ssikit_trustchain/raw/branch/master/didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
```
|
||||
4. Install the required packages:
|
||||
3. Install the required packages:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
5. Run migrations:
|
||||
4. Run migrations:
|
||||
```
|
||||
python manage.py migrate
|
||||
```
|
||||
6. Optionally you can install a minumum data set:
|
||||
5. Optionally you can install a minumum data set:
|
||||
```
|
||||
python manage.py initial_datas
|
||||
```
|
||||
7. Start the development server:
|
||||
6. Start the development server:
|
||||
```
|
||||
python manage.py runserver
|
||||
```
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"@context": {
|
||||
"credentialSchema": "https://idhub.pangea.org/context/#credentialSchema",
|
||||
"value": "https://idhub.pangea.org/context/#value",
|
||||
"lang": "https://idhub.pangea.org/context/#lang",
|
||||
"description": "https://idhub.pangea.org/context/#description",
|
||||
"name": "https://idhub.pangea.org/context/#name"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"personalIdentifier": "https://idhub.pangea.org/context/#personalIdentifier",
|
||||
"issuedDate": "https://idhub.pangea.org/context/#issuedDate",
|
||||
"modeOfInstruction": "https://idhub.pangea.org/context/#modeOfInstruction",
|
||||
"courseDuration": "https://idhub.pangea.org/context/#courseDuration",
|
||||
"courseDays": "https://idhub.pangea.org/context/#courseDays",
|
||||
"courseName": "https://idhub.pangea.org/context/#courseName",
|
||||
"courseDescription": "https://idhub.pangea.org/context/#courseDescription",
|
||||
"gradingScheme": "https://idhub.pangea.org/context/#gradingScheme",
|
||||
"scoreAwarded": "https://idhub.pangea.org/context/#scoreAwarded",
|
||||
"qualificationAwarded": "https://idhub.pangea.org/context/#qualificationAwarded",
|
||||
"courseLevel": "https://idhub.pangea.org/context/#courseLevel",
|
||||
"courseFramework": "https://idhub.pangea.org/context/#courseFramework",
|
||||
"courseCredits": "https://idhub.pangea.org/context/#courseCredits",
|
||||
"dateOfAssessment": "https://idhub.pangea.org/context/#dateOfAssessment",
|
||||
"evidenceAssessment": "https://idhub.pangea.org/context/#evidenceAssessment",
|
||||
"email": "https://idhub.pangea.org/context/#email"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"@context": {
|
||||
"legalName": "https://idhub.pangea.org/context/#legalName",
|
||||
"accreditedBy": "https://idhub.pangea.org/context/#accreditedBy",
|
||||
"operatorNumber": "https://idhub.pangea.org/context/#operatorNumber",
|
||||
"limitJurisdiction": "https://idhub.pangea.org/context/#limitJurisdiction",
|
||||
"accreditedFor": "https://idhub.pangea.org/context/#accreditedFor",
|
||||
"role": "https://idhub.pangea.org/context/#role",
|
||||
"email": "https://idhub.pangea.org/context/#email"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@context": {
|
||||
"federation": "https://idhub.pangea.org/context/#federation",
|
||||
"legalName": "https://idhub.pangea.org/context/#legalName",
|
||||
"shortName": "https://idhub.pangea.org/context/#shortName",
|
||||
"registrationIdentifier": "https://idhub.pangea.org/context/#registrationIdentifier",
|
||||
"publicRegistry": "https://idhub.pangea.org/context/#publicRegistry",
|
||||
"streetAddress": "https://idhub.pangea.org/context/#streetAddress",
|
||||
"postCode": "https://idhub.pangea.org/context/#postCode",
|
||||
"city": "https://idhub.pangea.org/context/#city",
|
||||
"taxReference": "https://idhub.pangea.org/context/#taxReference",
|
||||
"membershipType": "https://idhub.pangea.org/context/#membershipType",
|
||||
"membershipStatus": "https://idhub.pangea.org/context/#membershipStatus",
|
||||
"membershipId": "https://idhub.pangea.org/context/#membershipId",
|
||||
"membershipSince": "https://idhub.pangea.org/context/#membershipSince",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"phone": "https://idhub.pangea.org/context/#phone",
|
||||
"website": "https://idhub.pangea.org/context/#website",
|
||||
"evidence": "https://idhub.pangea.org/context/#evidence",
|
||||
"certificationDate": "https://idhub.pangea.org/context/#certificationDate"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"phoneNumber": "https://idhub.pangea.org/context/#phoneNumber",
|
||||
"identityDocType": "https://idhub.pangea.org/context/#identityDocType",
|
||||
"identityNumber": "https://idhub.pangea.org/context/#identityNumber",
|
||||
"streetAddress": "https://idhub.pangea.org/context/#streetAddress",
|
||||
"socialWorkerName": "https://idhub.pangea.org/context/#socialWorkerName",
|
||||
"socialWorkerSurname": "https://idhub.pangea.org/context/#socialWorkerSurname",
|
||||
"financialVulnerabilityScore": "https://idhub.pangea.org/context/#financialVulnerabilityScore",
|
||||
"amountCoveredByOtherAids": "https://idhub.pangea.org/context/#amountCoveredByOtherAids",
|
||||
"connectivityOptionList": "https://idhub.pangea.org/context/#connectivityOptionList",
|
||||
"assessmentDate": "https://idhub.pangea.org/context/#assessmentDate"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"@context": {
|
||||
"firstName": "https://idhub.pangea.org/context/#firstName",
|
||||
"lastName": "https://idhub.pangea.org/context/#lastName",
|
||||
"email": "https://idhub.pangea.org/context/#email",
|
||||
"organisation": "https://idhub.pangea.org/context/#organisation",
|
||||
"membershipType": "https://idhub.pangea.org/context/#membershipType",
|
||||
"membershipId": "https://idhub.pangea.org/context/#membershipId",
|
||||
"affiliatedSince": "https://idhub.pangea.org/context/#iaffiliatedSince",
|
||||
"affiliatedUntil": "https://idhub.pangea.org/context/#affiliatedUntil",
|
||||
"typeOfPerson": "https://idhub.pangea.org/context/#typeOfPerson",
|
||||
"identityDocType": "https://idhub.pangea.org/context/#identityDocType",
|
||||
"identityNumber": "https://idhub.pangea.org/context/#identityNumber"
|
||||
}
|
||||
}
|
|
@ -6,16 +6,17 @@ import datetime
|
|||
from collections import OrderedDict
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utils.idhub_ssikit import (
|
||||
generate_did_controller_key,
|
||||
keydid_from_controller_key,
|
||||
sign_credential,
|
||||
webdid_from_controller_key,
|
||||
verify_credential,
|
||||
from pyvckit.did import (
|
||||
generate_keys,
|
||||
generate_did,
|
||||
gen_did_document,
|
||||
)
|
||||
from pyvckit.sign import sign
|
||||
from pyvckit.verify import verify_vc
|
||||
|
||||
from oidc4vp.models import Organization
|
||||
from idhub_auth.models import User
|
||||
|
||||
|
@ -469,15 +470,22 @@ class DID(models.Model):
|
|||
self.key_material = user.encrypt_data(value)
|
||||
|
||||
def set_did(self):
|
||||
new_key_material = generate_did_controller_key()
|
||||
new_key_material = generate_keys()
|
||||
self.set_key_material(new_key_material)
|
||||
|
||||
if self.type == self.Types.KEY:
|
||||
self.did = keydid_from_controller_key(new_key_material)
|
||||
self.did = generate_did(new_key_material)
|
||||
elif self.type == self.Types.WEB:
|
||||
didurl, document = webdid_from_controller_key(new_key_material, settings.DOMAIN)
|
||||
self.did = didurl
|
||||
self.didweb_document = document
|
||||
url = "https://{}".format(settings.DOMAIN)
|
||||
path = reverse("idhub:serve_did", args=["a"])
|
||||
|
||||
if path:
|
||||
path = path.split("/a/did.json")[0]
|
||||
url = "https://{}/{}".format(settings.DOMAIN, path)
|
||||
|
||||
self.did = generate_did(new_key_material, url)
|
||||
key = json.loads(new_key_material)
|
||||
url, self.didweb_document = gen_did_document(self.did, key)
|
||||
|
||||
def get_key(self):
|
||||
return json.loads(self.key_material)
|
||||
|
@ -681,15 +689,18 @@ class VerificableCredential(models.Model):
|
|||
|
||||
# hash of credential without sign
|
||||
self.hash = hashlib.sha3_256(self.render(domain).encode()).hexdigest()
|
||||
data = sign_credential(
|
||||
self.render(domain),
|
||||
self.issuer_did.get_key_material()
|
||||
)
|
||||
valid, reason = verify_credential(data)
|
||||
|
||||
key = self.issuer_did.get_key_material()
|
||||
credential = self.render(domain)
|
||||
|
||||
vc = sign(credential, key, self.issuer_did.did)
|
||||
vc_str = json.dumps(vc)
|
||||
valid = verify_vc(vc_str)
|
||||
|
||||
if not valid:
|
||||
return
|
||||
|
||||
self.data = self.user.encrypt_data(data)
|
||||
self.data = self.user.encrypt_data(vc_str)
|
||||
|
||||
self.status = self.Status.ISSUED
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/course-credential/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/course-credential.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
|
@ -67,5 +67,5 @@
|
|||
"id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/e-operator-claim/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/e-operator-claim.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
|
@ -64,4 +64,4 @@
|
|||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/federation-membership/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/federation-membership.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
|
@ -75,4 +75,4 @@
|
|||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||
"type": "FullJsonSchemaValidator2021"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/financial-vulnerability/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/financial-vulnerability.jsonld"
|
||||
],
|
||||
"id": "{{ vc_id }}",
|
||||
"type": [
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1",
|
||||
"https://idhub.pangea.org/credentials/base/v1",
|
||||
"https://idhub.pangea.org/credentials/membership-card/v1"
|
||||
"https://idhub.pangea.org/context/base.jsonld",
|
||||
"https://idhub.pangea.org/context/membership-card.jsonld"
|
||||
],
|
||||
"type": [
|
||||
"VerifiableCredential",
|
||||
|
|
|
@ -21,7 +21,7 @@ from .views import (
|
|||
LoginView,
|
||||
PasswordResetView,
|
||||
PasswordResetConfirmView,
|
||||
serve_did,
|
||||
ServeDidView,
|
||||
DobleFactorSendView,
|
||||
)
|
||||
from .admin import views as views_admin
|
||||
|
@ -183,7 +183,7 @@ urlpatterns = [
|
|||
name='admin_2fauth'),
|
||||
path('admin/auth/2f/', DobleFactorSendView.as_view(), name='confirm_send_2f'),
|
||||
|
||||
path('did-registry/<str:did_id>/did.json', serve_did)
|
||||
path('did-registry/<str:did_id>/did.json', ServeDidView, name="serve_did")
|
||||
|
||||
# path('verification_portal/verify/', views_verification_portal.verify,
|
||||
# name="verification_portal_verify")
|
||||
|
|
|
@ -90,7 +90,7 @@ class PasswordResetView(auth_views.PasswordResetView):
|
|||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
def serve_did(request, did_id):
|
||||
def ServeDidView(request, did_id):
|
||||
domain = settings.DOMAIN
|
||||
id_did = f'did:web:{domain}:did-registry:{did_id}'
|
||||
did = get_object_or_404(DID, did=id_did)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import json
|
||||
import uuid
|
||||
|
||||
from django import forms
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from utils.idhub_ssikit import create_verifiable_presentation
|
||||
from pyvckit.sign import sign
|
||||
from idhub.models import VerificableCredential
|
||||
|
||||
|
||||
|
@ -72,13 +73,19 @@ class AuthorizeForm(forms.Form):
|
|||
|
||||
def get_verificable_presentation(self):
|
||||
did = self.subject_did
|
||||
vc_list = [json.loads(x) for x in self.list_credentials]
|
||||
vp_template = get_template('credentials/verifiable_presentation.json')
|
||||
vc_list = json.dumps([json.loads(x) for x in self.list_credentials])
|
||||
|
||||
context = {
|
||||
"holder_did": did.did,
|
||||
"verifiable_credential_list": vc_list
|
||||
"id": str(uuid.uuid4())
|
||||
}
|
||||
unsigned_vp = vp_template.render(context)
|
||||
vp = json.loads(unsigned_vp)
|
||||
vp["verifiableCredential"] = vc_list
|
||||
vp_str = json.dumps(vp)
|
||||
|
||||
key_material = did.get_key_material()
|
||||
self.vp = create_verifiable_presentation(key_material, unsigned_vp)
|
||||
vp = sign(vp_str, key_material, did.did)
|
||||
self.vp = json.dumps(vp)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.http import QueryDict
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from idhub_auth.models import User
|
||||
from django.db import models
|
||||
from utils.idhub_ssikit import verify_presentation
|
||||
from pyvckit.verify import verify_vp
|
||||
|
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
@ -271,7 +271,7 @@ class OAuth2VPToken(models.Model):
|
|||
return self.authorization.code
|
||||
|
||||
def verifing(self):
|
||||
self.result_verify = verify_presentation(self.vp_token)
|
||||
self.result_verify = verify_vp(self.vp_token)
|
||||
|
||||
def get_result_verify(self):
|
||||
if not self.result_verify:
|
||||
|
@ -284,8 +284,7 @@ class OAuth2VPToken(models.Model):
|
|||
"redirect_uri": "",
|
||||
"response": "",
|
||||
}
|
||||
verification = json.loads(self.result_verify)
|
||||
if verification.get('errors') or verification.get('warnings'):
|
||||
if not self.result_verify:
|
||||
response["verify"] = "Error, {}".format(_("Failed verification"))
|
||||
return response
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"@context": [
|
||||
"https://www.w3.org/2018/credentials/v1"
|
||||
],
|
||||
"id": "http://example.org/presentations/3731",
|
||||
"id": "{{ id }}",
|
||||
"type": [
|
||||
"VerifiablePresentation"
|
||||
],
|
||||
"holder": "{{ holder_did }}",
|
||||
"verifiableCredential": {{ verifiable_credential_list|safe }}
|
||||
"verifiableCredential": ""
|
||||
}
|
||||
|
|
|
@ -174,11 +174,7 @@ class VerifyView(View):
|
|||
"""
|
||||
Send a email when a user is activated.
|
||||
"""
|
||||
verification = self.vp_token.get_result_verify()
|
||||
if not verification:
|
||||
return
|
||||
|
||||
if verification.get('errors') or verification.get('warnings'):
|
||||
if not self.vp_token.result_verify:
|
||||
return
|
||||
|
||||
email = self.get_email(user)
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from utils.idhub_ssikit import create_verifiable_presentation
|
||||
from oidc4vp.models import Organization, Authorization
|
||||
from promotion.models import Promotion
|
||||
|
||||
|
@ -66,4 +58,3 @@ class ContractForm(forms.Form):
|
|||
birthday = forms.CharField()
|
||||
gen = forms.CharField()
|
||||
lang = forms.CharField()
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ weasyprint==60.2
|
|||
ujson==5.9.0
|
||||
openpyxl==3.1.2
|
||||
jsonpath_ng==1.6.1
|
||||
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
||||
pyroaring==0.4.5
|
||||
coverage==7.4.3
|
||||
gunicorn==21.2.0
|
||||
pyvckit
|
||||
|
|
|
@ -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
|
||||
)
|
||||
```
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
|
Loading…
Reference in New Issue