diff options
-rw-r--r-- | accounts/__init__.py | 4 | ||||
-rw-r--r-- | accounts/default_settings.py | 2 | ||||
-rw-r--r-- | accounts/forms.py | 3 | ||||
-rw-r--r-- | accounts/utils/__init__.py | 29 | ||||
-rw-r--r-- | accounts/utils/sessions.py | 62 | ||||
-rw-r--r-- | accounts/views/default/__init__.py | 4 | ||||
-rw-r--r-- | requirements.txt | 1 |
7 files changed, 71 insertions, 34 deletions
diff --git a/accounts/__init__.py b/accounts/__init__.py index afa348b..d8abf32 100644 --- a/accounts/__init__.py +++ b/accounts/__init__.py @@ -6,6 +6,7 @@ import os from flask import Flask, g, session from utils import * +from utils.sessions import EncryptedSessionInterface from views import default, admin @@ -15,6 +16,7 @@ app.register_blueprint(admin.bp, url_prefix='/admin') app.config.from_object('accounts.default_settings') if 'SPLINE_ACCOUNT_WEB_SETTINGS' in os.environ: app.config.from_envvar('SPLINE_ACCOUNT_WEB_SETTINGS') +app.session_interface = EncryptedSessionInterface() app.all_services = account.SERVICES #TODO: take that from our json file or so app.user_backend = get_backend(app.config['USER_BACKEND'], app) @@ -38,7 +40,7 @@ def initialize_user(): if 'username' in session and 'password' in session: username = ensure_utf8(session['username']) - password = ensure_utf8(decrypt_password(session['password'])) + password = ensure_utf8(session['password']) try: g.user = current_app.user_backend.auth(username, password) except ldap.INVALID_CREDENTIALS: diff --git a/accounts/default_settings.py b/accounts/default_settings.py index 09d81f5..4fe53a6 100644 --- a/accounts/default_settings.py +++ b/accounts/default_settings.py @@ -5,7 +5,7 @@ DEBUG = False SECRET_KEY = 'remember to change this to something more random and secret' # CHANGE THIS! (e.g. os.urandom(32) ) -PASSWORD_ENCRYPTION_KEY = '.\x14\xa7\x1b\xa2:\x1b\xb7\xbck\x1bD w\xab\x87a\xb4\xb7\xca\xf1\x06\xb0\x9f?q\x13\x05\x8dY\xe5<' +SESSION_ENCRYPTION_KEY = '.\x14\xa7\x1b\xa2:\x1b\xb7\xbck\x1bD w\xab\x87a\xb4\xb7\xca\xf1\x06\xb0\x9f?q\x13\x05\x8dY\xe5<' MAIL_DEFAULT_SENDER = 'spline accounts <noreply@accounts.spline.de>' MAIL_REGISTER_NOTIFY = None diff --git a/accounts/forms.py b/accounts/forms.py index 4385e7a..9e2e8f3 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -5,8 +5,7 @@ from flask.ext.wtf import Form from wtforms import TextField, PasswordField, ValidationError, BooleanField,\ validators from functools import partial -from utils import _username_re, _username_exclude_re, decrypt_password,\ - NotRegexp, url_for +from utils import _username_re, _username_exclude_re, NotRegexp, url_for username = partial(TextField, 'Benutzername', [validators.Regexp(_username_re, diff --git a/accounts/utils/__init__.py b/accounts/utils/__init__.py index 8f68733..1538fd6 100644 --- a/accounts/utils/__init__.py +++ b/accounts/utils/__init__.py @@ -6,13 +6,11 @@ import pickle import re import struct from base64 import urlsafe_b64encode, urlsafe_b64decode -from Crypto.Cipher import AES from functools import wraps from flask import current_app, flash, g, redirect, render_template, request, session from flask import url_for as flask_url_for from hashlib import sha1 from itertools import izip -from random import randint from time import time from werkzeug.exceptions import Forbidden from wtforms.validators import Regexp, ValidationError @@ -78,7 +76,7 @@ def login_user(username, password): return False session['username'] = username - session['password'] = encrypt_password(password) + session['password'] = password return True @@ -89,31 +87,6 @@ def logout_user(): g.user = None -def pad(s, numbytes=32, padding='\0'): - return s + (numbytes - len(s) % numbytes) * padding - -def encrypt_password(password): - """ - Encrypt the given password with `config.PASSWORD_ENCRYPTION_KEY`. - The key must be 32 bytes long. - """ - assert len(current_app.config['PASSWORD_ENCRYPTION_KEY']) == 32 - - password = ensure_utf8(password) - - iv = ''.join(chr(randint(0, 0xff)) for i in range(16)) - encryptor = AES.new(current_app.config['PASSWORD_ENCRYPTION_KEY'], AES.MODE_CBC, iv) - return iv + encryptor.encrypt(pad(password)) - -def decrypt_password(ciphertext): - """ - Decrypt the given password with `config.PASSWORD_ENCRYPTION_KEY`. - """ - iv = ciphertext[:16] - encryptor = AES.new(current_app.config['PASSWORD_ENCRYPTION_KEY'], AES.MODE_CBC, iv) - return encryptor.decrypt(ciphertext[16:]).rstrip('\0').decode('utf8') - - def make_confirmation(realm, data): """ Create a confirmation token e.g. for confirmation mails. diff --git a/accounts/utils/sessions.py b/accounts/utils/sessions.py new file mode 100644 index 0000000..cd12030 --- /dev/null +++ b/accounts/utils/sessions.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from Crypto import Random +from Crypto.Cipher import AES +from flask import current_app +from flask.sessions import TaggedJSONSerializer, SecureCookieSessionInterface +from itsdangerous import BadPayload + + +def _pad(value, block_size): + padding = block_size - len(value) % block_size + return value + (padding * chr(padding)) + + +def _unpad(value): + pad_length = ord(value[len(value)-1:]) + return value[:-pad_length] + + +class EncryptedSerializer(TaggedJSONSerializer): + + def __init__(self): + self.block_size = AES.block_size + + def _cipher(self, iv): + return AES.new( + current_app.config['SESSION_ENCRYPTION_KEY'], + AES.MODE_CBC, iv) + + def dumps(self, value): + """ + Encrypt the serialized values with `config.SESSION_ENCRYPTION_KEY`. + The key must be 32 bytes long. + """ + assert len(current_app.config['SESSION_ENCRYPTION_KEY']) == 32 + + serialized_value = super(EncryptedSerializer, self).dumps(value) + + raw = _pad(serialized_value, self.block_size) + iv = Random.new().read(self.block_size) + return iv + self._cipher(iv).encrypt(raw) + + def loads(self, value): + """ + Decrypt the given serialized session data with + `config.SESSION_ENCRYPTION_KEY`. + """ + iv = value[:self.block_size] + raw = self._cipher(iv).decrypt(value[AES.block_size:]) + return super(EncryptedSerializer, self).loads(_unpad(raw)) + + +class EncryptedSessionInterface(SecureCookieSessionInterface): + serializer = EncryptedSerializer() + + def open_session(self, *args, **kwargs): + try: + parent = super(EncryptedSessionInterface, self) + return parent.open_session(*args, **kwargs) + except BadPayload: + return self.session_class() diff --git a/accounts/views/default/__init__.py b/accounts/views/default/__init__.py index c2099d5..52edb10 100644 --- a/accounts/views/default/__init__.py +++ b/accounts/views/default/__init__.py @@ -133,7 +133,7 @@ def lost_password_complete(token): current_app.user_backend.update(user, as_admin=True) session['username'] = username - session['password'] = encrypt_password(form.password.data) + session['password'] = form.password.data flash(u'Passwort geändert.', 'success') return redirect(url_for('.settings')) @@ -179,7 +179,7 @@ def settings(): if form.password.data: g.user.change_password(form.password.data, form.old_password.data) - session['password'] = encrypt_password(form.password.data) + session['password'] = form.password.data flash(u'Passwort geändert', 'success') changed = True diff --git a/requirements.txt b/requirements.txt index 62e9864..ea8476c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,6 @@ Flask-Script Werkzeug>=0.6 Jinja2>=2.4 WTForms>=1.0 +itsdangerous pycrypto python-ldap |