コード例 #1
0
class ClientHandler(asyncio.Protocol):
    def __init__(self, signal):
        """
		Default constructor
		"""
        self.signal = signal
        self.state = 0
        self.file = None
        self.file_name = None
        self.file_path = None
        self.storage_dir = storage_dir
        self.last_pos = 0
        self.new_key = False
        self.buffer = ''
        self.peername = ''

        self.symetric_ciphers = ['ChaCha20', 'AES', '3DES']
        self.cipher_modes = ['CBC', 'ECB', 'GCM']
        self.digest = ['SHA384', 'SHA256', 'SHA512', 'MD5', 'BLAKE2']
        self.choosen_cipher = None
        self.choosen_mode = None
        self.choosen_digest = None
        self.crypto = Crypto(self.choosen_cipher, self.choosen_mode,
                             self.choosen_digest)

        self.encrypted_data = ''
        self.decrypted_data = []

    def log_state(self, received):
        states = [
            'CONNECT', 'OPEN', 'DATA', 'CLOSE', 'KEY_ROTATION', 'NEGOTIATION',
            'DIFFIE HELLMAN'
        ]
        logger.info("------------")
        logger.info("State: {}".format(states[self.state]))
        logger.info("Received: {}".format(received))

    def connection_made(self, transport) -> None:
        """
		Called when a client connects

		:param transport: The transport stream to use with this client
		:return:
		"""
        self.peername = transport.get_extra_info('peername')
        logger.info('\n\nConnection from {}'.format(self.peername))
        self.transport = transport
        self.state = STATE_CONNECT

    def data_received(self, data: bytes) -> None:
        """
        Called when data is received from the client.
        Stores the data in the buffer

        :param data: The data that was received. This may not be a complete JSON message
        :return:
        """
        logger.debug('Received: {}'.format(data))

        try:
            self.buffer += data.decode()
        except:
            logger.exception('Could not decode data from client')

        idx = self.buffer.find('\r\n')

        while idx >= 0:  # While there are separators
            frame = self.buffer[:idx + 2].strip()  # Extract the JSON object
            self.buffer = self.buffer[
                idx + 2:]  # Removes the JSON object from the buffer

            self.on_frame(frame)  # Process the frame
            idx = self.buffer.find('\r\n')

        if len(self.buffer
               ) > 4096 * 1024 * 1024:  # If buffer is larger than 4M
            logger.warning('Buffer to large')
            self.buffer = ''
            self.transport.close()

    def on_frame(self, frame: str) -> None:
        """
		Called when a frame (JSON Object) is extracted

		:param frame: The JSON object to process
		:return:
		"""
        #logger.debug("Frame: {}".format(frame))

        try:
            message = json.loads(frame)
        except:
            logger.exception("Could not decode JSON message: {}".format(frame))
            self.transport.close()
            return

        mtype = message.get('type', "").upper()
        error = None
        self.log_state(mtype)
        if mtype == 'OPEN':
            if self.state == STATE_DH:  # Check if state equal DH
                ret = self.process_open(message)
            else:
                self._send({'type': 'OK'})
                self.state = STATE_OPEN
                ret = True

        if mtype == 'SECURE_X':
            self.encrypted_data += message['payload']
            ret = True

        elif mtype == 'MAC':

            (ret, error) = self.process_mac(message)

            if ret:
                iv = base64.b64decode(message['iv'])
                tag = base64.b64decode(message['tag'])
                nonce = base64.b64decode(message['nonce'])

                if iv == '':
                    iv = None
                if tag == '':
                    tag = None
                if nonce == '':
                    nonce = None

                self.decrypted_data.append(
                    self.crypto.decryption(
                        base64.b64decode(self.encrypted_data.encode()), iv,
                        tag, nonce))

                # process secure message
                self.process_secure()

        elif mtype == 'NEGOTIATION':
            logger.debug('Negotiation received')
            (ret, error) = self.process_negotiation(message)
            self.state = STATE_NEGOTIATION

        elif mtype == 'DH_PARAMETERS':
            logger.debug('DH ROTATION RECEIVED')
            ret = self.process_dh_parameters(message)

            # Generate a symetric key
            self.crypto.symmetric_key_gen()
            logger.debug("Key: {}".format(self.crypto.symmetric_key))

            message = {
                'type': 'DH_PARAMETERS_RESPONSE',
                'parameters': {
                    'public_key': str(self.crypto.public_key, 'ISO-8859-1')
                }
            }
            self._send(message)
            if self.state == STATE_DATA:
                self.state = STATE_KEY_ROTATION
            else:
                self.state = STATE_DH
            self.new_key = True

        else:
            logger.warning("Invalid message type: {}".format(message['type']))
            ret = False

        if not ret:
            try:
                self._send({
                    'type': 'ERROR',
                    'message': 'See server',
                    'data': error
                })
            except:
                pass  # Silently ignore

            logger.info("Closing transport")
            if self.file is not None:
                self.file.close()
                self.file = None

            self.state = STATE_CLOSE
            self.transport.close()

    def process_mac(self, message: str) -> bool:
        """
		Processes a MAC message from the client.
		It checks the authenticity/integrity of a previous received message.

		@param message: The message to process.
		"""
        logger.debug("Process MAC: {}".format(message))

        client_mac = base64.b64decode(message['data'])

        # Generate server MAC
        self.crypto.mac_gen(base64.b64decode(self.encrypted_data))
        logger.debug("Client mac: {}".format(base64.b64decode(client_mac)))
        logger.debug("Server mac: {}".format(self.crypto.mac))

        if client_mac == self.crypto.mac:
            logger.info("Integrity controll: Success")
            return (True, None)
        else:
            return (False, 'Integrity control failed.')

    def process_dh_parameters(self, message: str) -> bool:
        """
		Processes a DH_PARAMETERS message from the client.
		It computes a shared key necessary to the Diffie Helman algorithm.

		@param message: The message to process.
		"""
        logger.debug("Process DH parameters: {}".format(message))

        g = message['parameters']['g']
        p = message['parameters']['p']
        bytes_public_key = bytes(message['parameters']['public_key'],
                                 'ISO-8859-1')

        try:
            ret = self.crypto.diffie_helman_server(p, g, bytes_public_key)
            return ret
        except:
            return False

    def process_negotiation(self, message: str) -> bool:
        """
		Processes a NEGOTIATION message from the client.
		It checks which symmetric ciphers, cipher modes 
		and digest functions are available at the server.

		@param message: The message to process.
		"""
        logger.debug("Process Negotiation: {}".format(message))
        choosen_chipher = None
        choosen_mode = None
        choosen_digest = None
        flag = None

        for cipher in self.symetric_ciphers:
            if cipher in message['algorithms']['symetric_ciphers']:

                choosen_chipher = cipher
                break
        if choosen_chipher != 'ChaCha20':
            for cipher_mode in self.cipher_modes:
                if cipher_mode in message['algorithms']['chiper_modes']:
                    if cipher_mode == 'GCM' and choosen_chipher != 'AES':
                        continue
                    choosen_mode = cipher_mode
                    break
        else:
            choosen_mode = ''

        for digest in self.digest:
            if digest in message['algorithms']['digest']:
                choosen_digest = digest
                break
        print(f"Teste: {choosen_chipher} {choosen_mode} {choosen_digest}")
        if choosen_chipher is not None and choosen_mode is not None and choosen_digest is not None:
            # self.choosen_cipher=choosen_chipher
            # self.choosen_mode=choosen_mode
            # self.choosen_digest=choosen_digest

            self.crypto.symmetric_cipher = choosen_chipher
            self.crypto.cipher_mode = choosen_mode
            self.crypto.digest = choosen_digest

            flag = True
        else:
            flag = False
            return (False,
                    "Client algorithms not compatible with server algorithms")

        if flag:
            self._send({
                'type': 'NEGOTIATION_RESPONSE',
                'chosen_algorithms': {
                    'symetric_cipher': self.crypto.symmetric_cipher,
                    'chiper_mode': self.crypto.cipher_mode,
                    'digest': self.crypto.digest
                }
            })
            return (True, None)
        logger.debug("Choices {} {} {}".format(choosen_chipher, choosen_mode,
                                               choosen_digest))

    def process_open(self, message: str) -> bool:
        """
		Processes an OPEN message from the client
		This message should contain the filename

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Open: {}".format(message))

        if self.state != STATE_DH:
            logger.warning("Invalid state. Discarding")
            return False

        if not 'file_name' in message:
            logger.warning("No filename in Open")
            return False

        # Only chars and letters in the filename
        file_name = re.sub(r'[^\w\.]', '', message['file_name'])
        file_path = os.path.join(self.storage_dir, file_name)
        if not os.path.exists("files"):
            try:
                os.mkdir("files")
            except:
                logger.exception("Unable to create storage directory")
                return False

        try:
            self.file = open(file_path, "wb")  #TODO append bytes
            logger.info("File open")
        except Exception:
            logger.exception("Unable to open file")
            return False

        self._send({'type': 'OK'})

        self.file_name = file_name
        self.file_path = file_path
        self.state = STATE_OPEN
        return True

    def process_data(self, message: str) -> bool:
        """
		Processes a DATA message from the client
		This message should contain a chunk of the file

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Data: {}".format(message))

        if self.state == STATE_OPEN or self.state == STATE_KEY_ROTATION:
            self.state = STATE_DATA
            # First Packet

        elif self.state == STATE_DATA:
            # Next packets
            pass

        else:
            logger.warning("Invalid state. Discarding")
            return False

        try:
            data = message.get('data', None)
            if data is None:
                logger.debug("Invalid message. No data found")
                return False

            bdata = base64.b64decode(message['data'])
        except:
            logger.exception(
                "Could not decode base64 content from message.data")
            return False

        try:
            logger.debug("Writing data: {}".format(bdata))
            if self.new_key:
                self.new_key = False
                self.file.seek(self.last_pos)
                self.last_pos = 0
            self.file.write(bdata)
            self.last_pos = self.file.tell()
            self.file.flush()
        except:
            logger.exception("Could not write to file")
            return False

        return True

    def process_close(self, message: str) -> bool:
        """
		Processes a CLOSE message from the client.
		This message will trigger the termination of this session

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Close: {}".format(message))
        self.crypto.mac_gen(base64.b64decode(self.encrypted_data))
        logger.debug("My MAC: {}".format(self.crypto.mac))
        self.transport.close()

        if self.file is not None:
            self.file.close()
            self.file = None

        self.state = STATE_CLOSE
        return True

    def process_secure(self):
        """
		Processes a SECURE_X message from the client.
		It has an encrypted payload that should be decrypted.
		The payload has a JSON message that could be of type OPEN, DATA or CLOSE.
		"""
        logger.debug("Process Secure: {}".format(self.encrypted_data))
        message = json.loads(self.decrypted_data[0])
        mtype = message['type']

        if mtype == 'OPEN':
            if self.state == STATE_DH:
                ret = self.process_open(message)
            else:
                self._send({'type': 'OK'})
                self.state = STATE_OPEN
                ret = True

        elif mtype == 'DATA':
            message = {'type': 'DATA', 'data': ''}
            for msg in self.decrypted_data:
                message['data'] += json.loads(msg)['data']
            ret = self.process_data(message)
        elif mtype == 'CLOSE':
            ret = self.process_close(message)

        else:
            logger.warning("Invalid message type: {}".format(message['type']))
            ret = False

        if not ret:
            try:
                self._send({'type': 'ERROR', 'message': 'See server'})
            except:
                pass  # Silently ignore

            logger.info("Closing transport")
            if self.file is not None:
                self.file.close()
                self.file = None

            self.state = STATE_CLOSE
            self.transport.close()

        self.encrypted_data = ''
        self.decrypted_data = []
        return True

    def _send(self, message: str) -> None:
        """
		Effectively encodes and sends a message
		:param message:
		:return:
		"""
        logger.info("Send: {}".format(message['type']))
        logger.debug("Send: {}".format(message))

        message_b = (json.dumps(message) + '\r\n').encode()
        self.transport.write(message_b)
コード例 #2
0
class ClientProtocol(asyncio.Protocol):
    """
    Client that handles a single client
    """
    def __init__(self, file_name, loop):
        """
        Default constructor

        @param file_name: Name of the file to send
        @param loop: Asyncio Loop to use
        """

        self.file_name = file_name
        self.loop = loop
        self.chunk_count = 0
        self.last_pos = 0
        self.symetric_ciphers = ['ChaCha20', 'AES', '3DES']
        self.cipher_modes = ['CBC', 'ECB', 'GCM']
        self.digest = ['SHA384', 'SHA256', 'SHA512', 'MD5', 'BLAKE2']
        self.state = STATE_CONNECT  # Initial State
        self.buffer = ''  # Buffer to receive data chunks
        self.choosen_cipher = None
        self.choosen_mode = None
        self.choosen_digest = None
        self.host_name = "127.0.0.1"

        self.crypto = Crypto(self.choosen_cipher, self.choosen_mode,
                             self.choosen_digest)

        self.encrypted_data = ''

        self.credentials = {}
        self.server_public_key = None
        self.nonce = os.urandom(16)
        self.server_nonce = None

        self.validation_type = "CHALLENGE"  # CHALLENGE or CITIZEN_CARD

        self.rsa_public_key, self.rsa_private_key = self.crypto.key_pair_gen(
            4096)

        self.recv_encrypted_data = ''
        self.recv_decrypted_data = []

    def log_state(self, received):
        states = [
            'CONNECT', 'OPEN', 'DATA', 'CLOSE', 'KEY_ROTATION', 'NEGOTIATION',
            'DIFFIE HELLMAN'
        ]
        logger.info("------------")
        #logger.info("State: {}".format(states[self.state]))
        logger.info("Received: {}".format(received))

    def encrypt_payload(self, message: dict) -> None:
        """
        Called when a secure message will be sent, in order to encrypt its payload.

        @param message: JSON message of type OPEN, DATA or CLOSE
        """
        secure_message = {'type': 'SECURE_X', 'payload': None}
        payload = json.dumps(message).encode()
        if self.crypto.cipher_mode == 'GCM':
            criptogram = self.crypto.file_encryption(payload, b"HELLO")
        else:
            criptogram = self.crypto.file_encryption(payload)
        secure_message['payload'] = base64.b64encode(criptogram).decode()
        self.encrypted_data += secure_message['payload']

        return secure_message

    def send_mac(self) -> None:
        """
        Called when a secure message is sent and a MAC is necessary to check message authenticity.
        """
        self.crypto.mac_gen(base64.b64decode(self.encrypted_data))
        #logger.debug("My MAC: {}".format(self.crypto.mac))
        if self.crypto.iv is None:
            iv = ''
        else:
            iv = base64.b64encode(self.crypto.iv).decode()

        if self.crypto.gcm_tag is None:
            tag = ''
        else:
            tag = base64.b64encode(self.crypto.gcm_tag).decode()

        if self.crypto.nonce is None:
            nonce = ''
        else:
            nonce = base64.b64encode(self.crypto.nonce).decode()

        message = {
            'type': 'MAC',
            'data': base64.b64encode(self.crypto.mac).decode(),
            'iv': iv,
            'tag': tag,
            'nonce': nonce
        }
        self._send(message)
        self.encrypted_data = ''

    def process_mac(self, message: str) -> bool:
        """
		Processes a MAC message from the client.
		It checks the authenticity/integrity of a previous received message.

		@param message: The message to process.
		"""
        logger.debug("Process MAC: {}".format(message))
        client_mac = base64.b64decode(message['data'])

        # Generate server MAC
        self.crypto.mac_gen(base64.b64decode(self.recv_encrypted_data))

        if client_mac == self.crypto.mac:
            logger.info("Integrity control: Success")
            return (True, None)
        else:
            return (False, 'Integrity control failed.')

    def connection_made(self, transport) -> None:
        """
        Called when the client connects.

        @param transport: The transport stream to use for this client
        """
        self.transport = transport

        logger.debug('Connected to Server')
        logger.debug('Sending cipher algorithms')

        logger.info('Connection to Server')
        logger.info('LOGIN_REQUEST')

        message = {
            'type': 'NEGOTIATION',
            'algorithms': {
                'symetric_ciphers': self.symetric_ciphers,
                'chiper_modes': self.cipher_modes,
                'digest': self.digest
            }
        }

        #Generate a new NONCE
        self.crypto.auth_nonce = os.urandom(16)
        logger.debug(f"Nonce: {self.crypto.auth_nonce}")

        self._send(message)
        self.state = STATE_DH

    def data_received(self, data: str) -> None:
        """
        Called when data is received from the server.
        Stores the data in the buffer

        @param data: The data that was received. This may not be a complete JSON message
        """
        logger.debug('Received: {}'.format(data))
        try:
            self.buffer += data.decode()
        except:
            logger.exception('Could not decode data from client')

        idx = self.buffer.find('\r\n')

        while idx >= 0:  # While there are separators
            frame = self.buffer[:idx + 2].strip()  # Extract the JSON object
            self.buffer = self.buffer[
                idx + 2:]  # Removes the JSON object from the buffer

            self.on_frame(frame)  # Process the frame
            idx = self.buffer.find('\r\n')

        if len(self.buffer
               ) > 4096 * 1024 * 1024:  # If buffer is larger than 4M
            logger.warning('Buffer to large')
            self.buffer = ''
            self.transport.close()

    def on_frame(self, frame: str) -> None:
        """
        Processes a frame (JSON Object)

        @param frame: The JSON Object to process
        """
        logger.debug("Frame: {}".format(frame))

        try:
            message = json.loads(frame)
        except:
            logger.exception("Could not decode the JSON message")
            self.transport.close()
            return

        mtype = message.get('type', None)
        logger.info("Received: {}".format(mtype))

        if mtype == 'SECURE_X':
            self.recv_encrypted_data += message['payload']
            return

        elif mtype == 'MAC':
            (ret, error) = self.process_mac(message)

            if ret:
                iv = base64.b64decode(message['iv'])
                tag = base64.b64decode(message['tag'])
                nonce = base64.b64decode(message['nonce'])

                if iv == '':
                    iv = None
                if tag == '':
                    tag = None
                if nonce == '':
                    nonce = None

                self.recv_decrypted_data.append(
                    self.crypto.decryption(
                        base64.b64decode(self.recv_encrypted_data.encode()),
                        iv, tag, nonce))

                # process secure message
                self.process_secure()
            return

        elif mtype == 'CHALLENGE_REQUEST':
            self.process_challenge(message)
            return

        elif mtype == 'CARD_LOGIN_RESPONSE':
            self.process_login_response(message)
            return

        elif mtype == 'SERVER_AUTH_RESPONSE':
            flag = self.process_server_auth(message)
            if not flag:
                message = {'type': 'SERVER_AUTH_FAILED'}
                secure_message = self.encrypt_payload(message)
                self._send(secure_message)
                self.send_mac()
            if flag:

                #Generate a new NONCE
                self.crypto.auth_nonce = os.urandom(16)

                self.state = STATE_CLIENT_AUTH

                if self.validation_type == "CHALLENGE":
                    message = {
                        'type': 'LOGIN_REQUEST',
                        'nonce':
                        base64.b64encode(self.crypto.auth_nonce).decode(),
                        'public_key': self.rsa_public_key
                    }

                    secure_message = self.encrypt_payload(message)
                    self._send(secure_message)
                    self.send_mac()
                elif self.validation_type == "CITIZEN_CARD":
                    message = {
                        'type': 'CARD_LOGIN_REQUEST',
                        'nonce':
                        base64.b64encode(self.crypto.auth_nonce).decode()
                    }
                    secure_message = self.encrypt_payload(message)
                    self._send(secure_message)
                    self.send_mac()

                return

        elif mtype == 'AUTH_RESPONSE':
            if message['status'] == 'SUCCESS':
                logger.info('User authentication with success.')
                self.process_authentication(message)
            elif message['status'] == 'DENIED':
                logger.info('User authentication denied.')
            else:
                logger.info('User authentication failed.')
                self.nonce = os.urandom(16)
                message = {
                    'type': 'LOGIN_REQUEST',
                    'nonce': base64.b64encode(self.crypto.auth_nonce).decode(),
                    'public_key': self.rsa_public_key
                }
                secure_message = self.encrypt_payload(message)
                self._send(secure_message)
                self.send_mac()
                self.state = STATE_CLIENT_AUTH
            return

        elif mtype == 'FILE_REQUEST_RESPONSE':
            if message['status'] == 'PERMISSION_GRANTED':
                logger.info('Permission granted to transfer the file.')
                secure_message = self.encrypt_payload({
                    'type':
                    'OPEN',
                    'file_name':
                    self.file_name
                })
                self._send(secure_message)
                self.send_mac()
                self.state = STATE_OPEN
            else:
                logger.error('Permission denied to transfer the file.')
            return

        elif mtype == 'OK':  # Server replied OK. We can advance the state
            if self.state == STATE_OPEN:
                logger.info("Channel open")

                self.send_file(self.file_name)
            elif self.state == STATE_DATA:  # Got an OK during a message transfer.
                # Reserved for future use
                pass
            else:
                logger.warning("Ignoring message from server")
            return

        elif mtype == 'ERROR':
            logger.warning("Got error from server: {}".format(
                message.get('data', None)))

        elif mtype == 'INTEGRITY_CONTROL':
            flag = message['data']
            if flag == 'True':
                self._send(self.encrypt_payload({'type': 'CLOSE'}))
                self.send_mac()
                logger.info("File transferred. Closing transport")
                self.transport.close()

        elif mtype == 'DH_PARAMETERS_RESPONSE':
            logger.debug('DH_PARAMETERS_RESPONSE')
            public_key = bytes(message['parameters']['public_key'],
                               'ISO-8859-1')

            #Create shared key with the server public key
            self.crypto.create_shared_key(public_key)

            # Generate a symetric key
            self.crypto.symmetric_key_gen()
            logger.debug("Key: {}".format(self.crypto.symmetric_key))

            if self.state == STATE_KEY_ROTATION:
                self.state = STATE_OPEN
                self.send_file(self.file_name)

            elif self.state == STATE_DH:

                self.crypto.auth_nonce = os.urandom(16)
                message = {
                    'type': 'SERVER_AUTH_REQUEST',
                    'nonce': base64.b64encode(self.crypto.auth_nonce).decode()
                }
                secure_message = self.encrypt_payload(message)
                self.state = STATE_SERVER_AUTH
                self._send(secure_message)
                self.send_mac()

            return

        elif mtype == 'NEGOTIATION_RESPONSE':
            logger.debug("Negotiation response")

            # Receive the choosen algorithms by the server
            self.process_negotiation_response(message)

            # Generate Diffie Helman client private and public keys
            bytes_public_key, p, g, y = self.crypto.diffie_helman_client()

            message = {
                'type': 'DH_PARAMETERS',
                'parameters': {
                    'p': p,
                    'g': g,
                    'public_key': str(bytes_public_key, 'ISO-8859-1')
                }
            }
            self._send(message)
            self.state = STATE_DH

            return

        else:
            logger.warning("Invalid message type")

        logger.debug('Closing')
        self.transport.close()
        self.loop.stop()

    def process_secure(self):
        """
		Processes a SECURE_X message from the client.
		It has an encrypted payload that should be decrypted.
		The payload has a JSON message that could be of type OPEN, DATA or CLOSE.
		"""
        message = json.loads(self.recv_decrypted_data[0])
        mtype = message['type']
        logger.info("Process SECURE_X: {}".format(mtype))

        if mtype == 'CHALLENGE_REQUEST':
            self.process_challenge(message)

        elif mtype == 'CARD_LOGIN_RESPONSE':
            self.process_login_response(message)

        elif mtype == 'SERVER_AUTH_RESPONSE':
            flag = self.process_server_auth(message)
            if not flag:
                message = {'type': 'SERVER_AUTH_FAILED'}
                secure_message = self.encrypt_payload(message)
                self._send(secure_message)
                self.send_mac()
            if flag:

                #Generate a new NONCE
                self.crypto.auth_nonce = os.urandom(16)

                self.state = STATE_CLIENT_AUTH

                if self.validation_type == "CHALLENGE":
                    message = {
                        'type': 'LOGIN_REQUEST',
                        'nonce':
                        base64.b64encode(self.crypto.auth_nonce).decode(),
                        'public_key': self.rsa_public_key
                    }

                    secure_message = self.encrypt_payload(message)
                    self._send(secure_message)
                    self.send_mac()
                elif self.validation_type == "CITIZEN_CARD":
                    message = {
                        'type': 'CARD_LOGIN_REQUEST',
                        'nonce':
                        base64.b64encode(self.crypto.auth_nonce).decode()
                    }
                    secure_message = self.encrypt_payload(message)
                    self._send(secure_message)
                    self.send_mac()

        elif mtype == 'AUTH_RESPONSE':
            if message['status'] == 'SUCCESS':
                logger.info('User authentication with success.')
                self.process_authentication(message)
            elif message['status'] == 'DENIED':
                logger.info('User authentication denied.')
            else:
                logger.info('User authentication failed.')
                self.nonce = os.urandom(16)
                message = {
                    'type': 'LOGIN_REQUEST',
                    'nonce': base64.b64encode(self.crypto.auth_nonce).decode(),
                    'public_key': self.rsa_public_key
                }
                secure_message = self.encrypt_payload(message)
                self._send(secure_message)
                self.send_mac()
                self.state = STATE_CLIENT_AUTH

        elif mtype == 'FILE_REQUEST_RESPONSE':
            if message['status'] == 'PERMISSION_GRANTED':
                logger.info('Permission granted to transfer the file.')
                secure_message = self.encrypt_payload({
                    'type':
                    'OPEN',
                    'file_name':
                    self.file_name
                })
                self._send(secure_message)
                self.send_mac()
                self.state = STATE_OPEN
            else:
                logger.error('Permission denied to transfer the file.')

        self.recv_encrypted_data = ''
        self.recv_decrypted_data = []
        return

    def process_server_auth(self, message):
        """
		Called when a client receives the server certificates and signature.
        After that the client performs the necessary validations.
		"""
        self.crypto.signature = base64.b64decode(message['signature'].encode())
        server_cert_bytes = base64.b64decode(message['server_cert'].encode())
        server_ca_cert_bytes = base64.b64decode(
            message['server_roots'].encode())

        self.crypto.server_cert = self.crypto.load_cert_bytes(
            server_cert_bytes)
        self.crypto.server_public_key = self.crypto.server_cert.public_key()
        self.crypto.server_ca_cert = self.crypto.load_cert_bytes(
            server_ca_cert_bytes)

        # Validate server signature
        flag1 = self.crypto.rsa_signature_verification(
            self.crypto.signature, self.crypto.auth_nonce,
            self.crypto.server_public_key)
        logger.info(f'Server signature validation: {flag1}')

        #Validate common name
        flag2 = self.host_name == self.crypto.get_common_name(
            self.crypto.server_cert)
        logger.info(f'Server common_name validation: {flag2}')

        #Validate chain
        flag3 = self.crypto.validate_server_chain(self.crypto.server_cert,
                                                  self.crypto.server_ca_cert)

        logger.info(f'Server chain validation: {flag3}')

        if flag1 and flag2 and flag3:
            logger.info("Server validated")
            return True
        else:
            return False

    def process_authentication(self, message):
        """
		Called when a client is authenticated to perform access controll.
		"""
        secure_message = self.encrypt_payload({'type': 'FILE_REQUEST'})
        self._send(secure_message)
        self.send_mac()
        self.state = STATE_OPEN

    def process_login_response(self, message):
        """
		Called when a client is authenticating with citzent card.
        The client inserts it's username and creates a signature with it's card
		"""
        self.credentials['username'] = input("Username: "******"""
		Called when a client is authenticating with challenge.
        The client inserts it's username and password and creates a signature with it's private key 
		"""
        self.credentials['username'] = input("Username: "******"Password: "******"""
        Called when a response of type NEGOTIATION is received.

        @param message: Received message
        """
        logger.debug("Process Negotiation: {}".format(message))

        self.crypto.symmetric_cipher = message['chosen_algorithms'][
            'symetric_cipher']
        self.crypto.cipher_mode = message['chosen_algorithms']['chiper_mode']
        self.crypto.digest = message['chosen_algorithms']['digest']

        logger.info("Choosen algorithms: {} {} {}".format(
            self.crypto.symmetric_cipher, self.crypto.cipher_mode,
            self.crypto.digest))

    def connection_lost(self, exc):
        """
        Connection was lost for some reason.
        @param exc:
        """
        logger.info('The server closed the connection')
        self.loop.stop()

    def send_file(self, file_name: str) -> None:
        """
        Sends a file to the server.
        The file is read in chunks, encoded to Base64 and sent as part of a DATA JSON message
        @param file_name: File to send
        """

        with open(file_name, 'rb') as f:
            message = {'type': 'DATA', 'data': None}
            file_ended = False
            read_size = 16 * 60
            while True:
                if self.last_pos != 0:
                    f.seek(self.last_pos)
                    self.last_pos = 0

                if self.chunk_count == 1000:
                    self.state = STATE_KEY_ROTATION

                    #Generate Diffie Helman client private and public keys
                    bytes_public_key, p, g, y = self.crypto.diffie_helman_client(
                    )
                    message = {
                        'type': 'DH_PARAMETERS',
                        'parameters': {
                            'p': p,
                            'g': g,
                            'public_key': str(bytes_public_key, 'ISO-8859-1')
                        }
                    }
                    self.chunk_count = 0
                    self.last_pos = f.tell()
                    self._send(message)
                    break

                self.chunk_count += 1

                data = f.read(16 * 60)
                message['data'] = base64.b64encode(data).decode()
                #logger.debug("Data: {} read size {}".format(data,f.tell()))
                secure_message = self.encrypt_payload(message)

                self._send(secure_message)
                self.send_mac()

                if len(data) != read_size:
                    file_ended = True
                    break

            # When it ends create MAC
            if file_ended:
                self._send(self.encrypt_payload({'type': 'CLOSE'}))
                self.send_mac()
                logger.info("File transferred. Closing transport")
                self.transport.close()

    def _send(self, message: str) -> None:
        """
        Effectively encodes and sends a message
        :param message:
        :return:
        """
        logger.info("Sent: {}".format(message['type']))

        message_b = (json.dumps(message) + '\r\n').encode()
        self.transport.write(message_b)
コード例 #3
0
class ClientHandler(asyncio.Protocol):
    def __init__(self, signal):
        """
		Default constructor.
		"""
        self.signal = signal
        self.state = 0
        self.file = None
        self.file_name = None
        self.file_path = None
        self.storage_dir = storage_dir
        self.last_pos = 0
        self.new_key = False
        self.buffer = ''
        self.peername = ''

        self.symetric_ciphers = ['ChaCha20', 'AES', '3DES']
        self.cipher_modes = ['CBC', 'ECB', 'GCM']
        self.digest = ['SHA384', 'SHA256', 'SHA512', 'MD5', 'BLAKE2']
        self.choosen_cipher = None
        self.choosen_mode = None
        self.choosen_digest = None
        self.crypto = Crypto(self.choosen_cipher, self.choosen_mode,
                             self.choosen_digest)

        self.encrypted_data = ''
        self.decrypted_data = []

        self.client_nonce = None
        self.client_public_key = None

        self.registered_users = self.load_users()
        self.authentication_tries = 0
        self.authenticated_user = None

        self.sent_encrypted_data = ''

    def load_users(self):
        """
		Called to load a list of usernames and their respective passwords and permissions. 
		"""

        with open('./server_db/users.csv', 'r') as f:
            f.readline()  # ignore header
            users = {
                user.replace("\n", "").split("\t")[0]:
                user.replace("\n", "").split("\t")[1:]
                for user in f
            }
            return users

    def update_users(self):
        """
		Called to update, in persistence, the users registered in the server.
		Most of the times, this method will be called when a user has 3 incorrect login tries.
		"""

        with open('./server_db/users.csv', 'w') as f:
            f.write("Username\tPassword\tPermissions\n")
            for user in self.registered_users:
                f.write(
                    f"{user}\t{self.registered_users[user][0]}\t{self.registered_users[user][1]}\n"
                )

    def encrypt_payload(self, message: dict) -> None:
        """
        Called when a secure message will be sent, in order to encrypt its payload.

        @param message: JSON message of type OPEN, DATA or CLOSE
        """

        secure_message = {'type': 'SECURE_X', 'payload': None}
        payload = json.dumps(message).encode()
        if self.crypto.cipher_mode == 'GCM':
            criptogram = self.crypto.file_encryption(payload, b"HELLO")
        else:
            criptogram = self.crypto.file_encryption(payload)
        secure_message['payload'] = base64.b64encode(criptogram).decode()
        self.sent_encrypted_data += secure_message['payload']

        return secure_message

    def send_mac(self) -> None:
        """
        Called when a secure message is sent and a MAC is necessary to check message authenticity.
        """

        self.crypto.mac_gen(base64.b64decode(self.sent_encrypted_data))
        if self.crypto.iv is None:
            iv = ''
        else:
            iv = base64.b64encode(self.crypto.iv).decode()

        if self.crypto.gcm_tag is None:
            tag = ''
        else:
            tag = base64.b64encode(self.crypto.gcm_tag).decode()

        if self.crypto.nonce is None:
            nonce = ''
        else:
            nonce = base64.b64encode(self.crypto.nonce).decode()

        message = {
            'type': 'MAC',
            'data': base64.b64encode(self.crypto.mac).decode(),
            'iv': iv,
            'tag': tag,
            'nonce': nonce
        }
        self._send(message)
        self.sent_encrypted_data = ''

    def connection_made(self, transport) -> None:
        """
		Called when a client connects

		:param transport: The transport stream to use with this client
		:return:
		"""

        self.peername = transport.get_extra_info('peername')
        logger.info('\n\nConnection from {}'.format(self.peername))
        self.transport = transport
        self.state = STATE_CONNECT

    def data_received(self, data: bytes) -> None:
        """
        Called when data is received from the client.
        Stores the data in the buffer

        :param data: The data that was received. This may not be a complete JSON message
        :return:
        """
        logger.debug('Received: {}'.format(data))

        try:
            self.buffer += data.decode()
        except:
            logger.exception('Could not decode data from client')

        idx = self.buffer.find('\r\n')

        while idx >= 0:  # While there are separators
            frame = self.buffer[:idx + 2].strip()  # Extract the JSON object
            self.buffer = self.buffer[
                idx + 2:]  # Removes the JSON object from the buffer

            self.on_frame(frame)  # Process the frame
            idx = self.buffer.find('\r\n')

        if len(self.buffer
               ) > 4096 * 1024 * 1024:  # If buffer is larger than 4M
            logger.warning('Buffer to large')
            self.buffer = ''
            self.transport.close()

    def on_frame(self, frame: str) -> None:
        """
		Called when a frame (JSON Object) is extracted

		:param frame: The JSON object to process
		:return:
		"""

        try:
            message = json.loads(frame)
        except:
            logger.exception("Could not decode JSON message: {}".format(frame))
            self.transport.close()
            return

        mtype = message.get('type', "").upper()
        error = None
        logger.info("Received: {}".format(mtype))

        if mtype == 'OPEN':
            if self.state == STATE_DH:  # Check if state equal DH
                ret = self.process_open(message)
            else:
                self._send({'type': 'OK'})
                self.state = STATE_OPEN
                ret = True

        if mtype == 'LOGIN_REQUEST':
            ret = self.process_login_request(message)

        elif mtype == 'SERVER_AUTH_REQUEST':
            ret = self.process_server_auth(message)
            self.state = STATE_SERVER_AUTH

        elif mtype == 'CHALLENGE_RESPONSE':
            ret = self.process_challenge_response(message)

        elif mtype == 'FILE_REQUEST':
            ret = self.process_file_request(message)

        elif mtype == 'SECURE_X':
            self.encrypted_data += message['payload']
            ret = True

        elif mtype == 'MAC':

            (ret, error) = self.process_mac(message)

            if ret:
                iv = base64.b64decode(message['iv'])
                tag = base64.b64decode(message['tag'])
                nonce = base64.b64decode(message['nonce'])

                if iv == '':
                    iv = None
                if tag == '':
                    tag = None
                if nonce == '':
                    nonce = None

                self.decrypted_data.append(
                    self.crypto.decryption(
                        base64.b64decode(self.encrypted_data.encode()), iv,
                        tag, nonce))

                # process secure message
                self.process_secure()

        elif mtype == 'NEGOTIATION':
            logger.debug('Negotiation received')
            (ret, error) = self.process_negotiation(message)
            self.state = STATE_NEGOTIATION

        elif mtype == 'DH_PARAMETERS':
            logger.debug('DH ROTATION RECEIVED')
            ret = self.process_dh_parameters(message)

            # Generate a symetric key
            self.crypto.symmetric_key_gen()
            logger.debug("Key: {}".format(self.crypto.symmetric_key))

            message = {
                'type': 'DH_PARAMETERS_RESPONSE',
                'parameters': {
                    'public_key': str(self.crypto.public_key, 'ISO-8859-1')
                }
            }
            self._send(message)
            if self.state == STATE_DATA:
                self.state = STATE_KEY_ROTATION
            else:
                self.state = STATE_DH
            self.new_key = True

        else:
            logger.warning("Invalid message type: {}".format(message['type']))
            ret = False

        if not ret:
            try:
                self._send({
                    'type': 'ERROR',
                    'message': 'See server',
                    'data': error
                })
            except:
                pass  # Silently ignore

            logger.info("Closing transport")
            if self.file is not None:
                self.file.close()
                self.file = None

            self.state = STATE_CLOSE
            self.transport.close()

    def process_login_request(self, message):
        """
		Called when a client attempts to authentication with a challenge-response mechanism.
		The server receives the request and sends back a challenge.
		"""
        self.state = STATE_CLIENT_AUTH
        self.client_nonce = base64.b64decode(message['nonce'].encode())
        self.crypto.auth_nonce = os.urandom(16)
        self.client_public_key = base64.b64decode(
            message['public_key'].encode())
        message = {
            'type': 'CHALLENGE_REQUEST',
            'nonce': base64.b64encode(self.crypto.auth_nonce).decode()
        }
        secure_message = self.encrypt_payload(message)
        self._send(secure_message)
        self.send_mac()

        return True

    def process_card_login_request(self, message):
        """
		Called when a client sends an authentication request with Citizen Cardship.
		The server receives the request and sends back a challenge.
		"""
        self.state = STATE_CLIENT_AUTH
        self.client_nonce = base64.b64decode(message['nonce'].encode())
        self.crypto.auth_nonce = os.urandom(16)
        message = {
            'type': 'CARD_LOGIN_RESPONSE',
            'nonce': base64.b64encode(self.crypto.auth_nonce).decode()
        }
        secure_message = self.encrypt_payload(message)
        self._send(secure_message)
        self.send_mac()
        return True

    def process_server_auth(self, message):
        """
		Called when a client attempts to verify server authenticity.
		"""
        self.crypto.server_cert = self.crypto.load_cert(
            "server_cert/secure_server.pem")
        self.crypto.server_ca_cert = self.crypto.load_cert(
            "server_roots/Secure_Server_CA.pem")
        self.crypto.rsa_public_key = self.crypto.server_cert.public_key()
        self.crypto.rsa_private_key = self.crypto.load_key_from_file(
            "server_key/server_key.pem")
        nonce = base64.b64decode(message['nonce'].encode())

        # Encrypt NONCE received by client
        self.crypto.signature = self.crypto.rsa_signing(
            nonce, self.crypto.rsa_private_key)

        logger.info("Sending certificates for validation")
        message = {
            'type':
            'SERVER_AUTH_RESPONSE',
            'signature':
            base64.b64encode(self.crypto.signature).decode(),
            'server_cert':
            base64.b64encode(
                self.crypto.get_certificate_bytes(
                    self.crypto.server_cert)).decode(),
            'server_roots':
            base64.b64encode(
                self.crypto.get_certificate_bytes(
                    self.crypto.server_ca_cert)).decode()
        }
        secure_message = self.encrypt_payload(message)
        self._send(secure_message)
        self.send_mac()
        return True

    def process_challenge_response(self, message):
        """
		Called when a client requests a challenge-response authentication to server.
		The server checks if the client is registered in persistence.
		Then, it verifies the signed challenge.	
		"""
        username = message['credentials']['username']

        if username not in self.registered_users or 'A1' not in self.registered_users[
                username][1]:
            logger.info("{} authenticated denied.".format(username))
            message = {'type': 'AUTH_RESPONSE', 'status': 'DENIED'}
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return False

        signed_challenge = message['credentials']['signed_challenge']
        pw, permissions = self.registered_users[username][
            0], self.registered_users[username][1]
        signature_verification = self.crypto.rsa_signature_verification(
            base64.b64decode(signed_challenge.encode()),
            (str(self.client_nonce) + pw +
             str(self.crypto.auth_nonce)).encode(),
            self.crypto.load_public_key(self.client_public_key))

        if signature_verification:
            logger.info("{} authenticated with success!".format(username))
            message = {
                'type': 'AUTH_RESPONSE',
                'status': 'SUCCESS',
                'username': username
            }
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            self.authenticated_user = [username, permissions]

        else:
            self.authentication_tries += 1
            if self.authentication_tries == 3:
                # remove authentication permission
                self.registered_users[username][1] = self.registered_users[
                    username][1].replace('1', '0')
                self.update_users()
                logger.info("{} authenticated denied.".format(username))
                message = {'type': 'AUTH_RESPONSE', 'status': 'DENIED'}
                secure_message = self.encrypt_payload(message)
                self._send(secure_message)
                self.send_mac()
                return False

            logger.info("{} authenticated failed.".format(username))
            message = {'type': 'AUTH_RESPONSE', 'status': 'FAILED'}
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return True

        return True

    def process_file_request(self, message):
        """
		Called when a client asks permission to transfer a file. 
		The server checks if the claimant has permission to do the requested operation or not and
		sends back a message to the client with permission granted or denied.
		"""

        if 'T1' in self.authenticated_user[1]:
            message = {
                'type': 'FILE_REQUEST_RESPONSE',
                'status': 'PERMISSION_GRANTED'
            }
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
        else:
            message = {
                'type': 'FILE_REQUEST_RESPONSE',
                'status': 'PERMISSION_DENIED'
            }
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return False

        return True

    def process_mac(self, message: str) -> bool:
        """
		Processes a MAC message from the client.
		It checks the authenticity/integrity of a previous received message.

		@param message: The message to process.
		"""
        logger.debug("Process MAC: {}".format(message))

        client_mac = base64.b64decode(message['data'])

        # Generate server MAC
        self.crypto.mac_gen(base64.b64decode(self.encrypted_data))
        logger.debug("Client mac: {}".format(base64.b64decode(client_mac)))
        logger.debug("Server mac: {}".format(self.crypto.mac))

        if client_mac == self.crypto.mac:
            logger.info("Integrity control: Success")
            return (True, None)
        else:
            return (False, 'Integrity control failed.')

    def process_dh_parameters(self, message: str) -> bool:
        """
		Processes a DH_PARAMETERS message from the client.
		It computes a shared key necessary to the Diffie Helman algorithm.

		@param message: The message to process.
		"""
        logger.debug("Process DH parameters: {}".format(message))

        g = message['parameters']['g']
        p = message['parameters']['p']
        bytes_public_key = bytes(message['parameters']['public_key'],
                                 'ISO-8859-1')

        try:
            ret = self.crypto.diffie_helman_server(p, g, bytes_public_key)
            return ret
        except:
            return False

    def process_negotiation(self, message: str) -> bool:
        """
		Processes a NEGOTIATION message from the client.
		It checks which symmetric ciphers, cipher modes 
		and digest functions are available at the server.

		@param message: The message to process.
		"""
        logger.debug("Process Negotiation: {}".format(message))
        choosen_chipher = None
        choosen_mode = None
        choosen_digest = None
        flag = None

        for cipher in self.symetric_ciphers:
            if cipher in message['algorithms']['symetric_ciphers']:

                choosen_chipher = cipher
                break
        if choosen_chipher != 'ChaCha20':
            for cipher_mode in self.cipher_modes:
                if cipher_mode in message['algorithms']['chiper_modes']:
                    if cipher_mode == 'GCM' and choosen_chipher != 'AES':
                        continue
                    choosen_mode = cipher_mode
                    break
        else:
            choosen_mode = ''

        for digest in self.digest:
            if digest in message['algorithms']['digest']:
                choosen_digest = digest
                break

        if choosen_chipher is not None and choosen_mode is not None and choosen_digest is not None:
            self.crypto.symmetric_cipher = choosen_chipher
            self.crypto.cipher_mode = choosen_mode
            self.crypto.digest = choosen_digest

            flag = True
        else:
            flag = False
            return (False,
                    "Client algorithms not compatible with server algorithms")

        if flag:
            self._send({
                'type': 'NEGOTIATION_RESPONSE',
                'chosen_algorithms': {
                    'symetric_cipher': self.crypto.symmetric_cipher,
                    'chiper_mode': self.crypto.cipher_mode,
                    'digest': self.crypto.digest
                }
            })
            return (True, None)
        logger.debug("Choices {} {} {}".format(choosen_chipher, choosen_mode,
                                               choosen_digest))

    def process_open(self, message: str) -> bool:
        """
		Processes an OPEN message from the client
		This message should contain the filename

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Open: {}".format(message))

        if self.state != STATE_CLIENT_AUTH:
            logger.warning("Invalid state. Discarding")
            return False

        if not 'file_name' in message:
            logger.warning("No filename in Open")
            return False

        # Only chars and letters in the filename
        file_name = re.sub(r'[^\w\.]', '', message['file_name'])
        file_path = os.path.join(self.storage_dir, file_name)
        if not os.path.exists("files"):
            try:
                os.mkdir("files")
            except:
                logger.exception("Unable to create storage directory")
                return False

        try:
            self.file = open(file_path, "wb")
            logger.info("File open")
        except Exception:
            logger.exception("Unable to open file")
            return False

        self._send({'type': 'OK'})

        self.file_name = file_name
        self.file_path = file_path
        self.state = STATE_OPEN
        return True

    def process_data(self, message: str) -> bool:
        """
		Processes a DATA message from the client
		This message should contain a chunk of the file

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Data: {}".format(message))

        if self.state == STATE_OPEN or self.state == STATE_KEY_ROTATION:
            self.state = STATE_DATA
            # First Packet

        elif self.state == STATE_DATA:
            # Next packets
            pass

        else:
            logger.warning("Invalid state. Discarding")
            return False

        try:
            data = message.get('data', None)
            if data is None:
                logger.debug("Invalid message. No data found")
                return False

            bdata = base64.b64decode(message['data'])
        except:
            logger.exception(
                "Could not decode base64 content from message.data")
            return False

        try:
            logger.debug("Writing data: {}".format(bdata))
            if self.new_key:
                self.new_key = False
                self.file.seek(self.last_pos)
                self.last_pos = 0
            self.file.write(bdata)
            self.last_pos = self.file.tell()
            self.file.flush()
        except:
            logger.exception("Could not write to file")
            return False

        return True

    def process_close(self, message: str) -> bool:
        """
		Processes a CLOSE message from the client.
		This message will trigger the termination of this session

		:param message: The message to process
		:return: Boolean indicating the success of the operation
		"""
        logger.debug("Process Close: {}".format(message))
        self.crypto.mac_gen(base64.b64decode(self.encrypted_data))
        logger.debug("My MAC: {}".format(self.crypto.mac))
        self.transport.close()

        if self.file is not None:
            self.file.close()
            self.file = None

        self.state = STATE_CLOSE
        return True

    def process_client_certificate(self, message):
        """
		Called when a client sends his Citizen Cardship certificate to the server in order to authenticate itself.
		The server receives and validates the certificate and also a signature.
		"""
        self.crypto.client_cert = self.crypto.load_cert_bytes(
            base64.b64decode(message['cert'].encode()))
        self.crypto.signature = base64.b64decode(message['signature'].encode())
        username = message['credentials']['username']

        if username not in self.registered_users:
            message = {'type': 'AUTH_RESPONSE', 'status': 'DENIED'}
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return False

        permissions = self.registered_users[username][1]
        self.authenticated_user = [username, permissions]

        client_public_key = self.crypto.client_cert.public_key()

        # Verify client signature
        flag = self.crypto.cc_signature_validation(
            self.crypto.signature, self.client_nonce + self.crypto.auth_nonce,
            client_public_key)
        logger.info(f'CC signature validation: {flag}')

        # Verify chain
        flag1 = self.crypto.validate_cc_chain(self.crypto.client_cert)
        logger.info(f'CC certificate chain validation: {flag1}')

        if flag1 and flag:
            logger.info("Client validated")
            message = {
                'type': 'AUTH_RESPONSE',
                'status': 'SUCCESS',
                'username': self.authenticated_user[0]
            }
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return True
        else:
            message = {'type': 'AUTH_RESPONSE', 'status': 'DENIED'}
            secure_message = self.encrypt_payload(message)
            self._send(secure_message)
            self.send_mac()
            return False

    def process_secure(self):
        """
		Processes a SECURE_X message from the client.
		It has an encrypted payload that should be decrypted.
		The payload has a JSON message that could be of type OPEN, DATA or CLOSE.
		"""
        message = json.loads(self.decrypted_data[0])
        mtype = message['type']
        logger.info("Process SECURE_X: {}".format(mtype))

        if mtype == 'OPEN':
            if self.state == STATE_CLIENT_AUTH:
                ret = self.process_open(message)
            else:
                self._send({'type': 'OK'})
                self.state = STATE_OPEN
                ret = True

        elif mtype == 'DATA':
            message = {'type': 'DATA', 'data': ''}
            for msg in self.decrypted_data:
                message['data'] += json.loads(msg)['data']
            ret = self.process_data(message)

        elif mtype == 'LOGIN_REQUEST':
            ret = self.process_login_request(message)

        elif mtype == 'CARD_LOGIN_REQUEST':
            ret = self.process_card_login_request(message)

        elif mtype == 'SERVER_AUTH_REQUEST':
            ret = self.process_server_auth(message)

        elif mtype == 'SERVER_AUTH_FAILED':
            logger.warning("Server AUTH failed.")
            ret = False

        elif mtype == 'FILE_REQUEST':
            ret = self.process_file_request(message)

        elif mtype == 'AUTH_CERTIFICATE':
            ret = self.process_client_certificate(message)

        elif mtype == 'CLOSE':
            ret = self.process_close(message)

        else:
            logger.warning("Invalid message type: {}".format(message['type']))
            ret = False

        if not ret:
            try:
                self._send({'type': 'ERROR', 'message': 'See server'})
            except:
                pass  # Silently ignore

            logger.info("Closing transport")
            if self.file is not None:
                self.file.close()
                self.file = None

            self.state = STATE_CLOSE
            self.transport.close()

        self.encrypted_data = ''
        self.decrypted_data = []
        return True

    def _send(self, message: str) -> None:
        """
		Effectively encodes and sends a message
		:param message:
		:return:
		"""
        logger.info("Sent: {}".format(message['type']))

        message_b = (json.dumps(message) + '\r\n').encode()
        self.transport.write(message_b)