diff options
Diffstat (limited to 'src/leap/crypto/srpauth.py')
-rw-r--r-- | src/leap/crypto/srpauth.py | 606 |
1 files changed, 0 insertions, 606 deletions
diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py deleted file mode 100644 index fc0533fc..00000000 --- a/src/leap/crypto/srpauth.py +++ /dev/null @@ -1,606 +0,0 @@ -# -*- coding: utf-8 -*- -# srpauth.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import binascii -import logging - -import requests -import srp -import json - -#this error is raised from requests -from simplejson.decoder import JSONDecodeError -from functools import partial - -from PySide import QtCore -from twisted.internet import threads - -from leap.common.check import leap_assert -from leap.util.constants import REQUEST_TIMEOUT -from leap.util import request_helpers as reqhelper -from leap.common.events import signal as events_signal -from leap.common.events import events_pb2 as proto - -logger = logging.getLogger(__name__) - - -class SRPAuthenticationError(Exception): - """ - Exception raised for authentication errors - """ - pass - - -class SRPAuthConnectionError(SRPAuthenticationError): - """ - Exception raised when there's a connection error - """ - pass - - -class SRPAuthUnknownUser(SRPAuthenticationError): - """ - Exception raised when trying to authenticate an unknown user - """ - pass - - -class SRPAuthBadStatusCode(SRPAuthenticationError): - """ - Exception raised when we received an unknown bad status code - """ - pass - - -class SRPAuthNoSalt(SRPAuthenticationError): - """ - Exception raised when we don't receive the salt param at a - specific point in the auth process - """ - pass - - -class SRPAuthNoB(SRPAuthenticationError): - """ - Exception raised when we don't receive the B param at a specific - point in the auth process - """ - pass - - -class SRPAuthBadDataFromServer(SRPAuthenticationError): - """ - Generic exception when we receive bad data from the server. - """ - pass - - -class SRPAuthJSONDecodeError(SRPAuthenticationError): - """ - Exception raised when there's a problem decoding the JSON content - parsed as received from th e server. - """ - pass - - -class SRPAuthBadPassword(SRPAuthenticationError): - """ - Exception raised when the user provided a bad password to auth. - """ - pass - - -class SRPAuthVerificationFailed(SRPAuthenticationError): - """ - Exception raised when we can't verify the SRP data received from - the server. - """ - pass - - -class SRPAuthNoSessionId(SRPAuthenticationError): - """ - Exception raised when we don't receive a session id from the - server. - """ - pass - - -class SRPAuth(QtCore.QObject): - """ - SRPAuth singleton - """ - - class __impl(QtCore.QObject): - """ - Implementation of the SRPAuth interface - """ - - LOGIN_KEY = "login" - A_KEY = "A" - CLIENT_AUTH_KEY = "client_auth" - SESSION_ID_KEY = "_session_id" - - def __init__(self, provider_config): - """ - Constructor for SRPAuth implementation - - :param server: Server to which we will authenticate - :type server: str - """ - QtCore.QObject.__init__(self) - - leap_assert(provider_config, - "We need a provider config to authenticate") - - self._provider_config = provider_config - - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - self._srp = srp - self._hashfun = self._srp.SHA256 - self._ng = self._srp.NG_1024 - # **************************************************** # - - self._session = self._fetcher.session() - self._session_id = None - self._session_id_lock = QtCore.QMutex() - self._uid = None - self._uid_lock = QtCore.QMutex() - self._token = None - self._token_lock = QtCore.QMutex() - - self._srp_user = None - self._srp_a = None - - def _safe_unhexlify(self, val): - """ - Rounds the val to a multiple of 2 and returns the - unhexlified value - - :param val: hexlified value - :type val: str - - :rtype: binary hex data - :return: unhexlified val - """ - return binascii.unhexlify(val) \ - if (len(val) % 2 == 0) else binascii.unhexlify('0' + val) - - def _authentication_preprocessing(self, username, password): - """ - Generates the SRP.User to get the A SRP parameter - - :param username: username to login - :type username: str - :param password: password for the username - :type password: str - """ - logger.debug("Authentication preprocessing...") - self._srp_user = self._srp.User(username, - password, - self._hashfun, - self._ng) - _, A = self._srp_user.start_authentication() - - self._srp_a = A - - def _start_authentication(self, _, username): - """ - Sends the first request for authentication to retrieve the - salt and B parameter - - Might raise all SRPAuthenticationError based: - SRPAuthenticationError - SRPAuthConnectionError - SRPAuthUnknownUser - SRPAuthBadStatusCode - SRPAuthNoSalt - SRPAuthNoB - - :param _: IGNORED, output from the previous callback (None) - :type _: IGNORED - :param username: username to login - :type username: str - - :return: salt and B parameters - :rtype: tuple - """ - logger.debug("Starting authentication process...") - try: - auth_data = { - self.LOGIN_KEY: username, - self.A_KEY: binascii.hexlify(self._srp_a) - } - sessions_url = "%s/%s/%s/" % \ - (self._provider_config.get_api_uri(), - self._provider_config.get_api_version(), - "sessions") - init_session = self._session.post(sessions_url, - data=auth_data, - verify=self._provider_config. - get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - # Clean up A value, we don't need it anymore - self._srp_a = None - except requests.exceptions.ConnectionError as e: - logger.error("No connection made (salt): %r" % - (e,)) - raise SRPAuthConnectionError("Could not establish a " - "connection") - except Exception as e: - logger.error("Unknown error: %r" % (e,)) - raise SRPAuthenticationError("Unknown error: %r" % - (e,)) - - content, mtime = reqhelper.get_content(init_session) - - if init_session.status_code not in (200,): - logger.error("No valid response (salt): " - "Status code = %r. Content: %r" % - (init_session.status_code, content)) - if init_session.status_code == 422: - raise SRPAuthUnknownUser(self.tr("Unknown user")) - - raise SRPAuthBadStatusCode(self.tr("There was a problem with" - " authentication")) - - json_content = json.loads(content) - salt = json_content.get("salt", None) - B = json_content.get("B", None) - - if salt is None: - logger.error("No salt parameter sent") - raise SRPAuthNoSalt(self.tr("The server did not send " - "the salt parameter")) - if B is None: - logger.error("No B parameter sent") - raise SRPAuthNoB(self.tr("The server did not send " - "the B parameter")) - - return salt, B - - def _process_challenge(self, salt_B, username): - """ - Given the salt and B processes the auth challenge and - generates the M2 parameter - - Might raise SRPAuthenticationError based: - SRPAuthenticationError - SRPAuthBadDataFromServer - SRPAuthConnectionError - SRPAuthJSONDecodeError - SRPAuthBadPassword - - :param salt_B: salt and B parameters for the username - :type salt_B: tuple - :param username: username for this session - :type username: str - - :return: the M2 SRP parameter - :rtype: str - """ - logger.debug("Processing challenge...") - try: - salt, B = salt_B - unhex_salt = self._safe_unhexlify(salt) - unhex_B = self._safe_unhexlify(B) - except (TypeError, ValueError) as e: - logger.error("Bad data from server: %r" % (e,)) - raise SRPAuthBadDataFromServer( - self.tr("The data sent from the server had errors")) - M = self._srp_user.process_challenge(unhex_salt, unhex_B) - - auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(), - self._provider_config. - get_api_version(), - "sessions", - username) - - auth_data = { - self.CLIENT_AUTH_KEY: binascii.hexlify(M) - } - - try: - auth_result = self._session.put(auth_url, - data=auth_data, - verify=self._provider_config. - get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - except requests.exceptions.ConnectionError as e: - logger.error("No connection made (HAMK): %r" % (e,)) - raise SRPAuthConnectionError(self.tr("Could not connect to " - "the server")) - - try: - content, mtime = reqhelper.get_content(auth_result) - except JSONDecodeError: - raise SRPAuthJSONDecodeError("Bad JSON content in auth result") - - if auth_result.status_code == 422: - error = "" - try: - error = json.loads(content).get("errors", "") - except ValueError: - logger.error("Problem parsing the received response: %s" - % (content,)) - except AttributeError: - logger.error("Expecting a dict but something else was " - "received: %s", (content,)) - logger.error("[%s] Wrong password (HAMK): [%s]" % - (auth_result.status_code, error)) - raise SRPAuthBadPassword(self.tr("Wrong password")) - - if auth_result.status_code not in (200,): - logger.error("No valid response (HAMK): " - "Status code = %s. Content = %r" % - (auth_result.status_code, content)) - raise SRPAuthBadStatusCode(self.tr("Unknown error (%s)") % - (auth_result.status_code,)) - - return json.loads(content) - - def _extract_data(self, json_content): - """ - Extracts the necessary parameters from json_content (M2, - id, token) - - Might raise SRPAuthenticationError based: - SRPBadDataFromServer - - :param json_content: Data received from the server - :type json_content: dict - """ - try: - M2 = json_content.get("M2", None) - uid = json_content.get("id", None) - token = json_content.get("token", None) - except Exception as e: - logger.error(e) - raise SRPAuthBadDataFromServer("Something went wrong with the " - "login") - - self.set_uid(uid) - self.set_token(token) - - if M2 is None or self.get_uid() is None: - logger.error("Something went wrong. Content = %r" % - (json_content,)) - raise SRPAuthBadDataFromServer(self.tr("Problem getting data " - "from server")) - - events_signal( - proto.CLIENT_UID, content=uid, - reqcbk=lambda req, res: None) # make the rpc call async - - return M2 - - def _verify_session(self, M2): - """ - Verifies the session based on the M2 parameter. If the - verification succeeds, it sets the session_id for this - session - - Might raise SRPAuthenticationError based: - SRPAuthBadDataFromServer - SRPAuthVerificationFailed - - :param M2: M2 SRP parameter - :type M2: str - """ - logger.debug("Verifying session...") - try: - unhex_M2 = self._safe_unhexlify(M2) - except TypeError: - logger.error("Bad data from server (HAWK)") - raise SRPAuthBadDataFromServer(self.tr("Bad data from server")) - - self._srp_user.verify_session(unhex_M2) - - if not self._srp_user.authenticated(): - logger.error("Auth verification failed") - raise SRPAuthVerificationFailed(self.tr("Auth verification " - "failed")) - logger.debug("Session verified.") - - session_id = self._session.cookies.get(self.SESSION_ID_KEY, None) - if not session_id: - logger.error("Bad cookie from server (missing _session_id)") - raise SRPAuthNoSessionId(self.tr("Session cookie " - "verification " - "failed")) - - events_signal( - proto.CLIENT_SESSION_ID, content=session_id, - reqcbk=lambda req, res: None) # make the rpc call async - - self.set_session_id(session_id) - - def _threader(self, cb, res, *args, **kwargs): - return threads.deferToThread(cb, res, *args, **kwargs) - - def authenticate(self, username, password): - """ - Executes the whole authentication process for a user - - Might raise SRPAuthenticationError - - :param username: username for this session - :type username: str - :param password: password for this user - :type password: str - - :returns: A defer on a different thread - :rtype: twisted.internet.defer.Deferred - """ - leap_assert(self.get_session_id() is None, "Already logged in") - - d = threads.deferToThread(self._authentication_preprocessing, - username=username, - password=password) - - d.addCallback( - partial(self._threader, - self._start_authentication), - username=username) - d.addCallback( - partial(self._threader, - self._process_challenge), - username=username) - d.addCallback( - partial(self._threader, - self._extract_data)) - d.addCallback(partial(self._threader, - self._verify_session)) - - return d - - def logout(self): - """ - Logs out the current session. - Expects a session_id to exists, might raise AssertionError - """ - logger.debug("Starting logout...") - - leap_assert(self.get_session_id(), - "Cannot logout an unexisting session") - - logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(), - self._provider_config. - get_api_version(), - "sessions") - try: - self._session.delete(logout_url, - data=self.get_session_id(), - verify=self._provider_config. - get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - except Exception as e: - logger.warning("Something went wrong with the logout: %r" % - (e,)) - - self.set_session_id(None) - self.set_uid(None) - # Also reset the session - self._session = self._fetcher.session() - logger.debug("Successfully logged out.") - - def set_session_id(self, session_id): - QtCore.QMutexLocker(self._session_id_lock) - self._session_id = session_id - - def get_session_id(self): - QtCore.QMutexLocker(self._session_id_lock) - return self._session_id - - def set_uid(self, uid): - QtCore.QMutexLocker(self._uid_lock) - self._uid = uid - - def get_uid(self): - QtCore.QMutexLocker(self._uid_lock) - return self._uid - - def set_token(self, token): - QtCore.QMutexLocker(self._token_lock) - self._token = token - - def get_token(self): - QtCore.QMutexLocker(self._token_lock) - return self._token - - __instance = None - - authentication_finished = QtCore.Signal(bool, str) - logout_finished = QtCore.Signal(bool, str) - - def __init__(self, provider_config): - """ - Creates a singleton instance if needed - """ - QtCore.QObject.__init__(self) - - # Check whether we already have an instance - if SRPAuth.__instance is None: - # Create and remember instance - SRPAuth.__instance = SRPAuth.__impl(provider_config) - - # Store instance reference as the only member in the handle - self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance - - def authenticate(self, username, password): - """ - Executes the whole authentication process for a user - - Might raise SRPAuthenticationError based - - :param username: username for this session - :type username: str - :param password: password for this user - :type password: str - """ - - d = self.__instance.authenticate(username, password) - d.addCallback(self._gui_notify) - d.addErrback(self._errback) - return d - - def _gui_notify(self, _): - """ - Callback that notifies the UI with the proper signal. - - :param _: IGNORED, output from the previous callback (None) - :type _: IGNORED - """ - logger.debug("Successful login!") - self.authentication_finished.emit(True, self.tr("Succeeded")) - - def _errback(self, failure): - """ - General errback for the whole login process. Will notify the - UI with the proper signal. - - :param failure: Failure object captured from a callback. - :type failure: twisted.python.failure.Failure - """ - logger.error("Error logging in %s" % (failure,)) - self.authentication_finished.emit(False, "%s" % (failure.value,)) - failure.trap(Exception) - - def get_session_id(self): - return self.__instance.get_session_id() - - def get_uid(self): - return self.__instance.get_uid() - - def get_token(self): - return self.__instance.get_token() - - def logout(self): - """ - Logs out the current session. - Expects a session_id to exists, might raise AssertionError - """ - try: - self.__instance.logout() - self.logout_finished.emit(True, self.tr("Succeeded")) - return True - except Exception as e: - self.logout_finished.emit(False, "%s" % (e,)) - return False |