From ddcd748b000164e9b1f9d333025299ede397e25d Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 09:27:18 +0100 Subject: [PATCH 1/8] simplify initial_datas --- idhub/management/commands/initial_datas.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index 40cfa8b..8205918 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -22,8 +22,6 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): ADMIN_EMAIL = config('ADMIN_EMAIL', 'admin@example.org') ADMIN_PASSWORD = config('ADMIN_PASSWORD', '1234') - KEY_DIDS = config('KEY_DIDS') - cache.set("KEY_DIDS", KEY_DIDS, None) self.create_admin_users(ADMIN_EMAIL, ADMIN_PASSWORD) if settings.CREATE_TEST_USERS: @@ -37,6 +35,7 @@ class Command(BaseCommand): f = csv.reader(csvfile, delimiter=';', quotechar='"') for r in f: self.create_organizations(r[0].strip(), r[1].strip()) + if settings.SYNC_ORG_DEV == 'y': self.sync_credentials_organizations("pangea.org", "somconnexio.coop") self.sync_credentials_organizations("local 8000", "local 9000") @@ -44,17 +43,13 @@ class Command(BaseCommand): def create_admin_users(self, email, password): su = User.objects.create_superuser(email=email, password=password) - su.set_encrypted_sensitive_data() su.save() - self.create_defaults_dids(su) def create_users(self, email, password): u = User.objects.create(email=email, password=password) u.set_password(password) - u.set_encrypted_sensitive_data() u.save() - self.create_defaults_dids(u) def create_organizations(self, name, url): @@ -70,11 +65,6 @@ class Command(BaseCommand): org1.save() org2.save() - def create_defaults_dids(self, u): - did = DID(label="Default", user=u, type=DID.Types.WEB) - did.set_did() - did.save() - def create_schemas(self): schemas_files = os.listdir(settings.SCHEMAS_DIR) for x in schemas_files: From 22bca789bc1a1230611a4328584c69d6ffd47028 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 10:57:47 +0100 Subject: [PATCH 2/8] confirm than your organization is created --- idhub/management/commands/initial_datas.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index 8205918..363d7fb 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -7,9 +7,8 @@ from utils import credtools from django.conf import settings from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model -from django.core.cache import cache from decouple import config -from idhub.models import DID, Schemas +from idhub.models import Schemas from oidc4vp.models import Organization @@ -36,6 +35,9 @@ class Command(BaseCommand): for r in f: self.create_organizations(r[0].strip(), r[1].strip()) + # You need to confirm than your Organization is created + assert Organization.objects.filter(name=settings.ORGANIZATION).exists() + if settings.SYNC_ORG_DEV == 'y': self.sync_credentials_organizations("pangea.org", "somconnexio.coop") self.sync_credentials_organizations("local 8000", "local 9000") From 365c58d87ad47df2c4e0b442576bf1f14741c190 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 10:58:14 +0100 Subject: [PATCH 3/8] add secrets and encrypt system to organization --- oidc4vp/models.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/oidc4vp/models.py b/oidc4vp/models.py index 9a4a4d4..0664532 100644 --- a/oidc4vp/models.py +++ b/oidc4vp/models.py @@ -1,6 +1,11 @@ import json import requests import secrets +import nacl +import base64 + +from nacl import pwhash, secret +from django.core.cache import cache from django.conf import settings from django.http import QueryDict @@ -64,6 +69,8 @@ class Organization(models.Model): help_text=_("Url where to send the verificable presentation"), max_length=250 ) + encrypted_sensitive_data = models.CharField(max_length=255) + salt = models.CharField(max_length=255) def send(self, vp, code): """ @@ -90,6 +97,65 @@ class Organization(models.Model): auth = (self.my_client_id, self.my_client_secret) return requests.get(url, auth=auth) + def derive_key_from_password(self, password=None): + if not password: + password = cache.get("KEY_DIDS").encode('utf-8') + + kdf = pwhash.argon2i.kdf + ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE + mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE + return kdf( + secret.SecretBox.KEY_SIZE, + password, + self.get_salt(), + opslimit=ops, + memlimit=mem + ) + + def decrypt_sensitive_data(self, data=None): + sb_key = self.derive_key_from_password() + sb = secret.SecretBox(sb_key) + if not data: + data = self.get_encrypted_sensitive_data() + if not isinstance(data, bytes): + data = data.encode('utf-8') + + return sb.decrypt(data).decode('utf-8') + + def encrypt_sensitive_data(self, data): + sb_key = self.derive_key_from_password() + sb = secret.SecretBox(sb_key) + if not isinstance(data, bytes): + data = data.encode('utf-8') + + return base64.b64encode(sb.encrypt(data)).decode('utf-8') + + def get_salt(self): + return base64.b64decode(self.salt.encode('utf-8')) + + def set_salt(self): + self.salt = base64.b64encode(nacl.utils.random(16)).decode('utf-8') + + def get_encrypted_sensitive_data(self): + return base64.b64decode(self.encrypted_sensitive_data.encode('utf-8')) + + def set_encrypted_sensitive_data(self): + key = base64.b64encode(nacl.utils.random(64)) + self.set_salt() + + key_crypted = self.encrypt_sensitive_data(key) + self.encrypted_sensitive_data = key_crypted + + def change_password_key(self, new_password): + data = self.decrypt_sensitive_data() + sb_key = self.derive_key_from_password(new_password) + sb = secret.SecretBox(sb_key) + if not isinstance(data, bytes): + data = data.encode('utf-8') + + encrypted_data = base64.b64encode(sb.encrypt(data)).decode('utf-8') + self.encrypted_sensitive_data = encrypted_data + def __str__(self): return self.name From a290b2e45c1fabaa712d83dad7fef23ba1fd61c6 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 16:50:31 +0100 Subject: [PATCH 4/8] registar dids as organization --- idhub/admin/views.py | 1 - idhub/models.py | 22 ++++++++++++++++------ idhub_auth/models.py | 10 ++++++---- oidc4vp/models.py | 22 ++++++++++++++++++++-- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/idhub/admin/views.py b/idhub/admin/views.py index b5216a4..ad907a6 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -781,7 +781,6 @@ class DidRegisterView(Credentials, CreateView): object = None def form_valid(self, form): - form.instance.user = self.request.user form.instance.set_did() form.save() messages.success(self.request, _('DID created successfully')) diff --git a/idhub/models.py b/idhub/models.py index 2f563bf..8db241c 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -16,6 +16,7 @@ from utils.idhub_ssikit import ( webdid_from_controller_key, verify_credential, ) +from oidc4vp.models import Organization from idhub_auth.models import User @@ -442,18 +443,24 @@ class DID(models.Model): # JSON-serialized DID document didweb_document = models.TextField() - def get_key_material(self): - return self.user.decrypt_data(self.key_material) - - def set_key_material(self, value): - self.key_material = self.user.encrypt_data(value) - @property def is_organization_did(self): if not self.user: return True return False + def get_key_material(self): + user = self.user or self.get_organization() + return user.decrypt_data(self.key_material) + + def set_key_material(self, value): + # import pdb; pdb.set_trace() + user = self.user or self.get_organization() + if not user.encrypted_sensitive_data: + user.set_encrypted_sensitive_data() + user.save() + self.key_material = user.encrypt_data(value) + def set_did(self): new_key_material = generate_did_controller_key() self.set_key_material(new_key_material) @@ -468,6 +475,9 @@ class DID(models.Model): def get_key(self): return json.loads(self.key_material) + def get_organization(self): + return Organization.objects.get(name=settings.ORGANIZATION) + class Schemas(models.Model): type = models.CharField(max_length=250) file_schema = models.CharField(max_length=250) diff --git a/idhub_auth/models.py b/idhub_auth/models.py index 49a6281..c75c7e3 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -145,17 +145,19 @@ class User(AbstractBaseUser): self.encrypted_sensitive_data = key_crypted def encrypt_data(self, data): - sb = self.get_secret_box() + pw = self.decrypt_sensitive_data() + sb = self.get_secret_box(pw) value_enc = sb.encrypt(data.encode('utf-8')) return base64.b64encode(value_enc).decode('utf-8') def decrypt_data(self, data): - sb = self.get_secret_box() + pw = self.decrypt_sensitive_data() + sb = self.get_secret_box(pw) value = base64.b64decode(data.encode('utf-8')) return sb.decrypt(value).decode('utf-8') - def get_secret_box(self): - sb_key = self.derive_key_from_password() + def get_secret_box(self, password): + sb_key = self.derive_key_from_password(password) return secret.SecretBox(sb_key) def change_password_key(self, new_password): diff --git a/oidc4vp/models.py b/oidc4vp/models.py index 0664532..b0d8b26 100644 --- a/oidc4vp/models.py +++ b/oidc4vp/models.py @@ -69,8 +69,8 @@ class Organization(models.Model): help_text=_("Url where to send the verificable presentation"), max_length=250 ) - encrypted_sensitive_data = models.CharField(max_length=255) - salt = models.CharField(max_length=255) + encrypted_sensitive_data = models.CharField(max_length=255, default=None, null=True) + salt = models.CharField(max_length=255, default=None, null=True) def send(self, vp, code): """ @@ -131,6 +131,8 @@ class Organization(models.Model): return base64.b64encode(sb.encrypt(data)).decode('utf-8') def get_salt(self): + if not self.salt: + return '' return base64.b64decode(self.salt.encode('utf-8')) def set_salt(self): @@ -146,6 +148,22 @@ class Organization(models.Model): key_crypted = self.encrypt_sensitive_data(key) self.encrypted_sensitive_data = key_crypted + def encrypt_data(self, data): + pw = self.decrypt_sensitive_data() + sb = self.get_secret_box(pw) + value_enc = sb.encrypt(data.encode('utf-8')) + return base64.b64encode(value_enc).decode('utf-8') + + def decrypt_data(self, data): + pw = self.decrypt_sensitive_data() + sb = self.get_secret_box(pw) + value = base64.b64decode(data.encode('utf-8')) + return sb.decrypt(value).decode('utf-8') + + def get_secret_box(self, password): + sb_key = self.derive_key_from_password(password) + return secret.SecretBox(sb_key) + def change_password_key(self, new_password): data = self.decrypt_sensitive_data() sb_key = self.derive_key_from_password(new_password) From 8191b1aaee34444cab6879a38bf180ad994d15a0 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 19:17:14 +0100 Subject: [PATCH 5/8] add new encryption fields in organization --- ...ation_encrypted_sensitive_data_and_more.py | 22 +++++++++++++++++++ oidc4vp/models.py | 8 +++---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 oidc4vp/migrations/0002_organization_encrypted_sensitive_data_and_more.py diff --git a/oidc4vp/migrations/0002_organization_encrypted_sensitive_data_and_more.py b/oidc4vp/migrations/0002_organization_encrypted_sensitive_data_and_more.py new file mode 100644 index 0000000..5108fc3 --- /dev/null +++ b/oidc4vp/migrations/0002_organization_encrypted_sensitive_data_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.5 on 2024-02-23 13:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('oidc4vp', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='encrypted_sensitive_data', + field=models.CharField(default=None, max_length=255, null=True), + ), + migrations.AddField( + model_name='organization', + name='salt', + field=models.CharField(default=None, max_length=255, null=True), + ), + ] diff --git a/oidc4vp/models.py b/oidc4vp/models.py index b0d8b26..b616c11 100644 --- a/oidc4vp/models.py +++ b/oidc4vp/models.py @@ -149,24 +149,24 @@ class Organization(models.Model): self.encrypted_sensitive_data = key_crypted def encrypt_data(self, data): - pw = self.decrypt_sensitive_data() + pw = self.decrypt_sensitive_data().encode('utf-8') sb = self.get_secret_box(pw) value_enc = sb.encrypt(data.encode('utf-8')) return base64.b64encode(value_enc).decode('utf-8') def decrypt_data(self, data): - pw = self.decrypt_sensitive_data() + pw = self.decrypt_sensitive_data().encode('utf-8') sb = self.get_secret_box(pw) value = base64.b64decode(data.encode('utf-8')) return sb.decrypt(value).decode('utf-8') def get_secret_box(self, password): - sb_key = self.derive_key_from_password(password) + sb_key = self.derive_key_from_password(password=password) return secret.SecretBox(sb_key) def change_password_key(self, new_password): data = self.decrypt_sensitive_data() - sb_key = self.derive_key_from_password(new_password) + sb_key = self.derive_key_from_password(password=new_password) sb = secret.SecretBox(sb_key) if not isinstance(data, bytes): data = data.encode('utf-8') From 38c9f74b7b526bc5596eb2d87f78fce975a2cf71 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 19:17:56 +0100 Subject: [PATCH 6/8] add organizations dids without user --- idhub/admin/forms.py | 11 +++++++---- idhub/admin/views.py | 7 +------ idhub/models.py | 5 ++--- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/idhub/admin/forms.py b/idhub/admin/forms.py index 29ce073..f9ceecc 100644 --- a/idhub/admin/forms.py +++ b/idhub/admin/forms.py @@ -75,6 +75,10 @@ class EncryptionKeyForm(forms.Form): if commit: cache.set("KEY_DIDS", self._key, None) + if not DID.objects.exists(): + did = DID.objects.create(label='Default', type=DID.Types.WEB) + did.set_did() + did.save() return @@ -155,9 +159,8 @@ class ImportForm(forms.Form): self.rows = {} self.properties = {} self.users = [] - self.user = kwargs.pop('user', None) super().__init__(*args, **kwargs) - dids = DID.objects.filter(user=self.user) + dids = DID.objects.filter(user__isnull=True) self.fields['did'].choices = [ (x.did, x.label) for x in dids.filter(eidas1=False) ] @@ -176,7 +179,7 @@ class ImportForm(forms.Form): def clean(self): data = self.cleaned_data["did"] did = DID.objects.filter( - user=self.user, + user__isnull=True, did=data ) @@ -188,7 +191,7 @@ class ImportForm(forms.Form): eidas1 = self.cleaned_data.get('eidas1') if eidas1: self._eidas1 = DID.objects.filter( - user=self.user, + user__isnull=True, eidas1=True, did=eidas1 ).first() diff --git a/idhub/admin/views.py b/idhub/admin/views.py index ad907a6..1c5aaa4 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -759,7 +759,7 @@ class DidsView(Credentials, SingleTableView): def get_context_data(self, **kwargs): queryset = kwargs.pop('object_list', None) - dids = DID.objects.filter(user=self.request.user) + dids = DID.objects.filter(user__isnull=True) if queryset is None: self.object_list = dids.all() @@ -1062,11 +1062,6 @@ class ImportAddView(NotifyActivateUserByEmail, ImportExport, FormView): form_class = ImportForm success_url = reverse_lazy('idhub:admin_import') - def get_form_kwargs(self): - kwargs = super().get_form_kwargs() - kwargs['user'] = self.request.user - return kwargs - def form_valid(self, form): creds = form.save() if creds: diff --git a/idhub/models.py b/idhub/models.py index 8db241c..6b9d0e4 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -420,8 +420,8 @@ class Event(models.Model): class DID(models.Model): class Types(models.IntegerChoices): - KEY = 1, "Key" - WEB = 2, "Web" + WEB = 1, "Web" + KEY = 2, "Key" type = models.PositiveSmallIntegerField( _("Type"), choices=Types.choices, @@ -454,7 +454,6 @@ class DID(models.Model): return user.decrypt_data(self.key_material) def set_key_material(self, value): - # import pdb; pdb.set_trace() user = self.user or self.get_organization() if not user.encrypted_sensitive_data: user.set_encrypted_sensitive_data() From 7be032bdc9ccb9e4edf267931e99209225769b59 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 23 Feb 2024 19:18:44 +0100 Subject: [PATCH 7/8] fix encoding passwords --- idhub_auth/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/idhub_auth/models.py b/idhub_auth/models.py index c75c7e3..f50e59e 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -145,13 +145,13 @@ class User(AbstractBaseUser): self.encrypted_sensitive_data = key_crypted def encrypt_data(self, data): - pw = self.decrypt_sensitive_data() + pw = self.decrypt_sensitive_data().encode('utf-8') sb = self.get_secret_box(pw) value_enc = sb.encrypt(data.encode('utf-8')) return base64.b64encode(value_enc).decode('utf-8') def decrypt_data(self, data): - pw = self.decrypt_sensitive_data() + pw = self.decrypt_sensitive_data().encode('utf-8') sb = self.get_secret_box(pw) value = base64.b64decode(data.encode('utf-8')) return sb.decrypt(value).decode('utf-8') From 3259c93bb7513e6121ed0dec03400ce57207e7d3 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 26 Feb 2024 11:03:07 +0100 Subject: [PATCH 8/8] add is admin to manual creation user --- idhub/templates/idhub/admin/user.html | 9 +++++++++ idhub_auth/forms.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/idhub/templates/idhub/admin/user.html b/idhub/templates/idhub/admin/user.html index fbb04d1..65be22d 100644 --- a/idhub/templates/idhub/admin/user.html +++ b/idhub/templates/idhub/admin/user.html @@ -43,6 +43,15 @@ {{ object.email }} + {% if object.is_admin %} +
+
+ {% trans "Is Admin" %} +
+
+
+
+ {% endif %} diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py index d9ff2f7..a608cd1 100644 --- a/idhub_auth/forms.py +++ b/idhub_auth/forms.py @@ -11,7 +11,7 @@ class ProfileForm(forms.ModelForm): class Meta: model = User - fields = ['first_name', 'last_name', 'email'] + fields = ['first_name', 'last_name', 'email', 'is_admin'] def clean_first_name(self): first_name = super().clean()['first_name']