Add basic server that supports registration and display of the data, coded in 30 minutes
This commit is contained in:
parent
2df0f16463
commit
26a92162dc
|
@ -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!**
|
||||||
|
|
|
@ -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
|
||||||
|
])
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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)
|
Loading…
Reference in New Issue