Changed model definitions and added logic to decrypt and set user key in session storage

This commit is contained in:
Daniel Armengod 2023-12-01 06:39:26 +01:00
parent 5eb606c4a4
commit 37dc8335a7
4 changed files with 58 additions and 3 deletions

View File

@ -6,6 +6,8 @@ from django.db import models
from django.conf import settings from django.conf import settings
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from nacl import secret
from utils.idhub_ssikit import ( from utils.idhub_ssikit import (
generate_did_controller_key, generate_did_controller_key,
keydid_from_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. # 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) # 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 = models.ForeignKey(
User, User,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -417,6 +421,18 @@ class DID(models.Model):
null=True, 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 @property
def is_organization_did(self): def is_organization_did(self):
if not self.user: if not self.user:
@ -427,7 +443,8 @@ class DID(models.Model):
self.key_material = generate_did_controller_key() self.key_material = generate_did_controller_key()
self.did = keydid_from_controller_key(self.key_material) 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) return json.loads(self.key_material)
@ -464,7 +481,10 @@ class VerificableCredential(models.Model):
created_on = models.DateTimeField(auto_now=True) created_on = models.DateTimeField(auto_now=True)
issued_on = models.DateTimeField(null=True) issued_on = models.DateTimeField(null=True)
subject_did = models.CharField(max_length=250) 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() csv_data = models.TextField()
status = models.PositiveSmallIntegerField( status = models.PositiveSmallIntegerField(
choices=Status.choices, choices=Status.choices,
@ -486,6 +506,18 @@ class VerificableCredential(models.Model):
related_name='vcredentials', 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 @property
def get_schema(self): def get_schema(self):
if not self.data: if not self.data:

View File

@ -25,4 +25,8 @@ class LoginView(auth_views.LoginView):
if self.extra_context['success_url'] == user_dashboard: if self.extra_context['success_url'] == user_dashboard:
self.extra_context['success_url'] = admin_dashboard self.extra_context['success_url'] = admin_dashboard
auth_login(self.request, user) 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']) return HttpResponseRedirect(self.extra_context['success_url'])

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
from nacl import secret, pwhash
class UserManager(BaseUserManager): class UserManager(BaseUserManager):
@ -43,6 +44,10 @@ class User(AbstractBaseUser):
is_admin = models.BooleanField(default=False) is_admin = models.BooleanField(default=False)
first_name = models.CharField(max_length=255, blank=True, null=True) first_name = models.CharField(max_length=255, blank=True, null=True)
last_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() objects = UserManager()
@ -85,3 +90,16 @@ class User(AbstractBaseUser):
for r in s.service.rol.all(): for r in s.service.rol.all():
roles.append(r.name) roles.append(r.name)
return ", ".join(set(roles)) 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)

View File

@ -11,3 +11,4 @@ didkit==0.3.2
jinja2==3.1.2 jinja2==3.1.2
jsonref==1.1.0 jsonref==1.1.0
pyld==2.0.3 pyld==2.0.3
pynacl==1.5.0