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