diff --git a/modoboa/lib/cryptutils.py b/modoboa/lib/cryptutils.py index d7f25c5ca..5f729bf50 100644 --- a/modoboa/lib/cryptutils.py +++ b/modoboa/lib/cryptutils.py @@ -1,58 +1,49 @@ -# coding: utf-8 +# -*- coding: utf-8 -*- + """Crypto related utilities.""" from __future__ import unicode_literals import base64 -import random -import string -from django.utils.encoding import force_bytes, force_text +from django.conf import settings +from django.utils.crypto import get_random_string +from django.utils.encoding import smart_bytes, smart_text -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.backends import default_backend - -from modoboa.parameters import tools as param_tools +from cryptography.fernet import Fernet -def random_key(l=16): - """Generate a random key. - - :param integer l: the key's length - :return: a string - """ - punctuation = """!#$%&'()*+,-./:;<=>?@[]^_`{|}~""" - population = string.digits + string.ascii_letters + punctuation - while True: - key = "".join(random.sample(population * l, l)) - if len(key) == l: - return key +def random_key(length=16): + """Generate a new key used to encrypt user passwords in session storage.""" + chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' + return get_random_string(length, chars) -def encrypt(clear): - key = force_bytes( - param_tools.get_global_parameter("secret_key", app="core")) - backend = default_backend() - cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) - encryptor = cipher.encryptor() - block_size = algorithms.AES.block_size - clear = force_bytes(clear) - if len(clear) % block_size: - clear += b" " * (block_size - len(clear) % block_size) - ct = encryptor.update(clear) + encryptor.finalize() - return force_text(base64.b64encode(ct)) +def _get_fernet(): + """Create a Fernet instance.""" + secret_key = base64.urlsafe_b64encode(smart_bytes(settings.SECRET_KEY[:32])) + return Fernet(secret_key) -def decrypt(ct): - backend = default_backend() - key = param_tools.get_global_parameter("secret_key", app="core") - cipher = Cipher( - algorithms.AES(force_bytes(key)), modes.ECB(), backend=backend) - ct = base64.b64decode(force_bytes(ct)) - decryptor = cipher.decryptor() - clear = decryptor.update(ct) + decryptor.finalize() - return force_text(clear.rstrip(b" ")) +def encrypt(clear_value): + """Encrypt a value using secret_key.""" + return smart_text(_get_fernet().encrypt(smart_bytes(clear_value))) + + +def decrypt(encrypted_value): + """Decrypt a value using secret_key.""" + return smart_text(_get_fernet().decrypt(smart_bytes(encrypted_value))) def get_password(request): - return decrypt(request.session["password"]) + """ + Retrieve and decrypt the users password from session storage. + + This is used by modoboa-webmail to allow the user to send/receive e-mails + without having to ask the user for a password on each connection to the + postfix/dovecot server. + """ + try: + return decrypt(request.session["password"]) + except KeyError: + return None diff --git a/modoboa/lib/tests/test_cryptutils.py b/modoboa/lib/tests/test_cryptutils.py new file mode 100644 index 000000000..4c3f852d9 --- /dev/null +++ b/modoboa/lib/tests/test_cryptutils.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +"""Tests for crypto utility functions.""" + +from __future__ import unicode_literals + +from django.test import SimpleTestCase +from django.test.utils import override_settings + +from modoboa.lib import cryptutils + + +@override_settings(SECRET_KEY="!8o(-dbbl3e+*bh7nx-^xysdt)1gso*%@4ze4-9_9o+i&" + ";t--u_") +class CryptUtilsTest(SimpleTestCase): + + """Tests for modoboa.lib.cryptutils""" + + def test_valid_secret_key_generated(self): + """Generate a 32 byte key""" + secret_key = cryptutils.random_key(32) + self.assertEqual(len(secret_key), 32) + + def test_encrypt(self): + """Encrypt a string""" + value = "test" + expected_output = "test" + output = cryptutils.encrypt(value) + output = cryptutils.decrypt(output) + self.assertEqual(output, expected_output) + + def test_decrypt(self): + """Decrypt a string""" + value = "gAAAAABaXSWk4Via-aYP7diek9MmfknUiEY8szrgxIytdXrbfc"\ + "YlbOYiNG01zqkn3a06P1xPXe5XD2SP4UrIvCWzhLs-FO19Cw==" + expected_output = "test" + output = cryptutils.decrypt(value) + self.assertEqual(output, expected_output)