From 72f7d25a69b6ed41a15eef973f9fbb821931c901 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Tue, 18 Jul 2017 03:35:52 +0000 Subject: Initial import --- manage.py | 64 +++++++++++++++++++++++++++++++++++++++++ server.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ var/ssl-cert.pem | 17 +++++++++++ var/ssl-key.pem | 28 ++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100755 manage.py create mode 100644 server.py create mode 100644 var/ssl-cert.pem create mode 100644 var/ssl-key.pem diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..8838a1e --- /dev/null +++ b/manage.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +import os +import sys +import socket +from OpenSSL import crypto +from OpenSSL.crypto import PKey, X509 + +from server import app + + +VAR_PATH = "var" +LOG_PATH = os.path.join(VAR_PATH, "log") +KEY_PATH = os.path.join(VAR_PATH, "ssl-key.pem") +CERT_PATH = os.path.join(VAR_PATH, "ssl-cert.pem") + + +def generate_cert(serial=1): + ca = X509() + ca.set_version(2) + ca.set_serial_number(serial) + ca.get_subject().CN = socket.getfqdn() + ca.gmtime_adj_notBefore(0) + ca.gmtime_adj_notAfter(24 * 60 * 60) + ca.set_issuer(ca.get_subject()) + ca.set_pubkey(key) + ca.sign(key, "sha256") + + with open(CERT_PATH, "wb") as fp: + fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca)) + + +if not os.path.exists(VAR_PATH): + os.mkdir(VAR_PATH) + +if not os.path.exists(LOG_PATH): + os.mkdir(LOG_PATH) + +if not os.path.exists(KEY_PATH): + key = PKey() + key.generate_key(crypto.TYPE_RSA, 2048) + + with open(KEY_PATH, "wb") as fp: + os.chmod(KEY_PATH, 0o600) + fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) +else: + with open(KEY_PATH, "rb") as fp: + key = crypto.load_privatekey(crypto.FILETYPE_PEM, fp.read()) + + +if not os.path.exists(CERT_PATH): + generate_cert() +else: + with open(CERT_PATH, "rb") as fp: + cert = crypto.load_certificate(crypto.FILETYPE_PEM, fp.read()) + + if cert.has_expired(): + generate_cert(cert.get_serial_number() + 1) + + +if __name__ == "__main__" and sys.argv[-1] == "runserver": + app.run(host="0.0.0.0", port=5000, + ssl_context=("var/ssl-cert.pem", "var/ssl-key.pem"), + threaded=True, debug=True) diff --git a/server.py b/server.py new file mode 100644 index 0000000..0bebe2a --- /dev/null +++ b/server.py @@ -0,0 +1,87 @@ +import flask +import urllib.parse +from flask import request + + +# Application Config +OIDC_ISSUER = "https://id.crute.me/" +OIDC_AUTH_ENDPOINT = "{}login".format(OIDC_ISSUER) +OIDC_JWKS_ENDPOINT = "{}jwks".format(OIDC_ISSUER) +DOMAIN_WHITELIST = ["crute.me", "crute.us"] + + +# Generic Constants +JRD_MIMETYPE = "application/jrd+json" +OIDC_ISSUER_QUERY = "http://openid.net/specs/connect/1.0/issuer" +JRD_404 = flask.Response("Not found", status=404, mimetype=JRD_MIMETYPE) + + +app = flask.Flask(__name__) + + +def parse_email_addr(addr): + try: + user, domain = addr.split("@") + return user, domain + except ValueError: + return None + + +@app.route("/.well-known/webfinger", methods=["GET"]) +def webfinger(): + resource = request.args.get("resource") + rel = request.args.get("rel") + + # Only support OIDC queries + if not rel or not resource or not rel == OIDC_ISSUER_QUERY: + return JRD_404 + + # Only support email address queries + acct = urllib.parse.urlparse(resource) + if acct.scheme != "acct": + return JRD_404 + + # Ensure that the query is in a whitelisted domain + _, domain = parse_email_addr(acct.path) + if domain not in DOMAIN_WHITELIST: + return JRD_404 + + # We don't validate that the user exists to prevent leaking information + # about what users exist; plus, it doesn't really matter since the querier + # only cares about the OIDC issuer endpoint which is user independent + res = flask.jsonify({ + "subject": resource, + "links": [{ + "rel": OIDC_ISSUER_QUERY, + "href": OIDC_ISSUER, + }] + }) + res.mimetype = JRD_MIMETYPE + return res + + + +@app.route("/.well-known/openid-configuration", methods=["GET"]) +def openid_configuration(): + return flask.jsonify({ + "issuer": OIDC_ISSUER, + "authorization_endpoint": OIDC_AUTH_ENDPOINT, + "jwks_uri": OIDC_JWKS_ENDPOINT, + "scopes_supported": ["openid"], + "response_types_supported": ["id_token"], + "response_modes_supported": ["query"], + "grant_types_supported": ["implicit"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "acr_values_supported": ["want_mfa", "need_mfa"], + }) + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + pass + + +@app.route("/jwks", methods=["GET"]) +def keys(): + pass diff --git a/var/ssl-cert.pem b/var/ssl-cert.pem new file mode 100644 index 0000000..207aea1 --- /dev/null +++ b/var/ssl-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwTCCAamgAwIBAgIBATANBgkqhkiG9w0BAQsFADAkMSIwIAYDVQQDDBltY3J1 +dGUtdmlydC5zZWExLmNydXRlLm1lMB4XDTE3MDIyNTAzNTAwNloXDTE3MDIyNjAz +NTAwNlowJDEiMCAGA1UEAwwZbWNydXRlLXZpcnQuc2VhMS5jcnV0ZS5tZTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKZqUzkQdVMdNX0ozFb1BF2SXJao +oDHs1gz4yvNTpfCMVKl+DIpb6QOkfEL4UOs2MSRGm0we2Va7e87gnmRGdmjLuSQQ +SDEVCnGH5eqL9WqqZzMB0TJsHm1saeJakGePVeDiJMlRFxCjS/8TXBkRP0LJge6i +TQQ0ZVbAVxEvmuGRdPIIQGqBifKm6zaGszD992xARt8vYDiAlGKEssmLnrTpZ1kp +dc/r+tKe+FHWVLggEvP9xkWVmTsABGd00CvYfI/716BZrG7dpQrRfMGFOcq3lpPA +uIp+uHaOVmpT85GlJl/LM6qbnjh3xL9iJFFgdIWVZD6USlWTlVaFm1l8os8CAwEA +ATANBgkqhkiG9w0BAQsFAAOCAQEAnwpJAp2TM7EeW/o3656dmmr35A5txEpCMJ16 +Mb5izuj67lrgcCqGltFRxZSyFRDii3/BN2SMGt2Uhtt/cctDJwU1Bl3Tt4WfsGgc +pLARspitlVEbpeOwoZdXj1m6ILHuQ2qsuTNBwKqW8ZmeaxKodPDWB9L9tCWypJSz +GvWAOvTqVqDJ3ob+UcCNHBH2on8Y2K14fCqcTsEFQs3vcPKfqBPKGs8xv7cZ+mrl +Wh++JI8GprNcPMF6owMGKhwVn/bXmDPXIb67OM05PvlfmbRs8DQEzzz5oQj7yLY7 +iI7zu/OBHprWeUvQcvZcWzWaeVp2a89X4t9O3ODa5QZmDELHNw== +-----END CERTIFICATE----- diff --git a/var/ssl-key.pem b/var/ssl-key.pem new file mode 100644 index 0000000..7cd8796 --- /dev/null +++ b/var/ssl-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmalM5EHVTHTV9 +KMxW9QRdklyWqKAx7NYM+MrzU6XwjFSpfgyKW+kDpHxC+FDrNjEkRptMHtlWu3vO +4J5kRnZoy7kkEEgxFQpxh+Xqi/VqqmczAdEybB5tbGniWpBnj1Xg4iTJURcQo0v/ +E1wZET9CyYHuok0ENGVWwFcRL5rhkXTyCEBqgYnypus2hrMw/fdsQEbfL2A4gJRi +hLLJi5606WdZKXXP6/rSnvhR1lS4IBLz/cZFlZk7AARndNAr2HyP+9egWaxu3aUK +0XzBhTnKt5aTwLiKfrh2jlZqU/ORpSZfyzOqm544d8S/YiRRYHSFlWQ+lEpVk5VW +hZtZfKLPAgMBAAECggEAcMlFCDrYWXFFbEM3YoQC5mwo5k74631JgVcpLYr2vzZM +tubgFvG91iMnuLfVF+UNnzlfTVLnGDpO6eIgV3POEq5oF0IVu3Y4MsPZFoKu3REK +im5j2xmd8al1hdy9XAKwQI7kQbD8weD6w7DaTX778gbiUdqb+gqM2CPZnqM2BEPZ +8mDyQ9YteDykbC/ZWDEnorb35F9zKp4BFBOgLpRm7Ylj9VYqYxutj94fAA8juKyu +cB8XntnDZE40VSY6hPhivmF3Bi8OcHBnKNuVW/+Cg8SqbvuxeMRN+lgV8AcRvQFR +eFE0WRo0uEpkwsoFwXW5jdOyPYzeNJNLcjKBNhdz6QKBgQDYwBdQZIW3iGLjiEtY +j5naThzm2QyIcV5TolIOE2iFT0c7ZpM2r9k5iWGbN1BVJYp3A4WI4Pf9TdpA3upQ +nGnm7e2TF2pbZmb7dNCV+8SkHxqE0TmZAfX2zHVYa62Ak+4/CaN45CR+3NLpmc7E +4A9JqshcLyJpIgJpXohpSa3KkwKBgQDEjNmzcxpDZson3v1wVfRpREE/+ImztwMb +wQeMO4IdQ8PVcaTS/9Tfo72ttjJEQJKJWOQK/DrJJB3tbTfv7OxXW+9g40uiNb4y +P/1Zj9osAWQ9US5SR2Diu2TJf+RnLOONHDFlolp6h2ODp6eZitRedVrlNOhIKjRE +0X6YvHIgVQKBgHhmG404iV5tkCC7sL685cVx5nQE1QVlk/P5EoNnHIQQiYzhaZzT +CWMAgQtrKmBhvgxmFGL4NEisWjP2n0mv2NPCDlnKg/XJaCL9vG/TrmNMM7rbTZdE +k5BH+lbnWTU29vxlKj0Y3XzqxO2l1kld/J/7EdjDBeZIUfad4EA8ASGpAoGAcLbR +VY5pfkOhJJJFRrmUKxXgUV4nIa+PEch1mp65tCybTnkYa4QYwJN2//pfAzMAldEr +HX2d/KFApFqg3G9C7aokMazHP+OQCeMWV9kd5WR65or6hGa4ke2jE8bK5bwhVlfX +oBAl0OR5VhO8ElpCBVVJZe4cUt4ZEWoOLmrLC+0CgYBLDlBZsvfE1x4UEn4LFLQC +LnNEjFvYbtKXP4Tm5Kk3FhDUMoBkwm69AhXgfq+ddCXUrBjsMaYZhdi2FPqa5qEh +K6RPu42yIElN50UnabRkZbShCUEEeioOGTjAjhyd77O9ol9C7M57L38OnEs8MmRa +gvVNvuCVuHjcnd06dDTWPw== +-----END PRIVATE KEY----- -- cgit v1.2.3