sig credential in pdf

This commit is contained in:
Cayo Puigdefabregas 2024-01-10 13:53:43 +01:00
parent 0ec40f66eb
commit c836112f36
10 changed files with 228 additions and 31 deletions

View file

@ -1,11 +1,14 @@
import csv import csv
import json import json
import base64
import pandas as pd import pandas as pd
from pyhanko.sign import signers
from django import forms from django import forms
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 import credtools from utils import credtools, certs
from idhub.models import ( from idhub.models import (
DID, DID,
File_datas, File_datas,
@ -216,3 +219,67 @@ class UserRolForm(forms.ModelForm):
raise forms.ValidationError(msg) raise forms.ValidationError(msg)
return data['service'] return data['service']
class ImportCertificateForm(forms.Form):
label = forms.CharField(label=_("Label"))
password = forms.CharField(
label=_("Password of certificate"),
widget=forms.PasswordInput
)
file_import = forms.FileField(label=_("File import"))
def __init__(self, *args, **kwargs):
self._did = None
self._s = None
self._label = None
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
def clean(self):
data = super().clean()
# import pdb; pdb.set_trace()
file_import = data.get('file_import')
self.pfx_file = file_import.read()
self.file_name = file_import.name
self._pss = data.get('password')
self._label = data.get('label')
if not self.pfx_file or not self._pss:
msg = _("Is not a valid certificate")
raise forms.ValidationError(msg)
self.signer_init()
if not self._s:
msg = _("Is not a valid certificate")
raise forms.ValidationError(msg)
self.new_did()
return data
def new_did(self):
cert = self.pfx_file
keys = {
"cert": base64.b64encode(self.pfx_file).decode('utf-8'),
"passphrase": self._pss
}
key_material = json.dumps(keys)
self._did = DID(
key_material=key_material,
did=self.file_name,
label=self._label,
eidas1=True,
user=self.user
)
def save(self, commit=True):
if commit:
self._did.save()
return self._did
return
def signer_init(self):
self._s = certs.load_cert(
self.pfx_file, self._pss.encode('utf-8')
)

View file

@ -30,6 +30,7 @@ from idhub.admin.forms import (
MembershipForm, MembershipForm,
SchemaForm, SchemaForm,
UserRolForm, UserRolForm,
ImportCertificateForm,
) )
from idhub.admin.tables import ( from idhub.admin.tables import (
DashboardTable DashboardTable
@ -695,11 +696,27 @@ class WalletCredentialsView(Credentials):
wallet = True wallet = True
class WalletConfigIssuesView(Credentials): class WalletConfigIssuesView(Credentials, FormView):
template_name = "idhub/admin/wallet_issues.html" template_name = "idhub/admin/wallet_issues.html"
subtitle = _('Configure credential issuance') subtitle = _('Configure credential issuance')
icon = 'bi bi-patch-check-fill' icon = 'bi bi-patch-check-fill'
wallet = True wallet = True
form_class = ImportCertificateForm
success_url = reverse_lazy('idhub:admin_dids')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
cred = form.save()
if cred:
messages.success(self.request, _("The credential was imported successfully!"))
Event.set_EV_ORG_DID_CREATED_BY_ADMIN(cred)
else:
messages.error(self.request, _("Error importing the credential!"))
return super().form_valid(form)
class SchemasView(SchemasMix): class SchemasView(SchemasMix):

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-12-11 08:35 # Generated by Django 4.2.5 on 2024-01-10 11:52
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -26,9 +26,10 @@ class Migration(migrations.Migration):
), ),
), ),
('created_at', models.DateTimeField(auto_now=True)), ('created_at', models.DateTimeField(auto_now=True)),
('label', models.CharField(max_length=50)), ('label', models.CharField(max_length=50, verbose_name='Label')),
('did', models.CharField(max_length=250)), ('did', models.CharField(max_length=250)),
('key_material', models.CharField(max_length=250)), ('key_material', models.TextField()),
('eidas1', models.BooleanField(default=False)),
( (
'user', 'user',
models.ForeignKey( models.ForeignKey(
@ -256,8 +257,11 @@ class Migration(migrations.Migration):
verbose_name='ID', verbose_name='ID',
), ),
), ),
('created', models.DateTimeField(auto_now=True)), ('created', models.DateTimeField(auto_now=True, verbose_name='Date')),
('message', models.CharField(max_length=350)), (
'message',
models.CharField(max_length=350, verbose_name='Description'),
),
( (
'type', 'type',
models.PositiveSmallIntegerField( models.PositiveSmallIntegerField(
@ -295,7 +299,8 @@ class Migration(migrations.Migration):
(28, 'Organisational DID deleted by admin'), (28, 'Organisational DID deleted by admin'),
(29, 'User deactivated'), (29, 'User deactivated'),
(30, 'User activated'), (30, 'User activated'),
] ],
verbose_name='Event',
), ),
), ),
( (
@ -327,6 +332,7 @@ class Migration(migrations.Migration):
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name='users', related_name='users',
to='idhub.service', to='idhub.service',
verbose_name='Service',
), ),
), ),
( (

View file

@ -409,7 +409,8 @@ class DID(models.Model):
# In JWK format. Must be stored as-is and passed whole to library functions. # In JWK format. Must be stored as-is and passed whole to library functions.
# Example key material: # Example key material:
# '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}' # '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}'
key_material = models.CharField(max_length=250) key_material = models.TextField()
eidas1 = models.BooleanField(default=False)
user = models.ForeignKey( user = models.ForeignKey(
User, User,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View file

@ -6,4 +6,27 @@
<i class="{{ icon }}"></i> <i class="{{ icon }}"></i>
{{ subtitle }} {{ subtitle }}
</h3> </h3>
{% load django_bootstrap5 %}
<form role="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
<button class="close" type="button" data-dismiss="alert" aria-label="Close">
<span class="mdi mdi-close" aria-hidden="true"></span>
</button>
{% for field, error in form.errors.items %}
{{ error }}
{% endfor %}
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'idhub:admin_import' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Upload' %}" />
</div>
</form>
{% endblock %} {% endblock %}

View file

@ -1,7 +1,9 @@
import os import os
import json
import base64 import base64
import qrcode import qrcode
import logging import logging
import datetime
import weasyprint import weasyprint
import qrcode.image.svg import qrcode.image.svg
@ -28,6 +30,7 @@ from idhub.user.forms import (
RequestCredentialForm, RequestCredentialForm,
DemandAuthorizationForm DemandAuthorizationForm
) )
from utils import certs
from idhub.mixins import UserView from idhub.mixins import UserView
from idhub.models import DID, VerificableCredential, Event from idhub.models import DID, VerificableCredential, Event
@ -130,17 +133,16 @@ class CredentialJsonView(MyWallet, TemplateView):
_pss = '123456' _pss = '123456'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# pk = kwargs['pk'] pk = kwargs['pk']
# self.object = get_object_or_404( self.user = self.request.user
# VerificableCredential, self.object = get_object_or_404(
# pk=pk, VerificableCredential,
# user=self.request.user pk=pk,
# ) user=self.request.user
# return self.render_to_response(context=self.get_context_data()) )
data = self.build_certificate() data = self.build_certificate()
doc = self.insert_signature(data) doc = self.insert_signature(data)
import pdb; pdb.set_trace()
response = HttpResponse(doc, content_type="application/pdf") response = HttpResponse(doc, content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename={}'.format(self.file_name) response['Content-Disposition'] = 'attachment; filename={}'.format(self.file_name)
return response return response
@ -159,10 +161,31 @@ class CredentialJsonView(MyWallet, TemplateView):
img_head = base64.b64encode(_f.read()).decode('utf-8') img_head = base64.b64encode(_f.read()).decode('utf-8')
qr = self.generate_qr_code("http://localhost") qr = self.generate_qr_code("http://localhost")
if DID.objects.filter(eidas1=True).exists():
qr = ""
first_name = self.user.first_name and self.user.first_name.upper() or ""
last_name = self.user.first_name and self.user.last_name.upper() or ""
document_id = "0000000-L"
course = "COURSE 1"
address = "ADDRESS"
date_course = datetime.datetime.now()
n_hours = 40
n_lections = 5
issue_date = datetime.datetime.now()
context.update({ context.update({
# 'object': self.object, 'object': self.object,
"image_signature": img_sig, "image_signature": img_sig,
"image_header": img_head, "image_header": img_head,
"first_name": first_name,
"last_name": last_name,
"document_id": document_id,
"course": course,
"address": address,
"date_course": date_course,
"n_hours": n_hours,
"n_lections": n_lections,
"issue_date": issue_date,
"qr": qr "qr": qr
}) })
return context return context
@ -188,17 +211,31 @@ class CredentialJsonView(MyWallet, TemplateView):
return base64.b64encode(img_buffer.getvalue()).decode('utf-8') return base64.b64encode(img_buffer.getvalue()).decode('utf-8')
def get_pfx_data(self):
did = DID.objects.filter(eidas1=True).first()
if not did:
return None, None
key_material = json.loads(did.key_material)
cert = key_material.get("cert")
passphrase = key_material.get("passphrase")
if cert and passphrase:
return base64.b64decode(cert), passphrase.encode('utf-8')
return None, None
def signer_init(self): def signer_init(self):
fname = "examples/signerDNIe004.pfx" pfx_data, passphrase = self.get_pfx_data()
# pfx_buffer = BytesIO() s = certs.load_cert(
pfx_file= next(Path.cwd().glob(fname)) pfx_data, passphrase
s = signers.SimpleSigner.load_pkcs12(
pfx_file=pfx_file, passphrase=self._pss.encode('utf-8')
) )
return s return s
def insert_signature(self, doc): def insert_signature(self, doc):
# import pdb; pdb.set_trace()
sig = self.signer_init() sig = self.signer_init()
if not sig:
return
_buffer = BytesIO() _buffer = BytesIO()
_buffer.write(doc) _buffer.write(doc)
w = IncrementalPdfFileWriter(_buffer) w = IncrementalPdfFileWriter(_buffer)

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-12-11 08:35 # Generated by Django 4.2.5 on 2024-01-10 11:52
from django.db import migrations, models from django.db import migrations, models
@ -31,13 +31,23 @@ class Migration(migrations.Migration):
( (
'email', 'email',
models.EmailField( models.EmailField(
max_length=255, unique=True, verbose_name='email address' max_length=255, unique=True, verbose_name='Email address'
), ),
), ),
('is_active', models.BooleanField(default=True)), ('is_active', models.BooleanField(default=True)),
('is_admin', models.BooleanField(default=False)), ('is_admin', models.BooleanField(default=False)),
('first_name', models.CharField(blank=True, max_length=255, null=True)), (
('last_name', models.CharField(blank=True, max_length=255, null=True)), 'first_name',
models.CharField(
blank=True, max_length=255, null=True, verbose_name='First name'
),
),
(
'last_name',
models.CharField(
blank=True, max_length=255, null=True, verbose_name='Last name'
),
),
], ],
options={ options={
'abstract': False, 'abstract': False,

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-12-11 08:35 # Generated by Django 4.2.5 on 2024-01-10 11:52
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -30,7 +30,7 @@ class Migration(migrations.Migration):
'code', 'code',
models.CharField(default=oidc4vp.models.set_code, max_length=24), models.CharField(default=oidc4vp.models.set_code, max_length=24),
), ),
('code_used', models.BooleanField()), ('code_used', models.BooleanField(default=False)),
('created', models.DateTimeField(auto_now=True)), ('created', models.DateTimeField(auto_now=True)),
('presentation_definition', models.CharField(max_length=250)), ('presentation_definition', models.CharField(max_length=250)),
], ],
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
models.ForeignKey( models.ForeignKey(
null=True, null=True,
on_delete=django.db.models.deletion.SET_NULL, on_delete=django.db.models.deletion.SET_NULL,
related_name='oauth2vptoken', related_name='vp_tokens',
to='oidc4vp.authorization', to='oidc4vp.authorization',
), ),
), ),

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-12-11 08:35 # Generated by Django 4.2.5 on 2024-01-10 11:52
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion

