From 26a92162dc8954e78109eb31e61f49e88d8692d7 Mon Sep 17 00:00:00 2001 From: khuxkm fbexl Date: Mon, 4 May 2020 03:21:40 -0400 Subject: [PATCH] Add basic server that supports registration and display of the data, coded in 30 minutes --- README.md | 1 + crypto.py | 63 +++++++++++++++++++++++++++++++++++++ ex_private.pem | 27 ++++++++++++++++ public.pem | 9 ++++++ server.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 crypto.py create mode 100644 ex_private.pem create mode 100644 public.pem create mode 100644 server.py diff --git a/README.md b/README.md index cdde52a..f3b33f7 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,4 @@ Goals: - private key sign transactions to the api for CRUD of your 1k - ability to set mime-type of your 1k +**WARNING: The keys `ex_private.pem` and `public.pem` exist solely to serve as placeholders during testing. Do not use them in a setting where security is needed!** diff --git a/crypto.py b/crypto.py new file mode 100644 index 0000000..4eb56fa --- /dev/null +++ b/crypto.py @@ -0,0 +1,63 @@ +"""The cryptography backend for the1024.club. Here lie dragons. + +Everything will be explained to the best of my ability, but please understand +that even I am lost about how most of these functions work. + +At the moment, we only support RSA public keys in the backend. This is because +finding the right parameters for signature verification is like shoving bamboo +under my fingernails (that is to say, extremely painful), and I don't want to +do it any more than necessary. If you want another public key format to be +implemented, figure it out your own damn self and submit a pull request. + +Sincerely yours, +Robert Miles +~khuxkm + +P.S. This code (at least, the parts written by me) is licensed under MIT, in +addition to whatever license the rest of the1024.club's code is licensed under.""" +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.serialization import load_pem_public_key +from cryptography.hazmat.primitives.asymmetric import padding, rsa +from cryptography.hazmat.primitives.hashes import SHA256 +from cryptography.exceptions import InvalidSignature +"""Key formats we support. For a class to be in here, it must have a corresponding isinstance() check in verify_signature.""" +SUPPORTED_PUBLIC_KEY_FORMATS = [rsa.RSAPublicKey] + + +class UnknownKeyFormat(Exception): + pass + + +def verify_signature(block_data, signature, pubkey): + """Verifies a signature `signature`, given data `block_data` and a public key `pubkey`. + + To generate a signature that can be verified by this function, use: + $ openssl dgst -sha256 -sign private.key -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen=32 -out """ + # load the public key + public_key = load_pem_public_key(pubkey, backend=default_backend()) + # is it an RSA public key? + if isinstance(public_key, rsa.RSAPublicKey): + try: + # public_key.verify raises InvalidSignature on verification failure, so call it + public_key.verify( + signature, + block_data, + # PSS padding, with SHA256-digest MGF-1 and a salt length of 32 + padding.PSS(padding.MGF1(SHA256()), 32), + # SHA256 digest + SHA256()) + return True + except InvalidSignature: + return False + raise UnknownKeyFormat(type(public_key).__name__) + + +def supports(pubkey): + """Returns whether public key `pubkey` is of a supported type.""" + # load the public key + public_key = load_pem_public_key(pubkey, backend=default_backend()) + # if public_key is an instance of any of SUPPORTED_PUBLIC_KEY_FORMATS, we support it + return any([ + isinstance(public_key, format) + for format in SUPPORTED_PUBLIC_KEY_FORMATS + ]) diff --git a/ex_private.pem b/ex_private.pem new file mode 100644 index 0000000..fe3edec --- /dev/null +++ b/ex_private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAm0JAl65MyCCzVBPQviuFi3XjB8Lou3IJFDh8Q4ve5sL5/zxF +4R38XWQr7LRXi4QrBgMjTedg/X4ZglJcFSWbCDRvbWJMfJM4LLwGw7+aJpCxrRfr +gUcgO8bLMKkZRPOLyoz3nfa5a8L0BqmDH1wd9ZMDphf0Ed/MXcNIbF7kPkdx43wR +gYHsdT/VIU1SbaeF6DAC+RjlTsXHheE1c02HNs4NAE7IxHjK8KDhm8FBCjqfeavZ +DQDK3S/Syj0lMf7JYF0nqEtxm5H7YIh9sazay2fgdlp5ibhSHPZjG7tutz3SFxij +IUsGc+7RNLzgrG6RvMH66p59CA2ZYFeh0iQEAQIDAQABAoIBAFyCq441s1aD9Zah +f/3wXNBuW+qh/p7CIhrEVXbB/v0tLF0jAthST68uIuYVxxxAkoowkHa46Zfh7T0z +6d5H3AqVg9d/fOe1qQEuwDEfF7DnApeK6y7KvLAhkaza9U2aeK8mM90RyAYn0dOu +aYzkDI3pHYF4QFvuKA2ucJbyK7nXVovzqF5zoXbmK98T9Dvu2XkhtTAwLoXtBCD/ +FeMcqOxy5vRHLNJj/Wo62iik7NxXhGC7p4QuBe+mcI/WoXbNypAx4ounv0pm3ZQK +fOiAUYAYYzs+xA8clOkFeLzoZKGyKeZ4jNq6YZ+AXN2s8on7dDqe2jt12Nw25sG5 +aBO6jCkCgYEAzts0eGftdGzvQ4NEClYAtQXM3qT1ITP7r0loEnOuPFNkbNMfrFVI +H5mP5Ank7ZKYuEDRDWhts4fZ5bzQnSfBlXR8Ha6zCX6C6jti+vAr9BfNZl+096Mh +mCDhLEP5wIrYxVGP9NhkGcqYcCK0bCJPQGcSSXNZoGRPuLOkNj3Ln68CgYEAwCTw +mhHHV3XWKzxAqy0K1WUPkVnBaay1jHtVmVqSiO/yv6LYR7sjnJNCUCKx9AjF6l3/ +wT8ifV5NJy/BP6s2ak68bVYOE0TJDWBOAXHetpR9pjyoDjuZTy6PKjbLLRCenI9k +3Wlvufk9BPMUB/v0nM2suXY4NQ/9rzbeIBb4U08CgYA2DvVEbiyRG5LfuuNCLD2d +ETn8CXICTlp88ZsgD1k+bLC2++mwGM1zbKc8+hT4vTHqHQ5FCcB5hYw9TL3cBDLE +AyAYhbpGReiinAh++dsSvFGyalqZyOkTn6wY7F7NobuKvYj4fMCUOOzjiT5LcB/z +/bu6tEl88Bohc9j7T+TyAwKBgFUFQ215JnK90nmpHJhhwuRv5naTd7DR2jeCL3s/ +B44OVqSTYw5CcVyMEJCy+XkZFLJCy7Nvw0wkGRGY4PLSSK67jkb3CB1gl3ISxhF/ +mGPDyuFu/5Hkr07JNFIikr6rFvQBw0jxqZ1p+qUhBiEOTMvBeodKF07rd347Ughz +AYijAoGBALZKugj6HYXQnWw0QILyqJ3lnVDbp1oIqSnntvR9IXmBid06iJSY7Gi8 +uApYklfheDcpNFNQ6YyGzCD3hiAmaKIUXcn83GvOc+Dvej2lkvzOO93qGiaOaG7r +qnucrj9t/c61SWTZtRpLkfWAO1NLkD+aIolNouiSQ/+1rwxmMD6S +-----END RSA PRIVATE KEY----- diff --git a/public.pem b/public.pem new file mode 100644 index 0000000..c8dc694 --- /dev/null +++ b/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm0JAl65MyCCzVBPQviuF +i3XjB8Lou3IJFDh8Q4ve5sL5/zxF4R38XWQr7LRXi4QrBgMjTedg/X4ZglJcFSWb +CDRvbWJMfJM4LLwGw7+aJpCxrRfrgUcgO8bLMKkZRPOLyoz3nfa5a8L0BqmDH1wd +9ZMDphf0Ed/MXcNIbF7kPkdx43wRgYHsdT/VIU1SbaeF6DAC+RjlTsXHheE1c02H +Ns4NAE7IxHjK8KDhm8FBCjqfeavZDQDK3S/Syj0lMf7JYF0nqEtxm5H7YIh9saza +y2fgdlp5ibhSHPZjG7tutz3SFxijIUsGc+7RNLzgrG6RvMH66p59CA2ZYFeh0iQE +AQIDAQAB +-----END PUBLIC KEY----- diff --git a/server.py b/server.py new file mode 100644 index 0000000..5c75ea2 --- /dev/null +++ b/server.py @@ -0,0 +1,85 @@ +from flask import * +from flask_sqlalchemy import SQLAlchemy +import crypto, hashlib + +app = Flask("the1024club") +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///the1024.db" +db = SQLAlchemy(app) + + +class User(db.Model): + __tablename__ = "the1024" + id = db.Column(db.Integer, primary_key=True) + fingerprint = db.Column(db.Text, nullable=False, unique=True) + pubkey = db.Column(db.LargeBinary, nullable=False) + data = db.Column(db.LargeBinary) + mime_type = db.Column(db.Text, default="text/plain; charset=UTF-8") + + +db.create_all() + + +@app.route("/") +def home(): + return "TODO: implement home page" + + +@app.route("/register", methods=["GET", "POST"]) +def register(): + if request.method == "GET": + return "TODO: registration info page (registration form?)" + pubkey = request.files.get("public_key") + if not pubkey: + response = make_response( + jsonify(dict(error="Provide a public key as file `public_key`.")), + 400) + response.headers["Content-Type"] = "application/json" + return response + pubkey = pubkey.read() + if not crypto.supports(pubkey): + response = make_response( + jsonify( + dict(error="Unsupported public key type. Supported types: " + + (", ".join([ + f.__name__.replace("PublicKey", "") + for f in crypto.SUPPORTED_PUBLIC_KEY_FORMATS + ])))), 400) + response.headers["Content-Type"] = "application/json" + return response + fingerprint = hashlib.sha256(pubkey).hexdigest() + # is there already a hash with that fingerprint in the database? + if User.query.filter_by(fingerprint=fingerprint).all(): + response = make_response( + jsonify( + dict( + error= + "A public key with this hash already exists in the database." + )), 400) + response.headers["Content-Type"] = "application/json" + return response + # do the magic + new_user = User(fingerprint=fingerprint, pubkey=pubkey) + db.session.add(new_user) + db.session.commit() + response = make_response( + jsonify( + dict( + error=None, + url=url_for("render_data", fingerprint=fingerprint))), 200) + response.headers["Content-Type"] = "application/json" + return response + + +@app.route("/", methods=["GET", "UPDATE", "DELETE"]) +def render_data(fingerprint): + if request.method == "GET": + user = User.query.filter_by(fingerprint=fingerprint).first() + if not user: return abort(404) + resp = make_response(user.data or b"", 200) + resp.headers["Content-Type"] = user.mime_type + return resp + return abort(501) + + +if __name__ == "__main__": + app.run(port=65532)