Merge pull request 'pyvckit' (#1) from pyvckit into release
Reviewed-on: #1
This commit is contained in:
commit
5b5afa5c4c
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
|
python -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
```
|
```
|
||||||
3. Install the DIDKit wheel
|
3. Install the required packages:
|
||||||
```
|
|
||||||
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:
|
|
||||||
```
|
```
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
5. Run migrations:
|
4. Run migrations:
|
||||||
```
|
```
|
||||||
python manage.py migrate
|
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
|
python manage.py initial_datas
|
||||||
```
|
```
|
||||||
7. Start the development server:
|
6. Start the development server:
|
||||||
```
|
```
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
1
cache_context.json
Normal file
1
cache_context.json
Normal file
File diff suppressed because one or more lines are too long
9
context/base.jsonld
Normal file
9
context/base.jsonld
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
22
context/course-credential.jsonld
Normal file
22
context/course-credential.jsonld
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
11
context/e-operator-claim.jsonld
Normal file
11
context/e-operator-claim.jsonld
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
22
context/federation-membership.jsonld
Normal file
22
context/federation-membership.jsonld
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
17
context/financial-vulnerability.jsonld
Normal file
17
context/financial-vulnerability.jsonld
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
15
context/membership-card.jsonld
Normal file
15
context/membership-card.jsonld
Normal file
|
@ -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 collections import OrderedDict
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from pyvckit.did import (
|
||||||
from utils.idhub_ssikit import (
|
generate_keys,
|
||||||
generate_did_controller_key,
|
generate_did,
|
||||||
keydid_from_controller_key,
|
gen_did_document,
|
||||||
sign_credential,
|
|
||||||
webdid_from_controller_key,
|
|
||||||
verify_credential,
|
|
||||||
)
|
)
|
||||||
|
from pyvckit.sign import sign
|
||||||
|
from pyvckit.verify import verify_vc
|
||||||
|
|
||||||
from oidc4vp.models import Organization
|
from oidc4vp.models import Organization
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
|
|
||||||
|
@ -469,15 +470,22 @@ class DID(models.Model):
|
||||||
self.key_material = user.encrypt_data(value)
|
self.key_material = user.encrypt_data(value)
|
||||||
|
|
||||||
def set_did(self):
|
def set_did(self):
|
||||||
new_key_material = generate_did_controller_key()
|
new_key_material = generate_keys()
|
||||||
self.set_key_material(new_key_material)
|
self.set_key_material(new_key_material)
|
||||||
|
|
||||||
if self.type == self.Types.KEY:
|
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:
|
elif self.type == self.Types.WEB:
|
||||||
didurl, document = webdid_from_controller_key(new_key_material, settings.DOMAIN)
|
url = "https://{}".format(settings.DOMAIN)
|
||||||
self.did = didurl
|
path = reverse("idhub:serve_did", args=["a"])
|
||||||
self.didweb_document = document
|
|
||||||
|
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):
|
def get_key(self):
|
||||||
return json.loads(self.key_material)
|
return json.loads(self.key_material)
|
||||||
|
@ -681,15 +689,18 @@ class VerificableCredential(models.Model):
|
||||||
|
|
||||||
# hash of credential without sign
|
# hash of credential without sign
|
||||||
self.hash = hashlib.sha3_256(self.render(domain).encode()).hexdigest()
|
self.hash = hashlib.sha3_256(self.render(domain).encode()).hexdigest()
|
||||||
data = sign_credential(
|
|
||||||
self.render(domain),
|
key = self.issuer_did.get_key_material()
|
||||||
self.issuer_did.get_key_material()
|
credential = self.render(domain)
|
||||||
)
|
|
||||||
valid, reason = verify_credential(data)
|
vc = sign(credential, key, self.issuer_did.did)
|
||||||
|
vc_str = json.dumps(vc)
|
||||||
|
valid = verify_vc(vc_str)
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.data = self.user.encrypt_data(data)
|
self.data = self.user.encrypt_data(vc_str)
|
||||||
|
|
||||||
self.status = self.Status.ISSUED
|
self.status = self.Status.ISSUED
|
||||||
|
|
||||||
|
|
|
@ -1,71 +1,71 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/credentials/base/v1",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/credentials/course-credential/v1"
|
"https://idhub.pangea.org/context/course-credential.jsonld"
|
||||||
],
|
],
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
"VerifiableAttestation",
|
"VerifiableAttestation",
|
||||||
"CourseCredential"
|
"CourseCredential"
|
||||||
],
|
],
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"id": "{{ issuer_did }}",
|
"id": "{{ issuer_did }}",
|
||||||
"name": "{{ organisation }}"
|
"name": "{{ organisation }}"
|
||||||
|
},
|
||||||
|
"issuanceDate": "{{ issuance_date }}",
|
||||||
|
"validFrom": "{{ issuance_date }}",
|
||||||
|
"validUntil": "{{ validUntil }}",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "NGO Course Credential for participants",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"issuanceDate": "{{ issuance_date }}",
|
{
|
||||||
"validFrom": "{{ issuance_date }}",
|
"value": "Credencial per participants d'un curs impartit per una ONG",
|
||||||
"validUntil": "{{ validUntil }}",
|
"lang": "ca_ES"
|
||||||
"name": [
|
|
||||||
{
|
|
||||||
"value": "NGO Course Credential for participants",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial per participants d'un curs impartit per una ONG",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial para participantes de un curso impartido por una ONG",
|
|
||||||
"lang": "es"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": [
|
|
||||||
{
|
|
||||||
"value": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat",
|
|
||||||
"lang": "en"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"credentialSubject": {
|
|
||||||
"id": "{{ subject_did }}",
|
|
||||||
"firstName": "{{ firstName }}",
|
|
||||||
"lastName": "{{ lastName }}",
|
|
||||||
"email": "{{ email }}",
|
|
||||||
"personalIdentifier": "{{ personalIdentifier }}",
|
|
||||||
"issuedDate": "{{ issuedDate }}",
|
|
||||||
"modeOfInstruction": "{{ modeOfInstruction }}",
|
|
||||||
"courseDuration": "{{ courseDuration }}",
|
|
||||||
"courseDays": "{{ courseDays }}",
|
|
||||||
"courseName": "{{ courseName }}",
|
|
||||||
"courseDescription": "{{ courseDescription }}",
|
|
||||||
"gradingScheme": "{{ gradingScheme }}",
|
|
||||||
"scoreAwarded": "{{ scoreAwarded }}",
|
|
||||||
"qualificationAwarded": "{{ qualificationAwarded }}",
|
|
||||||
"courseLevel": "{{ courseLevel }}",
|
|
||||||
"courseFramework": "{{ courseFramework }}",
|
|
||||||
"courseCredits": "{{ courseCredits }}",
|
|
||||||
"dateOfAssessment": "{{ dateOfAssessment }}",
|
|
||||||
"evidenceAssessment": "{{ evidenceAssessment }}"
|
|
||||||
},
|
},
|
||||||
"credentialStatus": {
|
{
|
||||||
"id": "{{ credential_status_id}}",
|
"value": "Credencial para participantes de un curso impartido por una ONG",
|
||||||
"type": "RevocationBitmap2022",
|
"lang": "es"
|
||||||
"revocationBitmapIndex": "{{ id_credential }}"
|
|
||||||
},
|
|
||||||
"credentialSchema": {
|
|
||||||
"id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
|
|
||||||
"type": "FullJsonSchemaValidator2021"
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"value": "A NGO Course Credential Schema awarded by a NGO federation and their NGO members, as proposed by Lafede.cat",
|
||||||
|
"lang": "en"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "{{ subject_did }}",
|
||||||
|
"firstName": "{{ firstName }}",
|
||||||
|
"lastName": "{{ lastName }}",
|
||||||
|
"email": "{{ email }}",
|
||||||
|
"personalIdentifier": "{{ personalIdentifier }}",
|
||||||
|
"issuedDate": "{{ issuedDate }}",
|
||||||
|
"modeOfInstruction": "{{ modeOfInstruction }}",
|
||||||
|
"courseDuration": "{{ courseDuration }}",
|
||||||
|
"courseDays": "{{ courseDays }}",
|
||||||
|
"courseName": "{{ courseName }}",
|
||||||
|
"courseDescription": "{{ courseDescription }}",
|
||||||
|
"gradingScheme": "{{ gradingScheme }}",
|
||||||
|
"scoreAwarded": "{{ scoreAwarded }}",
|
||||||
|
"qualificationAwarded": "{{ qualificationAwarded }}",
|
||||||
|
"courseLevel": "{{ courseLevel }}",
|
||||||
|
"courseFramework": "{{ courseFramework }}",
|
||||||
|
"courseCredits": "{{ courseCredits }}",
|
||||||
|
"dateOfAssessment": "{{ dateOfAssessment }}",
|
||||||
|
"evidenceAssessment": "{{ evidenceAssessment }}"
|
||||||
|
},
|
||||||
|
"credentialStatus": {
|
||||||
|
"id": "{{ credential_status_id}}",
|
||||||
|
"type": "RevocationBitmap2022",
|
||||||
|
"revocationBitmapIndex": "{{ id_credential }}"
|
||||||
|
},
|
||||||
|
"credentialSchema": {
|
||||||
|
"id": "https://idhub.pangea.org/vc_schemas/course-credential.json",
|
||||||
|
"type": "FullJsonSchemaValidator2021"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,67 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/credentials/base/v1",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/credentials/e-operator-claim/v1"
|
"https://idhub.pangea.org/context/e-operator-claim.jsonld"
|
||||||
],
|
],
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
"VerifiableAttestation",
|
"VerifiableAttestation",
|
||||||
"EOperatorClaim"
|
"EOperatorClaim"
|
||||||
],
|
],
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"id": "{{ issuer_did }}",
|
"id": "{{ issuer_did }}",
|
||||||
"name": "{{ organisation }}"
|
"name": "{{ organisation }}"
|
||||||
|
},
|
||||||
|
"issuanceDate": "{{ issuance_date }}",
|
||||||
|
"validFrom": "{{ issuance_date }}",
|
||||||
|
"validUntil": "{{ validUntil }}",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "Product and waste electronics operator claim",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"issuanceDate": "{{ issuance_date }}",
|
{
|
||||||
"validFrom": "{{ issuance_date }}",
|
"value": "Declaració d'operador de productes i residus electrònics",
|
||||||
"validUntil": "{{ validUntil }}",
|
"lang": "ca_ES"
|
||||||
"name": [
|
|
||||||
{
|
|
||||||
"value": "Product and waste electronics operator claim",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Declaració d'operador de productes i residus electrònics",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Declaración de operador de productos y residuos electrónicos",
|
|
||||||
"lang": "es"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": [
|
|
||||||
{
|
|
||||||
"value": "Credential for e-product and e-waste operator claim",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial per operador de productes i residus electrònics",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial para operador de productos y residuos electrónicos",
|
|
||||||
"lang": "es"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"credentialSubject": {
|
|
||||||
"id": "{{ subject_did }}",
|
|
||||||
"legalName": "{{ legalName }}",
|
|
||||||
"accreditedBy": "{{ accreditedBy }}",
|
|
||||||
"operatorNumber": "{{ operatorNumber }}",
|
|
||||||
"limitJurisdiction": "{{ limitJurisdiction }}",
|
|
||||||
"accreditedFor": "{{ accreditedFor }}",
|
|
||||||
"role": "{{ role }}",
|
|
||||||
"email": "{{ email }}"
|
|
||||||
},
|
},
|
||||||
"credentialStatus": {
|
{
|
||||||
"id": "{{ credential_status_id}}",
|
"value": "Declaración de operador de productos y residuos electrónicos",
|
||||||
"type": "RevocationBitmap2022",
|
"lang": "es"
|
||||||
"revocationBitmapIndex": "{{ id_credential }}"
|
|
||||||
},
|
|
||||||
"credentialSchema": {
|
|
||||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
|
||||||
"type": "FullJsonSchemaValidator2021"
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"value": "Credential for e-product and e-waste operator claim",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial per operador de productes i residus electrònics",
|
||||||
|
"lang": "ca_ES"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial para operador de productos y residuos electrónicos",
|
||||||
|
"lang": "es"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "{{ subject_did }}",
|
||||||
|
"legalName": "{{ legalName }}",
|
||||||
|
"accreditedBy": "{{ accreditedBy }}",
|
||||||
|
"operatorNumber": "{{ operatorNumber }}",
|
||||||
|
"limitJurisdiction": "{{ limitJurisdiction }}",
|
||||||
|
"accreditedFor": "{{ accreditedFor }}",
|
||||||
|
"role": "{{ role }}",
|
||||||
|
"email": "{{ email }}"
|
||||||
|
},
|
||||||
|
"credentialStatus": {
|
||||||
|
"id": "{{ credential_status_id}}",
|
||||||
|
"type": "RevocationBitmap2022",
|
||||||
|
"revocationBitmapIndex": "{{ id_credential }}"
|
||||||
|
},
|
||||||
|
"credentialSchema": {
|
||||||
|
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||||
|
"type": "FullJsonSchemaValidator2021"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/credentials/base/v1",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/credentials/federation-membership/v1"
|
"https://idhub.pangea.org/context/federation-membership.jsonld"
|
||||||
],
|
],
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
"VerifiableAttestation",
|
"VerifiableAttestation",
|
||||||
"FederationMembership"
|
"FederationMembership"
|
||||||
],
|
],
|
||||||
"issuer": {
|
"issuer": {
|
||||||
"id": "{{ issuer_did }}",
|
"id": "{{ issuer_did }}",
|
||||||
"name": "{{ organisation }}"
|
"name": "{{ organisation }}"
|
||||||
|
},
|
||||||
|
"issuanceDate": "{{ issuance_date }}",
|
||||||
|
"validFrom": "{{ issuance_date }}",
|
||||||
|
"validUntil": "{{ validUntil }}",
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"value": "NGO federation membership attestation credential",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"issuanceDate": "{{ issuance_date }}",
|
{
|
||||||
"validFrom": "{{ issuance_date }}",
|
"value": "Credencial d'atestat de pertinença a federació d'ONG",
|
||||||
"validUntil": "{{ validUntil }}",
|
"lang": "ca_ES"
|
||||||
"name": [
|
|
||||||
{
|
|
||||||
"value": "NGO federation membership attestation credential",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial d'atestat de pertinença a federació d'ONG",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial de atestado de membresía de Federación de ONG",
|
|
||||||
"lang": "es"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": [
|
|
||||||
{
|
|
||||||
"value": "Credential for NGOs that are members of a NGO federation",
|
|
||||||
"lang": "en"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial para ONG que son miembros de una federación de ONG",
|
|
||||||
"lang": "es"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "Credencial per a les ONG que són membres d'una federació d'ONG",
|
|
||||||
"lang": "ca_ES"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"credentialSubject": {
|
|
||||||
"id": "{{ subject_did }}",
|
|
||||||
"federation": "{{ federation }}",
|
|
||||||
"legalName": "{{ legalName }}",
|
|
||||||
"shortName": "{{ shortName }}",
|
|
||||||
"registrationIdentifier": "{{ registrationIdentifier }}",
|
|
||||||
"publicRegistry": "{{ publicRegistry }}",
|
|
||||||
"streetAddress": "{{ streetAddress }}",
|
|
||||||
"postCode": "{{ postCode }}",
|
|
||||||
"city": "{{ city }}",
|
|
||||||
"taxReference": "{{ taxReference }}",
|
|
||||||
"membershipType": "{{ membershipType }}",
|
|
||||||
"membershipStatus": "{{ membershipStatus }}",
|
|
||||||
"membershipId": "{{ membershipId }}",
|
|
||||||
"membershipSince": "{{ membershipSince }}",
|
|
||||||
"email": "{{ email }}",
|
|
||||||
"phone": "{{ phone }}",
|
|
||||||
"website": "{{ website }}",
|
|
||||||
"evidence": "{{ evidence }}",
|
|
||||||
"certificationDate": "{{ certificationDate }}"
|
|
||||||
},
|
},
|
||||||
"credentialStatus": {
|
{
|
||||||
"id": "{{ credential_status_id}}",
|
"value": "Credencial de atestado de membresía de Federación de ONG",
|
||||||
"type": "RevocationBitmap2022",
|
"lang": "es"
|
||||||
"revocationBitmapIndex": "{{ id_credential }}"
|
|
||||||
},
|
|
||||||
"credentialSchema": {
|
|
||||||
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
|
||||||
"type": "FullJsonSchemaValidator2021"
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"value": "Credential for NGOs that are members of a NGO federation",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial para ONG que son miembros de una federación de ONG",
|
||||||
|
"lang": "es"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Credencial per a les ONG que són membres d'una federació d'ONG",
|
||||||
|
"lang": "ca_ES"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialSubject": {
|
||||||
|
"id": "{{ subject_did }}",
|
||||||
|
"federation": "{{ federation }}",
|
||||||
|
"legalName": "{{ legalName }}",
|
||||||
|
"shortName": "{{ shortName }}",
|
||||||
|
"registrationIdentifier": "{{ registrationIdentifier }}",
|
||||||
|
"publicRegistry": "{{ publicRegistry }}",
|
||||||
|
"streetAddress": "{{ streetAddress }}",
|
||||||
|
"postCode": "{{ postCode }}",
|
||||||
|
"city": "{{ city }}",
|
||||||
|
"taxReference": "{{ taxReference }}",
|
||||||
|
"membershipType": "{{ membershipType }}",
|
||||||
|
"membershipStatus": "{{ membershipStatus }}",
|
||||||
|
"membershipId": "{{ membershipId }}",
|
||||||
|
"membershipSince": "{{ membershipSince }}",
|
||||||
|
"email": "{{ email }}",
|
||||||
|
"phone": "{{ phone }}",
|
||||||
|
"website": "{{ website }}",
|
||||||
|
"evidence": "{{ evidence }}",
|
||||||
|
"certificationDate": "{{ certificationDate }}"
|
||||||
|
},
|
||||||
|
"credentialStatus": {
|
||||||
|
"id": "{{ credential_status_id}}",
|
||||||
|
"type": "RevocationBitmap2022",
|
||||||
|
"revocationBitmapIndex": "{{ id_credential }}"
|
||||||
|
},
|
||||||
|
"credentialSchema": {
|
||||||
|
"id": "https://idhub.pangea.org/vc_schemas/federation-membership.json",
|
||||||
|
"type": "FullJsonSchemaValidator2021"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/credentials/base/v1",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/credentials/financial-vulnerability/v1"
|
"https://idhub.pangea.org/context/financial-vulnerability.jsonld"
|
||||||
],
|
],
|
||||||
"id": "{{ vc_id }}",
|
"id": "{{ vc_id }}",
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1",
|
"https://www.w3.org/2018/credentials/v1",
|
||||||
"https://idhub.pangea.org/credentials/base/v1",
|
"https://idhub.pangea.org/context/base.jsonld",
|
||||||
"https://idhub.pangea.org/credentials/membership-card/v1"
|
"https://idhub.pangea.org/context/membership-card.jsonld"
|
||||||
],
|
],
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiableCredential",
|
"VerifiableCredential",
|
||||||
|
|
|
@ -21,7 +21,7 @@ from .views import (
|
||||||
LoginView,
|
LoginView,
|
||||||
PasswordResetView,
|
PasswordResetView,
|
||||||
PasswordResetConfirmView,
|
PasswordResetConfirmView,
|
||||||
serve_did,
|
ServeDidView,
|
||||||
DobleFactorSendView,
|
DobleFactorSendView,
|
||||||
)
|
)
|
||||||
from .admin import views as views_admin
|
from .admin import views as views_admin
|
||||||
|
@ -183,7 +183,7 @@ urlpatterns = [
|
||||||
name='admin_2fauth'),
|
name='admin_2fauth'),
|
||||||
path('admin/auth/2f/', DobleFactorSendView.as_view(), name='confirm_send_2f'),
|
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,
|
# path('verification_portal/verify/', views_verification_portal.verify,
|
||||||
# name="verification_portal_verify")
|
# name="verification_portal_verify")
|
||||||
|
|
|
@ -90,7 +90,7 @@ class PasswordResetView(auth_views.PasswordResetView):
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
def serve_did(request, did_id):
|
def ServeDidView(request, did_id):
|
||||||
domain = settings.DOMAIN
|
domain = settings.DOMAIN
|
||||||
id_did = f'did:web:{domain}:did-registry:{did_id}'
|
id_did = f'did:web:{domain}:did-registry:{did_id}'
|
||||||
did = get_object_or_404(DID, did=id_did)
|
did = get_object_or_404(DID, did=id_did)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from utils.idhub_ssikit import create_verifiable_presentation
|
from pyvckit.sign import sign
|
||||||
from idhub.models import VerificableCredential
|
from idhub.models import VerificableCredential
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,13 +73,19 @@ class AuthorizeForm(forms.Form):
|
||||||
|
|
||||||
def get_verificable_presentation(self):
|
def get_verificable_presentation(self):
|
||||||
did = self.subject_did
|
did = self.subject_did
|
||||||
|
vc_list = [json.loads(x) for x in self.list_credentials]
|
||||||
vp_template = get_template('credentials/verifiable_presentation.json')
|
vp_template = get_template('credentials/verifiable_presentation.json')
|
||||||
vc_list = json.dumps([json.loads(x) for x in self.list_credentials])
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"holder_did": did.did,
|
"holder_did": did.did,
|
||||||
"verifiable_credential_list": vc_list
|
"id": str(uuid.uuid4())
|
||||||
}
|
}
|
||||||
unsigned_vp = vp_template.render(context)
|
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()
|
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 django.utils.translation import gettext_lazy as _
|
||||||
from idhub_auth.models import User
|
from idhub_auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from utils.idhub_ssikit import verify_presentation
|
from pyvckit.verify import verify_vp
|
||||||
|
|
||||||
|
|
||||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
@ -271,7 +271,7 @@ class OAuth2VPToken(models.Model):
|
||||||
return self.authorization.code
|
return self.authorization.code
|
||||||
|
|
||||||
def verifing(self):
|
def verifing(self):
|
||||||
self.result_verify = verify_presentation(self.vp_token)
|
self.result_verify = verify_vp(self.vp_token)
|
||||||
|
|
||||||
def get_result_verify(self):
|
def get_result_verify(self):
|
||||||
if not self.result_verify:
|
if not self.result_verify:
|
||||||
|
@ -284,8 +284,7 @@ class OAuth2VPToken(models.Model):
|
||||||
"redirect_uri": "",
|
"redirect_uri": "",
|
||||||
"response": "",
|
"response": "",
|
||||||
}
|
}
|
||||||
verification = json.loads(self.result_verify)
|
if not self.result_verify:
|
||||||
if verification.get('errors') or verification.get('warnings'):
|
|
||||||
response["verify"] = "Error, {}".format(_("Failed verification"))
|
response["verify"] = "Error, {}".format(_("Failed verification"))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/2018/credentials/v1"
|
"https://www.w3.org/2018/credentials/v1"
|
||||||
],
|
],
|
||||||
"id": "http://example.org/presentations/3731",
|
"id": "{{ id }}",
|
||||||
"type": [
|
"type": [
|
||||||
"VerifiablePresentation"
|
"VerifiablePresentation"
|
||||||
],
|
],
|
||||||
"holder": "{{ holder_did }}",
|
"holder": "{{ holder_did }}",
|
||||||
"verifiableCredential": {{ verifiable_credential_list|safe }}
|
"verifiableCredential": ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,11 +174,7 @@ class VerifyView(View):
|
||||||
"""
|
"""
|
||||||
Send a email when a user is activated.
|
Send a email when a user is activated.
|
||||||
"""
|
"""
|
||||||
verification = self.vp_token.get_result_verify()
|
if not self.vp_token.result_verify:
|
||||||
if not verification:
|
|
||||||
return
|
|
||||||
|
|
||||||
if verification.get('errors') or verification.get('warnings'):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
email = self.get_email(user)
|
email = self.get_email(user)
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
|
|
||||||
import json
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
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 oidc4vp.models import Organization, Authorization
|
||||||
from promotion.models import Promotion
|
from promotion.models import Promotion
|
||||||
|
|
||||||
|
@ -66,4 +58,3 @@ class ContractForm(forms.Form):
|
||||||
birthday = forms.CharField()
|
birthday = forms.CharField()
|
||||||
gen = forms.CharField()
|
gen = forms.CharField()
|
||||||
lang = forms.CharField()
|
lang = forms.CharField()
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ weasyprint==60.2
|
||||||
ujson==5.9.0
|
ujson==5.9.0
|
||||||
openpyxl==3.1.2
|
openpyxl==3.1.2
|
||||||
jsonpath_ng==1.6.1
|
jsonpath_ng==1.6.1
|
||||||
./didkit-0.3.2-cp311-cp311-manylinux_2_34_x86_64.whl
|
|
||||||
pyroaring==0.4.5
|
pyroaring==0.4.5
|
||||||
coverage==7.4.3
|
coverage==7.4.3
|
||||||
gunicorn==21.2.0
|
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 a new issue