36
utils/certs.py Normal file
View file

@ -0,0 +1,36 @@
from pyhanko.sign.signers import SimpleSigner
from cryptography.hazmat.primitives.serialization import pkcs12
from pyhanko_certvalidator.registry import SimpleCertificateStore
from pyhanko.keys import _translate_pyca_cryptography_cert_to_asn1
from pyhanko.keys import _translate_pyca_cryptography_key_to_asn1
def load_cert(pfx_bytes, passphrase):
try:
(
private_key,
cert,
other_certs_pkcs12,
) = pkcs12.load_key_and_certificates(pfx_bytes, passphrase)
except (IOError, ValueError, TypeError) as e:
# logger.error(
# 'Could not load key material from PKCS#12 file', exc_info=e
# )
return None
kinfo = _translate_pyca_cryptography_key_to_asn1(private_key)
cert = _translate_pyca_cryptography_cert_to_asn1(cert)
other_certs_pkcs12 = set(
map(_translate_pyca_cryptography_cert_to_asn1, other_certs_pkcs12)
)
cs = SimpleCertificateStore()
certs_to_register = set(other_certs_pkcs12)
cs.register_multiple(certs_to_register)
return SimpleSigner(
signing_key=kinfo,
signing_cert=cert,
cert_registry=cs,
signature_mechanism=None,
prefer_pss=False,
)