diff --git a/README.rst b/README.rst index cdccbf9..dc8a79e 100644 --- a/README.rst +++ b/README.rst @@ -13,8 +13,8 @@ Features -------- * Standard request, sign, revoke workflow via web interface. -* Colored command-line interface, check out ``butterknife list`` -* OpenVPN integration, check out ``butterknife setup openvpn server`` and ``butterknife setup openvpn client`` +* Colored command-line interface, check out ``certidude list`` +* OpenVPN integration, check out ``certidude setup openvpn server`` and ``certidude setup openvpn client`` * Privilege isolation, separate signer process is spawned per private key isolating private key use from the the web interface. * Certificate numbering obfuscation, certificate serial numbers are intentionally @@ -42,7 +42,7 @@ To install Certidude: .. code:: bash - apt-get install python3 python3-dev build-essential + apt-get install python3 python3-pip python3-dev cython3 build-essential libffi-dev libssl-dev pip3 install certidude Create a user for ``certidude``: @@ -96,7 +96,7 @@ Use web interface or following to sign a certificate on Certidude server: Production deployment --------------------- -Unstall uWSGI: +Install uWSGI: .. code:: bash @@ -120,8 +120,8 @@ Configure uUWSGI application in ``/etc/uwsgi/apps-available/certidude.ini``: callable = app chmod-socket = 660 chown-socket = certidude:www-data - env = CERTIDUDE_EVENT_PUBLISH=http://localhost/event/publish/%s - env = CERTIDUDE_EVENT_SUBSCRIBE=http://localhost/event/subscribe/%s + env = CERTIDUDE_EVENT_PUBLISH=http://localhost/event/publish/%(channel)s + env = CERTIDUDE_EVENT_SUBSCRIBE=http://localhost/event/subscribe/%(channel)s Also enable the application: @@ -135,7 +135,7 @@ configure the site in /etc/nginx/sites-available.d/certidude: .. code:: upstream certidude_api { - server unix:///run/uwsgi/app/certidude/socket; + server unix:///run/certidude/api/uwsgi.sock; } server { @@ -175,24 +175,24 @@ Also adjust ``/etc/nginx/nginx.conf``: pid /run/nginx.pid; events { - worker_connections 768; - # multi_accept on; + worker_connections 768; + # multi_accept on; } http { push_stream_shared_memory_size 32M; - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - include /etc/nginx/mime.types; - default_type application/octet-stream; - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - gzip on; - gzip_disable "msie6"; - include /etc/nginx/sites-enabled.d/*; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + gzip on; + gzip_disable "msie6"; + include /etc/nginx/sites-enabled/*; } Restart the services: diff --git a/certidude/api.py b/certidude/api.py index c3bfcd7..fdd0926 100644 --- a/certidude/api.py +++ b/certidude/api.py @@ -235,7 +235,7 @@ class RequestListResource(CertificateAuthorityBase): url_template = os.getenv("CERTIDUDE_EVENT_SUBSCRIBE") if url_template: # Redirect to nginx pub/sub - url = url_template % request.fingerprint() + url = url_template % dict(channel=request.fingerprint()) click.echo("Redirecting to: %s" % url) resp.status = falcon.HTTP_FOUND resp.append_header("Location", url) diff --git a/certidude/cli.py b/certidude/cli.py index 47a0c88..45ec1cf 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -15,6 +15,7 @@ import logging import signal import netifaces import urllib.request +import subprocess from humanize import naturaltime from ipaddress import ip_network from time import sleep @@ -320,11 +321,12 @@ def certidude_setup_client(quiet, **kwargs): type=click.File(mode="w", atomic=True, lazy=True), help="OpenVPN configuration file") @click.option("--directory", "-d", default="/etc/openvpn/keys", help="Directory for keys, /etc/openvpn/keys by default") -@click.option("--key-path", "-k", default=HOSTNAME + ".key", help="Key path, %s.key relative to --directory by default" % HOSTNAME) -@click.option("--request-path", "-r", default=HOSTNAME + ".csr", help="Request path, %s.csr relative to --directory by default" % HOSTNAME) -@click.option("--certificate-path", "-c", default=HOSTNAME + ".crt", help="Certificate path, %s.crt relative to --directory by default" % HOSTNAME) -@click.option("--authority-path", "-a", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default") -def certidude_setup_openvpn_server(url, config, subnet, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, local, proto, port): +@click.option("--key-path", "-key", default=HOSTNAME + ".key", help="Key path, %s.key relative to --directory by default" % HOSTNAME) +@click.option("--request-path", "-csr", default=HOSTNAME + ".csr", help="Request path, %s.csr relative to --directory by default" % HOSTNAME) +@click.option("--certificate-path", "-crt", default=HOSTNAME + ".crt", help="Certificate path, %s.crt relative to --directory by default" % HOSTNAME) +@click.option("--dhparam-path", "-dh", default="dhparam2048.pem", help="Diffie/Hellman parameters path, dhparam2048.pem relative to --directory by default") +@click.option("--authority-path", "-ca", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default") +def certidude_setup_openvpn_server(url, config, subnet, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, dhparam_path, local, proto, port): # TODO: Intelligent way of getting last IP address in the subnet subnet_first = None subnet_last = None @@ -345,6 +347,7 @@ def certidude_setup_openvpn_server(url, config, subnet, email_address, common_na certificate_path = os.path.join(directory, certificate_path) request_path = os.path.join(directory, request_path) authority_path = os.path.join(directory, authority_path) + dhparam_path = os.path.join(directory, dhparam_path) if not os.path.exists(certificate_path): click.echo("As OpenVPN server certificate needs specific key usage extensions please") @@ -365,6 +368,10 @@ def certidude_setup_openvpn_server(url, config, subnet, email_address, common_na extended_key_usage="serverAuth", wait=True) + if not os.path.exists(dhparam_path): + cmd = "openssl", "dhparam", "-out", dhparam_path, "2048" + subprocess.check_call(cmd) + if retval: return retval diff --git a/certidude/signer.py b/certidude/signer.py index 6c8dc88..234d267 100644 --- a/certidude/signer.py +++ b/certidude/signer.py @@ -35,9 +35,12 @@ def raw_sign(private_key, ca_cert, request, basic_constraints, lifetime, key_usa cert = crypto.X509() - + # Set public key cert.set_pubkey(request.get_pubkey()) + # Set issuer + cert.set_issuer(ca_cert.get_subject()) + # TODO: Assert openssl.cnf policy for subject attributes # if request.get_subject().O != ca_cert.get_subject().O: # raise ValueError("Orgnization name mismatch!") diff --git a/certidude/templates/client-to-site.ovpn b/certidude/templates/client-to-site.ovpn index 24944cf..08759c2 100644 --- a/certidude/templates/client-to-site.ovpn +++ b/certidude/templates/client-to-site.ovpn @@ -1,5 +1,6 @@ client remote {{remote}} +remote-cert-tls server proto {{proto}} dev tap0 nobind diff --git a/certidude/templates/site-to-client.ovpn b/certidude/templates/site-to-client.ovpn index 329f955..5f91951 100644 --- a/certidude/templates/site-to-client.ovpn +++ b/certidude/templates/site-to-client.ovpn @@ -7,6 +7,7 @@ local {{local}} key {{key_path}} cert {{certificate_path}} ca {{authority_path}} +dh {{dhparam_path}} comp-lzo user nobody group nogroup diff --git a/certidude/wrappers.py b/certidude/wrappers.py index e92efd4..8cccd89 100644 --- a/certidude/wrappers.py +++ b/certidude/wrappers.py @@ -28,7 +28,7 @@ def notify(func): assert isinstance(cert, Certificate), "notify wrapped function %s returned %s" % (func, type(cert)) url_template = os.getenv("CERTIDUDE_EVENT_PUBLISH") if url_template: - url = url_template % csr.fingerprint() + url = url_template % dict(channel=csr.fingerprint()) notification = urllib.request.Request(url, cert.dump().encode("ascii")) notification.add_header("User-Agent", "Certidude API") notification.add_header("Content-Type", "application/x-x509-user-cert") diff --git a/certidude/wsgi.py b/certidude/wsgi.py index 8a0fe59..4d6fdb1 100644 --- a/certidude/wsgi.py +++ b/certidude/wsgi.py @@ -1,5 +1,5 @@ - +import os import falcon from certidude.wrappers import CertificateAuthorityConfig from certidude.api import CertificateAuthorityResource, \ @@ -13,6 +13,9 @@ from certidude.api import CertificateAuthorityResource, \ config = CertificateAuthorityConfig("/etc/ssl/openssl.cnf") +assert os.getenv("CERTIDUDE_EVENT_SUBSCRIBE"), "Please set CERTIDUDE_EVENT_SUBSCRIBE to your web server's subscribe URL" +assert os.getenv("CERTIDUDE_EVENT_PUBLISH"), "Please set CERTIDUDE_EVENT_SUBSCRIBE to your web server's subscribe URL" + app = falcon.API() app.add_route("/api/{ca}/ocsp/", CertificateStatusResource(config)) app.add_route("/api/{ca}/signed/{cn}/openvpn", ApplicationConfigurationResource(config)) diff --git a/setup.py b/setup.py index 24adf0c..1827f7d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup setup( name = "certidude", - version = "0.1.3", + version = "0.1.7", author = u"Lauri Võsandi", author_email = "lauri.vosandi@gmail.com", description = "Certidude is a novel X.509 Certificate Authority management tool aiming to support PKCS#11 and in far future WebCrypto.", @@ -25,7 +25,9 @@ setup( "pyopenssl", "pycountry", "humanize", - "pycrypto" + "pycrypto", + "cryptography", + "markupsafe" ], scripts=[ "misc/certidude"