From de08ba759d702be38002b2c753e81c46cf5a7c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauri=20V=C3=B5sandi?= Date: Sun, 10 Jan 2016 19:51:54 +0200 Subject: [PATCH] Release version 0.1.20 --- README.rst | 16 ++- certidude/api/__init__.py | 4 +- certidude/api/cfg.py | 113 ++++++++++++++++++ certidude/api/lease.py | 6 +- certidude/api/signed.py | 3 + certidude/api/tag.py | 58 +++++++-- certidude/api/whois.py | 21 ++-- certidude/cli.py | 42 +++++-- certidude/config.py | 3 +- certidude/signer.py | 2 +- certidude/static/authority.html | 7 +- certidude/static/configuration.html | 31 +++++ certidude/static/css/style.css | 3 + .../static/img/iconmonstr-lock-3-icon.svg | 13 ++ .../static/img/iconmonstr-wireless-6-icon.svg | 16 +++ certidude/static/index.html | 1 + certidude/static/js/certidude.js | 93 ++++++++------ certidude/static/request.html | 2 +- certidude/static/signed.html | 9 +- certidude/static/status.html | 2 +- certidude/static/tagtypes.html | 11 ++ setup.py | 2 +- 22 files changed, 371 insertions(+), 87 deletions(-) create mode 100644 certidude/api/cfg.py create mode 100644 certidude/static/configuration.html create mode 100644 certidude/static/img/iconmonstr-lock-3-icon.svg create mode 100644 certidude/static/img/iconmonstr-wireless-6-icon.svg create mode 100644 certidude/static/tagtypes.html diff --git a/README.rst b/README.rst index 82dfe7b..6282e00 100644 --- a/README.rst +++ b/README.rst @@ -95,16 +95,14 @@ You can check it with: The command should return ca.example.co Certidude can set up CA relatively easily, following will set up -CA in /var/lib/certidude/hostname.domain: +CA in /var/lib/certidude/hostname.domain.tld: .. code:: bash certidude setup authority -Tweak command-line options until you meet your requirements and -then insert generated section to your /etc/ssl/openssl.cnf - -Spawn the signer process: +Tweak the configuration in /etc/certidude/server.conf until you meet your requirements and +spawn the signer process: .. code:: bash @@ -126,13 +124,13 @@ Use following command to request a certificate on a machine: certidude setup client ca.example.com -Use following to list signing requests, certificates and revoked certificates: +Use following to list signing requests, certificates and revoked certificates on server: .. code:: certidude list -Use web interface or following to sign a certificate on Certidude server: +Use web interface or following to sign a certificate on server: .. code:: @@ -173,7 +171,7 @@ Otherwise manually configure ``uwsgi`` application in ``/etc/uwsgi/apps-availabl buffer-size = 32768 env = LANG=C.UTF-8 env = LC_ALL=C.UTF-8 - env = KRB5_KTNAME=/etc/certidude.keytab + env = KRB5_KTNAME=/etc/certidude/server.keytab Also enable the application: @@ -298,7 +296,7 @@ Set up Kerberos keytab for the web service: .. code:: bash - KRB5_KTNAME=FILE:/etc/certidude.keytab net ads keytab add HTTP -U Administrator + KRB5_KTNAME=FILE:/etc/certidude/server.keytab net ads keytab add HTTP -U Administrator Setting up authorization diff --git a/certidude/api/__init__.py b/certidude/api/__init__.py index b350334..c9244cb 100644 --- a/certidude/api/__init__.py +++ b/certidude/api/__init__.py @@ -80,6 +80,7 @@ def certidude_app(): from .whois import WhoisResource from .log import LogResource from .tag import TagResource, TagDetailResource + from .cfg import ConfigResource, ScriptResource app = falcon.API() @@ -94,6 +95,8 @@ def certidude_app(): app.add_route("/api/log/", LogResource()) app.add_route("/api/tag/", TagResource()) app.add_route("/api/tag/{identifier}/", TagDetailResource()) + app.add_route("/api/config/", ConfigResource()) + app.add_route("/api/script/", ScriptResource()) app.add_route("/api/", SessionResource()) # Gateway API calls, should this be moved to separate project? @@ -115,7 +118,6 @@ def certidude_app(): class PushLogHandler(logging.Handler): def emit(self, record): from certidude.push import publish - print("EVENT HAPPENED:", record.created) publish("log-entry", dict( created = datetime.fromtimestamp(record.created), message = record.msg % record.args, diff --git a/certidude/api/cfg.py b/certidude/api/cfg.py new file mode 100644 index 0000000..45f2515 --- /dev/null +++ b/certidude/api/cfg.py @@ -0,0 +1,113 @@ +import falcon +import logging +import ipaddress +import string +from random import choice +from certidude import config +from certidude.auth import login_required, authorize_admin +from certidude.decorators import serialize +from jinja2 import Environment, FileSystemLoader + +logger = logging.getLogger("api") + +env = Environment(loader=FileSystemLoader("/etc/certidude/scripts"), trim_blocks=True) + +SQL_SELECT_INHERITED = """ +select `key`, `value` from tag_inheritance where tag_id in (select + tag.id +from + device_tag +join + tag on device_tag.tag_id = tag.id +join + device on device_tag.device_id = device.id +where + device.cn = %s) +""" + +SQL_SELECT_TAGS = """ +select + tag.`key` as `key`, + tag.`value` as `value` +from + device_tag +join + tag on device_tag.tag_id = tag.id +join + device on device_tag.device_id = device.id +where + device.cn = %s +""" + +SQL_SELECT_INHERITANCE = """ +select + tag_inheritance.`id` as `id`, + tag.id as `tag_id`, + tag.`key` as `match_key`, + tag.`value` as `match_value`, + tag_inheritance.`key` as `key`, + tag_inheritance.`value` as `value` +from tag_inheritance +join tag on tag.id = tag_inheritance.tag_id +""" + +class ConfigResource(object): + @serialize + @login_required + @authorize_admin + def on_get(self, req, resp): + conn = config.DATABASE_POOL.get_connection() + cursor = conn.cursor(dictionary=True) + cursor.execute(SQL_SELECT_INHERITANCE) + def g(): + for row in cursor: + yield row + cursor.close() + conn.close() + return g() + +class ScriptResource(object): + def on_get(self, req, resp): + from certidude.api.whois import address_to_identity + + node = address_to_identity( + config.DATABASE_POOL.get_connection(), + ipaddress.ip_address(req.env["REMOTE_ADDR"]) + ) + if not node: + resp.body = "Could not map IP address: %s" % req.env["REMOTE_ADDR"] + resp.status = falcon.HTTP_404 + return + + address, acquired, identity = node + + key, common_name = identity.split("=") + assert "=" not in common_name + + conn = config.DATABASE_POOL.get_connection() + cursor = conn.cursor() + + resp.set_header("Content-Type", "text/x-shellscript") + + args = common_name, + ctx = dict() + + for query in SQL_SELECT_INHERITED, SQL_SELECT_TAGS: + cursor.execute(query, args) + + for key, value in cursor: + current = ctx + if "." in key: + path, key = key.rsplit(".", 1) + + for component in path.split("."): + if component not in current: + current[component] = dict() + current = current[component] + current[key] = value + cursor.close() + conn.close() + + resp.body = env.get_template("uci.sh").render(ctx) + + # TODO: Assert time is within reasonable range diff --git a/certidude/api/lease.py b/certidude/api/lease.py index f6f8597..67f8733 100644 --- a/certidude/api/lease.py +++ b/certidude/api/lease.py @@ -54,8 +54,8 @@ class LeaseResource(object): addresses.id desc """ - cnx = config.DATABASE_POOL.get_connection() - cursor = cnx.cursor() + conn = config.DATABASE_POOL.get_connection() + cursor = conn.cursor() cursor.execute(SQL_LEASES) for acquired, released, address, identity in cursor: @@ -66,3 +66,5 @@ class LeaseResource(object): "identity": parse_dn(bytes(identity)) } + cursor.close() + conn.close() diff --git a/certidude/api/signed.py b/certidude/api/signed.py index 753897b..c743cbd 100644 --- a/certidude/api/signed.py +++ b/certidude/api/signed.py @@ -28,6 +28,9 @@ class SignedCertificateListResource(object): class SignedCertificateDetailResource(object): @serialize def on_get(self, req, resp, cn): + # Compensate for NTP lag + from time import sleep + sleep(5) try: logger.info("Served certificate %s to %s", cn, req.env["REMOTE_ADDR"]) resp.set_header("Content-Disposition", "attachment; filename=%s.crt" % cn) diff --git a/certidude/api/tag.py b/certidude/api/tag.py index 4003a72..229b1a6 100644 --- a/certidude/api/tag.py +++ b/certidude/api/tag.py @@ -7,6 +7,26 @@ from certidude.decorators import serialize logger = logging.getLogger("api") +SQL_TAG_LIST = """ +select + device_tag.id as `id`, + tag.key as `key`, + tag.value as `value`, + device.cn as `cn` +from + device_tag +join + tag +on + device_tag.tag_id = tag.id +join + device +on + device_tag.device_id = device.id +""" + +SQL_TAG_DETAIL = SQL_TAG_LIST + " where device_tag.id = %s" + class TagResource(object): @serialize @login_required @@ -14,7 +34,7 @@ class TagResource(object): def on_get(self, req, resp): conn = config.DATABASE_POOL.get_connection() cursor = conn.cursor(dictionary=True) - cursor.execute("select * from tag") + cursor.execute(SQL_TAG_LIST) def g(): for row in cursor: @@ -30,10 +50,24 @@ class TagResource(object): from certidude import push conn = config.DATABASE_POOL.get_connection() cursor = conn.cursor() - args = req.get_param("cn"), req.get_param("key"), req.get_param("value") + + args = req.get_param("cn"), cursor.execute( - "insert into tag (`cn`, `key`, `value`) values (%s, %s, %s)", args) + "insert ignore device (`cn`) values (%s) on duplicate key update used = NOW();", args) + device_id = cursor.lastrowid + + args = req.get_param("key"), req.get_param("value") + cursor.execute( + "insert into tag (`key`, `value`) values (%s, %s) on duplicate key update used = NOW();", args) + tag_id = cursor.lastrowid + + args = device_id, tag_id + cursor.execute( + "insert into device_tag (`device_id`, `tag_id`) values (%s, %s);", args) + push.publish("tag-added", str(cursor.lastrowid)) + + args = req.get_param("cn"), req.get_param("key"), req.get_param("value") logger.debug("Tag cn=%s, key=%s, value=%s added" % args) conn.commit() cursor.close() @@ -47,7 +81,7 @@ class TagDetailResource(object): def on_get(self, req, resp, identifier): conn = config.DATABASE_POOL.get_connection() cursor = conn.cursor(dictionary=True) - cursor.execute("select * from tag where `id` = %s", (identifier,)) + cursor.execute(SQL_TAG_DETAIL, (identifier,)) for row in cursor: cursor.close() conn.close() @@ -63,11 +97,21 @@ class TagDetailResource(object): from certidude import push conn = config.DATABASE_POOL.get_connection() cursor = conn.cursor() - cursor.execute("update tag set `value` = %s where `id` = %s limit 1", - (req.get_param("value"), identifier)) + + # Create tag if necessary + args = req.get_param("key"), req.get_param("value") + cursor.execute( + "insert into tag (`key`, `value`) values (%s, %s) on duplicate key update used = NOW();", args) + tag_id = cursor.lastrowid + + # Attach tag to device + cursor.execute("update device_tag set tag_id = %s where `id` = %s limit 1", + (tag_id, identifier)) conn.commit() + cursor.close() conn.close() + logger.debug("Tag %s updated, value set to %s", identifier, req.get_param("value")) push.publish("tag-updated", identifier) @@ -80,7 +124,7 @@ class TagDetailResource(object): from certidude import push conn = config.DATABASE_POOL.get_connection() cursor = conn.cursor() - cursor.execute("delete from tag where tag.id = %s", (identifier,)) + cursor.execute("delete from device_tag where id = %s", (identifier,)) conn.commit() cursor.close() conn.close() diff --git a/certidude/api/whois.py b/certidude/api/whois.py index 9ec1df4..1b4c0db 100644 --- a/certidude/api/whois.py +++ b/certidude/api/whois.py @@ -6,7 +6,7 @@ from certidude import config from certidude.decorators import serialize from certidude.api.lease import parse_dn -def address_to_identity(cnx, addr): +def address_to_identity(conn, addr): """ Translate currently online client's IP-address to distinguished name """ @@ -27,29 +27,32 @@ def address_to_identity(cnx, addr): released is not null """ - cursor = cnx.cursor() + cursor = conn.cursor() import struct cursor.execute(SQL_LEASES, (struct.pack("!L", int(addr)),)) for acquired, released, identity in cursor: - return { - "address": addr, - "acquired": datetime.utcfromtimestamp(acquired), - "identity": parse_dn(bytes(identity)) - } + cursor.close() + return addr, datetime.utcfromtimestamp(acquired), parse_dn(bytes(identity)) + + cursor.close() return None class WhoisResource(object): @serialize def on_get(self, req, resp): + conn = config.DATABASE_POOL.get_connection() + identity = address_to_identity( - config.DATABASE_POOL.get_connection(), + conn, ipaddress.ip_address(req.get_param("address") or req.env["REMOTE_ADDR"]) ) + conn.close() + if identity: - return identity + return dict(address=identity[0], acquired=identity[1], identity=identity[2]) else: resp.status = falcon.HTTP_403 resp.body = "Failed to look up node %s" % req.env["REMOTE_ADDR"] diff --git a/certidude/cli.py b/certidude/cli.py index 2d5571d..eeec993 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -69,6 +69,9 @@ def certidude_spawn(kill, no_interaction): """ from certidude import config + _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") + os.setgid(gid) + # Check whether we have privileges os.umask(0o027) uid = os.getuid() @@ -472,7 +475,7 @@ def certidude_setup_strongswan_networkmanager(url, email_address, common_name, o @click.option("--username", default="certidude", help="Service user account, created if necessary, 'certidude' by default") @click.option("--hostname", default=HOSTNAME, help="nginx hostname, '%s' by default" % HOSTNAME) @click.option("--static-path", default=os.path.join(os.path.dirname(__file__), "static"), help="Static files") -@click.option("--kerberos-keytab", default="/etc/certidude.keytab", help="Specify Kerberos keytab") +@click.option("--kerberos-keytab", default="/etc/certidude/server.keytab", help="Specify Kerberos keytab") @click.option("--nginx-config", "-n", default="/etc/nginx/nginx.conf", type=click.File(mode="w", atomic=True, lazy=True), @@ -540,6 +543,10 @@ def certidude_setup_authority(parent, country, state, locality, organization, or if os.path.lexists(directory): raise click.ClickException("Output directory {} already exists.".format(directory)) + certidude_conf = os.path.join("/etc/certidude/server.conf") + if os.path.exists(certidude_conf): + raise click.ClickException("Configuration file %s already exists" % certidude_conf) + click.echo("CA configuration files are saved to: {}".format(directory)) click.echo("Generating 4096-bit RSA key...") @@ -588,6 +595,10 @@ def certidude_setup_authority(parent, country, state, locality, organization, or b"keyUsage", True, b"keyCertSign, cRLSign"), + crypto.X509Extension( + b"extendedKeyUsage", + True, + b"serverAuth,1.3.6.1.5.5.8.2.2"), crypto.X509Extension( b"subjectKeyIdentifier", False, @@ -606,6 +617,12 @@ def certidude_setup_authority(parent, country, state, locality, organization, or False, subject_alt_name.encode("ascii")) ]) + ca.add_extensions([ + crypto.X509Extension( + b"subjectAltName", + True, + ("DNS:%s" % common_name).encode("ascii")) + ]) if ocsp_responder_url: raise NotImplementedError() @@ -628,33 +645,40 @@ def certidude_setup_authority(parent, country, state, locality, organization, or ca.sign(key, "sha256") + _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") + os.setgid(gid) + + # Create authority directory with 750 permissions os.umask(0o027) if not os.path.exists(directory): os.makedirs(directory) + # Create subdirectories with 770 permissions os.umask(0o007) - for subdir in ("signed", "requests", "revoked"): if not os.path.exists(os.path.join(directory, subdir)): os.mkdir(os.path.join(directory, subdir)) + + # Create CRL and serial file with 644 permissions + os.umask(0o133) with open(ca_crl, "wb") as fh: crl = crypto.CRL() fh.write(crl.export(ca, key, days=revocation_list_lifetime)) with open(os.path.join(directory, "serial"), "w") as fh: fh.write("1") - os.umask(0o027) + # Set permission bits to 640 + os.umask(0o137) + with open(certidude_conf, "w") as fh: + fh.write(env.get_template("certidude.conf").render(locals())) with open(ca_crt, "wb") as fh: fh.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca)) - os.umask(0o077) + # Set permission bits to 600 + os.umask(0o177) with open(ca_key, "wb") as fh: fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) - certidude_conf = os.path.join("/etc/certidude.conf") - with open(certidude_conf, "w") as fh: - fh.write(env.get_template("certidude.conf").render(locals())) - click.echo() click.echo("Use following commands to inspect the newly created files:") click.echo() @@ -665,7 +689,7 @@ def certidude_setup_authority(parent, country, state, locality, organization, or click.echo() click.echo("Use following to launch privilege isolated signer processes:") click.echo() - click.echo(" certidude spawn") + click.echo(" certidude spawn -k") click.echo() click.echo("Use following command to serve CA read-only:") click.echo() diff --git a/certidude/config.py b/certidude/config.py index a868ebc..8ec6588 100644 --- a/certidude/config.py +++ b/certidude/config.py @@ -8,7 +8,7 @@ import string from random import choice cp = configparser.ConfigParser() -cp.read("/etc/certidude.conf") +cp.read("/etc/certidude/server.conf") ADMIN_USERS = set([j for j in cp.get("authorization", "admin_users").split(" ") if j]) ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j]) @@ -48,7 +48,6 @@ except configparser.NoOptionError: PUSH_LONG_POLL = PUSH_SERVER + "/lp/%s" PUSH_PUBLISH = PUSH_SERVER + "/pub?id=%s" - from urllib.parse import urlparse o = urlparse(cp.get("authority", "database")) if o.scheme == "mysql": diff --git a/certidude/signer.py b/certidude/signer.py index 9bc688e..55f0d9d 100644 --- a/certidude/signer.py +++ b/certidude/signer.py @@ -97,7 +97,7 @@ def raw_sign(private_key, ca_cert, request, basic_constraints, lifetime, key_usa extended_key_usage.encode("ascii"))]) # Set certificate lifetime - cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notBefore(-3600) cert.gmtime_adj_notAfter(lifetime * 24 * 60 * 60) # Generate serial from 0x10000000000000000000 to 0xffffffffffffffffffff diff --git a/certidude/static/authority.html b/certidude/static/authority.html index 690a386..79f5a0a 100644 --- a/certidude/static/authority.html +++ b/certidude/static/authority.html @@ -52,9 +52,7 @@

