diff --git a/Database.md b/Database.md index 6c96c5f9..df0a6d30 100644 --- a/Database.md +++ b/Database.md @@ -97,16 +97,20 @@ A Law Document looks like this: ``` # users -For every Registered user there is a -Public and Private PGP Key (GnuPG): +For every Registered user there is a private and public key. +The private key is encrypted with each of the users three passwords. +This makes it possible to use private keys with each of them ```json { "public_key" : "" -, "private_key" : "" +, "private_keys" : [""] +, "passwords" : [""] , "username" : "" , "first_name" : "" , "last_name" : "" , "expiration" : "" , "feed" : [""] +, "readings" : [""] +, "writings" : [""] , "old_keys" : [{ "expiration" : "" , "public_key" :"" , "private_key" : "" @@ -115,24 +119,17 @@ Public and Private PGP Key (GnuPG): ``` # messages -Anybody can send end-to-end encypted messages to anyone. -A message, that is addressed to `all` is a post. -The process of encrypting and decrypting a message -from Alice to Bob is like this: - - Alice's Message -> Alice's Private Key -> Bob's Public Key -> Send -> Bob's Private Key -> Alice's Public Key - -A post is just signed by the author - - Alice's Message -> Alice's Private Key -> Published - +For now, there can be only one kind of messages: +Public posts. +These messages can be read and are addressed to anyone. ```json { "from" : "" -, "to" : [ "" ] -, "body" : [{ "recipient_name" : "" - , "ciphertext" : "" - }] +, "to" : "all" +, "body" : { "title" : "" +, "draft" : <true if message is a draft and not yet ready to be send.> } ``` If in "to" there is `"all"` then the body isn't encrypted diff --git a/Server/Patches.py b/Server/Patches.py index 00a9ce89..66054107 100644 --- a/Server/Patches.py +++ b/Server/Patches.py @@ -2,70 +2,27 @@ import subprocess, os, json from Crypto.Hash import SHA256 from pymongo import MongoClient - -""" -Env Variables: -$PATCHES : Folder with the Git Repositories of the Patches (format: patcher-patch/) -$ORIGIN_REPOSITORY : Location of the Original Git Repository, from which patches are cloned from -and to whom patches are pushed after being approved. - -create a patch: -- clone the origin_repository, as stored in the $ORIGIN_REPOSITORY envvar. -- create an entry in the collection patches on the demnet database. - -This patch needs the following options: - -- The name of the patcher, that can either be the username in demnet -or a pseudonyme chosen by a non-user, which will then be noted in a separate -variable called "is_user". - -- The name of the patch, that should be descriptive (hopefully) - -- The simple_description of the patcher's intent. -This is useful for users, if the patcher wants to hold an election before -starting development. These users won't often be interested in the technical -details, for whom this description should suffice. - -- The technical_description is for all developers trying to understand -the patcher's intent on a deeper level. - -- As part of the technical_description, the patcher can also reference issues, -bug reports and generally link to ressources, detailing his intention. -This is separatly done in the "references" field. -The Patcher should put a most conclusive list here -and reference only some of it in the descriptions. - -- The Patcher may not want to start developing, waste hours and hours of time, -and see that the users don't even have an interest their patch. -All patches have to pass an election eventually, but if the patcher wants to, -they can hold an election before starting to develop, just to see if there -is any interest. If the election is lost, the patcher will either have to delete -the patch or modify it substantially. If they want to hold such an election, -"hold_pre_election" must be set to True. - -- After the Patch has been closed and the Patch Repository deleted, the -patch entry "closed" = True - -After creating the patch, -a SHA256 of all the data is created and -returned as the reference to the patch. -This is also put into one field called "sha256". -(That this isn't included in the calculation of the hash is self-evident.) -""" -def create(patcher,patch_name, options): +from typing import List + +def create ( patcher : str + , patch_name : str + , is_user : bool + , simple_description : str + , technical_description : str + , references : List[str] + ): client = MongoClient() db = client.demnet patches = db.patches patch = { "patcher" : patcher - , "is_user" : options['is_user'] + , "is_user" : is_user , "name" : patch_name - , "simple_description" : options['simple_description'] - , "technical_description" : options['technical_description'] - , "hold_pre_election" : options['hold_pre_election'] - , "references" : options['references'] + , "simple_description" : simple_description + , "technical_description" : technical_description + , "references" : references , "closed" : False } - patch['hash'] = SHA256.new(data=json.dumps(patch).encode('utf-8')).hexdigest() + patch['hash'] = SHA256.new().update(json.dumps(patch)).encode('utf-8')).hexdigest() patch_id = patches.insert_one(patch).inserted_id subprocess.run(['bash', 'patch.sh', 'create', os.environ["ORIGIN_REPOSITORY"], patcher, patch_name]) @@ -75,6 +32,14 @@ def create(patcher,patch_name, options): def merge(patcher, patch_name): subprocess.run(['bash', 'patch.sh', 'merge', patcher, patch_name]) +def lock(patcher, patch_name): + path_of_patch = path_of_patch(patcher,patch_name) + if not path_of_patch: + return False + else: + os.chmod(path_of_patch, 0444) # readonly to all + return True + """ Closing a patch means: Deleting the Git Repo. @@ -90,7 +55,7 @@ def close(patcher, patch_name, id, merge=False): , "closed" : False } ) - repo_path = f"{os.environ['PATCHES']}/{patcher}-{patch_name}" + repo_path = path_of_patch(patcher, patch_name, check_existence=False) if patch and os.path.isdir(repo_path): if merge: merge(patcher, patch_name) @@ -101,3 +66,10 @@ def close(patcher, patch_name, id, merge=False): return True else: return False + +def path_of_patch(patcher, patch_name, check_existence=True): + path = f"{os.environ["PATCHES"]}/{patcher}-{patch_name}" + if check_existence: + return path if os.path.isdir(path) else False + else: + return path diff --git a/Server/Type_checks.py b/Server/Type_checks.py new file mode 100644 index 00000000..36501ee8 --- /dev/null +++ b/Server/Type_checks.py @@ -0,0 +1,49 @@ +from typing import Dict, TypeVar +class Type_Check_Error (Exception): + def __init__(self,data_name, msg): + self.data_name = data_name + self.msg = msg + +Dict_Values = TypeVar("Dict_Values") +def check_for_type(dictionary_name : str, dictionary : Dict[str, Dict_Values]): + try: + if dictionary_name == "additions": + for addition in dictionary: + assert check_for_type(addition, { "title" : "" + , "book" : "" + , "articles" : [] + } + ) + elif dictionary == "amendments": + for amendment in dictionary: + assert check_for_type(amendment, { "book" : "" + , "law" : "" + , "articles" : [] + } + ) + elif dictionary == "repeals": + for repeal in repeals: + assert check_for_type(repael, { "books" : [] + , "laws" : [] + }) + else: + raise Type_Check_Error(dictionary_name, "unknown dictionary name") + except AssertionError as assertion_error: + raise Type_Check_Error(dictionary_name, assertion_error.args[0]) + except Exception as e: + raise e + else: + return True + +def check_dictionary_for_type ( dictionary : Dict[str, Dict_Values] + , types : Dict[str, Dict_Values] + ): + try: + tests = [(type(types[key]) == type(dictionary[key]), key) for key in types] + test_failed = False in filter(lambda t: t[0], tests) + assert not test_failed, f"Type Check Failed: {list(map(lambda t: t[1], filter(lambda t: t[0] == False, tests)))}" + + except Exception as e: + raise e + else: + return True diff --git a/Server/Users.py b/Server/Users.py index ba7349f3..792f85e3 100644 --- a/Server/Users.py +++ b/Server/Users.py @@ -12,6 +12,7 @@ from Crypto.Hash import SHA256 from Crypto.Cipher import AES from pymongo import MongoClient +import pymongo import datetime, sys, json client = MongoClient() @@ -31,7 +32,7 @@ def login(username, passphrase): if keys.publickey().export_key(format="PEM") != user["public_key"]: users.update_one({ "username" : username }, { "$set" : { "public_key" : keys.publickey().export_key(format="PEM") } } ) - if datetime.datetime.strptime(user["expiration"], "%m/%d/%Y") > datetime.datetime.now(): + if datetime.date.fromisoformat(user["expiration"]) > datetime.datetime.now(): new_keys = RSA.generate(2048) new_expiration = datetime.timedelta (weeks=104 @@ -43,8 +44,9 @@ def login(username, passphrase): ,microseconds=0 ) + datetime.datetime.now() - private_key = new_keys.export_key(format="PEM", passphrase=passphrase) - public_key = new_keys.publickey().export_key(format="PEM") + new_expiration = new_expiration.isoformat() + private_key = new_keys.export_key(format="PEM", passphrase=passphrase) + public_key = new_keys.publickey().export_key(format="PEM") users.update_one( { "username" : username } @@ -129,9 +131,7 @@ def encrypt(message,keys): """ Publishing a message works in these steps: -1. Encrypt/Sign the message -2. Publish it in demnet.messages -3. Add a notification to the recipient's feed. +1. Publish it in demnet.messages Parameters: - *message* message document with unencrypted body - *keys* private and public keys of the author @@ -139,20 +139,13 @@ def encrypt(message,keys): True if successfull False if not """ -def publish(message, keys): - # 1. Encrypt/Sign the message - body = encrypt(message, keys) - if body != False: +def publish(message): + if message['body'] != False: # 2. Publish it in demnet.messages - message['body'] = body - message['hash'] = SHA256.new(json.dumps(body).encode('utf-8')).hexdigest() messages = db.messages + message['hash'] = SHA256.new(json.dumps(message['body']).encode('utf-8')).hexdigest() + message['upload_time'] = messages.find().sort("upload_time", pymongo.DESCENDING).limit(1)[0]["upload_time"] + 1 # Some inconsistent (race condition) is possible, but not critical. messages.insert_one(message) - # 3. Add a notification to the recipient's feed - for recipient_name in message['to']: - users.update_one({ "username" : recipient_name } - , { "$push" : { "feed" : message["hash"] } } - ) return True return False diff --git a/main.py b/main.py index 71e7f9ea..562aeeef 100644 --- a/main.py +++ b/main.py @@ -1,81 +1,553 @@ #!/usr/bin/env python3 -from Server import Elections, Patches, Users -from flask import Flask, request, render_template, send_file -import json, os -from Crypto.Hash import SHA3_256 +# OWN MODULES +from Server import Elections, Patches, Users, Type_checks +# FLASK +from flask import Flask, request, render_template, session, redirect +# MONGODB +import pymongo +from pymongo import MongoClient +# PYCRYPTODOME +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA +# UITILS +from typing import List +import json, os, datetime, random -app = Flask(__name__, static_url_path="/static", static_folder="output") +app = Flask( __name__ + , static_url_path="/static" + , static_folder="output/static" + , template_folder="output") app.secret_key = os.environ["SECRET_KEY"] +client = MongoClient() +db = client.demnet +messages = db.messages +users = db.users +elections = db.elections +patches = db.patches + # Errors -ok = 0 -invalidData = 1 -invalidContext = 2 +debug = os.environ.get("DEBUG") +errors = { "OK" : "0" + , "error_for_unknown_reason" : "1" + , "error_but_not" : "2" + , "invalid_data" : "3" + , "invalid_context" : "4" + , "not_logged_in" : "5" + , "invalid_user" : "6" + , "already_registered" : "7" + , "no_user_with_that_name" : "8" + , "invalid_password" : "9" + , "already_voted" : "A" + } + +errors = { key : errors[key] if not debug else key for key in errors } + +class Error (Exception): + def status(self): + return errors[self.args[0]] + @app.route("/", methods=["GET"]) def index(): - return send_file("output/index.html") + try: + messages_count = 10 + sorted_messages = list(messages.find({ "draft" : False })) + upload_time_cut = max(sorted_messages,key=lambda m: m["upload_time"])["upload_time"] - messages_count + sorted_messages = list(filter(lambda m: m["upload_time"] >= upload_time_cut, sorted_messages)) + sorted_messages = list(map( lambda m: { "title" : m["body"]["title"] + , "hash" : m["hash"] } + , sorted_messages + ) + ) + response = render_template ( "index.html" + , messages = sorted_messages + , logged_in = session.get("username") != None + ) + + except Exception as e: + raise e + else: + return response -@app.route("/login",methods=["POST"]) + +@app.route("/login", methods=["GET", "POST"]) def login(): - username = request.values.get("username") - password = request.values.get("password") - - if not session.get("keys") and username and password: - sha3_256 = SHA3_256.new() - sha3_256.update(password.encode('utf-8')) - passphrase = sha3_256.hexdigest() - keys = Users.login(username,passphrase) - if not keys: - return invalidData + try: + if request.method == "GET": + return render_template("login.html") + elif request.method == "POST": + username = request.values["username"] + password = SHA256.new().update(request.values["password"].encode("utf-8")).hexdigest() + + user = users.find_one({ "username" : username }) + if user: + if password in user["passwords"]: + session["username"] = username + else: + raise Error("invalid_password") + else: + raise Invalid_Data("no_user_with_that_name") + response = redirect("/") + + except KeyError: + return errors["invalid_data"] + except Error as e: + return e.status() + except Exception as e: + raise e + else: + return response + + +@app.route("/readings", methods=["GET"]) +def readings(): + try: + readings = users.find_one({ "username" : session["username"] })["readings"] + readings = [messages.find_one({ "hash" : reading }) for reading in readings] + readings = [(reading["body"]["title"], reading["hash"]) for reading in readings] + response = render_template("readings-index.html", readings=readings) + except KeyError: + return errors["not_logged_in"] + except Exception as e: + raise e + else: + return response + +@app.route("/writings", methods=["GET"]) +def writings(): + try: + writings = users.find_one({ "username" : session["username"] })["writings"] + writings = [messages.find_one({ "hash" : writing }) for writing in writings] + writings = [(writing["body"]["title"],writing["hash"]) for writing in writings] + response = render_template("writings-index.html", writings=writings) + except KeyError: + return errors["not_logged_in"] + except Exception as e: + raise e + else: + return response + +@app.route("/read/<reading_hash>", methods=["GET"]) +def read(reading_hash): + try: + reading = messages.find_one({ "hash" : reading_hash }) + if reading["draft"]: + raise Error("invalid_context") + else: + response = render_template("read.html", reading=reading) + except Error as e: + return e.status() + except Exception as e: + raise e + else: + return response + +@app.route("/write", methods=["GET","POST"]) +def write_new(): + try: + if request.method =="GET": + response = render_template("write.html", writing={}) + else: + title = request.values["title"] + content = request.values["content"] + author = session["username"] + user_author = users.find_one({ "username" : author }) + hash = f"{author}#{str(random.randint(0,99999)).zfill(5)}" #temporary, while files is still changing + message = { "body" : { "title" : title + , "content" : content + } + , "from" : author + , "hash" : hash + , "draft" : True + } + + messages.insert_one(message) + response = redirect(f"/write/{hash}") + except KeyError: + return errors["invalid_user"] + except Error as e: + return e.status() + except Exception as e: + raise e + else: + return response + + + +@app.route("/write/<writing_hash>", methods=["GET", "POST"]) +def write(writing_hash): + try: + if request.method == "GET": + writing = messages.find_one({ "hash" : writing_hash }) + + if session["username"] == writing["author"]: + response = render_template("write.html", writing=writing) + else: + raise Error("invalid_user") + else: + hash = request.values["hash"] + message = messages.find_one({"hash" : hash}) + message["title"] = request.values["title"] + message["content"] = request.values["content"] + publish = request.values["publish"] == "1" + + if publish: + message["hash"] = SHA256.new().update(json.dumps(message).encode("utf-8")).hexdigest() + message["draft"] = not publish + messages.replace_one({ "hash" : hash }, message) + else: + messages.update_one({ "hash" : hash }, message) + + except Error as e: + return e.status() + except KeyError: + return errors["invalid_context"] + else: + return response + +""" +# The three stage process of an election. +The last step of an election is clear, the vote. +But before that can happen two other steps have to prepare the vote. + +0. Proposing the vote +Here a problem is explained. +This can be in form of a simple title +or a long study, containing reasoning, +technical details and a concret case for why this problem needs addressing. + +1. Collecting options +After the problem has been detailed, everybody is free to create a solution +and propose it as an option. + +2. Voting +Now every user is held to vote on the issue and select +the best solution to the problem. + +The first stage can take as long as it wants. +One can research an issue for years and deliver an extremely +detailed proposal for the issue, but once it is detailed and +officially proposed, the solutions must be proposed over a one +month period. +It is thus a good advice to anyone proposing a +problem to have published their results long enough beforehand, +so a solution can be developed. + +After the one month period, voting opens and for 2 weeks voters +can cast their votes. + +In conclusion: + +0. Proposing the vote : as much time as it needs +1. Collecting options : 4 weeks +2. Voting : 2 weeks +""" + +@app.route("/voting/propose", methods=["GET", "POST"]) +def propose_vote(): + try: + if request.method == "GET": + response = render_template("propose_vote.html") + else: + if not session.get("username"): + raise Errors("not_logged_in") + else: + title = request.values["title"] + description = request.values["description"] + vote = { "title" : title + , "description" : description + , "stage" : 1 + , "author" : session["username"] + , "options" : [] + } + vote["id"] = SHA256.new().update(json.dumps(vote)).encode("utf-8")).hexdigest() + elections.insert_one(vote) + + # Initate third stage after four weeks + + response = redirect(f"/voting/option/{vote["id"]}") + except Error as e: + return e.status() + except KeyError: + return errors["invalid_data"] + except Exception as e: + raise e + else: + return response + +@app.route("/voting/option/<vote_id>", methods=["GET,POST"]) +def propose_option(vote_id): + try: + if request.method == "GET": + response = render_template("propose_option.html") else: - session["keys"] = keys - session["SHA3-256_passphrase"] = passphrase - session["username"] = username - return ok + if not session.get("author"): + raise Error("not_logged_in") + else: + type_of_option = request.values["type"] + author = session["username"] + election = elections.find_one({ "id" : vote_id, "stage" : 1 }) + + title = request.values["title"] + description = request.values["description"] + + if not election: + raise Error("invalid_data") + elif type_of_option == "0": + + + additions = json.loads(request.values["additions"]) + amendments = json.loads(request.values["amendments"]) + repeals = json.loads(request.values["repeals"]) + + Type_checks.check_for_type("additions", additions) + Type_checks.check_for_type("amendments", amendments) + Type_checks.check_for_type("repeals", repeals) + + option = { "title" : request.values["title"] + , "description" : request.values["description"] + , "additions" : additions + , "amendments" : amendments + , "repeals" : repeals + , "kind" : 0 + } + elections.update_one({ "id" : vote_id, "stage" : 1 }, { "$push" : { "options" : option}}) + + else: + patcher = request.values["patcher"] + patch_name = request.values["patch_name"] + comment = request.values["comment"] + path_of_patch = Patches.path_of_patch(patcher, patch_name) + + if path_of_patch and patcher == session["username"]: + Patches.lock(patcher, patch_name) + patch = patches.find_one({ "patcher" : patcher, "patch_name" : patch_name }) + patch_hash = patch["hash"] + option = { "patch" : patch_hash + , "comment" : comment + } + elections.update_one({ "id" : vote_id, "stage" : 1 }, { "$push" : { "options" : option}}) + + elif not path_of_patch: + raise Error("invalid_data") + else: + raise Error("invalid_user") + + except json.JSONDecodeError as json_error: + # See parsing of the request.values for probable cause. + return f"{errors["invalid_data"]} { str(json_error) if os.environ.get("DEBUG") else "JSON Error"}" + except Type_Check_Error as type_error: + return errors["invalid_data"] + type_error.data_name + except Error as e: + return e.status() + except KeyError: + return Error("invalid_data").status() + except Exception as e: + raise e else: - return invalidContext + return response ################################################################### ############################ CRITICAL ############################# ################################################################### -@app.route("/vote", methods=["POST"]) +@app.route("/vote", methods=["POST", "GET"]) def vote(): - # Stop Logging Temporarily to anything but errors - app.logger.setLevel(100) # Higher then **CRITICAL** logs must be send, for them to be logged + try: + if request.method == "GET": + election = request.values['hash'] + election = elections.find_one({ "hash" : election }) + election = { key : election[key] for key in election if (key == "title" or key == "options") } + response = render_template("vote.html", election=election) + else: + app.logger.setLevel(100) - username = session.get("username") - election = request.values.get('election') - vote = request.values.get('vote') + if not session.get("username"): + raise Error("invalid_context") + username = session["username"] + hash = request.values['hash'] + vote = json.loads(request.values['vote']) - if username and election and vote: - Elections.vote(election, vote, username) # After this function is called, nobody has any knowledge of the association between user and vote. + # Don't make or use encryption for voting **yet** + election = elections.find_one({ "hash" : hash }) + if election: + if username not in election["participants"]: + elections.update({ "$push" : { "participants" : username + , "votes" : vote + } + }) + else: + raise Error("already_voted") + else: + raise Error("invalid_context") - app.logger.setLevel(0) # The crucial unnoticable part has past. - # Not even the client is notified, if there was anything wrong, except if they get a timeout. - return ok + app.logger.setLevel(0) + response = errors["OK"] + except Error as e: + return e.status() + except (KeyError, TypeError): + return errors["invalid_data"] + except Exception as e: + raise e + else: + return response ################################################################### -############################ CRITICAL OVER ######################## +############################ /CRITICAL ############################ ################################################################### @app.route("/message", methods=["POST"]) def message(): - author = session.get("username") - recipients = json.loads(request.values.get('to')) - body = request.values.get('body') - keys = session.get("keys") - passphrase = session.get("passphrase") - - if author and recipients and body and keys: - message = { "body" : body - , "to" : recipients - , "from" : author - } + try: + if not session.get("username") or not session.get("keys"): + raise Error("invalid_context") + + author = session["username"] + body = json.loads(request.values["body"]) + keys = session["keys"] + + message = { "body" : body + , "from" : author + } + Users.publish( message, keys ) - return ok + except KeyError: + return errors["invalid_data"] + except Error as e: + return e.status() + except Exception as e: + raise e + else: + return errors["OK"] + +# REGISTRATION + +@app.route("/register", methods=["GET","POST"]) +def register_route(): + try: + if request.method == "GET": + response = render_template("register.html") + else: + if session["username"] != "joris": + raise Error("invalid_context") + else: + username = request.values["username"] + id = request.values["id"] + passwords = json.loads(request.values["passwords"]) + first_name = request.values["first_name"] + last_name = request.values["last_name"] + response = register(username, id, passwords, first_name, last_name) + response = errors["OK"] if response else errors["error_for_unknown_reason"] + except Error as e: + return e.status() + except Exception as e: + raise e + else: + return response + +def register( username : str + , id_token : str + , passwords : List[str] + , first_name : str + , last_name : str + ): + try: + id = SHA256.new().update(id_token.encode("utf-8")).hexdigest().encode("utf-8") + passwords = [SHA256.new().update(password.encode("utf-8")).hexdigest() for password in passwords] + user = { "username" : username + , "id" : id + , "passwords" : passwords + , "first_name" : first_name + , "last_name" : last_name + , "readings" : [] + , "writings" : [] + , "expiration" : (datetime.timedelta (weeks=104 + ,days=0 + ,hours=0 + ,minutes=0 + ,seconds=0 + ,milliseconds=0 + ,microseconds=0 + ) + datetime.datetime.now()).isoformat() + } + + if users.find_one({ "id" : id }) or users.find_one({ "username" : username }): + raise Error("already_registered") + else: + users.insert_one(user) + except Exception as e: + raise e + else: + return True + + + + +# CRYPTOGRAPHY +from Crypto.Cipher import AES, PKCS1_OAEP +from Crypto.Random import get_random_bytes +from Crypto.Signature import pkcs1_15 + +"""Encrypt string with AES Key and encrypt them with recipients public key. +Returns : (Signed encryption key, AES Nonce, tag, ciphertext) +""" +def encrypt(message : str, recipient_keys : RSA.RsaKey): + try: + # Create and encrypt AES Key + aes_session_key = get_random_bytes(16) + cipher_rsa = PKCS1_OAEP.new(recipient_keys) + enc_session_key = cipher_rsa.encrypt(aes_session_key) + + # Encrypt using previously generated AES Key. + cipher_aes = AES.new(aes_session_key, AES.MODE_EAX) + ciphertext, tag = cipher_aes.encrypt_and_digest(message.encode("utf-8")) + except Exception as e: + raise e + else: + return (enc_session_key, cipher_aes.nonce, tag, ciphertext) + + +"""Inverse of encrypt and takes the results of encrypt and the private key of the recipient. +""" +def decrypt (recipients_private_key : RSA.RsaKey + , enc_session_key : bytes + , nonce : bytes + , tag : bytes + , ciphertext : bytes + ): + try: + rsa_cipher = PKCS1_OAEP.new(recipients_private_key) + aes_session_key = cipher_rsa.decrypt(enc_session_key) + + cipher_aes = AES.new(aes_session_key, AES.MODE_EAX , nonce=nonce) + message = cipher_aes.decrypt_and_verify(ciphertext, tag) + + + except ValueError: + return False + except Exception as e: + raise e + else: + return message + +"""Return signed hash of a string with private key. +""" +def sign(message : str, author_key : RSA.RsaKey): + hash = SHA256.new().update(message.encode("utf-8")).hexdigest() + signature = pkcs1_15.new(author_key).sign(hash) + return (signature : bytes, hash : SHA256) + +"""Verify a message signed by sign(). +""" +def verify(signature : bytes, hash : SHA256, author_public_key : RSA.RsaKey): + try: + pkcs1_15.new(author_public_key).verify(hash, signature) + except ValueError: + return False + except Exception as e: + raise e else: - return invalidData + return True diff --git a/output/home.html b/output/home.html deleted file mode 100644 index 4dc1a01e..00000000 --- a/output/home.html +++ /dev/null @@ -1,24 +0,0 @@ -<!DOCTYPE html> -<html lang="en" dir="ltr"> - <head> - <meta charset="utf-8"> - <title>DemNet - - - -
-

| Die post und vorschläge anderer personen die am meisten gelikt wurden |

-
- - - - - - - - diff --git a/output/index.html b/output/index.html index 9c331473..8d0f9712 100644 --- a/output/index.html +++ b/output/index.html @@ -1,10321 +1,24 @@ - - - - - Main - - - - - -

-
-
-
-
-
\ No newline at end of file
+
+
+  
+    
+    DemNet
+    
+  
+  
+     
+ {% for message in messages %} +

{{ message.title }}

+ {% endfor %} +
+ + + + + + diff --git a/output/login.html b/output/login.html index dd9f161a..0ec83edc 100644 --- a/output/login.html +++ b/output/login.html @@ -4,23 +4,22 @@ DemNet - + -
-
+ +

DemNet

Log in for your world

- Username:
-
-
- Password:
-
- -
- - + + + + + + + +
- - - + + + diff --git a/output/read.html b/output/read.html new file mode 100644 index 00000000..65b4156e --- /dev/null +++ b/output/read.html @@ -0,0 +1,11 @@ + + + + + DemNet - {{ reading.body.title }} + + +

{{ reading.body.title }}

+

{{ reading.body.content }}

+ + diff --git a/output/home.css b/output/static/index.css similarity index 100% rename from output/home.css rename to output/static/index.css diff --git a/requirements.txt b/requirements.txt index fa7d88f4..40c75c5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +APScheduler==3.6.3 Click==7.0 Flask==1.1.1 gunicorn==20.0.4 @@ -6,4 +7,7 @@ Jinja2==2.10.3 MarkupSafe==1.1.1 pycryptodome==3.9.4 pymongo==3.10.1 +pytz==2019.3 +six==1.14.0 +tzlocal==2.0.0 Werkzeug==0.16.0