Ejemplo n.º 1
0
    def __init__(self, file_name, loop):
        """
        Default constructor
        :param file_name: Name of the file to send
        :param loop: Asyncio Loop to use
        """

        self.shared = None
        self.file_name = file_name
        self.loop = loop
        self.state = STATE_CONNECT  # Initial State
        self.buffer = ''  # Buffer to receive data chunks
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()
        self.otp = OTP()

        self.challenge_passed = False
        self.citizen_card = None
        self.citizen_auth = None
        self.face_rec = None

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self._public_key = None
        self._private_key = None
        self.server_pub = None
        self.password = None
        self.certificate = None

        self.auth_type = None
        self.user = None
        self.uPass = None
Ejemplo n.º 2
0
    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.buffer = ''
        self.peername = ''
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None
        self.client_pub = None
        self.server_pub = None
        self.server_priv = None
Ejemplo n.º 3
0
    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.buffer = ''
        self.peername = ''
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()
        self.citizen_card = CitizenCard_All()
        self.otp = OTP()

        self.sCert = server_cert.ServerCert()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self.client_pub = None
        self.cert_pubkey = None

        self.server_pub = None
        self.server_priv = None

        self.server_cert = None
        self.server_cert_priv = None

        self.one_time_nonce = None
        self.auth_type = None

        # validate server cert
        server_cert.main()
Ejemplo n.º 4
0
    def __init__(self, file_name, loop):
        """
        Default constructor
        :param file_name: Name of the file to send
        :param loop: Asyncio Loop to use
        """

        self.shared = None
        self.file_name = file_name
        self.loop = loop
        self.state = STATE_CONNECT  # Initial State
        self.buffer = ''  # Buffer to receive data chunks
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self._public_key = None
        self._private_key = None
        self.server_pub = None
        self.password = None
