├── .gitignore ├── README.md ├── Secure Communications System.pptx ├── client.py ├── create_db.py ├── logs.txt ├── mykeypad.py ├── requirement.txt ├── server.py └── user_db.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Super Secure Chat Application using Raspberry Pi 2 | 3 | In this project, we are developing a super secure communication system using 4 | Raspberry Pi microcontroller kits to build custom terminals to communicate with each other. The 5 | communication is peer-to-peer between the Raspberry Pi peers, but they communicate with the 6 | server to log into the system and to get information. Once the communication between Pi 7 | terminals is established, the server is idle. 8 | This project can be developed in stages. Encryption is desirable but adds a layer of complexity 9 | therefore it can be left out initially. The most important is the communication between the client 10 | (Raspberry Pi terminal) and the server. As the client-server interaction progresses, the client 11 | moves through several states. The demonstration will be considered successful if the terminals 12 | manage to connect for one round trip exchange: “Hello, are you there?”, “Yes, here I am!”. 13 | 14 | --- 15 | 16 | ## Hardware 17 | 18 | 19 | 20 | The secure terminal consists of the Pi, monitor and mouse, with a 4×4 keypad that will be 21 | used to enter the user’s passcode. The server is simply the Windows PC (or a student owned laptop). The terminals and server are connected to each other over the IP network (wired or wireless). 22 | 23 | --- 24 | 25 | ## Typical session 26 | 27 | Both users must first log into the system. Each sends an Authentication Request to the server, including the user ID and passcode. The passcode was entered on the 4×4 keypad attached to each Pi. The server replies with an Authentication Reply granting them access, in the case of a passcode match, or denying access otherwise. When a user is logged in, the server remembers that user’s IP address. After logging in successfully, each terminal opens a socket to listen for incoming connections on port 8085. 28 | 29 | A typical session begins by user A (Ali) at one terminal wishing to communicate with user B (Bianca) at her terminal. Ali sends a Lookup Request to the server asking for Bianca’s address. If encryption is implemented, the server also gives Ali the encryption key for Bianca. Next, Ali’s terminal attempts to socket.connect(ADDRESS) a TCP connection to Bianca’s address on port 8085. If Bianca is logged in and her terminal is socket.accept()-ing connections, Ali’s terminal sends a Connect Request. Bianca must make sure that the connection request is legitimate. Bianca sends a Lookup Request to the server to verify Ali’s IP address and (optionally, if implemented) Ali’s encryption key. Once Bianca has verified Ali’s connection request, Bianca sends a Connection Reply to Ali. Finally, the communication proceeds. Each terminal goes through an infinite loop of input()-->send() and recv()-->print(). 30 | The communication ends when either user types CTRL-C to end the chat session. After the end, each terminal continues to listen for connection requests to begin another chat session. A typical session session might look like this. Here ali talks to bianca then types CTRL-C to end the connection (what ali types is in italics). 31 | 32 | --- 33 | 34 | **Login: ali** 35 | **Enter passcode on keypad.** 36 | **Logged in.** 37 | **Enter destination user ID: bianca** 38 | **Connection established, type your messages** 39 | **Hello, how are you today?** 40 | **I am fine, how about you?** 41 | **I am well. Goodbye.** 42 | **CTRL-C connection ended** 43 | **Enter destination user ID:** 44 | 45 | --- 46 | 47 | In another session, ali is logged in and receives a connection request from bianca who then 48 | types CTRL-C to end the connection (what ali types is in italics). 49 | 50 | --- 51 | 52 | **Login:** 53 | **Enter passcode on keypad.** 54 | **Logged in.** 55 | **Enter destination user ID:** 56 | **Connection received from user bianca, type your messages** 57 | **Hello, how are you today?** 58 | **I am fine, how about you?** 59 | **I am well. Goodbye.** 60 | **Connection ended** 61 | **Enter destination user ID:** 62 | 63 | --- 64 | 65 | # Requirements 66 | 67 | --- 68 | 69 | ## Passcode 70 | 71 | The user must use the keypad to enter a numeric passcode, consisting of one or more digits 0—9. The ‘A’ key serves as the “Enter & Validate” key. The passcode is never displayed, not even “*” characters that would “leak” the number of digits. 72 | 73 | **Optional:** The ‘B’ key is Backspace and the ‘C’ key is Cancel 74 | 75 | --- 76 | 77 | ## Communications protocol 78 | 79 | The chat session must start with several phases to log in to the system, request user information and request a chat connection before any messages can be exchanged. The chat client goes through several states during the phases of the connection. 80 | 81 | Information exchange between server and client is done with JSON formatted messages. Each message is a sequence of name:value pairs. Message types are identified with the msgtype key present in all messages. 82 | 83 | --- 84 | 85 | ### Authentication phase 86 | 87 | Each client sends an Authentication Request to the server. The authentication request includes the user ID and the hashed passcode. The server looks up the user ID and compares the hashed passcode to the hashed passcode it has stored. If the hashed passcodes match, the server returns an Authentication Reply message and also stores the current IP address of the user who has just authenticated. 88 | 89 | The client sends an Authentication Request message to the server. 90 | 91 | --- 92 | 93 | * **msgtype: “AUTHREQ” 94 | * **userid: string as entered by the user making the request; 95 | * **passcode:** the user’s passcode entered by the user on the keypad, then salted and hashed 96 | 97 | **Authentication Request Message Example** 98 | 99 | { 100 | "msgtype": "AUTHREQ", 101 | "userid": "sachin", 102 | "passcode": "0123hh3ghjgjh23gh23f23fewrew13" 103 | } 104 | 105 | The server sends the Authentication Reply message to the client telling the client if the user is authenticated or not. The value of the status key is either “GRANTED” if successful (Listing 2), or “REFUSED” otherwise 106 | 107 | 108 | * **msgtype:** “AUTHREPLY” 109 | * **userid:** string as entered by the user making the request; 110 | * **status:** “GRANTED” if the credentials were accepted, “REFUSED” otherwise. 111 | 112 | --- 113 | 114 | **Authentication Grant Reply Message Example - Authentication Granted** 115 | 116 | { 117 | "msgtype": "AUTHREPLY", 118 | "status": "Granted" 119 | } 120 | 121 | **Authentication Grant Reply Message Example - Authentication Refused** 122 | 123 | { 124 | "msgtype": "AUTHREPLY", 125 | "status": "REFUSED" 126 | } 127 | 128 | --- 129 | 130 | ## Lookup phase 131 | 132 | The initiating client sends a Lookup Request to the server. The request includes the user ID of the initiator and the user ID of the person the initiator wishes to contact. The server replies with a Lookup Reply message giving information about the person, or no information if that person is not logged in. As a precaution against requests by users not logged in, the server also replies with no information if the user making the request is not logged in. The server replies with a Lookup Reply message. The reply includes the user ID of the person the initiator wishes to contact, the IP address of her/his client and (optional) the encryption key to be used when communicating with that person. 133 | 134 | The client sends the Lookup Request message to the server 135 | 136 | * **msgtype:** “LOOKUPREQ”; 137 | * **userid:** string as entered by the user making the request; 138 | * **lookup:** the user ID of the person that the user is calling. 139 | 140 | **Lookup Request message example** 141 | 142 | { 143 | "msgtype": "LOOKUPREQ", 144 | "userid": "sachin", 145 | "lookup": "rajat" 146 | } 147 | 148 | **The server sends the Lookup Reply message to the client, with “status”:“SUCCESS” and information if found, or “status”:“NOTFOUND” and empty information fields otherwise.** 149 | 150 | * **msgtype:** “LOOKUPREPLY”; 151 | * **status:** “SUCCESS” if the user ID is logged in, “NOTFOUND” otherwise 152 | * **answer:** the user ID of the person that the user is calling; 153 | * **address:** the IP address of the remote user; 154 | * **encryptionkey:** the encryption key as a string. 155 | 156 | **Lookup Reply message example – User found, information returned** 157 | 158 | { 159 | "msgtype": "LOOKUPREPLY", 160 | "status": "SUCCESS", 161 | "answer": "rajat", 162 | "address": "192.168.0.12", 163 | "encryptionkey": "1234", 164 | } 165 | 166 | **Lookup Reply message example – User not found** 167 | 168 | { 169 | "msgtype": "LOOKUPREPLY", 170 | "status": "SUCCESS", 171 | "answer": "", 172 | "address": "", 173 | "encryptionkey": "", 174 | } 175 | 176 | --- 177 | 178 | **Connection Phase** 179 | 180 | The initiating terminal sends a Connection Request to the destination terminal. The request includes the user ID of the initiator. The destination terminal performs a Lookup Request of its own to the server and, if the address of the initiating user’s terminal matches the one registered with the server, the destination terminal replies with a Connect Reply “accepted” message to go ahead with the chat session, otherwise it sends a Connect Reply “refused” message. The initiator sends the Connect Request message to the destination terminal 181 | 182 | * **msgtype:** “CONNECTREQ”; 183 | * **initiator:** the user ID of the person making the request. 184 | 185 | **Connect Request message example – sachin requests connection to rajat** 186 | 187 | { 188 | "msgtype": "CONNECTREQ", 189 | "initiator": "ali" 190 | } 191 | 192 | **The destination sends the Connect Reply message “status”:“ACCEPTED” to the initiator terminal, or “status”:“REFUSED” otherwise.** 193 | 194 | * msgtype: “CONNECTREPLY” 195 | * answer: “ACCEPTED” or “REFUSED” 196 | 197 | 198 | 199 | **Connect Reply message example – Connection request accepted by destination** 200 | 201 | { 202 | "msgtype": "CONNECTREPLY", 203 | "status": "ACCEPTED" 204 | } 205 | 206 | **Connect Reply message example – Connection request refused** 207 | 208 | { 209 | "msgtype": "CONNECTREPLY", 210 | "status": "REFUSED" 211 | } 212 | 213 | --- 214 | 215 | 216 | **Chat phase** 217 | 218 | No special messaging format is required in the chat phase. The two-way connection is established between terminals and the main loop can simply read from the open connection as if it was a stream of bytes. Each client can “catch” the CTRL-C KeyboardInterrupt to end the chat at any time. No special protocol is needed to end a session. 219 | 220 | --- 221 | ## Cryptography 222 | 223 | The extra functionality of hashing the passcodes and encrypting/decrypting the messages and chat exchanges is left as an option. A bonus of up to 2 points will be awarded for reasonably convincing hashing of the passcodes. A bonus of up to 3 points for reasonably convincing symmetric encryption of the messages and chat exchanges using the cryptographymodule in Python. 224 | 225 | 226 | --- 227 | 228 | --- 229 | -------------------------------------------------------------------------------- /Secure Communications System.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachinyadav3496/SuperSecureChatApplication/b7b9df8d31c0246d16568ae2e7ebe4edc2ad605c/Secure Communications System.pptx -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | """ 2 | client file for super secure communication 3 | a client will try to connect with auth request to server 4 | using userid, passcode (inserted from matrix keypad on resberry pi) 5 | and auth request header and process further the chat session by look 6 | up request for user and starts a chat session. 7 | """ 8 | #!/usr/bin/env python 9 | import socket 10 | #importing socket module create sockets for 11 | #receiving and sending bytes over network 12 | import json 13 | #importing json module data serialization and deserialization tool 14 | import threading 15 | #importing threading module use concurrent programming 16 | import hashlib 17 | #hashlib module for hashing passcodes 18 | import sys 19 | #importing sys module for system i/o or exit functionality 20 | import time 21 | #importing time moddule for introducing delay and time functionality 22 | from cryptography.fernet import Fernet 23 | #cryptography module for encryting ad decryting messages for secure communication 24 | import digitalio 25 | #digitalio module to access i/o ports of resberry pi 26 | import adafruit_matrixkeypad 27 | #adafruit_matrixkeypad module to access 4x4 matrix keypad 28 | import board 29 | #board module to acces i/o pins of resberry pi 30 | 31 | #SERVER_IP = "192.168.1.7" 32 | SERVER_PORT = 8082 33 | #Default Server Port 34 | 35 | def log(msg): 36 | """ 37 | log messages for debugging purpose. 38 | """ 39 | #print(msg) 40 | open('access_log.txt', 'a').write(msg+"\n") 41 | 42 | class State(): 43 | """Abstract parent state class.""" 44 | def __init__(self): 45 | pass 46 | 47 | @property 48 | def name(self): 49 | """ 50 | state name property 51 | """ 52 | return '' 53 | 54 | def enter(self, machine): 55 | """ 56 | tasks to be completed on entry point of state 57 | """ 58 | 59 | def exit(self, machine): 60 | """ 61 | exiting from the state 62 | """ 63 | 64 | def update(self, machine): 65 | """ 66 | transition from one state to another happens here 67 | """ 68 | 69 | class Idle(State): 70 | """ 71 | Idle State 72 | """ 73 | def __init__(self): 74 | State.__init__(self) 75 | self.string = "" 76 | self.is_authenticated = False 77 | self.rows = [] 78 | self.cols = [] 79 | self.keys = None 80 | self.keypad = None 81 | @property 82 | def name(self): 83 | """ 84 | state name property 85 | """ 86 | return "Idle" 87 | 88 | def enter(self, machine): 89 | """ 90 | initilizing keypad and printing welcome message 91 | """ 92 | log("Inside Enter event of state Idle") 93 | log("Initlizing keypad into Chat Application") 94 | if not self.keypad: 95 | machine.keypad = self.initlize_keypad() 96 | log(f"added keypad to machine as {machine.keypad}") 97 | print("\nWelcome to Super Secure Chat Application\n") 98 | 99 | def exit(self, machine): 100 | """ 101 | "exiting from IDLE State" 102 | """ 103 | log("exiting from Idle State") 104 | #State.exit(self, machine) 105 | def update(self, machine): 106 | """ 107 | processing input from user 108 | userid, passcode input 109 | """ 110 | try: 111 | log("inside update method of Idle state") 112 | machine.userid = input("User id: ") 113 | log("processing keypad press events") 114 | machine.passcode = self.keypress(machine) 115 | log(f"you have pressed passcode {machine.passcode}") 116 | log(f"Your userid is {machine.userid}") 117 | if machine.userid and machine.passcode: 118 | log("ready to authenticate your userid & passcode") 119 | auth = self.auth_request(machine) 120 | log(f"recevied authentication as {auth}") 121 | if auth: 122 | log("Authentication Granted by server") 123 | log("Switching to LoggedIn State") 124 | machine.gotostate("LoggedIn") 125 | else: 126 | log("Server has Refused your authentication") 127 | log("Make sure your userid are correct") 128 | print("\nInvalid Credentials\n") 129 | log("Switching to Idle State") 130 | machine.gotostate("Idle") 131 | else: 132 | log("No userid or passcode is pressed so swithching to Idle State") 133 | print("\nInvalid Credentials\n") 134 | machine.gotostate("Idle") 135 | except KeyboardInterrupt: 136 | log("..........Exiting Program.......") 137 | sys.exit(0) 138 | 139 | def initlize_keypad(self): 140 | """ 141 | initlizing 4x4 pi keypad 142 | """ 143 | log("Initlization of keypad starts in initlize keypad method") 144 | row_pins = [board.D21, board.D20, board.D16, board.D12] 145 | log(f"row_pins are {row_pins}") 146 | col_pins = [board.D26, board.D19, board.D13, board.D6] 147 | log(f"col_pins are {col_pins}") 148 | self.rows = [] 149 | self.cols = [] 150 | log(f"cols = {self.cols} and rows = {self.rows}") 151 | for row_pin in row_pins: 152 | self.rows.append(digitalio.DigitalInOut(row_pin)) 153 | log(f"after initlizing i/o pins rows = {self.rows}") 154 | for col_pin in col_pins: 155 | self.cols.append(digitalio.DigitalInOut(col_pin)) 156 | log(f"after initlizing i/o pins cols = {self.cols}") 157 | log("Defining Keypad Layout") 158 | self.keys = ( 159 | ("D", "#", 0, "*"), 160 | ("C", 9, 8, 7), 161 | ("B", 6, 5, 4), 162 | ("A", 3, 2, 1) 163 | ) 164 | log(f"keypad is \n{self.keys}") 165 | self.keypad = adafruit_matrixkeypad.Matrix_Keypad(self.rows, self.cols, self.keys) 166 | log(f"matrix keypad now is {self.keypad}") 167 | print("Intilization of keypad is sucessfull") 168 | return self.keypad 169 | 170 | def keypress(self, machine): 171 | """ 172 | Reading input from 4x4 pi input keyapd 173 | passcode keys --> 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 174 | B --> backspace 175 | C --> cancel 176 | A --> enter 177 | """ 178 | log("Inside keypress event of matrix keypad") 179 | print("Enter your Passcode: ", end='', flush=True) 180 | log("accessing machin keypad as {machine.keypad}") 181 | #keypad = machine.keypad 182 | try: 183 | self.string = "" 184 | while True: 185 | key_pressed = machine.keypad.pressed_keys 186 | time.sleep(0.2) 187 | if key_pressed: 188 | key_pressed = str(key_pressed[0]) 189 | log(f"Pressed keys: {key_pressed}") 190 | #print(f"Pressed: {kp}") 191 | if key_pressed == "C": 192 | print(".............Exiting the Program...........") 193 | log("You have cancelled the input event redirecting to Idle state") 194 | machine.gotostate("Idle") 195 | elif key_pressed == "B": 196 | if self.string: 197 | log("You have pressed backspace key") 198 | self.string = self.string[:-1] 199 | 200 | elif key_pressed in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']: 201 | log(f"You have pressed key {key_pressed}") 202 | self.string += key_pressed 203 | #print("*", end="", flush=True) 204 | 205 | elif key_pressed == "A": 206 | log(f"You have submitted {self.string} as passcode") 207 | return self.string 208 | 209 | else: 210 | print("Invalid Key Pressed") 211 | 212 | except KeyboardInterrupt: 213 | print("-"*30, "Exiting", "-"*30) 214 | sys.exit(0) 215 | 216 | def auth_request(self, machine): 217 | """ 218 | for sending an auth request to server and getting authenticated 219 | """ 220 | log("Entering into auth request method of Idle state") 221 | #gagandeep, krishna, yaneisi 222 | salts = {'ali': '00', 'bianca':'11', 'gagandeep': '22', 223 | 'krishna': '33', 'yaneisi': '44'} 224 | salt = salts.get(machine.userid, 'xx') 225 | log(f"Salt value of user {machine.userid} is selected as {salt}") 226 | passcode = salt + hashlib.sha256((salt+machine.passcode).encode()).hexdigest() 227 | log(f"encrypted passcode is {passcode}") 228 | log("making auth request") 229 | request = json.dumps({"msgtype": "AUTHREQ", 230 | "userid": machine.userid, 231 | "passcode": passcode}).encode("utf-8") 232 | log(f"auth request is : \n{request}") 233 | log("Opening a socket to send request to server") 234 | client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 235 | client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 236 | log(f"connecting to server at {SERVER_IP}:{SERVER_PORT}") 237 | client.connect((SERVER_IP, SERVER_PORT)) 238 | log(f"Connection Established to Server sucessfully....sending request") 239 | client.send(request) 240 | log("request sucessfully send....reciving response") 241 | try: 242 | response = json.loads(client.recv(1024).decode('utf-8')) 243 | log(f"server response for auth request is \n {response}\n") 244 | client.close() 245 | log("processing auth response") 246 | if response['msgtype'] == 'AUTHREPLY' and response["status"] == "GRANTED": 247 | machine.my_enc_key = response['key'] 248 | log(f"got own encryption key as {machine.my_enc_key}") 249 | self.is_authenticated = True 250 | log("Authentication is sucessfull so returning True") 251 | return True 252 | log("Authentication is Denied from server so returning False") 253 | return False 254 | 255 | except json.JSONDecodeError: 256 | log(f"Invalid response from server as {response}") 257 | return False 258 | 259 | class LoggedIn(State): 260 | """ 261 | LoggedIn State to make further connect to a client requests 262 | or accept connections from remote client 263 | """ 264 | def __init__(self): 265 | State.__init__(self) 266 | @property 267 | def name(self): 268 | """ 269 | name property of LoggedIn Class 270 | """ 271 | return "LoggedIn" 272 | def enter(self, machine): 273 | """ 274 | LoggedIn enter state to initlize a socket to listen on port 8085 for all 275 | incoming traffic or requests. 276 | """ 277 | log(f"Entering into LoggedIn State of server") 278 | #log("Starting a thread to accept income connection to this machine") 279 | #machine.accept_thread = threading.Thread(target=self.accept_connection, args=(machine,)) 280 | #machine.chat_flag = False 281 | #log("Starting a thread accept_connection") 282 | #machine.accept_thread.start() 283 | #log("Thread has been started returning to update state") 284 | def exit(self, machine): 285 | """ 286 | exit state to ensure to go to state Idle again 287 | """ 288 | #machine.gotostate("Idle") 289 | def update(self, machine): 290 | """ 291 | update state to start communication by usig userid 292 | and lookup reqeust 293 | """ 294 | machine.gotostate("Chatting") 295 | class Chatting(State): 296 | """ 297 | chatting state where client will send or recv messages untill connection is break. 298 | """ 299 | def __init__(self): 300 | State.__init__(self) 301 | self.chatting = False 302 | @property 303 | def name(self): 304 | """ 305 | name property of Chatting State 306 | """ 307 | return "Chatting" 308 | 309 | def enter(self, machine): 310 | """ 311 | Entering in chatting session 312 | """ 313 | log("inside chatting enter state") 314 | log("..........Starting Chat Session........") 315 | 316 | def update(self, machine): 317 | print("\n\n1. Start Session\n2.Join Session") 318 | try: 319 | choice = int(input("Your Choice : ")) 320 | if choice == 1: 321 | self.accept_connection(machine) 322 | elif choice == 2: 323 | self.make_connection(machine) 324 | else: 325 | print("\n choose 1 or 2") 326 | machine.gotostate("LoggedIn") 327 | except ValueError: 328 | print("\n choose 1 or 2") 329 | machine.gotostate("LoggedIn") 330 | except KeyboardInterrupt: 331 | print(".........Exiting......") 332 | sys.exit(0) 333 | def make_connection(self, machine): 334 | """ 335 | open a socket and reqeuest client to accept your chat request. 336 | """ 337 | userid = input("\nEnter destination user ID: ") 338 | #log(f"Now let's lookup into server to check whather user {userid} is LoggedIn or not") 339 | #log("Calling Lookup Method") 340 | lookup_answer = self.lookup(machine, userid) 341 | #log(f"got lookup answer as {lookup_answer}") 342 | if lookup_answer: 343 | try: 344 | #log("now lookup is successfull so start connection request") 345 | machine.client_enc_key = lookup_answer.get('key') 346 | #log(f"client encryption key is {machine.client_enc_key}") 347 | machine.client_name = lookup_answer.get('answer') 348 | #log(f"client name is {machine.client_name}") 349 | cip = lookup_answer.get('address') 350 | #log(f"client address {cip}") 351 | machine.client_ip = cip 352 | machine.client_socket = socket.socket() 353 | machine.client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 354 | #log(f"requesting to client at {cip}:{8085}") 355 | machine.client_socket.connect((cip, 8085)) 356 | #log("Got connected to client") 357 | req = json.dumps({ 358 | "msgtype": "CONNECTREQ", 359 | "initiator": machine.userid 360 | }).encode('utf-8') 361 | #log(f"Sending connect request {req}") 362 | machine.client_socket.send(req) 363 | #log("request send successfully") 364 | ans = json.loads(machine.client_socket.recv(1024).decode("utf-8")) 365 | #log(f"client is replied as answere {ans}") 366 | if ans.get("msgtype") == "CONNECTREPLY" and ans.get("status") == "ACCEPTED": 367 | #log("client is ready to start chatting....start Chatting Session") 368 | print("\nConnection established, type your messages\n") 369 | self.start_chatting(machine, machine.client_socket) 370 | machine.gotostate('LoggedIn') 371 | else: 372 | log("Connection request is REFUSED.....closing socket") 373 | try: 374 | if hasattr(machine, "client_socket"): 375 | machine.client_socket.close() 376 | del machine.client_socket 377 | except AttributeError: 378 | pass 379 | #log("Switching to LoggedIn State") 380 | machine.gotostate("LoggedIn") 381 | except json.JSONDecodeError: 382 | log("Invalid Response from server") 383 | try: 384 | if hasattr(machine, "client_socket"): 385 | machine.client_socket.close() 386 | del machine.client_socket 387 | except AttributeError: 388 | pass 389 | machine.gotostate("LoggedIn") 390 | except OSError: 391 | log("os error ") 392 | try: 393 | if hasattr(machine, "client_socket"): 394 | machine.client_socket.close() 395 | del machine.client_socket 396 | except AttributeError: 397 | pass 398 | machine.gotostate("LoggedIn") 399 | else: 400 | print("User is not LoggedIn right now try again later") 401 | machine.gotostate("LoggedIn") 402 | def accept_connection(self, machine): 403 | """ 404 | a thread to listen for clients on port 8085 405 | """ 406 | try: 407 | #log("inside accept connection method to accept request of a client") 408 | machine.server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 409 | machine.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 410 | #log("created a socket to listen on") 411 | machine.server_socket.bind(('', 8085)) 412 | #log("opening socket at port 8085") 413 | machine.server_socket.listen() 414 | #log("listing for client request") 415 | machine.client_socket, address = machine.server_socket.accept() 416 | #log(f"received a request from {address[0]}:{address[1]}") 417 | machine.client_ip, machine.client_port = address 418 | #log("receving client request") 419 | client_request = machine.client_socket.recv(1024).decode("utf-8") 420 | client_request = json.loads(client_request) 421 | #log(f"received client reqeust as {client_request}") 422 | conditions = [ 423 | client_request.get("msgtype") == "CONNECTREQ", 424 | client_request.get("initiator") 425 | ] 426 | if all(conditions): 427 | #log(f"client reqeust seems good...verifing client by looking up into server") 428 | lookup_answer = self.lookup(machine, client_request.get("initiator")) 429 | #log(f"lookup answer for client is {lookup_answer}") 430 | if lookup_answer and lookup_answer['address'] == machine.client_ip: 431 | machine.chat_flag = True 432 | machine.client_enc_key = lookup_answer.get('key') 433 | #log(f"lookup client user key is {machine.client_enc_key}") 434 | machine.client_name = lookup_answer.get('answer') 435 | #log(f"lookup client user {machine.client_name}") 436 | resp = json.dumps({ 437 | "msgtype": "CONNECTREPLY", 438 | "status": "ACCEPTED" 439 | }).encode('utf-8') 440 | #log(f"sending response is {resp} to client") 441 | machine.client_socket.send(resp) 442 | log("Switching to state Chatting") 443 | print("\n\nConnection received from user bianca, type yur messages\n\n") 444 | self.start_chatting(machine, machine.client_socket) 445 | else: 446 | #log("Un-authorized connect request by Client") 447 | #log("swithching to LoggedIn state") 448 | resp = json.dumps({ 449 | "msgtype": "CONNECTREPLY", 450 | "status": "REFUSED" 451 | }).encode('utf-8') 452 | #log(f"sending response as {resp}") 453 | machine.client_socket.send(resp) 454 | else: 455 | print("Invalid Connect Request by Client") 456 | resp = json.dumps({ 457 | "msgtype": "CONNECTREPLY", 458 | "status": "REFUSED" 459 | }).encode('utf-8') 460 | #log(f"sending response as {resp}") 461 | machine.client_socket.send(resp) 462 | except json.JSONDecodeError: 463 | log("Invalid Request") 464 | resp = json.dumps({ 465 | "msgtype": "CONNECTREPLY", 466 | "status": "REFUSED" 467 | }).encode('utf-8') 468 | #log(f"sending response as {resp}") 469 | machine.client_socket.send(resp) 470 | except OSError: 471 | log("Os Error occured due to connection abort") 472 | try: 473 | if hasattr(machine, "client_socket"): 474 | machine.client_socket.close() 475 | del machine.client_socket 476 | except AttributeError: 477 | log("client socket is already closed") 478 | else: 479 | try: 480 | if hasattr(machine, "server_socket"): 481 | machine.server_socket.close() 482 | del machine.server_socket 483 | except AttributeError: 484 | log("server_socket already closed") 485 | machine.gotostate("LoggedIn") 486 | def lookup(self, machine, user): 487 | """ 488 | verifing a client by sending lookup reqeust to the server 489 | """ 490 | self.chatting = False 491 | log("Inside lookup method of LoggedIn class") 492 | log(f"making a request for user {user}") 493 | request = json.dumps({"msgtype": "LOOKUPREQ", 494 | "userid": machine.userid, 495 | "lookup": user}).encode("utf-8") 496 | log(f"LOOKUP Request is {request}") 497 | log("creating a lookup socket") 498 | lookup_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 499 | lookup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 500 | log("connecting to server for lookup request") 501 | lookup_socket.connect((SERVER_IP, SERVER_PORT)) 502 | log("Connection sucessfully made to server... sending request") 503 | lookup_socket.send(request) 504 | log("Request send sucessfull") 505 | try: 506 | response = json.loads(lookup_socket.recv(1024).decode('utf-8')) 507 | log("lookup response from server {response}") 508 | lookup_socket.close() 509 | log("lookup socket closed successfully") 510 | condition = [response.get("msgtype") == "LOOKUPREPLY", 511 | response.get("status") == "SUCCESS"] 512 | if all(condition): 513 | user = response.get("answer") 514 | log(f"response user {user}") 515 | address = response.get("address") 516 | log(f"response address {address}") 517 | key = response.get("key") 518 | log(f"response key {key}") 519 | return {'user': user, 'address': address, 'key': key, 'answer': user} 520 | log("got a reply with status NOTFOUND user") 521 | return False 522 | except json.JSONDecodeError: 523 | log(f"Invalid Response from server as {response}") 524 | lookup_socket.close() 525 | log("returning to False") 526 | return False 527 | def start_chatting(self, machine, client_socket): 528 | """ 529 | let's start chatting 530 | """ 531 | self.chatting = True 532 | th1 = threading.Thread(target=self.send, args=(machine, client_socket)) 533 | th2 = threading.Thread(target=self.recv, args=(machine, client_socket)) 534 | try: 535 | th1.start() 536 | th2.start() 537 | th1.join() 538 | th2.join() 539 | except KeyboardInterrupt: 540 | self.chatting = False 541 | def send(self, machine, client_socket): 542 | """ 543 | method to take message input and send to destination socket 544 | """ 545 | enc = Fernet(machine.my_enc_key) 546 | while True: 547 | try: 548 | if self.chatting: 549 | msg = (" "+input(f"\n{machine.userid}: ")+" ").encode('utf-8') 550 | msg = enc.encrypt(msg) 551 | client_socket.send(msg) 552 | else: 553 | break 554 | except ConnectionAbortedError: 555 | break 556 | except OSError: 557 | break 558 | except KeyboardInterrupt: 559 | msg = "EOFBYEEOF".encode('utf-8') 560 | msg = enc.encrypt(msg) 561 | client_socket.send(msg) 562 | break 563 | except EOFError: 564 | break 565 | self.chatting = False 566 | try: 567 | if hasattr(machine, "client_socket"): 568 | machine.client_socket.close() 569 | del machine.client_socket 570 | except AttributeError: 571 | pass 572 | def recv(self, machine, client_socket): 573 | """ 574 | recv mehtod to recieve messages from destination and print on standard output 575 | """ 576 | dec = Fernet(machine.client_enc_key) 577 | while True: 578 | try: 579 | if self.chatting: 580 | msg = client_socket.recv(1024) 581 | msg = dec.decrypt(msg).decode('utf-8') 582 | if msg and msg != "EOFBYEEOF": 583 | print("\n") 584 | print(f"{machine.client_name}: {msg}".rjust(50)) 585 | 586 | else: 587 | print("\n\nConnection closed by remote machine\n\n") 588 | break 589 | else: 590 | break 591 | except ConnectionAbortedError: 592 | break 593 | except OSError: 594 | break 595 | except KeyboardInterrupt: 596 | break 597 | except EOFError: 598 | break 599 | self.chatting = False 600 | try: 601 | if hasattr(machine, "client_socket"): 602 | machine.client_socket.close() 603 | del machine.client_socket 604 | except AttributeError: 605 | pass 606 | 607 | 608 | 609 | class ChatApplication: 610 | """ 611 | A pi-2-pi secure chat application using 4x4 matrix keypad on pi for passcodes 612 | """ 613 | def __init__(self): 614 | self.state = None 615 | self.states = {} 616 | 617 | def add_state(self, state): 618 | """ 619 | adding a state to Chat Application 620 | """ 621 | self.states[state.name] = state 622 | 623 | def gotostate(self, state_name): 624 | """ 625 | Transition from one state to another state 626 | """ 627 | log(f"Current state is {self.state}") 628 | if self.state: 629 | log(f"Exiting state {self.state}") 630 | self.state.exit(self) 631 | log(f"Changing State to {state_name}") 632 | self.state = self.states[state_name] 633 | log(f"Entering into state {self.state}") 634 | self.state.enter(self) 635 | log(f"Returned form enter state of {self.state}") 636 | 637 | def update(self): 638 | """ 639 | updating a state to complete the tasks which are given to it. 640 | """ 641 | log(f"Inside update") 642 | if self.state: 643 | log(f"update {self.state}") 644 | self.state.update(self) 645 | 646 | 647 | if __name__ == "__main__": 648 | if len(sys.argv) == 2: 649 | SERVER_IP = sys.argv[1] 650 | else: 651 | print("Usages: client.py server_ip_address") 652 | sys.exit(0) 653 | log("Creating an Instance of Application") 654 | CHAT = ChatApplication() 655 | log("Adding Idle state to Chat Application") 656 | CHAT.add_state(Idle()) 657 | log("Adding LoggedIn state to Chat Application") 658 | CHAT.add_state(LoggedIn()) 659 | log("Adding Idle state to Chat Application") 660 | CHAT.add_state(Chatting()) 661 | log("Switching to Idle state") 662 | CHAT.gotostate("Idle") 663 | log(f"state {CHAT.state} transistion is sucessfull") 664 | try: 665 | while True: 666 | log(f"Updating State to {CHAT.state}") 667 | CHAT.update() 668 | except KeyboardInterrupt: 669 | print("..............Exiting.........") 670 | sys.exit(0) 671 | -------------------------------------------------------------------------------- /create_db.py: -------------------------------------------------------------------------------- 1 | """ 2 | createing a dummy database for users 3 | """ 4 | import json 5 | import hashlib 6 | from cryptography.fernet import Fernet 7 | 8 | KEY_1 = Fernet.generate_key().decode() 9 | KEY_2 = Fernet.generate_key().decode() 10 | KEY_3 = Fernet.generate_key().decode() 11 | KEY_4 = Fernet.generate_key().decode() 12 | KEY_5 = Fernet.generate_key().decode() 13 | #gagandeep, krishna, yaneisi 14 | DATA = { 15 | 'ali': ["00"+hashlib.sha256(("00"+"1234").encode()).hexdigest(), KEY_1], 16 | 'bianca': ["11"+hashlib.sha256(("11"+"4321").encode()).hexdigest(), KEY_2], 17 | 'gagandeep': ["22"+hashlib.sha256(("22"+"1234").encode()).hexdigest(), KEY_3], 18 | 'krishna': ["33"+hashlib.sha256(("33"+"4321").encode()).hexdigest(), KEY_4], 19 | 'yaneisi': ["44"+hashlib.sha256(("22"+"1234").encode()).hexdigest(), KEY_5], 20 | } 21 | 22 | with open("user_db.json", "w") as fp: 23 | json.dump(DATA, fp) 24 | fp.close() 25 | 26 | print(json.dumps(DATA, indent=5)) 27 | -------------------------------------------------------------------------------- /logs.txt: -------------------------------------------------------------------------------- 1 | Connection from (192.168.0.120, 51243)Connection from (192.168.0.121, 44940)Connection from (192.168.0.120, 51245)Connection from (192.168.0.121, 44942)Connection from (192.168.0.121, 44944)Connection from (192.168.0.120, 51252)Connection from (192.168.0.121, 44946)Connection from (192.168.0.120, 51253)Connection from (192.168.0.121, 44950)Connection from (192.168.0.120, 51379)Connection from (192.168.0.121, 44952)Connection from (192.168.0.120, 51380)Connection from (192.168.0.120, 52442)Connection from (192.168.0.121, 45466)Connection from (192.168.0.120, 52445)Connection from (192.168.0.121, 45468) -------------------------------------------------------------------------------- /mykeypad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import board as bd 3 | import digitalio as dio 4 | import adafruit_matrixkeypad as mat_keypad 5 | import time 6 | 7 | col_pins = [bd.D26, bd.D19, bd.D13, bd.D6] 8 | row_pins = [bd.D21, bd.D20, bd.D16, bd.D12] 9 | 10 | rows = [] 11 | cols = [] 12 | 13 | for row_pin in row_pins: 14 | rows.append(dio.DigitalInOut(row_pin)) 15 | 16 | for col_pin in col_pins: 17 | cols.append(dio.DigitalInOut(col_pin)) 18 | 19 | keys = ( 20 | ("D", "#", 0, "*"), 21 | ("C", 9, 8, 7), 22 | ("B", 6, 5, 4), 23 | ("A", 3, 2, 1) 24 | ) 25 | keypad = mat_keypad.Matrix_Keypad(rows, cols, keys) 26 | 27 | while True: 28 | kp = keypad.pressed_keys 29 | if kp: 30 | print(f"Pressed : {kp}") 31 | time.sleep(0.2) 32 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | adafruit-blinka 2 | adafruit-circuitpython-matrixkeypad 3 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Server Socket which listen for clients and process there request as 3 | if client has login request, userid and passcode log them in active users 4 | or if client request for lookup than send valid request if user is logged in 5 | """ 6 | import json # data serialization library 7 | import socket # socket programming library 8 | import sys # command line arguments library 9 | 10 | class Server: 11 | """ 12 | TCP IP server to accept and process client requests according to super 13 | secure communication protocols. 14 | """ 15 | def __init__(self, ip): 16 | self.active_users = {} 17 | self.client_ip = None 18 | self.client_port = None 19 | self.client_socket = None 20 | self.server_port = 8082 21 | self.server_ip = ip 22 | self.initilize_socket() # initlize a server socket 23 | 24 | def initilize_socket(self): 25 | """ 26 | Initlize a socket on port 8082 and start listning to clients request 27 | """ 28 | self.server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 29 | #creating a TCP type IPV4 faimly Socket 30 | self.server.bind((self.server_ip, self.server_port)) 31 | #binding server to fix ip and port 8082 32 | self.server.listen(10) 33 | #server is ready to accept clients requests 34 | 35 | def accept(self): 36 | """ 37 | will accept requests of client 38 | """ 39 | client, (client_ip, client_port) = self.server.accept() 40 | self.client_ip = client_ip 41 | self.client_port = client_port 42 | self.client_socket = client 43 | # accept clients request for further processing 44 | self.log() 45 | # log client's ip address and port into log.txt file 46 | self.process_request() 47 | # process client request accoring to protocols 48 | self.client_socket.close() # closing client request 49 | self.client_ip = None 50 | self.client_port = None 51 | self.client_socket = None 52 | def process_request(self): 53 | """ 54 | process client request according to predefine protocols. 55 | """ 56 | try: 57 | request = json.loads(self.client_socket.recv(1024).decode('utf-8')) 58 | #print(request) 59 | if request['msgtype'] == "AUTHREQ": 60 | #print("auth request received") 61 | self.handle_auth_request(request) 62 | # handling authentication type request 63 | elif request['msgtype'] == "LOOKUPREQ": 64 | self.handle_lookup_request(request) 65 | # handling lookup request for user query 66 | elif request["msgtype"] == "LOGOUTREQ": 67 | self.handle_logout_request(request) 68 | # handling logout request of user 69 | else: 70 | response = {"msgtype": "RESPONSE", "status": "FAILED", 71 | "message": "Invalid request"} 72 | response = json.dumps(response).encode() 73 | self.client_socket.sendall(response) 74 | # sending answer to an invalid request 75 | except json.JSONDecodeError: 76 | #print("Invalid Request of Client") 77 | response = {"msgtype": "RESPONSE", "status": "FAILED", 78 | "message": "Invalid request"} 79 | response = json.dumps(response).encode() 80 | self.client_socket.sendall(response) 81 | # handling invalid request of client 82 | except TypeError: 83 | #print("Invalid JSON Data Send by Client") 84 | # handling invalid request of client 85 | response = {"msgtype": "RESPONSE", "status": "FAILED", 86 | "message": "Invalid request"} 87 | response = json.dumps(response).encode() 88 | self.client_socket.sendall(response) 89 | 90 | def log(self): 91 | """ 92 | log a connection request of client with client ip address and port from request 93 | has been send. 94 | """ 95 | with open("logs.txt", "a") as file_pointer: # opening log file for appending 96 | message = f"""Connection from ({self.client_ip}, {self.client_port})""" 97 | #creating a log message 98 | print(message)# printing log message 99 | file_pointer.write(message) # logging message 100 | file_pointer.close() #closing file 101 | 102 | def handle_auth_request(self, request): 103 | """ 104 | handling auth request and repling accoring to protocols 105 | """ 106 | #print("handling auth reqeust") 107 | database = json.load(open("user_db.json")) 108 | # loading database to check user authentication 109 | # 110 | #print(database) 111 | #print(request) 112 | if request['userid'] in database: 113 | # checking user exists or not in database 114 | #print("user fond in database") 115 | if request['passcode'] == database[request['userid']][0]: 116 | # checking passcode of user to verify it's authenticity 117 | #print("user has authenticated") 118 | response = {"msgtype": "AUTHREPLY", 119 | "status": "GRANTED", 120 | "key": database[request['userid']][1]} 121 | # preparing granted response 122 | response = json.dumps(response).encode('utf-8') 123 | #print(response) 124 | # serialzing and converting into bytes to send over network 125 | self.active_users[request['userid']] = [ 126 | self.client_ip, 127 | database[request['userid']][1]] 128 | # logging in user server side for lookup requests 129 | #print("active users ",self.active_users) 130 | self.client_socket.send(response) 131 | #print("Send answer") 132 | # sending response to client 133 | return 134 | response = json.dumps({"msgtype": "AUTHREPLY", "status": "REFUSED"}).encode('utf-8') 135 | # sending connection refused response for invalid authentication 136 | self.client_socket.sendall(response) 137 | def handle_lookup_request(self, request): 138 | """ 139 | handling lookup request of clients and anwering accordingly 140 | """ 141 | if request['userid'] in self.active_users: 142 | # checking ligitimacy of user 143 | user = request['lookup'] 144 | # looking up for user in active users 145 | if user in self.active_users: 146 | response = {"msgtype": "LOOKUPREPLY", "status": "SUCCESS", 147 | "answer": user, "address": self.active_users[user][0], 148 | "key": self.active_users[user][1]} 149 | response = json.dumps(response).encode() 150 | # sending response for sucessfull lookup of user 151 | self.client_socket.sendall(response) 152 | else: 153 | response = {"msgtype": "LOOKUPREPLY", "status": "NOTFOUND", 154 | "answer": "", "address": ""} 155 | response = json.dumps(response).encode() 156 | # sending response for un-sucessfull lookup of user 157 | self.client_socket.sendall(response) 158 | else: 159 | response = {"msgtype": "LOOKUPREPLY", "status": "INVALIDREQUEST", 160 | "answer": "", "address": ""} 161 | response = json.dumps(response).encode() 162 | # sending response for invalid lookup request 163 | self.client_socket.sendall(response) 164 | return 165 | def handle_logout_request(self, request): 166 | """ 167 | logging out a user 168 | """ 169 | if request['userid'] in self.active_users: 170 | del self.active_users[request['userid']] 171 | # logging out user from active users 172 | response = {"msgtype": "LOGOUTREPLY", "status": "SUCCESS", 173 | "answer": "loggout sucessfull"} 174 | response = json.dumps(response).encode() 175 | # sending response for sucessfull logout of a user 176 | self.client_socket.sendall(response) 177 | else: 178 | response = {"msgtype": "LOGOUTREPLY", "status": "INVALIDREQUEST", 179 | "answer": "auth error signin for access"} 180 | response = json.dumps(response).encode() 181 | # sending response for invalid logout request 182 | self.client_socket.sendall(response) 183 | 184 | def __del__(self): 185 | """ 186 | closing server socket. 187 | """ 188 | self.server.close() 189 | # closing server socket 190 | del self 191 | 192 | if __name__ == "__main__": 193 | try: 194 | if len(sys.argv) == 2: 195 | SERVER = Server(sys.argv[1]) 196 | # up and running server on given ip 197 | print(f"Server is Up and Running at {SERVER.server_ip}:{SERVER.server_port}") 198 | while True: 199 | SERVER.accept() 200 | # accept client requests forever 201 | else: 202 | print("Usages: server.py ip_address") 203 | # printing error message on invalid use of script (without ip) 204 | except KeyboardInterrupt: 205 | print("Closing Server Socket") 206 | # exiting message from server 207 | del SERVER 208 | -------------------------------------------------------------------------------- /user_db.json: -------------------------------------------------------------------------------- 1 | {"bianca": ["11a98c5e6e42ee39db0f66146ba4158333c9e55ca65c020a955c10a617820568f3", "UDocgFoueFc5ZJ-ViNtyLSF3WCkKSsBFjNL0F5LU8e8="], "gagandeep": ["22c54d38ad5515e6ed8ae3cd4c30f380646fd3b7a900d0a25f2a331e68d85f2796", "-zAdG9qkCC0xQwkd1aSni1SYKjHphrLQf4RGBbwyd28="], "yaneisi": ["44c54d38ad5515e6ed8ae3cd4c30f380646fd3b7a900d0a25f2a331e68d85f2796", "MUNeCKTuMu7HudxxdBLw32uPDQqWniWbWKPNlAQ66cs="], "krishna": ["33f9b8a9b8f1bcc8da65eedde022a2528e6315b4a847e78413b1b0cfecb4d55b66", "TzGhg6xLWzU4hxHPuXC--PxcwqdX_PT4FUtfx-y_7RA="], "ali": ["00dee57cd0c1d967776da5fe2d059fb4350d969c57e7b66a4b831bd9ee09a098dd", "WT4XEbqMkwQKkaRbzQiDn1H_-Tob-cdyDcOyCNlLUp8="]} --------------------------------------------------------------------------------