-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathServer.py
More file actions
209 lines (174 loc) · 9.63 KB
/
Server.py
File metadata and controls
209 lines (174 loc) · 9.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import socket
import json
import os
import certifi
from dotenv import load_dotenv
from pymongo.mongo_client import MongoClient
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import bcrypt
from nacl.public import PrivateKey
from nacl.bindings import crypto_scalarmult
# ---- Caricamento variabili ambiente ----
load_dotenv()
DB_USERNAME = os.getenv("DB_USERNAME")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_CLUSTER = os.getenv("DB_CLUSTER")
DB_NAME = os.getenv("DB_NAME")
uri = f"mongodb+srv://{DB_USERNAME}:{DB_PASSWORD}@{DB_CLUSTER}/?appName=ClusterArduino"
client = MongoClient(uri, tlsCAFile=certifi.where())
db = client[DB_NAME]
collection = db['utenti']
# ---- Funzioni AES ----
def encrypt_aes(plaintext, aes_key):
cipher = AES.new(aes_key, AES.MODE_ECB) # Crea un oggetto AES, con chiave e modalità di cifratura
padded_data = pad(plaintext.encode('utf-8'), AES.block_size) # Aggiunge il padding
return cipher.encrypt(padded_data).hex() # Cifra il testo (la dimensione dei blocchi è già definita) e restituisce il cifrato convertito da byte a stringa
def decrypt_aes(encrypted_hex, aes_key):
cipher = AES.new(aes_key, AES.MODE_ECB) # Crea un oggetto AES
decrypted = cipher.decrypt(bytes.fromhex(encrypted_hex)) # Converte il testo cifrato da stringa a byte e lo decifra
return unpad(decrypted, AES.block_size).decode('utf-8') # Rimuove il padding, riconverte il testo in chiaro da byte a stringa e lo restituisce
# ---- Funzione gestione client ----
def gestisci_client(conn, addr):
print(f"[SERVER] Connessione da {addr}")
# --- Genera coppia DH server ---
server_priv = PrivateKey.generate().encode()
server_pub = PrivateKey(server_priv).public_key.encode()
# --- Ricezione chiave pubblica del client ---
try:
# Leggi fino al primo newline (può includere \r\n)
data = b""
while True:
chunk = conn.recv(1)
if not chunk:
raise ConnectionError("Connessione chiusa prima della chiave")
data += chunk
if data.endswith(b'\n'):
break
# Rimuove eventuali \r e \n dalla fine
data = data.strip()
print(f"[SERVER] Ricevuto raw: {data}")
msg_json = json.loads(data.decode())
if msg_json["type"] != "pubkey" or len(msg_json["value"]) != 64:
raise ValueError("Chiave pubblica client invalida")
client_pub_bytes = bytes.fromhex(msg_json["value"])
print(f"[SERVER] Chiave pubblica client ricevuta: {msg_json['value'][:16]}...")
except Exception as e:
print(f"[SERVER] Errore ricezione chiave client: {e}")
conn.close()
return
# --- Invio chiave pubblica server al client ---
response = json.dumps({"type": "pubkey", "value": server_pub.hex()}) + "\n"
conn.sendall(response.encode())
print(f"[SERVER] Chiave pubblica server inviata: {server_pub.hex()[:16]}...")
# --- Calcola shared secret e AES key ---
try:
shared_secret = crypto_scalarmult(server_priv, client_pub_bytes)
aes_key = shared_secret[:16] # AES-128
print(f"[SERVER] AES key derivata: {aes_key.hex()}")
except Exception as e:
print(f"[SERVER] Errore calcolo shared secret: {e}")
conn.close()
return
utente_corrente = None
buffer = b""
# --- Loop principale per messaggi cifrati ---
while True:
try:
data = conn.recv(1024)
if not data:
print(f"[SERVER] {addr} ha chiuso la connessione")
break
buffer += data
while b'\n' in buffer:
line, buffer = buffer.split(b'\n', 1)
line = line.strip()
if not line:
continue
# --- Decifra e parse JSON ---
try:
# Converti in stringa hex prima di decifrare
encrypted_hex = line.decode('ascii')
decrypted_line = decrypt_aes(encrypted_hex, aes_key)
msg = json.loads(decrypted_line)
print(f"[SERVER] Messaggio decifrato: {msg}")
except Exception as e:
print(f"[SERVER] Errore decifratura/JSON: {e}")
continue
# Identifica il tipo di messaggio
tipo = msg.get("type")
valore = msg.get("value")
risposta = {}
# --- Gestione carta ---
if tipo == "card":
utente_corrente = None
# Cerca tutti gli utenti e verifica l'hash bcrypt per trovare corrispondenza
for utente in collection.find():
try:
card_hash = utente["card_id"]
# Se l'hash è salvato come stringa sul DB, lo converte in bytes
if isinstance(card_hash, str):
card_hash = card_hash.encode('utf-8')
# Verifica se il valore ricevuto corrisponde all'hash bcrypt memorizzato
if bcrypt.checkpw(valore.encode('utf-8'), card_hash):
utente_corrente = utente # Imposta l'utente corrente
risposta["status"] = "CARTA_VALIDA" # Setta lo stato a CARTA VALIDA
print(f"Carta valida per utente: {utente.get('nome', 'N/A')}")
break # Esce dal ciclo appena trova corrispondenza
except Exception as e:
print(f"Errore verifica carta per utente {utente.get('_id')}: {e}")
continue
# Se nessun utente è stato trovato
if not utente_corrente:
risposta["status"] = "CARTA_NON_VALIDA" # Setta lo stato a CARTA NON VALIDA
print(f"Carta non valida: {valore}")
# --- Gestione PIN ---
elif tipo == "pin":
if utente_corrente: # Verifica che ci sia un utente corrente (carta già validata)
try:
pin_hash = utente_corrente["pin"]
# Se l'hash è salvato come stringa sul DB, lo converte in bytes
if isinstance(pin_hash, str):
pin_hash = pin_hash.encode('utf-8')
# Confronta il PIN ricevuto con quello nel DB associato all'utente
if bcrypt.checkpw(valore.encode('utf-8'), pin_hash):
risposta["status"] = "ACCESSO_CONCESSO" # Se corretto setta lo stato ad ACCESSO CONCESSO
risposta["nome"] = utente_corrente["nome"]
risposta["cognome"] = utente_corrente["cognome"]
risposta["saldo"] = str(utente_corrente["saldo"])
print(f"PIN corretto per {utente_corrente['nome']} {utente_corrente['cognome']}")
else:
risposta["status"] = "ACCESSO_NEGATO" # Se sbagliato setta lo stato ad ACCESSO NEGATO
print("PIN errato")
except Exception as e:
risposta["status"] = "ACCESSO_NEGATO"
print(f"Errore verifica PIN: {e}")
else:
risposta["status"] = "ACCESSO_NEGATO"
print("Nessuna carta valida inserita")
else:
risposta["status"] = "ERRORE"
print("Tipo non riconosciuto:", tipo)
# Dopo aver ottenuti i dati dal database li inviamo all'arduino per mostrarli sullo schermo LCD, prima però vanno cifrati
encrypted_response = encrypt_aes(json.dumps(risposta), aes_key) # Converte il JSON, con i dati ottenuti, in una stringa che viene cifrata
conn.sendall((encrypted_response + "\n").encode()) # Aggiunge un carattere \n alla fine della stringa e la converte in byte, inviando i byte al client
print(f"[SERVER] Risposta inviata: {risposta}")
except ConnectionResetError:
print(f"[SERVER] {addr} ha chiuso bruscamente la connessione")
break
except Exception as e:
print(f"[SERVER] Errore sconosciuto con {addr}: {e}")
break
conn.close()
# ---- Socket Server ----
HOST = "0.0.0.0"
PORT = 5000
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # Crea un socket TCP
s.bind((HOST, PORT)) # Associa il socket all'indirizzo e alla porta del server
s.listen(5) # Mette il socket in modalità ascolto per accettare connessioni
print(f"[SERVER] In ascolto su porta {PORT}")
print("[SERVER] Pronto ad accettare connessioni...")
while True:
conn, addr = s.accept() # Blocca il programma finché un client non si connette al server,
# quando si connette, "conn" indica il nuovo socket e "addr" l'indirizzo del client
gestisci_client(conn, addr) # Le informazioni sono mandate alla funzione di gestione del client