From 37dc8335a72d48e1f550f0f11f524875d61904c6 Mon Sep 17 00:00:00 2001 From: Daniel Armengod Date: Fri, 1 Dec 2023 06:39:26 +0100 Subject: [PATCH 01/14] Changed model definitions and added logic to decrypt and set user key in session storage --- idhub/models.py | 38 +++++++++++++++++++++++++++++++++++--- idhub/views.py | 4 ++++ idhub_auth/models.py | 18 ++++++++++++++++++ requirements.txt | 1 + 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/idhub/models.py b/idhub/models.py index 53f8186..fbf00ef 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -6,6 +6,8 @@ from django.db import models from django.conf import settings from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ +from nacl import secret + from utils.idhub_ssikit import ( generate_did_controller_key, keydid_from_controller_key, @@ -409,7 +411,9 @@ class DID(models.Model): # In JWK format. Must be stored as-is and passed whole to library functions. # Example key material: # '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}' - key_material = models.CharField(max_length=250) + # CHANGED: `key_material` to `_key_material`, datatype from CharField to BinaryField and the key is now stored encrypted. + key_material = None + _key_material = models.BinaryField(max_length=250) user = models.ForeignKey( User, on_delete=models.CASCADE, @@ -417,6 +421,18 @@ class DID(models.Model): null=True, ) + def get_key_material(self, session): + if "sensitive_data_encryption_key" not in session: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") + sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + return sb.decrypt(self._key_material) + + def set_key_material(self, value, session): + if "sensitive_data_encryption_key" not in session: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") + sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + self._key_material = sb.encrypt(value) + @property def is_organization_did(self): if not self.user: @@ -427,7 +443,8 @@ class DID(models.Model): self.key_material = generate_did_controller_key() self.did = keydid_from_controller_key(self.key_material) - def get_key(self): + # TODO: darmengo: esta funcion solo se llama desde un fichero que sube cosas a s3 (??) Preguntar a ver que hace. + def get_key_deprecated(self): return json.loads(self.key_material) @@ -464,7 +481,10 @@ class VerificableCredential(models.Model): created_on = models.DateTimeField(auto_now=True) issued_on = models.DateTimeField(null=True) subject_did = models.CharField(max_length=250) - data = models.TextField() + # CHANGED: `data` to `_data`, datatype from TextField to BinaryField and the rendered VC is now stored encrypted. + # TODO: verify that BinaryField can hold arbitrary amounts of data (max_length = ???) + data = None + _data = models.BinaryField() csv_data = models.TextField() status = models.PositiveSmallIntegerField( choices=Status.choices, @@ -486,6 +506,18 @@ class VerificableCredential(models.Model): related_name='vcredentials', ) + def get_data(self, session): + if "sensitive_data_encryption_key" not in session: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") + sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + return sb.decrypt(self._data) + + def set_data(self, value, session): + if "sensitive_data_encryption_key" not in session: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") + sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + self._data = sb.encrypt(value) + @property def get_schema(self): if not self.data: diff --git a/idhub/views.py b/idhub/views.py index 53db736..f8a62a7 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -25,4 +25,8 @@ class LoginView(auth_views.LoginView): if self.extra_context['success_url'] == user_dashboard: self.extra_context['success_url'] = admin_dashboard auth_login(self.request, user) + # Decrypt the user's sensitive data encryption key and store it in the session. + password = form.cleaned_data.get("password") # TODO: Is this right???????? + sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) + self.request.session["sensitive_data_encryption_key"] = sensitive_data_encryption_key return HttpResponseRedirect(self.extra_context['success_url']) diff --git a/idhub_auth/models.py b/idhub_auth/models.py index ccda94c..cf1ac69 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import BaseUserManager, AbstractBaseUser +from nacl import secret, pwhash class UserManager(BaseUserManager): @@ -43,6 +44,10 @@ class User(AbstractBaseUser): is_admin = models.BooleanField(default=False) first_name = models.CharField(max_length=255, blank=True, null=True) last_name = models.CharField(max_length=255, blank=True, null=True) + # TODO: Hay que generar una clave aleatoria para cada usuario cuando se le da de alta en el sistema. + encrypted_sensitive_data_encryption_key = models.BinaryField(max_length=255) + # TODO: Hay que generar un salt aleatorio para cada usuario cuando se le da de alta en el sistema. + salt_of_sensitive_data_encryption_key = models.BinaryField(max_length=255) objects = UserManager() @@ -85,3 +90,16 @@ class User(AbstractBaseUser): for r in s.service.rol.all(): roles.append(r.name) return ", ".join(set(roles)) + + def derive_key_from_password(self, password): + kdf = pwhash.argon2i.kdf # TODO: Move the KDF choice to SETTINGS.PY + ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY + mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY + salt = self.salt_of_sensitive_data_encryption_key + return kdf(secret.SecretBox.KEY_SIZE, password, salt, opslimit=ops, memlimit=mem) + + def decrypt_sensitive_data_encryption_key(self, password): + sb_key = self.derive_key_from_password(password) + sb = secret.SecretBox(sb_key) + return sb.decrypt(self.encrypted_sensitive_data_encryption_key) + diff --git a/requirements.txt b/requirements.txt index 140c49f..c09c9d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ didkit==0.3.2 jinja2==3.1.2 jsonref==1.1.0 pyld==2.0.3 +pynacl==1.5.0 From 20f40b43d049d325ccc33b97bef0da784ac7d380 Mon Sep 17 00:00:00 2001 From: Daniel Armengod Date: Fri, 1 Dec 2023 07:01:51 +0100 Subject: [PATCH 02/14] Refactored all uses of DID.key_material --- idhub/admin/views.py | 2 +- idhub/models.py | 17 ++++++++++++----- idhub/user/forms.py | 3 ++- idhub/user/views.py | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/idhub/admin/views.py b/idhub/admin/views.py index f8fd6d0..1725339 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -645,7 +645,7 @@ class DidRegisterView(Credentials, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did() + form.instance.set_did(self.request.session) form.save() messages.success(self.request, _('DID created successfully')) Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance) diff --git a/idhub/models.py b/idhub/models.py index fbf00ef..6a326b9 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -439,9 +439,16 @@ class DID(models.Model): return True return False - def set_did(self): - self.key_material = generate_did_controller_key() - self.did = keydid_from_controller_key(self.key_material) + def set_did(self, session): + """ + Generates a new DID Controller Key and derives a DID from it. + Because DID Controller Keys are stored encrypted using a User's Sensitive Data Encryption Key, + this function needs to be called in the context of a request. + """ + new_key_material = generate_did_controller_key() + self.did = keydid_from_controller_key(new_key_material) + self.set_key_material(new_key_material, session) + # TODO: darmengo: esta funcion solo se llama desde un fichero que sube cosas a s3 (??) Preguntar a ver que hace. def get_key_deprecated(self): @@ -546,7 +553,7 @@ class VerificableCredential(models.Model): data = json.loads(self.csv_data).items() return data - def issue(self, did): + def issue(self, did, session): if self.status == self.Status.ISSUED: return @@ -555,7 +562,7 @@ class VerificableCredential(models.Model): self.issued_on = datetime.datetime.now().astimezone(pytz.utc) self.data = sign_credential( self.render(), - self.issuer_did.key_material + self.issuer_did.get_key_material(session) ) def get_context(self): diff --git a/idhub/user/forms.py b/idhub/user/forms.py index 53a1149..3735d64 100644 --- a/idhub/user/forms.py +++ b/idhub/user/forms.py @@ -18,6 +18,7 @@ class RequestCredentialForm(forms.Form): def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) + self.session = kwargs.pop('session', None) super().__init__(*args, **kwargs) self.fields['did'].choices = [ (x.did, x.label) for x in DID.objects.filter(user=self.user) @@ -45,7 +46,7 @@ class RequestCredentialForm(forms.Form): did = did[0].did cred = cred[0] try: - cred.issue(did) + cred.issue(did, self.session) except Exception: return diff --git a/idhub/user/views.py b/idhub/user/views.py index 482b40e..d59f7d6 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -128,6 +128,7 @@ class CredentialsRequestView(MyWallet, FormView): def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user + kwargs['session'] = self.request.session return kwargs def form_valid(self, form): @@ -189,7 +190,7 @@ class DidRegisterView(MyWallet, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did() + form.instance.set_did(self.request.session) form.save() messages.success(self.request, _('DID created successfully')) From d2f7e5395d73d4365ea8f87668834d4f2561b941 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 3 Jan 2024 17:52:46 +0100 Subject: [PATCH 03/14] encription from a env key and password admin --- idhub/admin/views.py | 2 +- idhub/models.py | 40 ++++++++++++++++++------------------ idhub/user/forms.py | 3 +-- idhub/user/views.py | 3 +-- idhub/views.py | 20 ++++++++++++++---- trustchain_idhub/settings.py | 2 ++ 6 files changed, 41 insertions(+), 29 deletions(-) diff --git a/idhub/admin/views.py b/idhub/admin/views.py index 1725339..f8fd6d0 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -645,7 +645,7 @@ class DidRegisterView(Credentials, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did(self.request.session) + form.instance.set_did() form.save() messages.success(self.request, _('DID created successfully')) Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance) diff --git a/idhub/models.py b/idhub/models.py index 6a326b9..6e6e18d 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -421,16 +421,16 @@ class DID(models.Model): null=True, ) - def get_key_material(self, session): - if "sensitive_data_encryption_key" not in session: - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") - sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + def get_key_material(self): + if not settings.KEY_CREDENTIALS_CLEAN: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") + sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) return sb.decrypt(self._key_material) - def set_key_material(self, value, session): - if "sensitive_data_encryption_key" not in session: - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") - sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + def set_key_material(self, value): + if not settings.KEY_CREDENTIALS_CLEAN: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") + sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) self._key_material = sb.encrypt(value) @property @@ -439,7 +439,7 @@ class DID(models.Model): return True return False - def set_did(self, session): + def set_did(self): """ Generates a new DID Controller Key and derives a DID from it. Because DID Controller Keys are stored encrypted using a User's Sensitive Data Encryption Key, @@ -447,7 +447,7 @@ class DID(models.Model): """ new_key_material = generate_did_controller_key() self.did = keydid_from_controller_key(new_key_material) - self.set_key_material(new_key_material, session) + self.set_key_material(new_key_material) # TODO: darmengo: esta funcion solo se llama desde un fichero que sube cosas a s3 (??) Preguntar a ver que hace. @@ -513,16 +513,16 @@ class VerificableCredential(models.Model): related_name='vcredentials', ) - def get_data(self, session): - if "sensitive_data_encryption_key" not in session: - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") - sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + def get_data(self): + if not settings.KEY_CREDENTIALS_CLEAN: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") + sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) return sb.decrypt(self._data) - def set_data(self, value, session): - if "sensitive_data_encryption_key" not in session: - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave de usuario.") - sb = secret.SecretBox(session["sensitive_data_encryption_key"]) + def set_data(self, value): + if not settings.KEY_CREDENTIALS_CLEAN: + raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") + sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) self._data = sb.encrypt(value) @property @@ -553,7 +553,7 @@ class VerificableCredential(models.Model): data = json.loads(self.csv_data).items() return data - def issue(self, did, session): + def issue(self, did): if self.status == self.Status.ISSUED: return @@ -562,7 +562,7 @@ class VerificableCredential(models.Model): self.issued_on = datetime.datetime.now().astimezone(pytz.utc) self.data = sign_credential( self.render(), - self.issuer_did.get_key_material(session) + self.issuer_did.get_key_material() ) def get_context(self): diff --git a/idhub/user/forms.py b/idhub/user/forms.py index 3735d64..53a1149 100644 --- a/idhub/user/forms.py +++ b/idhub/user/forms.py @@ -18,7 +18,6 @@ class RequestCredentialForm(forms.Form): def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) - self.session = kwargs.pop('session', None) super().__init__(*args, **kwargs) self.fields['did'].choices = [ (x.did, x.label) for x in DID.objects.filter(user=self.user) @@ -46,7 +45,7 @@ class RequestCredentialForm(forms.Form): did = did[0].did cred = cred[0] try: - cred.issue(did, self.session) + cred.issue(did) except Exception: return diff --git a/idhub/user/views.py b/idhub/user/views.py index d59f7d6..482b40e 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -128,7 +128,6 @@ class CredentialsRequestView(MyWallet, FormView): def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user - kwargs['session'] = self.request.session return kwargs def form_valid(self, form): @@ -190,7 +189,7 @@ class DidRegisterView(MyWallet, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did(self.request.session) + form.instance.set_did() form.save() messages.success(self.request, _('DID created successfully')) diff --git a/idhub/views.py b/idhub/views.py index f8a62a7..e87e5d7 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -1,8 +1,10 @@ from django.urls import reverse_lazy +from django.conf import settings from django.utils.translation import gettext_lazy as _ from django.contrib.auth import views as auth_views from django.contrib.auth import login as auth_login from django.http import HttpResponseRedirect +from nacl import secret class LoginView(auth_views.LoginView): @@ -24,9 +26,19 @@ class LoginView(auth_views.LoginView): admin_dashboard = reverse_lazy('idhub:admin_dashboard') if self.extra_context['success_url'] == user_dashboard: self.extra_context['success_url'] = admin_dashboard + password = form.cleaned_data.get("password") + # Decrypt the user's sensitive data encryption key and store it in the session. + self.decript_key(user, password) + auth_login(self.request, user) - # Decrypt the user's sensitive data encryption key and store it in the session. - password = form.cleaned_data.get("password") # TODO: Is this right???????? - sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) - self.request.session["sensitive_data_encryption_key"] = sensitive_data_encryption_key return HttpResponseRedirect(self.extra_context['success_url']) + + def decript_key(self, user, password): + if not settings.KEY_CREDENTIALS: + return + + sb_key = user.derive_key_from_password(password) + sb = secret.SecretBox(sb_key) + data_decript = sb.decrypt(settings.KEY_CREDENTIALS) + settings.KEY_CREDENTIALS_CLEAN = data_decript + diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py index 61d9637..305c929 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -184,3 +184,5 @@ USE_I18N = True USE_L10N = True AUTH_USER_MODEL = 'idhub_auth.User' +KEY_CREDENTIALS = config("KEY_CREDENTIALS") +KEY_CREDENTIALS_CLEAN = "" From c671ac489f2f3b159e878bf0f7c6a620c1ada721 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 3 Jan 2024 19:53:11 +0100 Subject: [PATCH 04/14] change settings for cache --- idhub/models.py | 21 +++++++++++++-------- idhub/views.py | 21 ++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/idhub/models.py b/idhub/models.py index 6e6e18d..05b163a 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -4,6 +4,7 @@ import requests import datetime from django.db import models from django.conf import settings +from django.core.cache import cache from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ from nacl import secret @@ -422,15 +423,17 @@ class DID(models.Model): ) def get_key_material(self): - if not settings.KEY_CREDENTIALS_CLEAN: + key_dids = cache.get("KEY_DIDS", {}) + if not key_dids.get(user.id): raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) + sb = secret.SecretBox(key_dids[user.id]) return sb.decrypt(self._key_material) def set_key_material(self, value): - if not settings.KEY_CREDENTIALS_CLEAN: + key_dids = cache.get("KEY_DIDS", {}) + if not key_dids.get(user.id): raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) + sb = secret.SecretBox(key_dids[user.id]) self._key_material = sb.encrypt(value) @property @@ -514,15 +517,17 @@ class VerificableCredential(models.Model): ) def get_data(self): - if not settings.KEY_CREDENTIALS_CLEAN: + key_dids = cache.get("KEY_DIDS", {}) + if not key_dids.get(user.id): raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) + sb = secret.SecretBox(key_dids[user.id]) return sb.decrypt(self._data) def set_data(self, value): - if not settings.KEY_CREDENTIALS_CLEAN: + key_dids = cache.get("KEY_DIDS", {}) + if not key_dids.get(user.id): raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(settings.KEY_CREDENTIALS_CLEAN) + sb = secret.SecretBox(key_dids[user.id]) self._data = sb.encrypt(value) @property diff --git a/idhub/views.py b/idhub/views.py index e87e5d7..463479c 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -1,5 +1,5 @@ from django.urls import reverse_lazy -from django.conf import settings +from django.core.cache import cache from django.utils.translation import gettext_lazy as _ from django.contrib.auth import views as auth_views from django.contrib.auth import login as auth_login @@ -26,19 +26,14 @@ class LoginView(auth_views.LoginView): admin_dashboard = reverse_lazy('idhub:admin_dashboard') if self.extra_context['success_url'] == user_dashboard: self.extra_context['success_url'] = admin_dashboard - password = form.cleaned_data.get("password") - # Decrypt the user's sensitive data encryption key and store it in the session. - self.decript_key(user, password) auth_login(self.request, user) + # Decrypt the user's sensitive data encryption key and store it in the session. + password = form.cleaned_data.get("password") + sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) + key_dids = cache.get("KEY_DIDS", {}) + key_dids[user.id] = sensitive_data_encryption_key + cache.set("KEY_DIDS", key_dids) + return HttpResponseRedirect(self.extra_context['success_url']) - def decript_key(self, user, password): - if not settings.KEY_CREDENTIALS: - return - - sb_key = user.derive_key_from_password(password) - sb = secret.SecretBox(sb_key) - data_decript = sb.decrypt(settings.KEY_CREDENTIALS) - settings.KEY_CREDENTIALS_CLEAN = data_decript - From 4225421147de10d45c9841701d515dbb02964a91 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 3 Jan 2024 19:54:10 +0100 Subject: [PATCH 05/14] fix settings --- trustchain_idhub/settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py index 305c929..61d9637 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -184,5 +184,3 @@ USE_I18N = True USE_L10N = True AUTH_USER_MODEL = 'idhub_auth.User' -KEY_CREDENTIALS = config("KEY_CREDENTIALS") -KEY_CREDENTIALS_CLEAN = "" From 3655291dc6849a1d995d02809a3975334e9307e6 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 3 Jan 2024 19:55:04 +0100 Subject: [PATCH 06/14] fix timeout --- idhub/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idhub/views.py b/idhub/views.py index 463479c..6d51159 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -33,7 +33,7 @@ class LoginView(auth_views.LoginView): sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) key_dids = cache.get("KEY_DIDS", {}) key_dids[user.id] = sensitive_data_encryption_key - cache.set("KEY_DIDS", key_dids) + cache.set("KEY_DIDS", key_dids, None) return HttpResponseRedirect(self.extra_context['success_url']) From f62348dcdb305a9e92db3a9b600773160102dec9 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 3 Jan 2024 20:14:04 +0100 Subject: [PATCH 07/14] fix perpetual key in cache --- idhub/views.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/idhub/views.py b/idhub/views.py index 6d51159..3db164f 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -21,19 +21,23 @@ class LoginView(auth_views.LoginView): def form_valid(self, form): user = form.get_user() + # Decrypt the user's sensitive data encryption key and store it in the session. + password = form.cleaned_data.get("password") + sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) + key_dids = cache.get("KEY_DIDS", {}) if not user.is_anonymous and user.is_admin: user_dashboard = reverse_lazy('idhub:user_dashboard') admin_dashboard = reverse_lazy('idhub:admin_dashboard') if self.extra_context['success_url'] == user_dashboard: self.extra_context['success_url'] = admin_dashboard + key_dids[user.id] = sensitive_data_encryption_key + cache.set("KEY_DIDS", key_dids, None) + else: + key_dids[user.id] = sensitive_data_encryption_key + cache.set("KEY_DIDS", key_dids) + auth_login(self.request, user) - # Decrypt the user's sensitive data encryption key and store it in the session. - password = form.cleaned_data.get("password") - sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) - key_dids = cache.get("KEY_DIDS", {}) - key_dids[user.id] = sensitive_data_encryption_key - cache.set("KEY_DIDS", key_dids, None) return HttpResponseRedirect(self.extra_context['success_url']) From bd84dbc3bb0e9772146e3b0cd79b5c29d74ee537 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 4 Jan 2024 12:43:24 +0100 Subject: [PATCH 08/14] encripted in reset password --- idhub/urls.py | 15 ++++++---- idhub/views.py | 14 +++++++-- idhub_auth/forms.py | 3 +- idhub_auth/models.py | 69 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/idhub/urls.py b/idhub/urls.py index 785f4d1..f7df70d 100644 --- a/idhub/urls.py +++ b/idhub/urls.py @@ -17,7 +17,7 @@ Including another URLconf from django.contrib.auth import views as auth_views from django.views.generic import RedirectView from django.urls import path, reverse_lazy -from .views import LoginView +from .views import LoginView, PasswordResetConfirmView from .admin import views as views_admin from .user import views as views_user @@ -44,13 +44,16 @@ urlpatterns = [ ), name='password_reset_done' ), - path('auth/reset///', - auth_views.PasswordResetConfirmView.as_view( - template_name='auth/password_reset_confirm.html', - success_url=reverse_lazy('idhub:password_reset_complete') - ), + path('auth/reset///', PasswordResetConfirmView.as_view(), name='password_reset_confirm' ), + # path('auth/reset///', + # auth_views.PasswordResetConfirmView.as_view( + # template_name='auth/password_reset_confirm.html', + # success_url=reverse_lazy('idhub:password_reset_complete') + # ), + # name='password_reset_confirm' + # ), path('auth/reset/done/', auth_views.PasswordResetCompleteView.as_view( template_name='auth/password_reset_complete.html' diff --git a/idhub/views.py b/idhub/views.py index 3db164f..8e7f542 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -4,7 +4,6 @@ from django.utils.translation import gettext_lazy as _ from django.contrib.auth import views as auth_views from django.contrib.auth import login as auth_login from django.http import HttpResponseRedirect -from nacl import secret class LoginView(auth_views.LoginView): @@ -23,7 +22,7 @@ class LoginView(auth_views.LoginView): user = form.get_user() # Decrypt the user's sensitive data encryption key and store it in the session. password = form.cleaned_data.get("password") - sensitive_data_encryption_key = user.decrypt_sensitive_data_encryption_key(password) + sensitive_data_encryption_key = user.decrypt_sensitive_data(password) key_dids = cache.get("KEY_DIDS", {}) if not user.is_anonymous and user.is_admin: user_dashboard = reverse_lazy('idhub:user_dashboard') @@ -41,3 +40,14 @@ class LoginView(auth_views.LoginView): return HttpResponseRedirect(self.extra_context['success_url']) + +class PasswordResetConfirmView(auth_views.PasswordResetConfirmView): + template_name = 'auth/password_reset_confirm.html' + success_url = reverse_lazy('idhub:password_reset_complete') + + def form_valid(self, form): + password = form.cleaned_data.get("password") + user = form.get_user() + user.set_encrypted_sensitive_data(password) + user.save() + return HttpResponseRedirect(self.success_url) diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py index f292182..c9ab1f6 100644 --- a/idhub_auth/forms.py +++ b/idhub_auth/forms.py @@ -2,7 +2,7 @@ import re from django import forms from django.utils.translation import gettext_lazy as _ -from idhub_auth.models import User +from idhub_auth.models import User, gen_salt class ProfileForm(forms.ModelForm): @@ -31,4 +31,3 @@ class ProfileForm(forms.ModelForm): return last_name - diff --git a/idhub_auth/models.py b/idhub_auth/models.py index cf1ac69..2fcf669 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -1,6 +1,9 @@ +import nacl +import base64 + from django.db import models +from django.core.cache import cache from django.contrib.auth.models import BaseUserManager, AbstractBaseUser -from nacl import secret, pwhash class UserManager(BaseUserManager): @@ -44,10 +47,8 @@ class User(AbstractBaseUser): is_admin = models.BooleanField(default=False) first_name = models.CharField(max_length=255, blank=True, null=True) last_name = models.CharField(max_length=255, blank=True, null=True) - # TODO: Hay que generar una clave aleatoria para cada usuario cuando se le da de alta en el sistema. - encrypted_sensitive_data_encryption_key = models.BinaryField(max_length=255) - # TODO: Hay que generar un salt aleatorio para cada usuario cuando se le da de alta en el sistema. - salt_of_sensitive_data_encryption_key = models.BinaryField(max_length=255) + encrypted_sensitive_data = models.CharField(max_length=255) + salt = models.CharField(max_length=255) objects = UserManager() @@ -92,14 +93,54 @@ class User(AbstractBaseUser): return ", ".join(set(roles)) def derive_key_from_password(self, password): - kdf = pwhash.argon2i.kdf # TODO: Move the KDF choice to SETTINGS.PY - ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY - mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE # TODO: Move the KDF choice to SETTINGS.PY - salt = self.salt_of_sensitive_data_encryption_key - return kdf(secret.SecretBox.KEY_SIZE, password, salt, opslimit=ops, memlimit=mem) + kdf = nacl.pwhash.argon2i.kdf + ops = nacl.pwhash.argon2i.OPSLIMIT_INTERACTIVE + mem = nacl.pwhash.argon2i.MEMLIMIT_INTERACTIVE + return kdf( + nacl.secret.SecretBox.KEY_SIZE, + password, + self.get_salt(), + opslimit=ops, + memlimit=mem + ) + + def decrypt_sensitive_data(self, password, data=None): + sb_key = self.derive_key_from_password(password.encode('utf-8')) + sb = nacl.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, password, data): + sb_key = self.derive_key_from_password(password.encode('utf-8')) + sb = nacl.secret.SecretBox(sb_key) + if not isinstance(data, bytes): + data = data.encode('utf-8') + + return 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, password): + key = base64.b64encode(nacl.utils.random(64)) + key_dids = cache.get("KEY_DIDS", {}) + + if key_dids.get(user.id): + key = key_dids[user.id] + else: + self.set_salt() + + key_crypted = self.encrypt_sensitive_data(password, key) + self.encrypted_sensitive_data = base64.b64encode(key_crypted).decode('utf-8') - def decrypt_sensitive_data_encryption_key(self, password): - sb_key = self.derive_key_from_password(password) - sb = secret.SecretBox(sb_key) - return sb.decrypt(self.encrypted_sensitive_data_encryption_key) From e910f3ceec6ec0771285d2e6e963b0e7f647a985 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 4 Jan 2024 16:27:27 +0100 Subject: [PATCH 09/14] make migrations and fix some things --- idhub/management/commands/initial_datas.py | 7 ++- idhub/migrations/0001_initial.py | 69 +++++++++++----------- idhub_auth/forms.py | 2 +- idhub_auth/migrations/0001_initial.py | 4 +- idhub_auth/models.py | 15 ++--- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index acdf6c7..fd644e9 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -31,12 +31,15 @@ class Command(BaseCommand): self.create_organizations(r[0].strip(), r[1].strip()) def create_admin_users(self, email, password): - User.objects.create_superuser(email=email, password=password) + su = User.objects.create_superuser(email=email, password=password) + su.set_encrypted_sensitive_data(password) + su.save() def create_users(self, email, password): - u= User.objects.create(email=email, password=password) + u = User.objects.create(email=email, password=password) u.set_password(password) + u.set_encrypted_sensitive_data(password) u.save() diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py index b4d6ac7..5bd4f31 100644 --- a/idhub/migrations/0001_initial.py +++ b/idhub/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-11-15 09:58 +# Generated by Django 4.2.5 on 2024-01-04 15:12 from django.conf import settings from django.db import migrations, models @@ -28,7 +28,7 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now=True)), ('label', models.CharField(max_length=50)), ('did', models.CharField(max_length=250)), - ('key_material', models.CharField(max_length=250)), + ('_key_material', models.BinaryField(max_length=250)), ( 'user', models.ForeignKey( @@ -169,7 +169,7 @@ class Migration(migrations.Migration): ('created_on', models.DateTimeField(auto_now=True)), ('issued_on', models.DateTimeField(null=True)), ('subject_did', models.CharField(max_length=250)), - ('data', models.TextField()), + ('_data', models.BinaryField()), ('csv_data', models.TextField()), ( 'status', @@ -274,36 +274,39 @@ class Migration(migrations.Migration): 'type', models.PositiveSmallIntegerField( choices=[ - (1, 'EV_USR_REGISTERED'), - (2, 'EV_USR_WELCOME'), - (3, 'EV_DATA_UPDATE_REQUESTED_BY_USER'), - (4, 'EV_DATA_UPDATE_REQUESTED'), - (5, 'EV_USR_UPDATED_BY_ADMIN'), - (6, 'EV_USR_UPDATED'), - (7, 'EV_USR_DELETED_BY_ADMIN'), - (8, 'EV_DID_CREATED_BY_USER'), - (9, 'EV_DID_CREATED'), - (10, 'EV_DID_DELETED'), - (11, 'EV_CREDENTIAL_DELETED_BY_ADMIN'), - (12, 'EV_CREDENTIAL_DELETED'), - (13, 'EV_CREDENTIAL_ISSUED_FOR_USER'), - (14, 'EV_CREDENTIAL_ISSUED'), - (15, 'EV_CREDENTIAL_PRESENTED_BY_USER'), - (16, 'EV_CREDENTIAL_PRESENTED'), - (17, 'EV_CREDENTIAL_ENABLED'), - (18, 'EV_CREDENTIAL_CAN_BE_REQUESTED'), - (19, 'EV_CREDENTIAL_REVOKED_BY_ADMIN'), - (20, 'EV_CREDENTIAL_REVOKED'), - (21, 'EV_ROLE_CREATED_BY_ADMIN'), - (22, 'EV_ROLE_MODIFIED_BY_ADMIN'), - (23, 'EV_ROLE_DELETED_BY_ADMIN'), - (24, 'EV_SERVICE_CREATED_BY_ADMIN'), - (25, 'EV_SERVICE_MODIFIED_BY_ADMIN'), - (26, 'EV_SERVICE_DELETED_BY_ADMIN'), - (27, 'EV_ORG_DID_CREATED_BY_ADMIN'), - (28, 'EV_ORG_DID_DELETED_BY_ADMIN'), - (29, 'EV_USR_DEACTIVATED_BY_ADMIN'), - (30, 'EV_USR_ACTIVATED_BY_ADMIN'), + (1, 'User registered'), + (2, 'User welcomed'), + (3, 'Data update requested by user'), + ( + 4, + 'Data update requested. Pending approval by administrator', + ), + (5, "User's data updated by admin"), + (6, 'Your data updated by admin'), + (7, 'User deactivated by admin'), + (8, 'DID created by user'), + (9, 'DID created'), + (10, 'DID deleted'), + (11, 'Credential deleted by user'), + (12, 'Credential deleted'), + (13, 'Credential issued for user'), + (14, 'Credential issued'), + (15, 'Credential presented by user'), + (16, 'Credential presented'), + (17, 'Credential enabled'), + (18, 'Credential available'), + (19, 'Credential revoked by admin'), + (20, 'Credential revoked'), + (21, 'Role created by admin'), + (22, 'Role modified by admin'), + (23, 'Role deleted by admin'), + (24, 'Service created by admin'), + (25, 'Service modified by admin'), + (26, 'Service deleted by admin'), + (27, 'Organisational DID created by admin'), + (28, 'Organisational DID deleted by admin'), + (29, 'User deactivated'), + (30, 'User activated'), ] ), ), diff --git a/idhub_auth/forms.py b/idhub_auth/forms.py index c9ab1f6..fa771d4 100644 --- a/idhub_auth/forms.py +++ b/idhub_auth/forms.py @@ -2,7 +2,7 @@ import re from django import forms from django.utils.translation import gettext_lazy as _ -from idhub_auth.models import User, gen_salt +from idhub_auth.models import User class ProfileForm(forms.ModelForm): diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py index d40f0a4..5655a6f 100644 --- a/idhub_auth/migrations/0001_initial.py +++ b/idhub_auth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2023-11-15 09:58 +# Generated by Django 4.2.5 on 2024-01-04 15:12 from django.db import migrations, models @@ -38,6 +38,8 @@ class Migration(migrations.Migration): ('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)), + ('encrypted_sensitive_data', models.CharField(max_length=255)), + ('salt', models.CharField(max_length=255)), ], options={ 'abstract': False, diff --git a/idhub_auth/models.py b/idhub_auth/models.py index 2fcf669..7c3434d 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -1,6 +1,7 @@ import nacl import base64 +from nacl import pwhash from django.db import models from django.core.cache import cache from django.contrib.auth.models import BaseUserManager, AbstractBaseUser @@ -93,9 +94,9 @@ class User(AbstractBaseUser): return ", ".join(set(roles)) def derive_key_from_password(self, password): - kdf = nacl.pwhash.argon2i.kdf - ops = nacl.pwhash.argon2i.OPSLIMIT_INTERACTIVE - mem = nacl.pwhash.argon2i.MEMLIMIT_INTERACTIVE + kdf = pwhash.argon2i.kdf + ops = pwhash.argon2i.OPSLIMIT_INTERACTIVE + mem = pwhash.argon2i.MEMLIMIT_INTERACTIVE return kdf( nacl.secret.SecretBox.KEY_SIZE, password, @@ -120,7 +121,7 @@ class User(AbstractBaseUser): if not isinstance(data, bytes): data = data.encode('utf-8') - return sb.encrypt(data).decode('utf-8') + return base64.b64encode(sb.encrypt(data)).decode('utf-8') def get_salt(self): return base64.b64decode(self.salt.encode('utf-8')) @@ -135,12 +136,12 @@ class User(AbstractBaseUser): key = base64.b64encode(nacl.utils.random(64)) key_dids = cache.get("KEY_DIDS", {}) - if key_dids.get(user.id): - key = key_dids[user.id] + if key_dids.get(self.id): + key = key_dids[self.id] else: self.set_salt() key_crypted = self.encrypt_sensitive_data(password, key) - self.encrypted_sensitive_data = base64.b64encode(key_crypted).decode('utf-8') + self.encrypted_sensitive_data = key_crypted From 10c6d20a10397f9f51a8adf17044de11c2991875 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 4 Jan 2024 19:17:18 +0100 Subject: [PATCH 10/14] fix command initial datas --- idhub/management/commands/initial_datas.py | 8 ++++++++ idhub/migrations/0001_initial.py | 4 ++-- idhub/models.py | 22 +++++++++------------- idhub_auth/migrations/0001_initial.py | 2 +- idhub_auth/models.py | 9 ++++++--- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index 62c048a..8481a81 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -7,6 +7,7 @@ from utils import credtools from django.conf import settings from django.core.management.base import BaseCommand, CommandError 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 oidc4vp.models import Organization @@ -43,6 +44,9 @@ class Command(BaseCommand): su = User.objects.create_superuser(email=email, password=password) su.set_encrypted_sensitive_data(password) su.save() + key = su.decrypt_sensitive_data(password) + key_dids = {su.id: key} + cache.set("KEY_DIDS", key_dids, None) def create_users(self, email, password): @@ -50,6 +54,10 @@ class Command(BaseCommand): u.set_password(password) u.set_encrypted_sensitive_data(password) u.save() + key_dids = cache.get("KEY_DIDS", {}) + key = u.decrypt_sensitive_data(password) + key_dids.update({u.id: key}) + cache.set("KEY_DIDS", key_dids) def create_organizations(self, name, url): diff --git a/idhub/migrations/0001_initial.py b/idhub/migrations/0001_initial.py index 751af85..05fdbf4 100644 --- a/idhub/migrations/0001_initial.py +++ b/idhub/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2024-01-04 16:59 +# Generated by Django 4.2.5 on 2024-01-04 18:09 from django.conf import settings from django.db import migrations, models @@ -28,7 +28,7 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now=True)), ('label', models.CharField(max_length=50, verbose_name='Label')), ('did', models.CharField(max_length=250)), - ('_key_material', models.BinaryField(max_length=250)), + ('key_material', models.CharField(max_length=255)), ( 'user', models.ForeignKey( diff --git a/idhub/models.py b/idhub/models.py index 30ad551..116f46c 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -412,9 +412,7 @@ class DID(models.Model): # In JWK format. Must be stored as-is and passed whole to library functions. # Example key material: # '{"kty":"OKP","crv":"Ed25519","x":"oB2cPGFx5FX4dtS1Rtep8ac6B__61HAP_RtSzJdPxqs","d":"OJw80T1CtcqV0hUcZdcI-vYNBN1dlubrLaJa0_se_gU"}' - # CHANGED: `key_material` to `_key_material`, datatype from CharField to BinaryField and the key is now stored encrypted. - key_material = None - _key_material = models.BinaryField(max_length=250) + key_material = models.CharField(max_length=255) user = models.ForeignKey( User, on_delete=models.CASCADE, @@ -423,18 +421,16 @@ class DID(models.Model): ) def get_key_material(self): - key_dids = cache.get("KEY_DIDS", {}) - if not key_dids.get(user.id): - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(key_dids[user.id]) - return sb.decrypt(self._key_material) + return self.user.decrypt_data(self.key_material) def set_key_material(self, value): - key_dids = cache.get("KEY_DIDS", {}) - if not key_dids.get(user.id): - raise Exception("Ojo! Se intenta acceder a datos cifrados sin tener la clave.") - sb = secret.SecretBox(key_dids[user.id]) - self._key_material = sb.encrypt(value) + self.key_material = self.user.encrypt_data(value) + + def get_data(self): + return self.user.decrypt_data(self.data) + + def set_data(self, value): + self.data = self.user.encrypt_data(value) @property def is_organization_did(self): diff --git a/idhub_auth/migrations/0001_initial.py b/idhub_auth/migrations/0001_initial.py index 8ea6578..ee16760 100644 --- a/idhub_auth/migrations/0001_initial.py +++ b/idhub_auth/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.5 on 2024-01-04 16:59 +# Generated by Django 4.2.5 on 2024-01-04 18:09 from django.db import migrations, models diff --git a/idhub_auth/models.py b/idhub_auth/models.py index aed2199..86da431 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -148,12 +148,13 @@ class User(AbstractBaseUser): def encrypt_data(self, data): sb = self.get_secret_box() value = base64.b64encode(data.encode('utf-8')) - return sb.encrypt(data) + 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() value = base64.b64decode(data.encode('utf-8')) - return sb.decrypt(data) + return sb.decrypt(value).decode('utf-8') def get_secret_box(self): key_dids = cache.get("KEY_DIDS", {}) @@ -162,4 +163,6 @@ class User(AbstractBaseUser): err += "data without having the key." raise Exception(_(err)) - return secret.SecretBox(key_dids[self.id]) + pw = base64.b64decode(key_dids[self.id].encode('utf-8')) + sb_key = self.derive_key_from_password(pw) + return nacl.secret.SecretBox(sb_key) From 5dc1577d9ee3d7e681429ae774fe3bf4d0ce5fb4 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 4 Jan 2024 21:11:11 +0100 Subject: [PATCH 11/14] encripted y download credential --- idhub/models.py | 7 ++++--- idhub/user/views.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/idhub/models.py b/idhub/models.py index 116f46c..c0e237a 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -3,7 +3,6 @@ import pytz import datetime from django.db import models from django.conf import settings -from django.core.cache import cache from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ from nacl import secret @@ -541,13 +540,15 @@ class VerificableCredential(models.Model): if self.status == self.Status.ISSUED: return - self.status = self.Status.ISSUED + # self.status = self.Status.ISSUED + import pdb; pdb.set_trace() self.subject_did = did self.issued_on = datetime.datetime.now().astimezone(pytz.utc) - self.data = sign_credential( + data = sign_credential( self.render(), self.issuer_did.get_key_material() ) + self.data = self.user.encrypt_data(data) def get_context(self): d = json.loads(self.csv_data) diff --git a/idhub/user/views.py b/idhub/user/views.py index e6e28dc..0a273ec 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -120,7 +120,7 @@ class CredentialJsonView(MyWallet, TemplateView): pk=pk, user=self.request.user ) - response = HttpResponse(self.object.data, content_type="application/json") + response = HttpResponse(self.object.get_data(), content_type="application/json") response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json") return response From cb9ef0b608c1067a89b0c5a70342f5b74adbbecf Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Sat, 6 Jan 2024 19:18:59 +0100 Subject: [PATCH 12/14] fix issue dids and credentials --- idhub/admin/views.py | 4 +- idhub/management/commands/initial_datas.py | 15 +++----- idhub/models.py | 44 +++++++++------------- idhub/user/forms.py | 4 +- idhub/user/views.py | 25 +++++++++++- idhub/views.py | 16 ++++---- idhub_auth/models.py | 18 +++------ trustchain_idhub/settings.py | 1 + 8 files changed, 68 insertions(+), 59 deletions(-) diff --git a/idhub/admin/views.py b/idhub/admin/views.py index b6dcbc8..634ec0b 100644 --- a/idhub/admin/views.py +++ b/idhub/admin/views.py @@ -17,6 +17,7 @@ from django.views.generic.edit import ( UpdateView, ) from django.shortcuts import get_object_or_404, redirect +from django.core.cache import cache from django.urls import reverse_lazy from django.http import HttpResponse from django.contrib import messages @@ -645,13 +646,14 @@ class DidRegisterView(Credentials, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did() + form.instance.set_did(cache.get("KEY_DIDS")) form.save() messages.success(self.request, _('DID created successfully')) Event.set_EV_ORG_DID_CREATED_BY_ADMIN(form.instance) return super().form_valid(form) + class DidEditView(Credentials, UpdateView): template_name = "idhub/admin/did_register.html" subtitle = _('Organization Identities (DID)') diff --git a/idhub/management/commands/initial_datas.py b/idhub/management/commands/initial_datas.py index 8481a81..0d8e0a2 100644 --- a/idhub/management/commands/initial_datas.py +++ b/idhub/management/commands/initial_datas.py @@ -37,7 +37,6 @@ class Command(BaseCommand): self.create_organizations(r[0].strip(), r[1].strip()) self.sync_credentials_organizations("pangea.org", "somconnexio.coop") self.sync_credentials_organizations("local 8000", "local 9000") - self.create_defaults_dids() self.create_schemas() def create_admin_users(self, email, password): @@ -47,6 +46,7 @@ class Command(BaseCommand): key = su.decrypt_sensitive_data(password) key_dids = {su.id: key} cache.set("KEY_DIDS", key_dids, None) + self.create_defaults_dids(su, key) def create_users(self, email, password): @@ -54,10 +54,8 @@ class Command(BaseCommand): u.set_password(password) u.set_encrypted_sensitive_data(password) u.save() - key_dids = cache.get("KEY_DIDS", {}) key = u.decrypt_sensitive_data(password) - key_dids.update({u.id: key}) - cache.set("KEY_DIDS", key_dids) + self.create_defaults_dids(u, key) def create_organizations(self, name, url): @@ -73,11 +71,10 @@ class Command(BaseCommand): org1.save() org2.save() - def create_defaults_dids(self): - for u in User.objects.all(): - did = DID(label="Default", user=u) - did.set_did() - did.save() + def create_defaults_dids(self, u, password): + did = DID(label="Default", user=u) + did.set_did(password) + did.save() def create_schemas(self): schemas_files = os.listdir(settings.SCHEMAS_DIR) diff --git a/idhub/models.py b/idhub/models.py index c0e237a..dee615f 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -3,6 +3,7 @@ import pytz import datetime from django.db import models from django.conf import settings +from django.core.cache import cache from django.template.loader import get_template from django.utils.translation import gettext_lazy as _ from nacl import secret @@ -419,25 +420,19 @@ class DID(models.Model): null=True, ) - def get_key_material(self): - return self.user.decrypt_data(self.key_material) + def get_key_material(self, password): + return self.user.decrypt_data(self.key_material, password) - def set_key_material(self, value): - self.key_material = self.user.encrypt_data(value) + def set_key_material(self, value, password): + self.key_material = self.user.encrypt_data(value, password) - def get_data(self): - return self.user.decrypt_data(self.data) - - def set_data(self, value): - self.data = self.user.encrypt_data(value) - @property def is_organization_did(self): if not self.user: return True return False - def set_did(self): + def set_did(self, password): """ Generates a new DID Controller Key and derives a DID from it. Because DID Controller Keys are stored encrypted using a User's Sensitive Data Encryption Key, @@ -445,12 +440,7 @@ class DID(models.Model): """ new_key_material = generate_did_controller_key() self.did = keydid_from_controller_key(new_key_material) - self.set_key_material(new_key_material) - - - # TODO: darmengo: esta funcion solo se llama desde un fichero que sube cosas a s3 (??) Preguntar a ver que hace. - def get_key_deprecated(self): - return json.loads(self.key_material) + self.set_key_material(new_key_material, password) class Schemas(models.Model): @@ -514,11 +504,13 @@ class VerificableCredential(models.Model): related_name='vcredentials', ) - def get_data(self): - return self.user.decrypt_data(self.data) + def get_data(self, password): + if not self.data: + return "" + return self.user.decrypt_data(self.data, password) - def set_data(self, value): - self.data = self.user.encrypt_data(value) + def set_data(self, value, password): + self.data = self.user.encrypt_data(value, password) def type(self): return self.schema.type @@ -536,19 +528,19 @@ class VerificableCredential(models.Model): data = json.loads(self.csv_data).items() return data - def issue(self, did): + def issue(self, did, password): if self.status == self.Status.ISSUED: return - # self.status = self.Status.ISSUED - import pdb; pdb.set_trace() + self.status = self.Status.ISSUED self.subject_did = did self.issued_on = datetime.datetime.now().astimezone(pytz.utc) + issuer_pass = cache.get("KEY_DIDS") data = sign_credential( self.render(), - self.issuer_did.get_key_material() + self.issuer_did.get_key_material(issuer_pass) ) - self.data = self.user.encrypt_data(data) + self.data = self.user.encrypt_data(data, password) def get_context(self): d = json.loads(self.csv_data) diff --git a/idhub/user/forms.py b/idhub/user/forms.py index 5ac04ad..efe9ad4 100644 --- a/idhub/user/forms.py +++ b/idhub/user/forms.py @@ -22,6 +22,7 @@ class RequestCredentialForm(forms.Form): def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) + self.password = kwargs.pop('password', None) super().__init__(*args, **kwargs) self.fields['did'].choices = [ (x.did, x.label) for x in DID.objects.filter(user=self.user) @@ -49,7 +50,8 @@ class RequestCredentialForm(forms.Form): did = did[0] cred = cred[0] try: - cred.issue(did) + if self.password: + cred.issue(did, self.password) except Exception: return diff --git a/idhub/user/views.py b/idhub/user/views.py index 0a273ec..dc83377 100644 --- a/idhub/user/views.py +++ b/idhub/user/views.py @@ -120,7 +120,15 @@ class CredentialJsonView(MyWallet, TemplateView): pk=pk, user=self.request.user ) - response = HttpResponse(self.object.get_data(), content_type="application/json") + pass_enc = self.request.session.get("key_did") + data = "" + if pass_enc: + user_pass = self.request.user.decrypt_data( + pass_enc, + self.request.user.password+self.request.session._session_key + ) + data = self.object.get_data(user_pass) + response = HttpResponse(data, content_type="application/json") response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json") return response @@ -135,6 +143,15 @@ class CredentialsRequestView(MyWallet, FormView): def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user + pass_enc = self.request.session.get("key_did") + if pass_enc: + user_pass = self.request.user.decrypt_data( + pass_enc, + self.request.user.password+self.request.session._session_key + ) + else: + pass_enc = None + kwargs['password'] = user_pass return kwargs def form_valid(self, form): @@ -205,7 +222,11 @@ class DidRegisterView(MyWallet, CreateView): def form_valid(self, form): form.instance.user = self.request.user - form.instance.set_did() + pw = self.request.user.decrypt_data( + self.request.session.get("key_did"), + self.request.user.password+self.request.session._session_key + ) + form.instance.set_did(pw) form.save() messages.success(self.request, _('DID created successfully')) diff --git a/idhub/views.py b/idhub/views.py index 8e7f542..176449b 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -20,23 +20,23 @@ class LoginView(auth_views.LoginView): def form_valid(self, form): user = form.get_user() - # Decrypt the user's sensitive data encryption key and store it in the session. password = form.cleaned_data.get("password") + auth_login(self.request, user) + sensitive_data_encryption_key = user.decrypt_sensitive_data(password) - key_dids = cache.get("KEY_DIDS", {}) + if not user.is_anonymous and user.is_admin: user_dashboard = reverse_lazy('idhub:user_dashboard') admin_dashboard = reverse_lazy('idhub:admin_dashboard') if self.extra_context['success_url'] == user_dashboard: self.extra_context['success_url'] = admin_dashboard - key_dids[user.id] = sensitive_data_encryption_key - cache.set("KEY_DIDS", key_dids, None) - else: - key_dids[user.id] = sensitive_data_encryption_key - cache.set("KEY_DIDS", key_dids) + cache.set("KEY_DIDS", sensitive_data_encryption_key, None) - auth_login(self.request, user) + self.request.session["key_did"] = user.encrypt_data( + sensitive_data_encryption_key, + user.password+self.request.session._session_key + ) return HttpResponseRedirect(self.extra_context['success_url']) diff --git a/idhub_auth/models.py b/idhub_auth/models.py index 86da431..189b421 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -145,24 +145,18 @@ class User(AbstractBaseUser): key_crypted = self.encrypt_sensitive_data(password, key) self.encrypted_sensitive_data = key_crypted - def encrypt_data(self, data): - sb = self.get_secret_box() + def encrypt_data(self, data, password): + sb = self.get_secret_box(password) value = base64.b64encode(data.encode('utf-8')) 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() + def decrypt_data(self, data, password): + sb = self.get_secret_box(password) value = base64.b64decode(data.encode('utf-8')) return sb.decrypt(value).decode('utf-8') - def get_secret_box(self): - key_dids = cache.get("KEY_DIDS", {}) - if not key_dids.get(self.id): - err = "An attempt is made to access encrypted " - err += "data without having the key." - raise Exception(_(err)) - - pw = base64.b64decode(key_dids[self.id].encode('utf-8')) + def get_secret_box(self, password): + pw = base64.b64decode(password.encode('utf-8')) sb_key = self.derive_key_from_password(pw) return nacl.secret.SecretBox(sb_key) diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py index b9eecc5..01567c6 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -149,6 +149,7 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ From 1e4323673c172fd560caeb3f60c24279db7e6a8b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 17 Jan 2024 12:40:54 +0100 Subject: [PATCH 13/14] encrypt admin dids with secret_key --- idhub/models.py | 5 ++++- idhub/views.py | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/idhub/models.py b/idhub/models.py index 0ff6499..128071b 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -535,7 +535,10 @@ class VerificableCredential(models.Model): self.status = self.Status.ISSUED self.subject_did = did self.issued_on = datetime.datetime.now().astimezone(pytz.utc) - issuer_pass = cache.get("KEY_DIDS") + issuer_pass = self.user.decrypt_data( + cache.get("KEY_DIDS"), + settings.SECRET_KEY, + ) data = sign_credential( self.render(), self.issuer_did.get_key_material(issuer_pass) diff --git a/idhub/views.py b/idhub/views.py index e746f02..3d64501 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -1,4 +1,5 @@ from django.urls import reverse_lazy +from django.conf import settings from django.core.cache import cache from django.utils.translation import gettext_lazy as _ from django.contrib.auth import views as auth_views @@ -30,7 +31,11 @@ class LoginView(auth_views.LoginView): if not user.is_anonymous and user.is_admin: admin_dashboard = reverse_lazy('idhub:admin_dashboard') self.extra_context['success_url'] = admin_dashboard - cache.set("KEY_DIDS", sensitive_data_encryption_key, None) + encryption_key = user.encrypt_data( + sensitive_data_encryption_key, + settings.SECRET_KEY + ) + cache.set("KEY_DIDS", encryption_key, None) self.request.session["key_did"] = user.encrypt_data( sensitive_data_encryption_key, From 9f2abf6a04ae3a9dfd58cb193e0e758a66ef83eb Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 17 Jan 2024 13:43:40 +0100 Subject: [PATCH 14/14] fix --- idhub/models.py | 9 +++++---- idhub/views.py | 11 ++++++----- idhub_auth/models.py | 11 +++-------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/idhub/models.py b/idhub/models.py index 128071b..0e3fe32 100644 --- a/idhub/models.py +++ b/idhub/models.py @@ -535,10 +535,11 @@ class VerificableCredential(models.Model): self.status = self.Status.ISSUED self.subject_did = did self.issued_on = datetime.datetime.now().astimezone(pytz.utc) - issuer_pass = self.user.decrypt_data( - cache.get("KEY_DIDS"), - settings.SECRET_KEY, - ) + issuer_pass = cache.get("KEY_DIDS") + # issuer_pass = self.user.decrypt_data( + # cache.get("KEY_DIDS"), + # settings.SECRET_KEY, + # ) data = sign_credential( self.render(), self.issuer_did.get_key_material(issuer_pass) diff --git a/idhub/views.py b/idhub/views.py index 3d64501..f544adb 100644 --- a/idhub/views.py +++ b/idhub/views.py @@ -31,11 +31,12 @@ class LoginView(auth_views.LoginView): if not user.is_anonymous and user.is_admin: admin_dashboard = reverse_lazy('idhub:admin_dashboard') self.extra_context['success_url'] = admin_dashboard - encryption_key = user.encrypt_data( - sensitive_data_encryption_key, - settings.SECRET_KEY - ) - cache.set("KEY_DIDS", encryption_key, None) + # encryption_key = user.encrypt_data( + # sensitive_data_encryption_key, + # settings.SECRET_KEY + # ) + # cache.set("KEY_DIDS", encryption_key, None) + cache.set("KEY_DIDS", sensitive_data_encryption_key, None) self.request.session["key_did"] = user.encrypt_data( sensitive_data_encryption_key, diff --git a/idhub_auth/models.py b/idhub_auth/models.py index 189b421..38224b2 100644 --- a/idhub_auth/models.py +++ b/idhub_auth/models.py @@ -135,28 +135,23 @@ class User(AbstractBaseUser): def set_encrypted_sensitive_data(self, password): key = base64.b64encode(nacl.utils.random(64)) - key_dids = cache.get("KEY_DIDS", {}) - - if key_dids.get(self.id): - key = key_dids[self.id] - else: - self.set_salt() + self.set_salt() key_crypted = self.encrypt_sensitive_data(password, key) self.encrypted_sensitive_data = key_crypted def encrypt_data(self, data, password): sb = self.get_secret_box(password) - value = base64.b64encode(data.encode('utf-8')) value_enc = sb.encrypt(data.encode('utf-8')) return base64.b64encode(value_enc).decode('utf-8') def decrypt_data(self, data, password): + # import pdb; pdb.set_trace() sb = self.get_secret_box(password) value = base64.b64decode(data.encode('utf-8')) return sb.decrypt(value).decode('utf-8') def get_secret_box(self, password): - pw = base64.b64decode(password.encode('utf-8')) + pw = base64.b64decode(password.encode('utf-8')*4) sb_key = self.derive_key_from_password(pw) return nacl.secret.SecretBox(sb_key)