From 12da22c5b4c53df9096825e902ca2c2ac6fcfa17 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Tue, 21 Nov 2023 19:54:31 +0530 Subject: [PATCH 01/13] Cleanup chat related code --- CLIclient_main.py | 52 +------------ client_modules/cli_menus.py | 18 +---- client_modules/db_handler.py | 78 ------------------- client_modules/packet_handler.py | 3 +- i18n.py | 2 +- .../conn_handler.py => requirements.txt | 0 6 files changed, 7 insertions(+), 146 deletions(-) delete mode 100644 client_modules/db_handler.py rename client_modules/conn_handler.py => requirements.txt (100%) diff --git a/CLIclient_main.py b/CLIclient_main.py index ba31b8d..6198180 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -5,6 +5,7 @@ from client_modules import encryption as en from client_modules import packet_handler as p from client_modules import db_handler as db +from client_modules import cli_menus as cli from client_modules import first_run from uuid import uuid4 import i18n @@ -98,7 +99,7 @@ async def main(host, port): while True: try: await asyncio.gather( - send_message(websocket), + send_message(websocket, cli.main_menu()), recv_message(websocket, await websocket.recv()), ) except Exception as eroa: @@ -142,54 +143,5 @@ async def main(host, port): print(i18n.firstrun.exit) exit() - try: - with open(f'{workingdir}/creds/chat_publickey', 'rb') as f: - pem_pubkey = f.read() - pubkey = en.deser_pem(pem_pubkey, 'public') - except FileNotFoundError: - print("Could not find chat encryption public key. Client will now generate it from the private key.") - try: - with open(f'{workingdir}/creds/chat_privatekey', 'rb') as f: - en_pem_prkey = f.read() - pem_prkey = fkey.decrypt(en_pem_prkey) - prkey = en.deser_pem(pem_prkey, 'private') - pubkey = prkey.public_key() - - with open(f'{workingdir}/creds/chat_publickey', 'wb') as f: - f.write(pem_pubkey) - except FileNotFoundError: - print("Could not find chat encryption keypair. Client will now generate it again.") - ch = input("THIS OPERATION IS NOT SUPPORTED YET. Continue? (y/n) > ") - if ch.lower() == 'y': - db.clear_queue(user=None) - first_run.save_chat_keypair(fkey, workingdir) - print(i18n.firstrun.exit) - exit() - if ch.lower() == 'n': - print("Key ah kaanume enna panradhu ippo?") - exit() - else: - with open(f'{workingdir}/creds/chat_privatekey', 'rb') as f: - en_pem_prkey = f.read() - pem_prkey = fkey.decrypt(en_pem_prkey) - prkey = en.deser_pem(pem_prkey, 'private') - asyncio.get_event_loop().run_until_complete(main(host,port)) asyncio.get_event_loop().run_forever() - -""" -import asyncio -import websockets - -# Main function to connect to the server and start the message handlers -async def main(): - async with websockets.connect(uri) as websocket: - # Start the message handlers - await asyncio.gather( - handle_incoming_messages(websocket), - handle_outgoing_messages(websocket), - ) - -# Run the main function -asyncio.run(main()) -""" \ No newline at end of file diff --git a/client_modules/cli_menus.py b/client_modules/cli_menus.py index 991b95c..605cb8c 100644 --- a/client_modules/cli_menus.py +++ b/client_modules/cli_menus.py @@ -12,19 +12,7 @@ def cls(): def main_menu(): while True: cls() - print( -""" ...... . .. - -======+++**##%+ %*---%::% @- - +@#+++*@@+=--:-%%. %. .% *= .+% - *@- :@# .%%. :@+++*- -+==#= - #@: -@#..:::-%%. =* .%. %. - .%%. :%%%%#####= :*- =- :%%###%%%%%+ *: +%+++*= - .@% *@- :@# ..-@@=:-@%:.. - -@* .=++++***:. %@: :@# .#@%%@@%%%@@###***= - =@+ .%@+===-=%%- %%. -@* :@# -@* -@#:*@*--. - +@= .%%. *@+ @# :@# :@# =@+ -@* =@+ - #@: .:%%======#@* @#-----==*@* -@%+#@#+=#@= +@= -.#@%%%%%%##*+++++=:. =******+++=. :===+==++- +@= CHAT""") + print("Account System Demonstration Frontend") print("Welcome!") print("Choose an operation:") print("1. Sign up") @@ -33,9 +21,9 @@ def main_menu(): c = int(input("> ")) match c: case 1: - signup() + p.signup() case 2: - login() + p.login() case 3: print("Goodbye!") sys.exit() diff --git a/client_modules/db_handler.py b/client_modules/db_handler.py deleted file mode 100644 index 6154049..0000000 --- a/client_modules/db_handler.py +++ /dev/null @@ -1,78 +0,0 @@ -import sqlite3 -import os -import pickle - -# for now, all the data lies in a single db file. this must be changed in the future. -initialize = """ -CREATE TABLE IF NOT EXISTS users ( - UUID TEXT NOT NULL, - USERNAME TEXT UNIQUE NOT NULL, - FULL_NAME TEXT NOT NULL, - DOB TEXT NOT NULL, - EMAIL TEXT, - CREATION DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (UUID) -); -CREATE TABLE IF NOT EXISTS rooms ( - ID INTEGER PRIMARY KEY AUTOINCREMENT, - CREATOR_UUID TEXT NOT NULL, - ROOM_TYPE INTEGER NOT NULL, - MEMBERS BLOB NOT NULL, - CHAT_TABLE TEXT NOT NULL, - FOREIGN KEY(CREATOR_UUID) REFERENCES chatapp_accounts.users(UUID) -); -""" -createroom = """CREATE TABLE ? ( - messageID INTEGER PRIMARY KEY AUTOINCREMENT, - messageUUID TEXT UNIQUE NOT NULL, - sender TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - message BLOB NOT NULL, - type TEXT NOT NULL, - pinned INTEGER NOT NULL DEFAULT 0 -); -""" - -queries = {'initialize': initialize, 'create_room':createroom} -fields_to_check = { - 'username':{'table':'users','attribute':'USERNAME'}, - 'room':{'table':'rooms', 'attribute':'CHAT_TABLE'} - } - -class db: - con = None - cur = None - -def create_db(workingdir): # todo: add password protection - conn = sqlite3.connect(f'{workingdir}/DATA.db') - conn.close() - -def decrypt_creds(fkey, workingdir): - with open(f'{workingdir}/creds/db', 'rb') as f: - data = f.read() - decrypted = fkey.decrypt(data) - dict = pickle.loads(decrypted) - try: - setattr(db, 'con', sqlite3.connect(dict['file'])) - setattr(db, 'cur', db.con.cursor()) - print('[INFO] Loaded Database') - except Exception as e: - print('[ERROR] Could not load database:', e) - -# # Create a connection object with the database -# conn = sqlite3.connect('my_database.db') - -# # Set password for the db file -# conn.execute("PRAGMA key='password'") - -# # Create tables -# conn.execute('''CREATE TABLE IF NOT EXISTS table1 (column1 datatype PRIMARY KEY (one or more columns), column2 datatype, column3 datatype, ….. columnN datatype);''') -# conn.execute('''CREATE TABLE IF NOT EXISTS table2 (column1 datatype PRIMARY KEY (one or more columns), column2 datatype, column3 datatype, ….. columnN datatype);''') - -# # Insert data into tables -# conn.execute("INSERT INTO table1 VALUES ('value1', 'value2', 'value3')") -# conn.execute("INSERT INTO table2 VALUES ('value4', 'value5', 'value6')") - -# # Commit changes and close connection -# conn.commit() -# conn.close() \ No newline at end of file diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index fcb70aa..59c95e6 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -87,8 +87,7 @@ def login(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): packet_map = { 'STATUS':status, 'CAPTCHA':captcha, -# 'TOKEN_GEN':save_token, - + 'TOKEN_GEN':save_token } async def handle(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): type = de_packet['type'] diff --git a/i18n.py b/i18n.py index da2d25b..7b19b7d 100644 --- a/i18n.py +++ b/i18n.py @@ -5,7 +5,7 @@ class firstrun(): config_not_found = "Configuration file not found!" exec = "client will now run its configuration process" fix_missing = "Please enter the client's" - welcome_message = "Welcome to PesuPy Chat client Software!" + welcome_message = "Welcome to the Account System Demonstration Frontend" setup_client_dir = "Please enter the path to a folder where the client can store its files" keypair_setup = "Setting up client Keypair..." initialize_db = "Setting up Databases for use..." diff --git a/client_modules/conn_handler.py b/requirements.txt similarity index 100% rename from client_modules/conn_handler.py rename to requirements.txt From 7a0f7f47f9da2cdd04f812a832c6dd01cec0e11b Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Wed, 22 Nov 2023 21:54:15 +0530 Subject: [PATCH 02/13] Fully functional, feature complete, but not async --- CLIclient_main.py | 48 ++++--------- captcha.png | Bin 9964 -> 9954 bytes client_modules/cli_menus.py | 49 ++++++++----- client_modules/first_run.py | 15 ---- client_modules/packet_handler.py | 120 ++++++++++++++++++++----------- 5 files changed, 122 insertions(+), 110 deletions(-) diff --git a/CLIclient_main.py b/CLIclient_main.py index 6198180..eab545e 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -1,10 +1,10 @@ import asyncio import websockets import os +import threading from cryptography.hazmat.primitives import serialization as s from client_modules import encryption as en from client_modules import packet_handler as p -from client_modules import db_handler as db from client_modules import cli_menus as cli from client_modules import first_run from uuid import uuid4 @@ -15,32 +15,21 @@ from sys import exit async def send_message(websocket, message): - outpacket = en.encrypt_data( - message, SERVER_CREDS['server_epbkey'] - ) - await websocket.send(outpacket) - response = await websocket.recv() - return await recv_message(websocket, response) - -async def handle_resp(websocket, response): - inpacket = en.decrypt_data(response, CLIENT_CREDS['client_eprkey']) - print(inpacket) - handled = await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) # handle here using type and data - print('resposne handaled') - return handled + if message: + await websocket.send(message) async def recv_message(websocket, message): inpacket = en.decrypt_data(message, CLIENT_CREDS['client_eprkey']) # handle check - await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) + outpacket = await p.handle(SERVER_CREDS, CLIENT_CREDS, websocket, inpacket) + if not outpacket: + await websocket.send(outpacket) + else: + pass def execute_firstrun(): first_run.main() print(i18n.firstrun.security) - db.decrypt_creds(en.fermat_gen(first_run.working_dir.workingdir), first_run.working_dir.workingdir) - print(i18n.firstrun.initialize_db) - db.initialize_schemas() - db.close() print(i18n.firstrun.exit) exit() @@ -80,7 +69,7 @@ def fill_missing_config(f, yaml, config): async def main(host, port): uri = f"ws://{host}:{port}" - async with websockets.connect(uri, ping_interval=30) as websocket: + async with websockets.connect(uri, ping_interval=120) as websocket: con_id = str(uuid4()) await websocket.send(pickle.dumps({'type':'CONN_INIT', 'data':con_id})) try: @@ -95,13 +84,10 @@ async def main(host, port): except websockets.exceptions.ConnectionClosedError as err: print("Disconected from Server! Error:\n",err) exit() - + while True: try: - await asyncio.gather( - send_message(websocket, cli.main_menu()), - recv_message(websocket, await websocket.recv()), - ) + await send_message(websocket, await cli.main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, workingdir)) except Exception as eroa: print("Disconected from Server! Error:\n",eroa) exit() @@ -133,15 +119,7 @@ async def main(host, port): execute_firstrun() workingdir = yaml['working_directory'] + CLIENT_CREDS['workingdir'] = workingdir host, port = yaml['homeserver_address'], yaml['homeserver_port'] - try: - fkey = en.fermat_gen(workingdir) - db.decrypt_creds(fkey, workingdir) - except Exception as w: - print("Error while decrypting database credentials. Check your password\n", w) - print(i18n.firstrun.exit) - exit() - - asyncio.get_event_loop().run_until_complete(main(host,port)) - asyncio.get_event_loop().run_forever() + asyncio.run(main(host,port)) diff --git a/captcha.png b/captcha.png index 15f268de694be331566aea7781e19ae5c3032bea..fa26c0f7f39be1ba30548cca262d3140384d0e1e 100644 GIT binary patch literal 9954 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 signup\t(Sign up)") + print("> login \t(Log in)") + print("> auth \t(Authenticate)") + print("> logout\t(Logout)") + print("> exit \t(Exit)") while True: - cls() - print("Account System Demonstration Frontend") - print("Welcome!") - print("Choose an operation:") - print("1. Sign up") - print("2. Log in") - print("3. Exit") - c = int(input("> ")) + print("What would you like to do?") + c = input("> ").lower() match c: - case 1: - p.signup() - case 2: - p.login() - case 3: + case 'signup': + cls() + return await p.signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + case 'login': + cls() + return await p.login(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + case 'auth': + cls() + return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + case 'logout': + cls() + flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + if flag == 'CANCELLED': + print("Cancelled.") + continue + else: + return flag + case 'exit': print("Goodbye!") sys.exit() - -def signup(): - p.signup() \ No newline at end of file + case other: + print("Enter a valid operation.") diff --git a/client_modules/first_run.py b/client_modules/first_run.py index c00fa1c..13aa8f8 100644 --- a/client_modules/first_run.py +++ b/client_modules/first_run.py @@ -2,7 +2,6 @@ from i18n import firstrun from yaml import dump as dumpyaml from . import encryption as e -from . import db_handler as db try: from tkinter import filedialog except: @@ -48,24 +47,10 @@ def setup_client_dir(): print(firstrun.savedata.data_exists) return spath -def save_chat_keypair(fkey, workingdir): - prkey, pubkey = e.create_rsa_key_pair() - pem_prkey, pem_pubkey = e.ser_key_pem(prkey, 'private'), e.ser_key_pem(pubkey, 'public') - en_pem_prkey = fkey.encrypt(pem_prkey) - with open(f'{workingdir}/creds/chat_privatekey', 'wb') as f: - f.write(en_pem_prkey) - with open(f'{workingdir}/creds/chat_publickey', 'wb') as f: - f.write(pem_pubkey) - def main(): print(firstrun.welcome_message) print(firstrun.setup_client_dir) workingdir = setup_client_dir() - fkey, passwd = e.fernet_initkey(workingdir) - save_chat_keypair(fkey, workingdir) - db.create_db(workingdir) # todo : add password protection - del passwd - del fkey print("You will be able to change these later") host = input("Enter Homeserver Address: ") port = int(input("Enter Homeserver Port: ")) diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index 59c95e6..e772b59 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -9,8 +9,6 @@ sig_map = { 'CONN_OK':"Connection to the server was successful", 'CAPTCHA_WRONG':"The CAPTCHA code you entered was wrong. Try performing the action again", - 'SIGNUP_OK':"The account was successfully created", - 'SIGNUP_MISSING_CREDS': '', 'SIGNUP_MISSING_CREDS': '', 'SIGNUP_USERNAME_ABOVE_LIMIT': '', 'SIGNUP_USERNAME_ALREADY_EXISTS': '', @@ -22,24 +20,15 @@ 'LOGIN_MISSING_CREDS': '', 'LOGIN_INCORRECT_PASSWORD': '', 'LOGIN_ACCOUNT_NOT_FOUND': '', - 'LOGIN_OK': '', - 'QUEUE_END': '', 'TOKEN_EXPIRED': '', 'TOKEN_INVALID': '', - 'CHAT_PUBKEY_MISSING': '', - 'CHAT_PUBKEY_INVALID': '', - 'CHAT_PUBKEY_OK': '', - 'ROOM_DNE': '', - 'MKROOM_INSUFFICIENT_PARTICIPANTS': '', - 'MKROOM_NOT_IMPLEMENTED': '', - 'MKROOM_OK': '', - 'SENT': '', - 'MSG_NOT_YOURS': '' + 'LOGOUT_ERR': '', + 'NOT_LOGGED_IN': 'You need to be logged in to do that!', + 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again', + 'ACCOUNT_DNE': 'No such account found. Check your username/email' } async def status(SERVER_CREDS, CLIENT_CREDS, websocket, data): - if data['sig'] == 'CHAT_PUBKEY_MISSING': - await send_pubkey(SERVER_CREDS, CLIENT_CREDS, websocket) print(sig_map[data['sig']]) async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): @@ -47,31 +36,37 @@ async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): f.write(data['challenge']) print("Open the captcha.png in the client folder and enter the digits here:") solved = int(input("> ")) - solved_packet = pickle.dumps({'type':'S_CAPTCHA', 'data':{'solved':solved}}) - outpacket = en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey']) - await websocket.send(outpacket) - return en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) - -async def send_pubkey(SERVER_CREDS, CLIENT_CREDS, websocket): - rootdir = os.path.dirname(os.path.abspath(__file__)) - f = open(f'{rootdir}/config.yml', 'r+') - yaml = loadyaml(f.read()) - + solved_packet = {'type':'S_CAPTCHA', 'data':{'solved':solved}} + await websocket.send(en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey'])) + resp = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + if resp['type'] == 'STATUS': + return resp['data']['sig'] + else: + return resp -def signup(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): +async def signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir): data = {} data['user'] = input("Enter your username: ") data['email'] = input("Enter your email: ") data['fullname'] = input("Enter your full name: ") data['dob'] = input("Enter your date of birth (YYYY-MM-DD or DD-MM-YYYY): ") - pwd = getpass("Enter your password: ") - if pwd == getpass("Confirm your password: "): - data['password'] = pwd + while True: + pwd = getpass("Enter your password: ") + conf = getpass("Confirm your password: ") + if pwd == conf: + data['password'] = pwd + break + else: + print("Passwords don't match. Try again") + await websocket.send(en.encrypt_data({'type':'SIGNUP', 'data':data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + if captcha_flag == 'SIGNUP_OK': + print("Signup complete! Login with your credentials.") else: - return 'ERR_CONFIRM' - return en.encrypt_data({'type':'SIGNUP', 'data':data}, SERVER_CREDS['server_epbkey']) + print("An error occurred:", sig_map[captcha_flag]) -def login(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): +async def login(SERVER_CREDS, CLIENT_CREDS, websocket, dir): # {'type':'LOGIN', 'data':{'id':username/email,'password':password,'save':True or False}} data = {} data['id'] = input("Enter username/email: ") @@ -80,9 +75,56 @@ def login(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): data['save'] = True else: data['save'] = False - return en.encrypt_data({'type':'LOGIN', 'data':data}, SERVER_CREDS['server_epbkey']) + await websocket.send(en.encrypt_data({'type':'LOGIN', 'data':data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + if isinstance(captcha_flag, str): + print(captcha_flag+":", sig_map[captcha_flag]) + else: + await save_token(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_flag) + +async def save_token(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): + # {'type':'TOKEN_GEN','data':{'token':access_token}} + dir = CLIENT_CREDS['workingdir'] + try: + with open(dir+'/creds/acc_token', 'w') as f: + f.write(de_p['data']['token']) + print("Successfully acquired token.") + except Exception as ee: + print("Token generation failed. Please try again. Error:") + print(ee) +async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + user = input("Enter username/email: ") + try: + with open(dir+'/creds/acc_token', 'r') as f: + token = f.read().rstrip('\r\n') + if token: + await websocket.send(en.encrypt_data({'type':'AUTH_TOKEN','data':{'user':user,'token':token}}, SERVER_CREDS['server_epbkey'])) + elif not token: + print("You have to login first! Token not found.") + return None + flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + if flag['data']['sig'] == 'LOGIN_OK': + print("You have been logged in successfully.\nYour session is now active and this concludes the demonstration.\nIf you exit the app and decide to login again, you can use > auth to do it.") + else: + print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) + except Exception as ee: + print("Token authentication failed. Please login again. Error:") + print(ee) + +async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + if input("Are you sure you want to log out?\nThis will reset your token and you will have to login again.").lower() == 'y': + await websocket.send(en.encrypt_data({'type':'LOGOUT', 'data':{}}, SERVER_CREDS['server_epbkey'])) + flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + match flag['data']['sig']: + case 'LOGOUT_OK': + print("Successfully logged out. Token has been reset") + case other: + print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) + else: + print("Cancelled logout.") packet_map = { 'STATUS':status, @@ -92,15 +134,7 @@ def login(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): async def handle(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): type = de_packet['type'] data = de_packet['data'] - - if type in packet_map.keys(): - func = packet_map[type] - return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) - -async def dispatch(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): - type = de_packet['type'] - data = de_packet['data'] - + print("HANDLING PACKET", type) if type in packet_map.keys(): func = packet_map[type] - await websocket.send(await func(SERVER_CREDS, CLIENT_CREDS, websocket, data)) \ No newline at end of file + return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) \ No newline at end of file From aa9354dfd96201d74021f41f254362e3baafec0c Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Wed, 22 Nov 2023 22:23:09 +0530 Subject: [PATCH 03/13] Update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index bed252b..54760f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ -# client-framework - -This repo provides all the necessary functions to interact with the [pesupy-chat server software](https://github.com/pesupy-chat/server). -It also includes an example CLI client which can also be used regularly. (CURRENTLY NOT FINISHED) +# README PENDING From d8e3bf94ad46211b580f781a4768c0effa0076cf Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Sun, 26 Nov 2023 19:21:38 +0530 Subject: [PATCH 04/13] CLEANUP + Full i18n --- CLIclient_main.py | 2 - captcha.png | Bin 9954 -> 0 bytes client_modules/first_run.py | 24 ++++---- client_modules/packet_handler.py | 69 ++++++++--------------- i18n.py | 93 ++++++++++++++++++++++--------- 5 files changed, 103 insertions(+), 85 deletions(-) delete mode 100644 captcha.png diff --git a/CLIclient_main.py b/CLIclient_main.py index eab545e..2e0d9b1 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -29,11 +29,9 @@ async def recv_message(websocket, message): def execute_firstrun(): first_run.main() - print(i18n.firstrun.security) print(i18n.firstrun.exit) exit() - def check_missing_config(f, yaml, config): try: if yaml[config] is None: diff --git a/captcha.png b/captcha.png deleted file mode 100644 index fa26c0f7f39be1ba30548cca262d3140384d0e1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9954 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 ")) solved_packet = {'type':'S_CAPTCHA', 'data':{'solved':solved}} await websocket.send(en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey'])) @@ -46,32 +25,32 @@ async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): async def signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir): data = {} - data['user'] = input("Enter your username: ") - data['email'] = input("Enter your email: ") - data['fullname'] = input("Enter your full name: ") - data['dob'] = input("Enter your date of birth (YYYY-MM-DD or DD-MM-YYYY): ") + data['user'] = input(demo.user_p) + data['email'] = input(demo.email_p) + data['fullname'] = input(demo.name_p) + data['dob'] = input(demo.dob_p.format("(YYYY-MM-DD or DD-MM-YYYY)")) while True: - pwd = getpass("Enter your password: ") - conf = getpass("Confirm your password: ") + pwd = getpass(demo.pass_p) + conf = getpass(demo.pass_confirm) if pwd == conf: data['password'] = pwd break else: - print("Passwords don't match. Try again") + print(demo.pass_mismatch) await websocket.send(en.encrypt_data({'type':'SIGNUP', 'data':data}, SERVER_CREDS['server_epbkey'])) captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) if captcha_flag == 'SIGNUP_OK': - print("Signup complete! Login with your credentials.") + print(demo.signup_success) else: - print("An error occurred:", sig_map[captcha_flag]) + print(demo.error_i, sig_map[captcha_flag]) async def login(SERVER_CREDS, CLIENT_CREDS, websocket, dir): # {'type':'LOGIN', 'data':{'id':username/email,'password':password,'save':True or False}} data = {} - data['id'] = input("Enter username/email: ") - data['password'] = getpass("Enter your password: ") - if input("Save login? No need to re-login for 30 days (y/N)").lower() == 'y': + data['id'] = input(demo.id_p) + data['password'] = getpass(demo.pass_p) + if input(demo.save_p+' (y/N) > ').lower() == 'y': data['save'] = True else: data['save'] = False @@ -89,38 +68,36 @@ async def save_token(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): try: with open(dir+'/creds/acc_token', 'w') as f: f.write(de_p['data']['token']) - print("Successfully acquired token.") + print(demo.token_got) except Exception as ee: - print("Token generation failed. Please try again. Error:") - print(ee) + print(demo.token_fail.format(ee)) async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): - user = input("Enter username/email: ") + user = input(demo.id_p) try: with open(dir+'/creds/acc_token', 'r') as f: token = f.read().rstrip('\r\n') if token: await websocket.send(en.encrypt_data({'type':'AUTH_TOKEN','data':{'user':user,'token':token}}, SERVER_CREDS['server_epbkey'])) elif not token: - print("You have to login first! Token not found.") + print(demo.token_nf) return None flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) if flag['data']['sig'] == 'LOGIN_OK': - print("You have been logged in successfully.\nYour session is now active and this concludes the demonstration.\nIf you exit the app and decide to login again, you can use > auth to do it.") + print(demo.end) else: print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) except Exception as ee: - print("Token authentication failed. Please login again. Error:") - print(ee) + print(demo.auth_err.format(ee)) async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): - if input("Are you sure you want to log out?\nThis will reset your token and you will have to login again.").lower() == 'y': + if input(demo.logout_p+' (y/N) > ').lower() == 'y': await websocket.send(en.encrypt_data({'type':'LOGOUT', 'data':{}}, SERVER_CREDS['server_epbkey'])) flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) match flag['data']['sig']: case 'LOGOUT_OK': - print("Successfully logged out. Token has been reset") + print(demo.logout_success) case other: print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) else: @@ -134,7 +111,7 @@ async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): async def handle(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): type = de_packet['type'] data = de_packet['data'] - print("HANDLING PACKET", type) + print(log.tags.debug + log.handle_packet.format(type)) if type in packet_map.keys(): func = packet_map[type] return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) \ No newline at end of file diff --git a/i18n.py b/i18n.py index 7b19b7d..aa367ce 100644 --- a/i18n.py +++ b/i18n.py @@ -1,37 +1,80 @@ class firstrun(): prompt1 = "Could not determine client's " - empty_config = "client configuration is empty. Deleting..." + empty_config = "Client configuration is empty. Deleting..." prompt2 = "Is this the first time you are running the client?" config_not_found = "Configuration file not found!" - exec = "client will now run its configuration process" - fix_missing = "Please enter the client's" + exec = "Client will now run its configuration process" + fix_missing = "Please enter the Client's" welcome_message = "Welcome to the Account System Demonstration Frontend" - setup_client_dir = "Please enter the path to a folder where the client can store its files" - keypair_setup = "Setting up client Keypair..." + setup_client_dir = "Please enter the path to a folder where the Client can store its files" + keypair_setup = "Setting up Client Keypair..." initialize_db = "Setting up Databases for use..." - security = "For security reasons, enter client launch password again." exit = "Client will now exit. Please run it again!" - class savedata(): - gui = "Opening file chooser dialog..." - nogui = "Cannot open file chooser! Enter the path manually:" - error = "An error occurred" - write_error = "An error occurred while trying to write client files.\nPlease choose another path" - not_a_dir = "Please enter path to a folder!" - creating = "Folder not accessible." - input_writable = "Please enter a writeable folder path" - created = "Written Client files successfully." - created_new = "Created Client folder successfully." - data_exists = "Previous Installation Detected! Please delete the files or choose another folder." - class passwd(): - explain = "\ - \ - " - input = "Enter the client's launch password: " - confirm = "Enter it again to confirm: " - retry = "Passwords do not match!" + config_modinfo = "You will be able to change these later" + hs_addr = "Enter Homeserver Address: " + hs_port = "Enter Homeserver Port: " + +class savedata(): + gui = "Opening file chooser dialog..." + nogui = "Cannot open file chooser! Enter the path manually:" + error = "An error occurred" + write_error = "An error occurred while trying to write client files.\nPlease choose another path" + not_a_dir = "Please enter path to a folder!" + creating = "Folder not accessible." + input_writable = "Please enter a writeable folder path" + created = "Written Client files successfully." + created_new = "Created Client folder successfully." + data_exists = "Previous Installation Detected! Please delete the files or choose another folder." + +class demo(): + captcha_p = "Open the captcha.png in the client folder and enter the digits here:" + user_p = "Enter your username: " + email_p = "Enter your email: " + name_p = "Enter your full name: " + dob_p = "Enter your date of birth in the format {}: " + pass_p = "Enter your password: " + pass_confirm = "Confirm your password: " + pass_mismatch = "Passwords don't match. Try again" + error_i = "An error occurred:" + signup_success = "Signup complete! Login with your credentials." + id_p = "Enter username/email: " + save_p = "Save login? No need to re-login for 30 days" + token_got = "Successfully acquired token." + token_fail = "Token generation failed. Please try again. Error:\n{}" + token_nf = "You have to login first! Token not found." + auth_err = "Token authentication failed. Please login again. Error:\n{}" + logout_p = "Are you sure you want to log out?\nThis will reset your token and you will have to login again." + logout_success = "Successfully logged out. Token has been reset" + end = "You have been logged in successfully.\nYour session is now active and this concludes the demonstration.\nIf you exit the app and decide to login again, you can use > auth to do it." + + class log(): class tags(): info = '[INFO] ' warn = '[WARN] ' error = '[ERR] ' - client_start = "Client starting from path {0}...." \ No newline at end of file + debug = '[DEBUG] ' + client_start = "Client starting from path {0}...." + handle_packet = "Handling packet {}..." + +sig_map = { + 'CONN_OK':"Connection to the server was successful", + 'CAPTCHA_WRONG':"The CAPTCHA code you entered was wrong. Try performing the action again", + 'SIGNUP_MISSING_CREDS': 'A field in your signup form is empty. Please signup again properly', + 'SIGNUP_USERNAME_ABOVE_LIMIT': 'Username exceeds 32 character limit', + 'SIGNUP_USERNAME_ALREADY_EXISTS': 'Username already exists', + 'SIGNUP_EMAIL_ABOVE_LIMIT': 'Your email ID is abnormally long. Are you sure its a valid address?', + 'SIGNUP_NAME_ABOVE_LIMIT': 'Your name is abnormally long. Are you Jugemu?', + 'SIGNUP_DOB_INVALID': 'Date of birth is Invalid. Please enter it in YYYY-MM-DD or DD-MM-YYYY format.', + 'SIGNUP_PASSWORD_ABOVE_LIMIT': 'No way in heaven will you remember the password. Choose a smaller one.', + 'SIGNUP_ERR': 'Unexpected error while signing up, please try again.', + 'LOGIN_MISSING_CREDS': 'A field in your login form is empty. Please signup again properly', + 'LOGIN_INCORRECT_PASSWORD': 'The password you entered is incorrect. Check your password', + 'LOGIN_ACCOUNT_NOT_FOUND': 'The requested account does not exist. Check your username/email', + 'TOKEN_EXPIRED': 'Your session has expired. Login again', + 'TOKEN_INVALID': 'Login again to fix this.', + 'LOGOUT_ERR': 'Unexpected error while logging out, please try again.', + 'NOT_LOGGED_IN': 'You need to be logged in to do that!', + 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again', + 'ACCOUNT_DNE': 'No such account found. Check your username/email' +} \ No newline at end of file From 839fc92d697b581565d824b8720cf7a06b3d6f98 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Sun, 26 Nov 2023 20:16:53 +0530 Subject: [PATCH 05/13] Refactor (thanks pycharm for yelling @me) --- CLIclient_main.py | 19 +++++++----- client_modules/cli_menus.py | 12 +++++--- client_modules/encryption.py | 45 +++++++++------------------ client_modules/first_run.py | 12 +++++--- client_modules/packet_handler.py | 53 +++++++++++++++++++------------- i18n.py | 20 +++++++----- 6 files changed, 84 insertions(+), 77 deletions(-) diff --git a/CLIclient_main.py b/CLIclient_main.py index 2e0d9b1..2b5b5ff 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -14,10 +14,12 @@ import pickle from sys import exit + async def send_message(websocket, message): if message: await websocket.send(message) + async def recv_message(websocket, message): inpacket = en.decrypt_data(message, CLIENT_CREDS['client_eprkey']) # handle check @@ -27,11 +29,13 @@ async def recv_message(websocket, message): else: pass + def execute_firstrun(): first_run.main() print(i18n.firstrun.exit) exit() + def check_missing_config(f, yaml, config): try: if yaml[config] is None: @@ -65,29 +69,30 @@ def fill_missing_config(f, yaml, config): f.seek(0) f.write(dumpyaml(yaml)) + async def main(host, port): uri = f"ws://{host}:{port}" async with websockets.connect(uri, ping_interval=120) as websocket: con_id = str(uuid4()) - await websocket.send(pickle.dumps({'type':'CONN_INIT', 'data':con_id})) + await websocket.send(pickle.dumps({'type': 'CONN_INIT', 'data': con_id})) try: key = await websocket.recv() key = pickle.loads(key) pubkey = s.load_pem_public_key(key['data']) SERVER_CREDS['server_epbkey'] = pubkey print("RECEIVED SERVER PUBLIC KEY") - await websocket.send(pickle.dumps({'type':'CONN_ENCRYPT_C','data':CLIENT_CREDS['client_epbkey']})) + await websocket.send(pickle.dumps({'type': 'CONN_ENCRYPT_C', 'data': CLIENT_CREDS['client_epbkey']})) print(en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey'])) - except websockets.exceptions.ConnectionClosedError as err: - print("Disconected from Server! Error:\n",err) + except websockets.exceptions.ConnectionClosedError as error: + print("Disconnected from Server! Error:\n", error) exit() while True: try: await send_message(websocket, await cli.main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, workingdir)) - except Exception as eroa: - print("Disconected from Server! Error:\n",eroa) + except Exception as e: + print("Disconnected from Server! Error:\n", e) exit() @@ -120,4 +125,4 @@ async def main(host, port): CLIENT_CREDS['workingdir'] = workingdir host, port = yaml['homeserver_address'], yaml['homeserver_port'] - asyncio.run(main(host,port)) + asyncio.run(main(host, port)) diff --git a/client_modules/cli_menus.py b/client_modules/cli_menus.py index af53bc6..d6ea3f2 100644 --- a/client_modules/cli_menus.py +++ b/client_modules/cli_menus.py @@ -2,6 +2,7 @@ from . import packet_handler as p import asyncio + def cls(): # for windows if os.name == 'nt': @@ -10,7 +11,8 @@ def cls(): else: _ = os.system('clear') -async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + +async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): print("Account System Demonstration Frontend") print("Welcome!") print("Available Operations:") @@ -25,16 +27,16 @@ async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, dir): match c: case 'signup': cls() - return await p.signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + return await p.signup(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) case 'login': cls() - return await p.login(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + return await p.login(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) case 'auth': cls() - return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) case 'logout': cls() - flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir) + flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) if flag == 'CANCELLED': print("Cancelled.") continue diff --git a/client_modules/encryption.py b/client_modules/encryption.py index 9885983..1e10d02 100644 --- a/client_modules/encryption.py +++ b/client_modules/encryption.py @@ -27,6 +27,7 @@ def create_rsa_key_pair(): public_key = private_key.public_key() return private_key, public_key + def ser_key_pem(key, type: str): if type == 'public': return key.public_bytes( @@ -39,12 +40,15 @@ def ser_key_pem(key, type: str): format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) + + def deser_pem(key, type): if type == 'public': return serialization.load_pem_public_key(key) elif type == 'private': return serialization.load_pem_private_key(key, password=None) + def encrypt_data(data, pubkey): data = pickle.dumps(data) # Generate symmetric key and encrypt it @@ -67,7 +71,8 @@ def encrypt_data(data, pubkey): encryptor = cipher.encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() - return pickle.dumps({'skey':encrypted_skey, 'cbc':cbc, 'ciphertext':ciphertext}) + return pickle.dumps({'skey': encrypted_skey, 'cbc': cbc, 'ciphertext': ciphertext}) + def decrypt_data(encrypted_data, privkey): try: @@ -93,39 +98,17 @@ def decrypt_data(encrypted_data, privkey): data = unpadder.update(decrypted_data) + unpadder.finalize() return pickle.loads(data) except Exception as error: - return {'type':'decrypt_error','data':f'{error}'} - -def fernet_initkey(workingdir): - passwd = '' - while True: - passwd = getpass(i18n.firstrun.passwd.input) - confirm = getpass(i18n.firstrun.passwd.confirm) - if passwd == confirm: - break - else: - print(i18n.firstrun.passwd.retry) - # Generate a Fernet key with the password and save the salt - salt = urandom(16) - with open(f"{workingdir}/creds/salt", "wb") as f: - f.write(salt) - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=600000, - ) - key = urlsafe_b64encode(kdf.derive(bytes(passwd, 'utf-8'))) - key = Fernet(key) - return key, passwd - + return {'type': 'decrypt_error', 'data': f'{error}'} + + """ THIS PART OF THE MODULE IS RESERVED FOR CHAT OPERATIONS """ -#def create_key_pair(): +# def create_key_pair(): # private_key_d = ec.generate_private_key(ec.SECP256K1()) # public_key_d = private_key_d.public_key() # return private_key_d, public_key_d -#def derive_key(eprkey, epbkey, keyinfo): +# def derive_key(eprkey, epbkey, keyinfo): # shared_key = eprkey.exchange( # ec.ECDH(), epbkey) # # Perform key derivation. @@ -137,16 +120,16 @@ def fernet_initkey(workingdir): # ).derive(shared_key) # return derived_key -#def encrypt_packet(data, key): +# def encrypt_packet(data, key): # data = pickle.dumps(data) # aesgcm = AESGCM(key) # nonce = urandom(12) # Unique nonce for each message # ciphertext = aesgcm.encrypt(nonce, data, None) # return pickle.dumps({'nonce': nonce, 'ciphertext': ciphertext}) -#def decrypt_packet(data, key): +# def decrypt_packet(data, key): # aesgcm = AESGCM(key) # nonce = data['nonce'] # ciphertext = data['ciphertext'] # plaintext = aesgcm.decrypt(nonce, ciphertext, None) -# return plaintext \ No newline at end of file +# return plaintext diff --git a/client_modules/first_run.py b/client_modules/first_run.py index bd5a775..7e73251 100644 --- a/client_modules/first_run.py +++ b/client_modules/first_run.py @@ -2,11 +2,13 @@ from i18n import firstrun, savedata from yaml import dump as dumpyaml from . import encryption as e -try: + +try: from tkinter import filedialog except: pass + def get_client_dir(): while True: try: @@ -20,6 +22,7 @@ def get_client_dir(): break return spath + def setup_client_dir(): while True: while True: @@ -34,8 +37,8 @@ def setup_client_dir(): print(savedata.creating, end=' ') try: os.mkdir(spath) - except OSError as e: - print(f"{savedata.error}:\n{e}") + except OSError as error: + print(f"{savedata.error}:\n{error}") spath = input(f"{savedata.input_writable}:\n") else: print(savedata.created) @@ -47,6 +50,7 @@ def setup_client_dir(): print(savedata.data_exists) return spath + def main(): print(firstrun.welcome_message) print(firstrun.setup_client_dir) @@ -56,4 +60,4 @@ def main(): port = int(input(firstrun.hs_port)) with open(f'{os.path.dirname(os.path.abspath(__file__))}/../config.yml', 'w') as fi: config = {'working_directory': workingdir, 'homeserver_address': host, 'homeserver_port': port} - fi.write(dumpyaml(config)) \ No newline at end of file + fi.write(dumpyaml(config)) diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index df3efa5..5343fb3 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -7,15 +7,17 @@ from yaml import load as loadyaml from i18n import sig_map, demo, log + async def status(SERVER_CREDS, CLIENT_CREDS, websocket, data): print(sig_map[data['sig']]) + async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): - with open('captcha.png','wb') as f: + with open('captcha.png', 'wb') as f: f.write(data['challenge']) print(demo.captcha_p) solved = int(input("> ")) - solved_packet = {'type':'S_CAPTCHA', 'data':{'solved':solved}} + solved_packet = {'type': 'S_CAPTCHA', 'data': {'solved': solved}} await websocket.send(en.encrypt_data(solved_packet, SERVER_CREDS['server_epbkey'])) resp = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) if resp['type'] == 'STATUS': @@ -23,6 +25,7 @@ async def captcha(SERVER_CREDS, CLIENT_CREDS, websocket, data): else: return resp + async def signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir): data = {} data['user'] = input(demo.user_p) @@ -37,48 +40,50 @@ async def signup(SERVER_CREDS, CLIENT_CREDS, websocket, dir): break else: print(demo.pass_mismatch) - await websocket.send(en.encrypt_data({'type':'SIGNUP', 'data':data}, SERVER_CREDS['server_epbkey'])) + await websocket.send(en.encrypt_data({'type': 'SIGNUP', 'data': data}, SERVER_CREDS['server_epbkey'])) captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) if captcha_flag == 'SIGNUP_OK': print(demo.signup_success) else: print(demo.error_i, sig_map[captcha_flag]) - + + async def login(SERVER_CREDS, CLIENT_CREDS, websocket, dir): # {'type':'LOGIN', 'data':{'id':username/email,'password':password,'save':True or False}} - data = {} - data['id'] = input(demo.id_p) - data['password'] = getpass(demo.pass_p) - if input(demo.save_p+' (y/N) > ').lower() == 'y': + data = {'id': input(demo.id_p), 'password': getpass(demo.pass_p)} + if input(demo.save_p + ' (y/N) > ').lower() == 'y': data['save'] = True else: data['save'] = False - await websocket.send(en.encrypt_data({'type':'LOGIN', 'data':data}, SERVER_CREDS['server_epbkey'])) + await websocket.send(en.encrypt_data({'type': 'LOGIN', 'data': data}, SERVER_CREDS['server_epbkey'])) captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) if isinstance(captcha_flag, str): - print(captcha_flag+":", sig_map[captcha_flag]) + print(captcha_flag + ":", sig_map[captcha_flag]) else: await save_token(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_flag) + async def save_token(SERVER_CREDS, CLIENT_CREDS, websocket, de_p): # {'type':'TOKEN_GEN','data':{'token':access_token}} dir = CLIENT_CREDS['workingdir'] try: - with open(dir+'/creds/acc_token', 'w') as f: + with open(dir + '/creds/acc_token', 'w') as f: f.write(de_p['data']['token']) print(demo.token_got) except Exception as ee: print(demo.token_fail.format(ee)) + async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): user = input(demo.id_p) try: - with open(dir+'/creds/acc_token', 'r') as f: + with open(dir + '/creds/acc_token', 'r') as f: token = f.read().rstrip('\r\n') if token: - await websocket.send(en.encrypt_data({'type':'AUTH_TOKEN','data':{'user':user,'token':token}}, SERVER_CREDS['server_epbkey'])) + await websocket.send(en.encrypt_data({'type': 'AUTH_TOKEN', 'data': {'user': user, 'token': token}}, + SERVER_CREDS['server_epbkey'])) elif not token: print(demo.token_nf) return None @@ -86,32 +91,36 @@ async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): if flag['data']['sig'] == 'LOGIN_OK': print(demo.end) else: - print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) + print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) except Exception as ee: print(demo.auth_err.format(ee)) + async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): - if input(demo.logout_p+' (y/N) > ').lower() == 'y': - await websocket.send(en.encrypt_data({'type':'LOGOUT', 'data':{}}, SERVER_CREDS['server_epbkey'])) + if input(demo.logout_p + ' (y/N) > ').lower() == 'y': + await websocket.send(en.encrypt_data({'type': 'LOGOUT', 'data': {}}, SERVER_CREDS['server_epbkey'])) flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) match flag['data']['sig']: case 'LOGOUT_OK': print(demo.logout_success) case other: - print(flag['data']['sig']+":", sig_map[flag['data']['sig']]) + print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) else: print("Cancelled logout.") + packet_map = { - 'STATUS':status, - 'CAPTCHA':captcha, - 'TOKEN_GEN':save_token -} + 'STATUS': status, + 'CAPTCHA': captcha, + 'TOKEN_GEN': save_token +} + + async def handle(SERVER_CREDS, CLIENT_CREDS, websocket, de_packet): type = de_packet['type'] data = de_packet['data'] print(log.tags.debug + log.handle_packet.format(type)) if type in packet_map.keys(): func = packet_map[type] - return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) \ No newline at end of file + return await func(SERVER_CREDS, CLIENT_CREDS, websocket, data) diff --git a/i18n.py b/i18n.py index aa367ce..3358529 100644 --- a/i18n.py +++ b/i18n.py @@ -1,4 +1,4 @@ -class firstrun(): +class firstrun: prompt1 = "Could not determine client's " empty_config = "Client configuration is empty. Deleting..." prompt2 = "Is this the first time you are running the client?" @@ -14,7 +14,8 @@ class firstrun(): hs_addr = "Enter Homeserver Address: " hs_port = "Enter Homeserver Port: " -class savedata(): + +class savedata: gui = "Opening file chooser dialog..." nogui = "Cannot open file chooser! Enter the path manually:" error = "An error occurred" @@ -26,7 +27,8 @@ class savedata(): created_new = "Created Client folder successfully." data_exists = "Previous Installation Detected! Please delete the files or choose another folder." -class demo(): + +class demo: captcha_p = "Open the captcha.png in the client folder and enter the digits here:" user_p = "Enter your username: " email_p = "Enter your email: " @@ -48,18 +50,20 @@ class demo(): end = "You have been logged in successfully.\nYour session is now active and this concludes the demonstration.\nIf you exit the app and decide to login again, you can use > auth to do it." -class log(): - class tags(): +class log: + class tags: info = '[INFO] ' warn = '[WARN] ' error = '[ERR] ' debug = '[DEBUG] ' + client_start = "Client starting from path {0}...." handle_packet = "Handling packet {}..." + sig_map = { - 'CONN_OK':"Connection to the server was successful", - 'CAPTCHA_WRONG':"The CAPTCHA code you entered was wrong. Try performing the action again", + 'CONN_OK': "Connection to the server was successful", + 'CAPTCHA_WRONG': "The CAPTCHA code you entered was wrong. Try performing the action again", 'SIGNUP_MISSING_CREDS': 'A field in your signup form is empty. Please signup again properly', 'SIGNUP_USERNAME_ABOVE_LIMIT': 'Username exceeds 32 character limit', 'SIGNUP_USERNAME_ALREADY_EXISTS': 'Username already exists', @@ -77,4 +81,4 @@ class tags(): 'NOT_LOGGED_IN': 'You need to be logged in to do that!', 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again', 'ACCOUNT_DNE': 'No such account found. Check your username/email' -} \ No newline at end of file +} From 639888bfd8c2e6320a74d8c5d110d6d1bb923496 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Tue, 28 Nov 2023 21:38:33 +0530 Subject: [PATCH 06/13] oops forgot to cleanup --- client_modules/encryption.py | 39 ------------------------------------ 1 file changed, 39 deletions(-) diff --git a/client_modules/encryption.py b/client_modules/encryption.py index 1e10d02..c335404 100644 --- a/client_modules/encryption.py +++ b/client_modules/encryption.py @@ -2,13 +2,8 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import padding as spadding -from cryptography.fernet import Fernet from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -### For chat operations from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -# from cryptography.hazmat.primitives.asymmetric import ec -# from cryptography.hazmat.primitives.kdf.hkdf import HKDF -# from cryptography.hazmat.primitives.ciphers.aead import AESGCM from os import urandom from getpass import getpass from base64 import urlsafe_b64encode @@ -99,37 +94,3 @@ def decrypt_data(encrypted_data, privkey): return pickle.loads(data) except Exception as error: return {'type': 'decrypt_error', 'data': f'{error}'} - - -""" THIS PART OF THE MODULE IS RESERVED FOR CHAT OPERATIONS """ - -# def create_key_pair(): -# private_key_d = ec.generate_private_key(ec.SECP256K1()) -# public_key_d = private_key_d.public_key() -# return private_key_d, public_key_d - -# def derive_key(eprkey, epbkey, keyinfo): -# shared_key = eprkey.exchange( -# ec.ECDH(), epbkey) -# # Perform key derivation. -# derived_key = HKDF( -# algorithm=hashes.SHA256(), -# length=32, -# salt=None, -# info=keyinfo.encode(), -# ).derive(shared_key) -# return derived_key - -# def encrypt_packet(data, key): -# data = pickle.dumps(data) -# aesgcm = AESGCM(key) -# nonce = urandom(12) # Unique nonce for each message -# ciphertext = aesgcm.encrypt(nonce, data, None) -# return pickle.dumps({'nonce': nonce, 'ciphertext': ciphertext}) - -# def decrypt_packet(data, key): -# aesgcm = AESGCM(key) -# nonce = data['nonce'] -# ciphertext = data['ciphertext'] -# plaintext = aesgcm.decrypt(nonce, ciphertext, None) -# return plaintext From 69d71ed6d156b47f79b3e98688ced29c309f7b91 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Tue, 28 Nov 2023 21:53:07 +0530 Subject: [PATCH 07/13] removed duplicate pickle import --- client_modules/encryption.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client_modules/encryption.py b/client_modules/encryption.py index c335404..c555ee8 100644 --- a/client_modules/encryption.py +++ b/client_modules/encryption.py @@ -9,7 +9,6 @@ from base64 import urlsafe_b64encode import pickle import i18n -import pickle def create_rsa_key_pair(): From 537ad30d8d86df7f9f3d03882fc28ae5a4fa3af6 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Thu, 30 Nov 2023 09:42:47 +0530 Subject: [PATCH 08/13] Account Deletion Functionality --- client_modules/cli_menus.py | 19 ++++++++++++++----- client_modules/packet_handler.py | 27 +++++++++++++++++---------- i18n.py | 2 +- requirements.txt | Bin 0 -> 254 bytes 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/client_modules/cli_menus.py b/client_modules/cli_menus.py index d6ea3f2..49d6911 100644 --- a/client_modules/cli_menus.py +++ b/client_modules/cli_menus.py @@ -1,7 +1,7 @@ import os, sys from . import packet_handler as p import asyncio - +from i18n import demo def cls(): # for windows @@ -20,6 +20,7 @@ async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): print("> login \t(Log in)") print("> auth \t(Authenticate)") print("> logout\t(Logout)") + print("> del\t(Delete)") print("> exit \t(Exit)") while True: print("What would you like to do?") @@ -36,12 +37,20 @@ async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) case 'logout': cls() - flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) - if flag == 'CANCELLED': - print("Cancelled.") - continue + if input(demo.logout_p + ' (y/N) > ').lower() == 'y': + flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) + return flag else: + print("Logout cancelled.") + case 'del': + cls() + print("Are you sure? Account deletion is irreversible") + c = input("(y/N) > ") + if c.lower() == 'y': + flag = await p.delete(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) return flag + else: + print("Account deletion cancelled.") case 'exit': print("Goodbye!") sys.exit() diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index 5343fb3..2e9b58d 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -98,16 +98,23 @@ async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): - if input(demo.logout_p + ' (y/N) > ').lower() == 'y': - await websocket.send(en.encrypt_data({'type': 'LOGOUT', 'data': {}}, SERVER_CREDS['server_epbkey'])) - flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) - match flag['data']['sig']: - case 'LOGOUT_OK': - print(demo.logout_success) - case other: - print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) - else: - print("Cancelled logout.") + await websocket.send(en.encrypt_data({'type': 'LOGOUT', 'data': {}}, SERVER_CREDS['server_epbkey'])) + flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + match flag['data']['sig']: + case 'LOGOUT_OK': + print(demo.logout_success) + case other: + print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) + + +async def delete(SERVER_CREDS, CLIENT_CREDS, websocket, dir): + data = {'id': input(demo.id_p), 'password': getpass(demo.pass_p)} + await websocket.send(en.encrypt_data({'type': 'DELETE', 'data': data}, SERVER_CREDS['server_epbkey'])) + captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) + captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) + print(captcha_flag + ":", sig_map[captcha_flag]) + if captcha_flag == 'ACC_DELETE_SUCCESS': + os.remove(dir + '/creds/acc_token') packet_map = { diff --git a/i18n.py b/i18n.py index 3358529..d9502e2 100644 --- a/i18n.py +++ b/i18n.py @@ -72,7 +72,7 @@ class tags: 'SIGNUP_DOB_INVALID': 'Date of birth is Invalid. Please enter it in YYYY-MM-DD or DD-MM-YYYY format.', 'SIGNUP_PASSWORD_ABOVE_LIMIT': 'No way in heaven will you remember the password. Choose a smaller one.', 'SIGNUP_ERR': 'Unexpected error while signing up, please try again.', - 'LOGIN_MISSING_CREDS': 'A field in your login form is empty. Please signup again properly', + 'LOGIN_MISSING_CREDS': 'A field in your form is empty. Please fill it again properly', 'LOGIN_INCORRECT_PASSWORD': 'The password you entered is incorrect. Check your password', 'LOGIN_ACCOUNT_NOT_FOUND': 'The requested account does not exist. Check your username/email', 'TOKEN_EXPIRED': 'Your session has expired. Login again', diff --git a/requirements.txt b/requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..aff1024dd112a0268c1aae00dbc802d3f6ffee2d 100644 GIT binary patch literal 254 zcmX|)%?iRW5QOI}_$WSvS5fdFh{v>ATdYk?Qc7Q5{WcW|$qVh~IRtyJGRSY)pGHlV0kzA?;}$bOV_;vJ2>$R#VJ>q_gLEO@#+s xzPKxm{AeD_)VvNY(H4KnSv#F|`m>9+I%r?`NV?Ot;E7B{($n$)XR!6!{Q|@9EFb^? literal 0 HcmV?d00001 From 83aee7571d2929e5f0eb362b4e1a2c1c86077eaf Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Fri, 1 Dec 2023 04:08:10 +0530 Subject: [PATCH 09/13] Complete i18n and account deletion --- client_modules/cli_menus.py | 26 ++++++++++---------------- client_modules/packet_handler.py | 10 ++++++---- i18n.py | 30 +++++++++++++++++++++++------- requirements.txt | Bin 254 -> 250 bytes 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/client_modules/cli_menus.py b/client_modules/cli_menus.py index 49d6911..36e7fb2 100644 --- a/client_modules/cli_menus.py +++ b/client_modules/cli_menus.py @@ -2,8 +2,10 @@ from . import packet_handler as p import asyncio from i18n import demo +from i18n import menu def cls(): + # function return value is assigned to _ to prevent it from printing to the console # for windows if os.name == 'nt': _ = os.system('cls') @@ -13,17 +15,9 @@ def cls(): async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): - print("Account System Demonstration Frontend") - print("Welcome!") - print("Available Operations:") - print("> signup\t(Sign up)") - print("> login \t(Log in)") - print("> auth \t(Authenticate)") - print("> logout\t(Logout)") - print("> del\t(Delete)") - print("> exit \t(Exit)") + print(menu.the_menu) while True: - print("What would you like to do?") + print(menu.input_p) c = input("> ").lower() match c: case 'signup': @@ -37,22 +31,22 @@ async def main_menu(SERVER_CREDS, CLIENT_CREDS, websocket, wdir): return await p.auth(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) case 'logout': cls() - if input(demo.logout_p + ' (y/N) > ').lower() == 'y': + if input(demo.logout_p + '\n(y/N) > ').lower() == 'y': flag = await p.logout(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) return flag else: - print("Logout cancelled.") + print(demo.logout_cancel) case 'del': cls() - print("Are you sure? Account deletion is irreversible") + print() c = input("(y/N) > ") if c.lower() == 'y': flag = await p.delete(SERVER_CREDS, CLIENT_CREDS, websocket, wdir) return flag else: - print("Account deletion cancelled.") + print() case 'exit': - print("Goodbye!") + print(menu.exit_msg) sys.exit() case other: - print("Enter a valid operation.") + print(menu.invalid_op) diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index 2e9b58d..66e298c 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -89,7 +89,7 @@ async def auth(SERVER_CREDS, CLIENT_CREDS, websocket, dir): return None flag = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) if flag['data']['sig'] == 'LOGIN_OK': - print(demo.end) + print(demo.login_success) else: print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) @@ -112,9 +112,11 @@ async def delete(SERVER_CREDS, CLIENT_CREDS, websocket, dir): await websocket.send(en.encrypt_data({'type': 'DELETE', 'data': data}, SERVER_CREDS['server_epbkey'])) captcha_prompt = en.decrypt_data(await websocket.recv(), CLIENT_CREDS['client_eprkey']) captcha_flag = await captcha(SERVER_CREDS, CLIENT_CREDS, websocket, captcha_prompt['data']) - print(captcha_flag + ":", sig_map[captcha_flag]) - if captcha_flag == 'ACC_DELETE_SUCCESS': - os.remove(dir + '/creds/acc_token') + match captcha_flag: + case 'ACC_DELETE_SUCCESS': + os.remove(dir + '/creds/acc_token') + case other: + print(captcha_flag + ":", sig_map[captcha_flag]) packet_map = { diff --git a/i18n.py b/i18n.py index d9502e2..8ff5684 100644 --- a/i18n.py +++ b/i18n.py @@ -46,9 +46,27 @@ class demo: token_nf = "You have to login first! Token not found." auth_err = "Token authentication failed. Please login again. Error:\n{}" logout_p = "Are you sure you want to log out?\nThis will reset your token and you will have to login again." + logout_cancel = "Logout cancelled." logout_success = "Successfully logged out. Token has been reset" - end = "You have been logged in successfully.\nYour session is now active and this concludes the demonstration.\nIf you exit the app and decide to login again, you can use > auth to do it." + login_success = "You have been logged in successfully.\nIf you exit the app and decide to login again, you can use > auth to do it." + del_p = "Are you sure? Account deletion is irreversible" + del_cancel = "Account deletion cancelled." +class menu: + the_menu = """ +Account System Demonstration Frontend +Welcome! +Available Operations: +> signup\t(Sign up) +> login \t(Log in) +> auth \t(Authenticate) +> logout\t(Logout) +> del \t(Delete) +> exit \t(Exit) +""" + input_p = "What would you like to do?" + invalid_op = "Enter a valid operation." + exit_msg = "Goodbye!" class log: class tags: @@ -62,9 +80,11 @@ class tags: sig_map = { + 'MISSING_CREDS': "A field in your form is empty. Please fill it again properly", + 'INCORRECT_PASSWORD': 'The password you entered is incorrect. Check your password', + 'ACCOUNT_NOT_FOUND': 'The requested account does not exist. Check your username/email', 'CONN_OK': "Connection to the server was successful", 'CAPTCHA_WRONG': "The CAPTCHA code you entered was wrong. Try performing the action again", - 'SIGNUP_MISSING_CREDS': 'A field in your signup form is empty. Please signup again properly', 'SIGNUP_USERNAME_ABOVE_LIMIT': 'Username exceeds 32 character limit', 'SIGNUP_USERNAME_ALREADY_EXISTS': 'Username already exists', 'SIGNUP_EMAIL_ABOVE_LIMIT': 'Your email ID is abnormally long. Are you sure its a valid address?', @@ -72,13 +92,9 @@ class tags: 'SIGNUP_DOB_INVALID': 'Date of birth is Invalid. Please enter it in YYYY-MM-DD or DD-MM-YYYY format.', 'SIGNUP_PASSWORD_ABOVE_LIMIT': 'No way in heaven will you remember the password. Choose a smaller one.', 'SIGNUP_ERR': 'Unexpected error while signing up, please try again.', - 'LOGIN_MISSING_CREDS': 'A field in your form is empty. Please fill it again properly', - 'LOGIN_INCORRECT_PASSWORD': 'The password you entered is incorrect. Check your password', - 'LOGIN_ACCOUNT_NOT_FOUND': 'The requested account does not exist. Check your username/email', 'TOKEN_EXPIRED': 'Your session has expired. Login again', 'TOKEN_INVALID': 'Login again to fix this.', 'LOGOUT_ERR': 'Unexpected error while logging out, please try again.', 'NOT_LOGGED_IN': 'You need to be logged in to do that!', - 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again', - 'ACCOUNT_DNE': 'No such account found. Check your username/email' + 'TOKEN_NOT_FOUND': 'The provided auth token does not exist on the server. Log in again' } diff --git a/requirements.txt b/requirements.txt index aff1024dd112a0268c1aae00dbc802d3f6ffee2d..3cc0df4d3cfe0282a5fd602eab51ec346744d86f 100644 GIT binary patch delta 6 Ncmeyz_=|DEF8~Y#1BCzp delta 11 Scmeyx_>XbIFBV<~E(QP{@dKa$ From 26e749a4c26f284384d475b6e088c25d3df32797 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Sun, 3 Dec 2023 12:50:44 +0530 Subject: [PATCH 10/13] just felt like doing it --- client_modules/packet_handler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client_modules/packet_handler.py b/client_modules/packet_handler.py index 66e298c..38eb843 100644 --- a/client_modules/packet_handler.py +++ b/client_modules/packet_handler.py @@ -103,6 +103,7 @@ async def logout(SERVER_CREDS, CLIENT_CREDS, websocket, dir): match flag['data']['sig']: case 'LOGOUT_OK': print(demo.logout_success) + os.remove(dir + '/creds/acc_token') case other: print(flag['data']['sig'] + ":", sig_map[flag['data']['sig']]) @@ -115,6 +116,10 @@ async def delete(SERVER_CREDS, CLIENT_CREDS, websocket, dir): match captcha_flag: case 'ACC_DELETE_SUCCESS': os.remove(dir + '/creds/acc_token') + # os.remove('sayori.chr') + # os.remove('natsuki.chr') + # os.remove('yuri.chr') + # Just Monika. case other: print(captcha_flag + ":", sig_map[captcha_flag]) From 44da74e06cf5a028fe4ce124f62c74196768d623 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Sun, 31 Dec 2023 22:01:46 +0530 Subject: [PATCH 11/13] more cleanup --- CLIclient_main.py | 1 - client_modules/encryption.py | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CLIclient_main.py b/CLIclient_main.py index 2b5b5ff..f16937e 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -1,7 +1,6 @@ import asyncio import websockets import os -import threading from cryptography.hazmat.primitives import serialization as s from client_modules import encryption as en from client_modules import packet_handler as p diff --git a/client_modules/encryption.py b/client_modules/encryption.py index c555ee8..708973a 100644 --- a/client_modules/encryption.py +++ b/client_modules/encryption.py @@ -1,14 +1,9 @@ -from cryptography.hazmat.primitives import serialization, hashes, hmac -from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import padding as spadding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from os import urandom -from getpass import getpass -from base64 import urlsafe_b64encode import pickle -import i18n def create_rsa_key_pair(): From 36be4c424b4a97255f888f446b70199d93af490e Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Mon, 1 Jan 2024 15:29:49 +0530 Subject: [PATCH 12/13] Update CLIclient_main.py --- CLIclient_main.py | 72 +++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/CLIclient_main.py b/CLIclient_main.py index f16937e..dc527cf 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -1,7 +1,6 @@ import asyncio import websockets import os -from cryptography.hazmat.primitives import serialization as s from client_modules import encryption as en from client_modules import packet_handler as p from client_modules import cli_menus as cli @@ -35,29 +34,52 @@ def execute_firstrun(): exit() -def check_missing_config(f, yaml, config): - try: - if yaml[config] is None: - print(i18n.firstrun.prompt1 + config) - if config == 'working_directory': - print(i18n.firstrun.prompt2) - while True: - choice = input("(Y / N) > ") - if choice.lower() == 'y': - print(i18n.firstrun.exec) - f.close() - os.remove(f'{rootdir}/config.yml') - first_run.main() - print(i18n.firstrun.exit) - exit() - elif choice.lower() == 'n': - fill_missing_config(f, yaml, 'working_directory') - break - else: - fill_missing_config(f, yaml, config) - except KeyError: - print(i18n.firstrun.prompt1 + config) - fill_missing_config(f, yaml, config) +# def check_missing_config(f, yaml, config): +# try: +# if yaml[config] is None: +# print(i18n.firstrun.prompt1 + config) +# if config == 'working_directory': +# print(i18n.firstrun.prompt2) +# while True: +# choice = input("(Y / N) > ") +# if choice.lower() == 'y': +# print(i18n.firstrun.exec) +# f.close() +# os.remove(f'{rootdir}/config.yml') +# first_run.main() +# print(i18n.firstrun.exit) +# exit() +# elif choice.lower() == 'n': +# fill_missing_config(f, yaml, 'working_directory') +# break +# else: +# fill_missing_config(f, yaml, config) +# except KeyError: +# print(i18n.firstrun.prompt1 + config) +# fill_missing_config(f, yaml, config) + +def check_missing_config(file, yaml_config, config_key): + if yaml_config.get(config_key) is not None: + return + print(i18n.firstrun.setting_not_found.format(config_key)) + + if config_key != 'working_directory': + fill_missing_config(file, yaml_config, config_key) + return + print(i18n.firstrun.ft_question) + + while True: + choice = input("(Y / N) > ").lower() + if choice == 'y': + print(i18n.firstrun.exec) + file.close() + os.remove(f'{rootdir}/config.yml') + first_run.main() + print(i18n.firstrun.exit) + exit() + elif choice == 'n': + fill_missing_config(file, yaml_config, 'working_directory') + break def fill_missing_config(f, yaml, config): @@ -77,7 +99,7 @@ async def main(host, port): try: key = await websocket.recv() key = pickle.loads(key) - pubkey = s.load_pem_public_key(key['data']) + pubkey = en.deser_pem(key['data']) SERVER_CREDS['server_epbkey'] = pubkey print("RECEIVED SERVER PUBLIC KEY") await websocket.send(pickle.dumps({'type': 'CONN_ENCRYPT_C', 'data': CLIENT_CREDS['client_epbkey']})) From 2185f4cba1426dfd6445d2619b632e087cdecf07 Mon Sep 17 00:00:00 2001 From: Ilamparithi M <5023573ilamparithi@gmail.com> Date: Mon, 1 Jan 2024 16:55:53 +0530 Subject: [PATCH 13/13] suddenly, i find a bug! --- CLIclient_main.py | 2 +- client_modules/first_run.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CLIclient_main.py b/CLIclient_main.py index dc527cf..7a8fc7a 100644 --- a/CLIclient_main.py +++ b/CLIclient_main.py @@ -99,7 +99,7 @@ async def main(host, port): try: key = await websocket.recv() key = pickle.loads(key) - pubkey = en.deser_pem(key['data']) + pubkey = en.deser_pem(key['data'], 'public') SERVER_CREDS['server_epbkey'] = pubkey print("RECEIVED SERVER PUBLIC KEY") await websocket.send(pickle.dumps({'type': 'CONN_ENCRYPT_C', 'data': CLIENT_CREDS['client_epbkey']})) diff --git a/client_modules/first_run.py b/client_modules/first_run.py index 7e73251..ed9dd9e 100644 --- a/client_modules/first_run.py +++ b/client_modules/first_run.py @@ -32,14 +32,16 @@ def setup_client_dir(): break # If either the path leads to a file or is not writable (or invalid) elif os.path.exists(spath) and not os.path.isdir(spath): - spath = input(f"{savedata.not_a_dir}:\n") + print(f"{savedata.not_a_dir}:\n") + spath = get_client_dir() elif not os.path.exists(spath): print(savedata.creating, end=' ') try: os.mkdir(spath) except OSError as error: print(f"{savedata.error}:\n{error}") - spath = input(f"{savedata.input_writable}:\n") + print(f"{savedata.input_writable}:\n") + spath = get_client_dir() else: print(savedata.created) break