diff --git a/Flowchart.vsd b/Flowchart.vsd index 8100d8e..7dd02b8 100644 Binary files a/Flowchart.vsd and b/Flowchart.vsd differ diff --git a/app_secure_client.py b/app_secure_client.py index b7f5e1c..9c04e53 100644 --- a/app_secure_client.py +++ b/app_secure_client.py @@ -13,7 +13,9 @@ import socket import json import io import struct - +import hashlib +from Crypto import Random +from Crypto.PublicKey import RSA def create_request(action, value): if action == "search": @@ -105,6 +107,55 @@ def process_response(sock, addr, jsonheader, _recv_buffer): print(f"got response: {repr(content)}") +def send_client_key(sock, data): + response = { + "content_bytes": data, + "content_type": "binary/custom-server-binary-type", + "content_encoding": "binary", + } + message = _create_message(**response) + print(f'Sending client public key and hash...') + sock.sendall(message) + +def recv_server_secrets(sock, _recv_buffer, key): + HDRLEN = 2 + # read and process protoheader (fixed length of 2 byte in network order) + read_to_buffer(sock, HDRLEN, _recv_buffer) + _jsonheader_len = struct.unpack(">H", _recv_buffer[:HDRLEN])[0] + _recv_buffer = _recv_buffer[HDRLEN:] + + # read and process jsonheader + read_to_buffer(sock, _jsonheader_len, _recv_buffer) + jsonheader = _json_decode(_recv_buffer[:_jsonheader_len], "utf-8") + _recv_buffer = _recv_buffer[_jsonheader_len:] + for reqhdr in ("byteorder", "content-length", "content-type", + "content-encoding",): + if reqhdr not in jsonheader: + raise ValueError(f'Missing required header "{reqhdr}".') + + content_len = jsonheader["content-length"] + read_to_buffer(sock, content_len, _recv_buffer) + data = _recv_buffer[:content_len] + _recv_buffer = _recv_buffer[content_len:] + sesskeyhash = data[-128:] + data = data[:-128] + encryptedsess = data[-256:] + # halfsesskey = key.decrypt(encryptedsess) + data = data[:-256] + pubkeyhash = data[-128:] + pubkey = data[:-128] + if hashlib.sha3_512(pubkey).hexdigest().encode() != pubkeyhash: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + raise Exception('Server public key does not match hash') + if hashlib.sha3_512(halfsesskey).hexdigest().encode() != sesskeyhash: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + raise Exception('Server public key does not match hash') + print(f"Received server secrets correctly.") + return pubkey, halfsesskey + + def main(): HDRLEN = 2 _recv_buffer = bytearray(b"") @@ -114,15 +165,23 @@ def main(): sys.exit(1) host, port = sys.argv[1], int(sys.argv[2]) - action, value = sys.argv[3], sys.argv[4] - request = create_request(action, value) - message = create_message(request) - addr = (host, port) print("starting connection to", addr) sock = socket.create_connection(addr) - sock.settimeout(5) + sock.settimeout(10) + # security handshake + key = RSA.generate(2048, Random.new().read) + publickey = key.publickey().exportKey('PEM') + keyhash = hashlib.sha3_512(publickey).hexdigest().encode() + data = publickey + keyhash + send_client_key(sock, data) + # receiving server secrets - public key and session key + serverkey, sessionkey = recv_server_secrets(sock, _recv_buffer, key) + + action, value = sys.argv[3], sys.argv[4] + request = create_request(action, value) + message = create_message(request) print(f"sending request {repr(message)} to {addr}") sock.sendall(message) diff --git a/app_secure_server.py b/app_secure_server.py index c59f268..0a744da 100644 --- a/app_secure_server.py +++ b/app_secure_server.py @@ -15,46 +15,51 @@ import lib_secure_server as libserver sel = selectors.DefaultSelector() -def accept_wrapper(sock): +def accept_wrapper(sock, passhash): conn, addr = sock.accept() # Should be ready to read print("accepted connection from", addr) conn.setblocking(False) - message = libserver.Message(sel, conn, addr) + message = libserver.Message(sel, conn, addr, passhash) sel.register(conn, selectors.EVENT_READ, data=message) +def main(): + if len(sys.argv) != 3: + print("usage:", sys.argv[0], " ") + sys.exit(1) + with open('password.txt', 'r') as f: + passhash = f.read() + passhash = passhash.encode() # into byte string + host, port = sys.argv[1], int(sys.argv[2]) + lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # Avoid bind() exception: OSError: [Errno 48] Address already in use + lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + lsock.bind((host, port)) + lsock.listen() + print("listening on", (host, port)) + lsock.setblocking(False) + sel.register(lsock, selectors.EVENT_READ, data=None) -if len(sys.argv) != 3: - print("usage:", sys.argv[0], " ") - sys.exit(1) + try: + while True: + events = sel.select(timeout=None) + for key, mask in events: + if key.data is None: + accept_wrapper(key.fileobj, passhash) + else: + message = key.data + try: + message.process_events(mask) + except Exception: + print( + "main: error: exception for", + f"{message.addr}:\n{traceback.format_exc()}", + ) + message.close() + except KeyboardInterrupt: + print("caught keyboard interrupt, exiting") + finally: + sel.close() + lsock.close() -host, port = sys.argv[1], int(sys.argv[2]) -lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# Avoid bind() exception: OSError: [Errno 48] Address already in use -lsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -lsock.bind((host, port)) -lsock.listen() -print("listening on", (host, port)) -lsock.setblocking(False) -sel.register(lsock, selectors.EVENT_READ, data=None) - -try: - while True: - events = sel.select(timeout=None) - for key, mask in events: - if key.data is None: - accept_wrapper(key.fileobj) - else: - message = key.data - try: - message.process_events(mask) - except Exception: - print( - "main: error: exception for", - f"{message.addr}:\n{traceback.format_exc()}", - ) - message.close() -except KeyboardInterrupt: - print("caught keyboard interrupt, exiting") -finally: - sel.close() - lsock.close() \ No newline at end of file +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lib_secure_server.py b/lib_secure_server.py index b85391a..331b603 100644 --- a/lib_secure_server.py +++ b/lib_secure_server.py @@ -9,7 +9,12 @@ import sys import selectors import json import io +import os import struct +import hashlib +from Crypto import Random +from Crypto.PublicKey import RSA + request_search = { "morpheus": "Follow the white rabbit. \U0001f430", @@ -19,7 +24,7 @@ request_search = { class Message: - def __init__(self, selector, sock, addr): + def __init__(self, selector, sock, addr, passhash): self.selector = selector self.sock = sock self.addr = addr @@ -29,6 +34,16 @@ class Message: self.jsonheader = None self.request = None self.response_created = False + # added for security + self.key = RSA.generate(2048, Random.new().read) + self.sessionkey = os.urandom(8) + self.passhash = passhash # pass phrase hash + self.clientkey = None # client public key + self.handshake = False + print(f"initiating message associated with {addr}") + + def __del__(self): + print(f"destructing messange associlated with {self.addr}") def _set_selector_events_mask(self, mode): """Set selector to listen for events: mode is 'r', 'w', or 'rw'.""" @@ -120,6 +135,14 @@ class Message: } return response + def _create_handshake_content(self, data): + response = { + "content_bytes": data, + "content_type": "binary/custom-server-binary-type", + "content_encoding": "binary", + } + return response + def process_events(self, mask): if mask & selectors.EVENT_READ: self.read() @@ -148,7 +171,7 @@ class Message: self._write() def close(self): - print("closing connection to", self.addr) + print(f"closing connection to {self.addr}\n") try: self.selector.unregister(self.sock) except Exception as e: @@ -192,23 +215,45 @@ class Message: if reqhdr not in self.jsonheader: raise ValueError(f'Missing required header "{reqhdr}".') + def send_my_secrets(self): + halfsesskey = self.sessionkey + encryptedsess = self.clientkey.encrypt(halfsesskey, None)[0] + sesskeyhash = hashlib.sha3_512(halfsesskey).hexdigest().encode() + mypubkey = self.key.publickey().exportKey() + mypubkeyhash = hashlib.sha3_512(mypubkey).hexdigest().encode() + mysecret = mypubkey + mypubkeyhash + encryptedsess + sesskeyhash + response = self._create_handshake_content(mysecret) + message = self._create_message(**response) + self._send_buffer += message + print(f'Sending server public key, hash, session key, hash...') + def process_request(self): content_len = self.jsonheader["content-length"] if not len(self._recv_buffer) >= content_len: return data = self._recv_buffer[:content_len] self._recv_buffer = self._recv_buffer[content_len:] - if self.jsonheader["content-type"] == "text/json": - encoding = self.jsonheader["content-encoding"] - self.request = self._json_decode(data, encoding) - print("received request", repr(self.request), "from", self.addr) + if not self.handshake: # data is client public key plus hash + key = data[:-128] + keyhash = data[-128:] + if hashlib.sha3_512(key).hexdigest().encode() == keyhash: + self.clientkey = RSA.importKey(key) + else: + self.sock.close() + raise Exception('Client plublic key does not match key hash') + self.send_my_secrets() else: - # Binary or unknown content-type - self.request = data - print( - f'received {self.jsonheader["content-type"]} request from', - self.addr, - ) + if self.jsonheader["content-type"] == "text/json": + encoding = self.jsonheader["content-encoding"] + self.request = self._json_decode(data, encoding) + print("received request", repr(self.request), "from", self.addr) + else: + # Binary or unknown content-type + self.request = data + print( + f'received {self.jsonheader["content-type"]} request from', + self.addr, + ) # Set selector to listen for write events, we're done reading. self._set_selector_events_mask("w")