Ejemplo n.º 5
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.shared = None
        self.file_name = file_name
        self.loop = loop
        self.state = STATE_CONNECT  # Initial State
        self.buffer = ''  # Buffer to receive data chunks
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self._public_key = None
        self._private_key = None
        self.server_pub = None
        self.password = None

    def handshake(self):

        logger.info("Introduce password to generate rsa key: ")
        self.password = getpass.getpass('Password:'******'Connected to Server')

        self.symmetric_cypher, self.cypher_mode, self.synthesis_algorithm = self.handshake(
        )

        print(self._public_key)
        message = {
            'type': 'OPEN',
            'file_name': self.file_name,
            'symmetric_cypher': self.symmetric_cypher,
            'cypher_mode': self.cypher_mode,
            'synthesis_algorithm': self.synthesis_algorithm,
            # É SUPOSTO ENVIAR ASSIM (str) A PUB KEY?
            'client_public_key': self._public_key.decode()
        }
        self._send(message)

        logger.info(message)

        self.state = STATE_OPEN

    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
        :return:
        """
        logger.debug('Received: {}'.format(data))
        if self.state == STATE_OPEN:
            data = self.symmetric.handshake_decrypt(data)
            # logger.debug('DEBUG: Decrypt: ', 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
        :return:
        """

        # 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)

        if mtype == 'OK':  # Server replied OK. We can advance the state
            if self.state == STATE_OPEN:
                logger.info("Channel open")
                self.server_pub = self.asymmetric_encrypt.load_pub_from_str(
                    message["server_pub_key"].encode())
                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)))
        else:
            logger.warning("Invalid message type")

        self.transport.close()
        self.loop.stop()

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

        if os.path.exists("client-keys"):
            os.remove("client-keys/" + 'client_private_rsa.pem')
            os.remove("client-keys/" + 'client_public_rsa.pem')

        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
        :return:  None
        """
        with open(file_name, 'rb') as f:
            message = {'type': 'DATA', 'data': None}
            read_size = 16 * 60

            while True:
                data = f.read(16 * 60)
                message['data'] = base64.b64encode(data).decode()
                self._send(message)

                if len(data) != read_size:
                    break

            self._send({'type': 'CLOSE'})
            logger.info("File transferred. Closing transport")
            self.transport.close()

    def _send(self, message: str) -> None:
        """
        Effectively encodes and sends a message
        :param message:
        :return:
        """

        message_b = (json.dumps(message) + '\r\n').encode()
        if self.state == STATE_CONNECT:
            message_b = self.symmetric.handshake_encrypt(message_b)
        else:
            message_b = self.symmetric.encrypt(self.symmetric_cypher,
                                               message_b,
                                               self.synthesis_algorithm,
                                               self.cypher_mode,
                                               pkey=self.server_pub)
        self.transport.write(message_b)

        # Este sleep é um workaround para os blocos/pacotes perdidos ou incompletos
        time.sleep(0.05)
Ejemplo n.º 6
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.buffer = ''
        self.peername = ''
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()
        self.citizen_card = CitizenCard_All()
        self.otp = OTP()

        self.sCert = server_cert.ServerCert()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self.client_pub = None
        self.cert_pubkey = None

        self.server_pub = None
        self.server_priv = None

        self.server_cert = None
        self.server_cert_priv = None

        self.one_time_nonce = None
        self.auth_type = None

        # validate server cert
        server_cert.main()

    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.server_priv, self.server_pub = self.asymmetric_encrypt.generate_rsa_keys(
        )
        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))

        if self.state == STATE_CONNECT:
            data = self.symmetric.handshake_decrypt(data)
        elif self.state == STATE_OPEN and self.auth_type == 'cc':
            if self.citizen_card.verify_signature(self.cert_pubkey,
                                                  data[-256:],
                                                  data[:len(data) - 256]):
                data = data[:len(data) - 256]
                data = self.symmetric.decrypt(self.symmetric_cypher,
                                              data,
                                              self.synthesis_algorithm,
                                              self.cypher_mode,
                                              privkey=self.server_priv)
        elif self.state == STATE_OPEN and self.auth_type == 'login':
            data = self.symmetric.decrypt(self.symmetric_cypher,
                                          data,
                                          self.synthesis_algorithm,
                                          self.cypher_mode,
                                          privkey=self.server_priv)
        else:
            data = self.symmetric.decrypt(self.symmetric_cypher,
                                          data,
                                          self.synthesis_algorithm,
                                          self.cypher_mode,
                                          privkey=self.server_priv)
        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()

        if mtype == 'OPEN':
            ret = self.process_open(message)
        elif mtype == 'CC':
            ret = self.authenticate_client(message)
        elif mtype == 'OTP':
            ret = self.process_OTP(message)
        elif mtype == 'DATA':
            ret = self.process_data(message)
        elif mtype == 'AUTHENTICATION_RESPONSE':
            ret = self.process_authentication(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()

    def authenticate_client(self, message: str) -> bool:
        """
        When new client connects with server validates its
        one time password if valid sends challenge to verify the users
        citizen card validity
        """
        if not 'token' in message:
            logger.warning("No token provided")
            return False

        if self.otp.verify(base64.b64decode(message['token'])) is True:
            self.one_time_nonce = secrets.token_urlsafe(32)
            message = {
                'type': 'AUTHENTICATION_CHALLENGE',
                'challenge': self.one_time_nonce
            }
            self._send(message)
            return True
        else:
            logger.warning("One Time Password not valid")
            return False

    def process_authentication(self, message: str) -> bool:
        """
        Auxiliary funcion to validate signed challenge from client
        """
        logger.debug("Process Authentication: {}".format(message))

        if self.state != STATE_CONNECT:
            logger.warning("Invalid state. Discarding")
            return False
        if not 'challenge' in message:
            logger.warning("No challenge in Authentication")
            return False

        if not 'response' in message:
            logger.warning("No challenge response in Authentication")
            return False

        if not 'certificate' in message:
            logger.warning("No client certificate in Authentication")
            return False

        if message['challenge'] != self.one_time_nonce:
            logger.warning("Challenge mismatch")
            return False
        else:
            logger.debug("Verifying challenge ")
            self.cert_pubkey = self.citizen_card.deserialize_x509_pem_cert_public_key(
                base64.b64decode(message['certificate']))

            # Need to verify signature with signature already stored inside server trust certificates
            if self.citizen_card.verify_cert_cc(
                    x509.load_pem_x509_certificate(
                        base64.b64decode(message['certificate']),
                        default_backend())):
                if self.citizen_card.verify_signature(
                        self.cert_pubkey,
                        base64.b64decode(message['response']),
                        bytes(self.one_time_nonce, encoding='utf8')):
                    logger.info("Client passed challenge waiting for file")
                    self._send({'type': 'CHALLENGE OK'})
                else:
                    logger.error("Client failed challenge")
                    self.transport.close()
            else:
                logger.error("Could not validate certificate")
                self.transport.close()

        return True

    def process_OTP(self, message: str) -> bool:
        """
        Proccess an OTP message from the client

        If one time password is valid send a
        positive response, else closes connection
        """
        if self.otp.verify(base64.b64decode(message['token'])) is True:
            self._send({'type': 'CHALLENGE OK'})
            return True
        else:
            self.transport.close()
            return False

    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_CONNECT:
            logger.warning("Invalid state. Discarding")
            return False

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

        self.client_pub = self.asymmetric_encrypt.load_pub_from_str(
            message["client_public_key"].encode())
        self.symmetric_cypher = message["symmetric_cypher"]
        self.cypher_mode = message["cypher_mode"]
        self.synthesis_algorithm = message["synthesis_algorithm"]

        # 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', 'server_pub_key': self.server_pub.decode()})

        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:
            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:
            self.file.write(bdata)
            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.transport.close()
        if self.file is not None:
            self.file.close()
            self.file = None

        self.state = STATE_CLOSE

        return True

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

        message_b = (json.dumps(message) + '\r\n').encode()
        if self.state == STATE_CONNECT:
            message_b = self.symmetric.handshake_encrypt(message_b)
            self.server_cert = self.sCert.load_cert()
            self.server_cert_priv = self.sCert.load_privKey_cert()
            message_b += self.asymmetric_encrypt.sign(self.server_cert_priv,
                                                      message_b)
        self.transport.write(message_b)
Ejemplo n.º 7
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.shared = None
        self.file_name = file_name
        self.loop = loop
        self.state = STATE_CONNECT  # Initial State
        self.buffer = ''  # Buffer to receive data chunks
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()
        self.otp = OTP()

        self.challenge_passed = False
        self.citizen_card = None
        self.citizen_auth = None
        self.face_rec = None

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None

        self._public_key = None
        self._private_key = None
        self.server_pub = None
        self.password = None
        self.certificate = None

        self.auth_type = None
        self.user = None
        self.uPass = None

    def authentication(self):
        logger.info("Starting authentication process ... ")

    def handshake(self):
        self._private_key, self._public_key = self.asymmetric_encrypt.generate_rsa_keys(
        )

        logger.info(
            "Select algorithm, mode and integrity control for negotiation")
        logger.info("Select symmetric cipher algorithm:")
        logger.info("1 - AES128")
        logger.info("2 - 3DES")
        logger.info("3 - CHACHA20")
        symmetric_cypher = int(input(">> "))

        logger.info("Select cipher mode:")
        logger.info("1 - CBC")
        logger.info("2 - CTR")
        cypher_mode = int(input(">> "))

        logger.info("Select synthesis algorithm:")
        logger.info("1 - SHA-256")
        logger.info("2 - SHA-512")
        synthesis_algorithm = int(input(">> "))

        if symmetric_cypher < 1 or symmetric_cypher > 3:
            logger.error("Symmetric Cypher not allowed, try again!")
            self.handshake()
        elif cypher_mode < 1 or cypher_mode > 2:
            logger.error("Cypher mode not allowed, try again!")
            self.handshake()
        elif synthesis_algorithm < 1 or synthesis_algorithm > 2:
            logger.error("Synthesis Algorithm not allowed, try again!")
            self.handshake()

        return symmetric_cypher, cypher_mode, synthesis_algorithm

    def simple_menu(self):
        """
        Let client select the type of authentication to use
        and triggers its process
        """
        logger.info("Select authentication type:")
        logger.info("1 - Citizen card")
        logger.info("2 - Login")

        opt = int(input(">> "))

        # gerar otp
        self.uPass = self.otp.generate()

        if opt == 1:
            self.auth_type = "cc"
            try:
                self.citizen_card = CitizenCard_Client()
                self.citizen_auth = CitizenCard_All()
                message = {
                    'type': 'CC',
                    'token': base64.b64encode(self.uPass).decode()
                }
            except:
                logger.error(
                    "Citizen card reader probably not connected! Exiting ...")
                exit(1)
            self.veritfy_card_connection()
            self._send(message)
        elif opt == 2:
            self.auth_type = "login"
            logger.info("Enter your username:"******">> ")
            message = {
                'type': 'OTP',
                'user': self.user,
                'token': base64.b64encode(self.uPass).decode()
            }
            self._send(message)
        else:
            logger.error("Enter correct number please.")
            self.simple_menu()

    def veritfy_card_connection(self):
        try:
            logger.info(self.citizen_card.get_name())
        except Exception:
            logger.error("Citizen card probably not connected! Exiting ...")
            exit(1)

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

        :param transport: The transport stream to use for this client
        :return: No return
        """
        logger.info("")

        self.transport = transport

        self.simple_menu()

        logger.debug('Connected to Server')
        self.symmetric_cypher, self.cypher_mode, self.synthesis_algorithm = self.handshake(
        )

        # Esta mensagem apenas será enviada se passar no challenge por isso foi o seu envio foi para a função on_frame
        #
        # message = {'type': 'OPEN',
        #            'file_name': self.file_name,
        #            'symmetric_cypher': self.symmetric_cypher,
        #            'cypher_mode': self.cypher_mode,
        #            'synthesis_algorithm': self.synthesis_algorithm,
        #            # É SUPOSTO ENVIAR ASSIM (str) A PUB KEY?
        #            'client_public_key': self._public_key.decode()
        #            }
        # self._send(message)
        #
        # logger.info(message)

        self.state = STATE_OPEN

    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
        :return:
        """
        logger.debug('Received: {}'.format(data))
        if self.state == STATE_OPEN:
            signature = data[-256:]
            data = data[:len(data) - 256]
            f = open(
                "certs\\server.pem"
                if sys.platform == 'win32' else "certs/server.pem", "rb")
            server_cert = x509.load_pem_x509_certificate(
                f.read(), default_backend())
            if not self.asymmetric_encrypt.verify(server_cert, data,
                                                  signature):
                self.transport.close()
                self.loop.stop()
            data = self.symmetric.handshake_decrypt(data)
            logger.debug('decrypted: {}'.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
        :return:
        """

        # 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)

        if mtype == 'OK':  # Server replied OK. We can advance the state
            if self.state == STATE_OPEN:
                logger.info("Channel open")
                self.server_pub = self.asymmetric_encrypt.load_pub_from_str(
                    message["server_pub_key"].encode())
                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 == 'AUTHENTICATION_CHALLENGE':  # Server replied with a challenge to authenticate clients
            if self.state == STATE_OPEN and self.auth_type == "cc":
                logger.info(
                    "Authentication process, signing challenge from server")
                challenge_response = self.citizen_card.sign_with_cc(
                    message["challenge"])

                # Only need the digital signature certificate from cc
                self.certificate = self.citizen_card.get_x509_certificates(
                    KEY_USAGE=lambda x: x.value.digital_signature)
                self.certificate = self.certificate[0]
                # Convert certificate to bytes
                bytes_cert = self.citizen_auth.serialize(self.certificate)

                self._send({
                    'type':
                    'AUTHENTICATION_RESPONSE',
                    'challenge':
                    message['challenge'],
                    'response':
                    base64.b64encode(bytes(challenge_response)).decode(),
                    'certificate':
                    base64.b64encode(bytes_cert).decode()
                })
        elif mtype == 'CHALLENGE OK':
            message = {
                'type': 'OPEN',
                'file_name': self.file_name,
                'symmetric_cypher': self.symmetric_cypher,
                'cypher_mode': self.cypher_mode,
                'synthesis_algorithm': self.synthesis_algorithm,
                'client_public_key': self._public_key.decode()
            }
            self._send(message)
            self.challenge_passed = True

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

        # self.transport.close()
        # self.loop.stop()

    def connection_lost(self, exc):
        """
        Connection was lost for some reason.
        :param exc:
        :return:
        """
        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
        :return:  None
        """
        with open(file_name, 'rb') as f:
            message = {'type': 'DATA', 'data': None}
            read_size = 16 * 60

            while True:
                data = f.read(16 * 60)
                message['data'] = base64.b64encode(data).decode()
                self._send(message)

                if len(data) != read_size:
                    break

            self._send({'type': 'CLOSE'})
            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.debug("Send {}".format(message))
        message_b = (json.dumps(message) + '\r\n').encode()
        if self.state == STATE_CONNECT:
            message_b = self.symmetric.handshake_encrypt(message_b)
        elif self.state == STATE_OPEN and self.challenge_passed is False:
            message_b = self.symmetric.handshake_encrypt(message_b)
        elif self.state == STATE_OPEN and self.challenge_passed is True:
            message_b = self.symmetric.encrypt(self.symmetric_cypher,
                                               message_b,
                                               self.synthesis_algorithm,
                                               self.cypher_mode,
                                               pkey=self.server_pub)
            self.state = STATE_DATA
        elif self.state == STATE_DATA:
            message_b = self.symmetric.encrypt(self.symmetric_cypher,
                                               message_b,
                                               self.synthesis_algorithm,
                                               self.cypher_mode,
                                               pkey=self.server_pub)
        self.transport.write(message_b)

        # Este sleep é um workaround para os blocos/pacotes perdidos ou incompletos
        time.sleep(0.05)
Ejemplo n.º 8
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.buffer = ''
        self.peername = ''
        self.asymmetric_encrypt = Asymmetric()
        self.symmetric = Symmetric()

        self.symmetric_cypher = None
        self.cypher_mode = None
        self.synthesis_algorithm = None
        self.client_pub = None
        self.server_pub = None
        self.server_priv = None

    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))

        logger.info(
            "New client connected, introduce password to generate rsa key")
        password = getpass.getpass('Password:'******'Received: {}'.format(data))
        if self.state == STATE_CONNECT:
            data = self.symmetric.handshake_decrypt(data)
        else:
            data = self.symmetric.decrypt(self.symmetric_cypher,
                                          data,
                                          self.synthesis_algorithm,
                                          self.cypher_mode,
                                          privkey=self.server_priv)

        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()

        if mtype == 'OPEN':
            ret = self.process_open(message)
        elif mtype == '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()

    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_CONNECT:
            logger.warning("Invalid state. Discarding")
            return False

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

        # print(message["client_public_key"])

        self.client_pub = self.asymmetric_encrypt.load_pub_from_str(
            message["client_public_key"].encode())
        self.symmetric_cypher = message["symmetric_cypher"]
        self.cypher_mode = message["cypher_mode"]
        self.synthesis_algorithm = message["synthesis_algorithm"]

        # 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', 'server_pub_key': self.server_pub.decode()})

        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:
            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:
            self.file.write(bdata)
            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.transport.close()
        if self.file is not None:
            self.file.close()
            self.file = None

        if os.path.exists("server-keys"):
            os.remove("server-keys/" + str(self.peername[1]) +
                      '_private_rsa.pem')
            os.remove("server-keys/" + str(self.peername[1]) +
                      '_public_rsa.pem')

        self.state = STATE_CLOSE

        return True

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

        message_b = (json.dumps(message) + '\r\n').encode()
        if self.state == STATE_CONNECT:
            message_b = self.symmetric.handshake_encrypt(message_b)
        self.transport.write(message_b)