khaganat-web/pwdb/models.py
2019-07-27 17:17:21 +02:00

117 lines
3.8 KiB
Python

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.backends import default_backend
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.db import models
from neluser.models import NelUser
import secrets
import uuid
KEY_LENGTH = 32
IV_LENGTH = 16
BLOCK_SIZE = 128
ENCODING = "UTF-8"
class SharedPassword(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=512)
url = models.CharField(max_length=512, blank=True)
description = models.TextField(blank=True)
iv = models.BinaryField(max_length=IV_LENGTH)
encrypted_password = models.BinaryField(max_length=2048)
@staticmethod
def get_key(pass_uuid, key=None):
backend = default_backend()
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=KEY_LENGTH,
salt=pass_uuid.bytes,
backend=backend,
info=None,
)
key = key or settings.SECRET_KEY
key = bytes(key, encoding=ENCODING)
return hkdf.derive(key)
@staticmethod
def get_cipher(key, iv):
backend = default_backend()
return Cipher(algorithms.Camellia(key), modes.CBC(iv), backend=backend)
@staticmethod
def padd_password(clear_password):
clear_password = bytes(clear_password, encoding=ENCODING)
padder = padding.PKCS7(BLOCK_SIZE).padder()
padded_password = padder.update(clear_password) + padder.finalize()
return padded_password
@staticmethod
def unpadd_password(clear_password):
unpadder = padding.PKCS7(BLOCK_SIZE).unpadder()
unpadded_password = unpadder.update(clear_password) + unpadder.finalize()
return unpadded_password.decode()
@staticmethod
def new(name, clear_password):
password = SharedPassword(
uuid=uuid.uuid4(),
name=name,
url="",
description="",
iv=secrets.token_bytes(IV_LENGTH),
encrypted_password=b"",
)
password.set_password(clear_password)
return password
def set_password(self, clear_password):
clear_password = SharedPassword.padd_password(clear_password)
key = SharedPassword.get_key(self.uuid)
cipher = SharedPassword.get_cipher(key, self.iv)
encryptor = cipher.encryptor()
self.encrypted_password = (
encryptor.update(clear_password) + encryptor.finalize()
)
def decrypt_password(self):
key = SharedPassword.get_key(self.uuid)
cipher = SharedPassword.get_cipher(key, self.iv)
decryptor = cipher.decryptor()
clear_password = (
decryptor.update(self.encrypted_password) + decryptor.finalize()
)
clear_password = SharedPassword.unpadd_password(clear_password)
return clear_password
def users(self):
lst = (
SharedPasswordAccess.objects.select_related("user")
.filter(password=self)
.order_by("user__email")
)
return [e.user for e in lst]
def __str__(self):
return self.name
class Meta:
verbose_name = _("shared_password")
verbose_name_plural = _("shared_passwords")
class SharedPasswordAccess(models.Model):
user = models.ForeignKey(
NelUser, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}
)
password = models.ForeignKey(SharedPassword, on_delete=models.CASCADE)
def __str__(self):
return "{}: {}".format(self.password, self.user)
class Meta:
verbose_name = _("shared_password_access")
verbose_name_plural = _("shared_passwords_access")