118 lines
3.8 KiB
Python
118 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")
|