From 453275eadf1e97b56d54bf3499693d3992af18f8 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Mon, 29 Jun 2020 11:30:59 +0300 Subject: [PATCH] Authentication: move to msgpack for the encrypted parts. --- django_etebase/serializers.py | 11 ++++++++++- django_etebase/views.py | 26 +++++++++++++++++--------- requirements.in/base.txt | 1 + requirements.txt | 1 + 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/django_etebase/serializers.py b/django_etebase/serializers.py index 2fe0a21..e7c0d50 100644 --- a/django_etebase/serializers.py +++ b/django_etebase/serializers.py @@ -64,6 +64,15 @@ class BinaryBase64Field(serializers.Field): return b64decode(data) +# This field does nothing to the data. It's useful for raw binary data +class RawField(serializers.Field): + def to_representation(self, value): + return value + + def to_internal_value(self, data): + return data + + class CollectionEncryptionKeyField(BinaryBase64Field): def get_attribute(self, instance): request = self.context.get('request', None) @@ -413,7 +422,7 @@ class AuthenticationLoginSerializer(serializers.Serializer): class AuthenticationLoginInnerSerializer(AuthenticationLoginChallengeSerializer): - challenge = BinaryBase64Field() + challenge = RawField() host = serializers.CharField() action = serializers.CharField() diff --git a/django_etebase/views.py b/django_etebase/views.py index 64acb18..c7fdab5 100644 --- a/django_etebase/views.py +++ b/django_etebase/views.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import json +import msgpack from functools import reduce from django.conf import settings @@ -72,6 +72,14 @@ from .serializers import ( User = get_user_model() +def msgpack_encode(content): + return msgpack.packb(content, use_bin_type=True) + + +def msgpack_decode(content): + return msgpack.unpackb(content, raw=False) + + class BaseViewSet(viewsets.ModelViewSet): authentication_classes = tuple(app_settings.API_AUTHENTICATORS) permission_classes = tuple(app_settings.API_PERMISSIONS) @@ -638,7 +646,7 @@ class AuthenticationViewSet(viewsets.ViewSet): enc_key = self.get_encryption_key(salt) box = nacl.secret.SecretBox(enc_key) - challenge_data = json.loads(box.decrypt(challenge).decode()) + challenge_data = msgpack_decode(box.decrypt(challenge)) now = int(datetime.now().timestamp()) if action != expected_action: content = {'code': 'wrong_action', 'detail': 'Expected "{}" but got something else'.format(expected_action)} @@ -680,8 +688,7 @@ class AuthenticationViewSet(viewsets.ViewSet): "timestamp": int(datetime.now().timestamp()), "userId": user.id, } - challenge = box.encrypt(json.dumps( - challenge_data, separators=(',', ':')).encode(), encoder=nacl.encoding.RawEncoder) + challenge = box.encrypt(msgpack_encode(challenge_data), encoder=nacl.encoding.RawEncoder) ret = { "salt": b64encode(salt), @@ -698,10 +705,11 @@ class AuthenticationViewSet(viewsets.ViewSet): outer_serializer.is_valid(raise_exception=True) response_raw = outer_serializer.validated_data['response'] - response = json.loads(response_raw.decode()) + response = msgpack_decode(response_raw) signature = outer_serializer.validated_data['signature'] - serializer = AuthenticationLoginInnerSerializer(data=response, context={'host': request.get_host()}) + context = {'host': request.get_host(), 'supports_binary': True} + serializer = AuthenticationLoginInnerSerializer(data=response, context=context) serializer.is_valid(raise_exception=True) bad_login_response = self.validate_login_request( @@ -730,11 +738,11 @@ class AuthenticationViewSet(viewsets.ViewSet): outer_serializer.is_valid(raise_exception=True) response_raw = outer_serializer.validated_data['response'] - response = json.loads(response_raw.decode()) + response = msgpack_decode(response_raw) signature = outer_serializer.validated_data['signature'] - serializer = AuthenticationChangePasswordInnerSerializer( - request.user.userinfo, data=response, context={'host': request.get_host()}) + context = {'host': request.get_host(), 'supports_binary': True} + serializer = AuthenticationChangePasswordInnerSerializer(request.user.userinfo, data=response, context=context) serializer.is_valid(raise_exception=True) bad_login_response = self.validate_login_request( diff --git a/requirements.in/base.txt b/requirements.in/base.txt index 1ab0e9a..d27e110 100644 --- a/requirements.in/base.txt +++ b/requirements.in/base.txt @@ -4,5 +4,6 @@ django-cors-headers django-fullurl djangorestframework drf-nested-routers +msgpack psycopg2-binary pynacl diff --git a/requirements.txt b/requirements.txt index 51b4d35..cd61ff1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ django==3.0.3 # via -r requirements.in/base.txt, django-anymail, dja djangorestframework==3.11.0 # via -r requirements.in/base.txt, drf-nested-routers drf-nested-routers==0.91 # via -r requirements.in/base.txt idna==2.8 # via requests +msgpack==1.0.0 # via -r requirements.in/base.txt psycopg2-binary==2.8.4 # via -r requirements.in/base.txt pycparser==2.20 # via cffi pynacl==1.3.0 # via -r requirements.in/base.txt