Add basic server that supports registration and display of the data, coded in 30 minutes

This commit is contained in:
khuxkm fbexl 2020-05-04 03:21:40 -04:00
parent 2df0f16463
commit 26a92162dc
5 changed files with 185 additions and 0 deletions

View File

@ -9,3 +9,4 @@ Goals:
- private key sign transactions to the api for CRUD of your 1k - private key sign transactions to the api for CRUD of your 1k
- ability to set mime-type 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!**

63
crypto.py Normal file
View File

@ -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 <signature> <file>"""
# 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
])

27
ex_private.pem Normal file
View File

@ -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-----

9
public.pem Normal file
View File

@ -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-----

85
server.py Normal file
View File

@ -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("/<fingerprint>", 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)