Authentication: move to msgpack for the encrypted parts.

This commit is contained in:
Tom Hacohen 2020-06-29 11:30:59 +03:00
parent 85de674ee2
commit 453275eadf
4 changed files with 29 additions and 10 deletions

View File

@ -64,6 +64,15 @@ class BinaryBase64Field(serializers.Field):
return b64decode(data) 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): class CollectionEncryptionKeyField(BinaryBase64Field):
def get_attribute(self, instance): def get_attribute(self, instance):
request = self.context.get('request', None) request = self.context.get('request', None)
@ -413,7 +422,7 @@ class AuthenticationLoginSerializer(serializers.Serializer):
class AuthenticationLoginInnerSerializer(AuthenticationLoginChallengeSerializer): class AuthenticationLoginInnerSerializer(AuthenticationLoginChallengeSerializer):
challenge = BinaryBase64Field() challenge = RawField()
host = serializers.CharField() host = serializers.CharField()
action = serializers.CharField() action = serializers.CharField()

View File

@ -12,7 +12,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import json import msgpack
from functools import reduce from functools import reduce
from django.conf import settings from django.conf import settings
@ -72,6 +72,14 @@ from .serializers import (
User = get_user_model() 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): class BaseViewSet(viewsets.ModelViewSet):
authentication_classes = tuple(app_settings.API_AUTHENTICATORS) authentication_classes = tuple(app_settings.API_AUTHENTICATORS)
permission_classes = tuple(app_settings.API_PERMISSIONS) permission_classes = tuple(app_settings.API_PERMISSIONS)
@ -638,7 +646,7 @@ class AuthenticationViewSet(viewsets.ViewSet):
enc_key = self.get_encryption_key(salt) enc_key = self.get_encryption_key(salt)
box = nacl.secret.SecretBox(enc_key) 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()) now = int(datetime.now().timestamp())
if action != expected_action: if action != expected_action:
content = {'code': 'wrong_action', 'detail': 'Expected "{}" but got something else'.format(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()), "timestamp": int(datetime.now().timestamp()),
"userId": user.id, "userId": user.id,
} }
challenge = box.encrypt(json.dumps( challenge = box.encrypt(msgpack_encode(challenge_data), encoder=nacl.encoding.RawEncoder)
challenge_data, separators=(',', ':')).encode(), encoder=nacl.encoding.RawEncoder)
ret = { ret = {
"salt": b64encode(salt), "salt": b64encode(salt),
@ -698,10 +705,11 @@ class AuthenticationViewSet(viewsets.ViewSet):
outer_serializer.is_valid(raise_exception=True) outer_serializer.is_valid(raise_exception=True)
response_raw = outer_serializer.validated_data['response'] response_raw = outer_serializer.validated_data['response']
response = json.loads(response_raw.decode()) response = msgpack_decode(response_raw)
signature = outer_serializer.validated_data['signature'] 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) serializer.is_valid(raise_exception=True)
bad_login_response = self.validate_login_request( bad_login_response = self.validate_login_request(
@ -730,11 +738,11 @@ class AuthenticationViewSet(viewsets.ViewSet):
outer_serializer.is_valid(raise_exception=True) outer_serializer.is_valid(raise_exception=True)
response_raw = outer_serializer.validated_data['response'] response_raw = outer_serializer.validated_data['response']
response = json.loads(response_raw.decode()) response = msgpack_decode(response_raw)
signature = outer_serializer.validated_data['signature'] signature = outer_serializer.validated_data['signature']
serializer = AuthenticationChangePasswordInnerSerializer( context = {'host': request.get_host(), 'supports_binary': True}
request.user.userinfo, data=response, context={'host': request.get_host()}) serializer = AuthenticationChangePasswordInnerSerializer(request.user.userinfo, data=response, context=context)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
bad_login_response = self.validate_login_request( bad_login_response = self.validate_login_request(

View File

@ -4,5 +4,6 @@ django-cors-headers
django-fullurl django-fullurl
djangorestframework djangorestframework
drf-nested-routers drf-nested-routers
msgpack
psycopg2-binary psycopg2-binary
pynacl pynacl

View File

@ -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 djangorestframework==3.11.0 # via -r requirements.in/base.txt, drf-nested-routers
drf-nested-routers==0.91 # via -r requirements.in/base.txt drf-nested-routers==0.91 # via -r requirements.in/base.txt
idna==2.8 # via requests 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 psycopg2-binary==2.8.4 # via -r requirements.in/base.txt
pycparser==2.20 # via cffi pycparser==2.20 # via cffi
pynacl==1.3.0 # via -r requirements.in/base.txt pynacl==1.3.0 # via -r requirements.in/base.txt