Change login flow to better verify all relevant fields.

This commit is contained in:
Tom Hacohen 2020-05-14 15:42:42 +03:00
parent 32a8b9c90d
commit 93a0e41f03
2 changed files with 46 additions and 36 deletions

View File

@ -244,19 +244,21 @@ class AuthenticationLoginChallengeSerializer(serializers.Serializer):
raise NotImplementedError() raise NotImplementedError()
class AuthenticationLoginSerializer(AuthenticationLoginChallengeSerializer): class AuthenticationLoginSerializer(serializers.Serializer):
challenge = BinaryBase64Field() response = BinaryBase64Field()
host = serializers.CharField()
signature = BinaryBase64Field() signature = BinaryBase64Field()
def validate(self, data): def create(self, validated_data):
host = self.context.get('host', None) raise NotImplementedError()
if data['host'] != host:
raise serializers.ValidationError( def update(self, instance, validated_data):
'Found wrong host name. Got: "{}" expected: "{}"'.format(data['host'], host)) raise NotImplementedError()
return super().validate(data)
class AuthenticationLoginInnerSerializer(AuthenticationLoginChallengeSerializer):
challenge = BinaryBase64Field()
host = serializers.CharField()
def create(self, validated_data): def create(self, validated_data):
raise NotImplementedError() raise NotImplementedError()

View File

@ -40,6 +40,7 @@ from .serializers import (
AuthenticationSignupSerializer, AuthenticationSignupSerializer,
AuthenticationLoginChallengeSerializer, AuthenticationLoginChallengeSerializer,
AuthenticationLoginSerializer, AuthenticationLoginSerializer,
AuthenticationLoginInnerSerializer,
CollectionSerializer, CollectionSerializer,
CollectionItemSerializer, CollectionItemSerializer,
CollectionItemRevisionSerializer, CollectionItemRevisionSerializer,
@ -368,35 +369,42 @@ class AuthenticationViewSet(viewsets.ViewSet):
def login(self, request): def login(self, request):
from datetime import datetime from datetime import datetime
serializer = AuthenticationLoginSerializer( outer_serializer = AuthenticationLoginSerializer(data=request.data)
data=request.data, context={'host': request.get_host()}) if outer_serializer.is_valid():
if serializer.is_valid(): response_raw = outer_serializer.validated_data['response']
user = self.get_login_user(serializer) response = json.loads(response_raw.decode())
challenge = serializer.validated_data['challenge'] signature = outer_serializer.validated_data['signature']
signature = serializer.validated_data['signature']
salt = user.userinfo.salt serializer = AuthenticationLoginInnerSerializer(data=response, context={'host': request.get_host()})
enc_key = self.get_encryption_key(salt) if serializer.is_valid():
box = nacl.secret.SecretBox(enc_key) user = self.get_login_user(serializer)
host = serializer.validated_data['host']
challenge = serializer.validated_data['challenge']
challenge_data = json.loads(box.decrypt(challenge).decode()) salt = user.userinfo.salt
now = int(datetime.now().timestamp()) enc_key = self.get_encryption_key(salt)
if now - challenge_data['timestamp'] > app_settings.CHALLENGE_VALID_SECONDS: box = nacl.secret.SecretBox(enc_key)
content = {'code': 'challenge_expired', 'detail': 'Login challange has expired'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
elif challenge_data['userId'] != user.id:
content = {'code': 'wrong_user', 'detail': 'This challenge is for the wrong user'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
host_hash = nacl.hash.blake2b( challenge_data = json.loads(box.decrypt(challenge).decode())
serializer.validated_data['host'].encode(), encoder=nacl.encoding.RawEncoder) now = int(datetime.now().timestamp())
verify_key = nacl.signing.VerifyKey(user.userinfo.pubkey, encoder=nacl.encoding.RawEncoder) if now - challenge_data['timestamp'] > app_settings.CHALLENGE_VALID_SECONDS:
verify_key.verify(challenge + host_hash, signature) content = {'code': 'challenge_expired', 'detail': 'Login challange has expired'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
elif challenge_data['userId'] != user.id:
content = {'code': 'wrong_user', 'detail': 'This challenge is for the wrong user'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
elif host != request.get_host():
detail = 'Found wrong host name. Got: "{}" expected: "{}"'.format(host, request.get_host())
content = {'code': 'wrong_host', 'detail': detail}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
data = { verify_key = nacl.signing.VerifyKey(user.userinfo.pubkey, encoder=nacl.encoding.RawEncoder)
'token': Token.objects.get_or_create(user=user)[0].key, verify_key.verify(response_raw, signature)
}
return Response(data, status=status.HTTP_200_OK) data = {
'token': Token.objects.get_or_create(user=user)[0].key,
}
return Response(data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)