import socket import struct import threading import time from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature class ClientTCP: """ Client TCP compatibile con Windows e Linux. """ MAX_FRAME_SIZE = 256 TIMEOUT_MS = 100 # Timeout in millisecondi # Tipi di messaggio TLV TYPE_TEXT_MESSAGE = 0x01 TYPE_CLIENT_PUBLIC_KEY = 0x02 TYPE_SERVER_PUBLIC_KEY = 0x03 TYPE_ENCRYPTED_MESSAGE = 0x04 TYPE_ACK = 0x05 def __init__(self, server_address, server_port): self.server_address = server_address self.server_port = server_port self.sock = None self.connected = False self.private_key = None self.public_key = None self.server_public_key = None self.buffer = {"fragments": [], "total_length": 0, "message_type": 0} self.receive_thread = None self.running = True def connect(self): try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.server_address, self.server_port)) self.connected = True # Genera chiavi RSA self.private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) self.public_key = self.private_key.public_key() # Invia chiave pubblica self._send_client_public_key() # Avvia thread di ricezione self.receive_thread = threading.Thread(target=self._receive_data) self.receive_thread.daemon = True self.receive_thread.start() except Exception as e: print(f"Errore durante la connessione: {e}") self.connected = False return False return True def _send_client_public_key(self): serialized_public_key = self.public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) self._send_to_server(self.TYPE_CLIENT_PUBLIC_KEY, serialized_public_key) def _send_to_server(self, message_type, message_data): header = struct.pack('!BI', message_type, len(message_data)) full_message = header + message_data for i in range(0, len(full_message), self.MAX_FRAME_SIZE): fragment = full_message[i:i + self.MAX_FRAME_SIZE] try: self.sock.sendall(fragment) except Exception as e: print(f"Errore durante l'invio: {e}") self.disconnect() return False return True def send_message(self, message): if not self.connected or self.server_public_key is None: print("Errore: Non connesso o chiave server non disponibile.") return False try: message_bytes = message.encode('utf-8') ciphertext = self.server_public_key.encrypt( message_bytes, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) return self._send_to_server(self.TYPE_ENCRYPTED_MESSAGE, ciphertext) except Exception as e: print(f"Errore durante cifratura/invio: {e}") return False def _receive_data(self): self.sock.setblocking(False) # Imposta il socket in modalità non bloccante while self.running: if not self.connected: break try: try: data = self.sock.recv(self.MAX_FRAME_SIZE) if not data: print("Server disconnesso.") self.disconnect() break self._process_received_data(data) except BlockingIOError: # Nessun dato disponibile (non bloccante) time.sleep(self.TIMEOUT_MS / 1000) # Attendi per evitare busy-waiting continue # Torna all'inizio del loop except ConnectionResetError: #Errore connessione resettata print("Connessione resettata dal server.") self.disconnect() break except Exception as ex: print("Errore nella ricezione:", ex) self.disconnect() break def _process_received_data(self, data): self.buffer["fragments"].append(data) self.buffer["total_length"] += len(data) while self.buffer["total_length"] >= 5: header_data = b"".join(self.buffer["fragments"])[:5] message_type, message_length = struct.unpack('!BI', header_data) if self.buffer["total_length"] >= 5 + message_length: message_data = b"".join(self.buffer["fragments"])[5:5 + message_length] remaining_data = b"".join(self.buffer["fragments"])[5 + message_length:] self.buffer["fragments"] = [remaining_data] if remaining_data else [] self.buffer["total_length"] = len(remaining_data) self.buffer["message_type"] = 0 self._handle_received_message(message_type, message_data) else: break def _handle_received_message(self, message_type, message_data): if message_type == self.TYPE_SERVER_PUBLIC_KEY: try: self.server_public_key = serialization.load_pem_public_key( message_data, backend=default_backend() ) print("Chiave pubblica del server ricevuta.\n") except Exception as e: print(f"Errore caricamento chiave server: {e}") elif message_type == self.TYPE_ENCRYPTED_MESSAGE: try: plaintext = self.private_key.decrypt( message_data, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) print(f"Messaggio dal server: {plaintext.decode('utf-8')}") except Exception as e: print(f"Errore decifrazione: {e}") elif message_type == self.TYPE_TEXT_MESSAGE: try: print(message_data.decode('utf-8').rstrip()) except UnicodeDecodeError: print("Errore decodifica.") elif message_type == self.TYPE_ACK: try: plaintext = self.private_key.decrypt( message_data, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None ) ) print(f"Messaggio ACK dal server: {plaintext.decode('utf-8')}") except Exception as e: print(f"Errore decifrazione: {e}") else: print(f"Tipo messaggio sconosciuto: {message_type}") def disconnect(self): self.running = False self.connected = False if self.sock: try: self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() except: pass if self.receive_thread and self.receive_thread.is_alive(): try: self.receive_thread.join(timeout=1) except: pass print("Disconnesso.") if __name__ == '__main__': server_address = 'localhost' server_port = 5001 client = ClientTCP(server_address, server_port) if not client.connect(): exit(1) try: while True: message = input("Messaggio (o 'quit'): ") if message.lower() == 'quit': break if not client.send_message(message): break time.sleep(0.1) #Piccola pausa except KeyboardInterrupt: print("Interrotto.") finally: client.disconnect()