Revoked certificates

To fetch certificate revocation list:

-
-    curl {{window.location.href}}api/revoked/ | openssl crl -text -noout
-    
+
curl {{window.location.href}}api/revoked/ | openssl crl -text -noout
+ + + + + diff --git a/certidude/static/img/iconmonstr-wireless-6-icon.svg b/certidude/static/img/iconmonstr-wireless-6-icon.svg new file mode 100644 index 0000000..467c13a --- /dev/null +++ b/certidude/static/img/iconmonstr-wireless-6-icon.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/certidude/static/index.html b/certidude/static/index.html index 538eb20..ed05e61 100644 --- a/certidude/static/index.html +++ b/certidude/static/index.html @@ -16,6 +16,7 @@
  • Requests
  • Signed
  • Revoked
  • +
  • Configuration
  • Log
  • diff --git a/certidude/static/js/certidude.js b/certidude/static/js/certidude.js index 9160cb0..9edcdbe 100644 --- a/certidude/static/js/certidude.js +++ b/certidude/static/js/certidude.js @@ -15,6 +15,7 @@ function onTagClicked() { url: "/api/tag/" + $(this).attr("data-id"), dataType: "json", data: { + key: $(this).attr("data-key"), value: updated } }); @@ -40,6 +41,11 @@ function onNewTagClicked() { }); } +function onTagFilterChanged() { + var key = $(event.target).val(); + console.info("New key is:", key); +} + function onLogEntry (e) { var entry = JSON.parse(e.data); if ($("#log_level_" + entry.severity).prop("checked")) { @@ -128,7 +134,7 @@ function onTagAdded(e) { dataType: "json", success: function(tag, status, xhr) { // TODO: Deduplicate - $tag = $("" + tag.value + ""); + $tag = $("" + tag.value + ""); $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend(" "); $tags = $("#signed_certificates [data-cn='" + tag.cn + "'] .tags").prepend($tag); $tag.click(onTagClicked); @@ -201,29 +207,6 @@ $(document).ready(function() { $("section#" + $(e.target).attr("data-section")).show(); }); - /** - * Fetch log entries - */ - $.ajax({ - method: "GET", - url: "/api/log/", - dataType: "json", - success:function(entries, status, xhr) { - console.info("Got", entries.length, "log entries"); - for (var j = 0; j < entries.length; j++) { - if ($("#log_level_" + entries[j].severity).prop("checked")) { - $("#log_entries").append(nunjucks.render("logentry.html", { - entry: { - created: new Date(entries[j].created).toLocaleString("et-EE"), - message: entries[j].message, - severity: entries[j].severity - } - })); - } - } - } - }); - /** * Set up search bar */ @@ -248,22 +231,35 @@ $(document).ready(function() { }); - /** - * Fetch tags for certificates - */ + + $.ajax({ method: "GET", - url: "/api/tag/", + url: "/api/config/", dataType: "json", - success:function(tags, status, xhr) { - console.info("Got", tags.length, "tags"); - for (var j = 0; j < tags.length; j++) { - // TODO: Deduplicate - $tag = $("" + tags[j].value + ""); - $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend(" "); - $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend($tag); - $tag.click(onTagClicked); - } + success: function(configuration, status, xhr) { + console.info("Appending " + configuration.length + " configuration items"); + $("#config").html(nunjucks.render('configuration.html', { configuration:configuration})); + /** + * Fetch tags for certificates + */ + $.ajax({ + method: "GET", + url: "/api/tag/", + dataType: "json", + success:function(tags, status, xhr) { + console.info("Got", tags.length, "tags"); + for (var j = 0; j < tags.length; j++) { + // TODO: Deduplicate + $tag = $("" + tags[j].value + ""); + console.info("Inserting tag", tags[j], $tag); + $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend(" "); + $tags = $("#signed_certificates [data-cn='" + tags[j].cn + "'] .tags").prepend($tag); + $tag.click(onTagClicked); + $("#tags_autocomplete").prepend(""); + } + } + }); } }); @@ -293,6 +289,29 @@ $(document).ready(function() { } }); + return; + /** + * Fetch log entries + */ + $.ajax({ + method: "GET", + url: "/api/log/", + dataType: "json", + success:function(entries, status, xhr) { + console.info("Got", entries.length, "log entries"); + for (var j = 0; j < entries.length; j++) { + if ($("#log_level_" + entries[j].severity).prop("checked")) { + $("#log_entries").append(nunjucks.render("logentry.html", { + entry: { + created: new Date(entries[j].created).toLocaleString("et-EE"), + message: entries[j].message, + severity: entries[j].severity + } + })); + } + } + } + }); } }); }); diff --git a/certidude/static/request.html b/certidude/static/request.html index 02d1be5..f0b6937 100644 --- a/certidude/static/request.html +++ b/certidude/static/request.html @@ -1,4 +1,4 @@ -
  • +
  • Fetch {% if request.signable %} diff --git a/certidude/static/signed.html b/certidude/static/signed.html index a7c4dfe..df28fb5 100644 --- a/certidude/static/signed.html +++ b/certidude/static/signed.html @@ -11,6 +11,8 @@ {% endif %} + {# +
    {% include 'img/iconmonstr-key-2-icon.svg' %} @@ -25,13 +27,12 @@ {{certificate.key_usage}}
    + #} +
    diff --git a/certidude/static/status.html b/certidude/static/status.html index 7502c55..ad8f715 100644 --- a/certidude/static/status.html +++ b/certidude/static/status.html @@ -8,7 +8,7 @@ {% if lease.released %} Last seen {{ lease.released }} at {{ lease.address }} {% else %} -Online since {{ lease.acquired }} at {{ lease.address }} +Online since {{ lease.acquired }} at {{ lease.address }} {% endif %} {% else %} Not seen diff --git a/certidude/static/tagtypes.html b/certidude/static/tagtypes.html new file mode 100644 index 0000000..93eebc1 --- /dev/null +++ b/certidude/static/tagtypes.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/setup.py b/setup.py index 77b6132..4ee3a9d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup setup( name = "certidude", - version = "0.1.18", + version = "0.1.20", 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.",