From 637afe8ba7315db7ff2c0d6d1134e5ac3be3aa99 Mon Sep 17 00:00:00 2001
From: Frank Sauerburger <frank@sauerburger.com>
Date: Sat, 27 Feb 2021 22:54:10 +0100
Subject: [PATCH] Merge owlca and fix pgp permission

---
 .gitignore                                    |   1 +
 .gitmodules                                   |   3 -
 app/Dockerfile                                |   2 +-
 app/owlca/.gitignore                          |   3 -
 app/owlca/{owlca => }/__init__.py             |   0
 app/owlca/{owlca => }/admin.py                |   0
 app/owlca/{owlca => }/apps.py                 |   0
 app/owlca/{owlca => }/forms.py                |   0
 .../{owlca => }/migrations/0001_initial.py    |   0
 .../0002_certificationauthority_title.py      |   0
 .../migrations/0003_auto_20210131_2224.py     |   0
 .../migrations/0004_auto_20210214_1130.py     |   0
 .../migrations/0005_auto_20210217_1823.py     |   0
 .../migrations/0006_auto_20210217_2125.py     |   0
 ...07_remove_certificationauthority_public.py |   0
 .../migrations/0008_auto_20210218_1332.py     |   0
 .../migrations/0009_auto_20210218_1334.py     |   0
 .../migrations/0010_auto_20210218_2056.py     |   0
 .../0011_remove_certificate_serial_number.py  |   0
 app/owlca/{owlca => }/migrations/__init__.py  |   0
 app/owlca/{owlca => }/models.py               |  58 ++++++++-
 .../templates/owlca/certificate_detail.html   |  32 -----
 .../templates/owlca/certificate_pickup.html   |  48 --------
 .../certificatesigningrequest_detail.html     |  41 -------
 .../owlca/certificatesignrequest_create.html  |  17 ---
 .../owlca/certificationauthority_create.html  |  11 --
 .../owlca/certificationauthority_detail.html  |  76 ------------
 .../owlca/certificationauthority_form.html    |  11 --
 .../owlca/certificationauthority_list.html    |  19 ---
 app/owlca/requirements.txt                    |   1 -
 app/owlca/setup.py                            |  39 ------
 .../{owlca => }/templates/owlca/base.html     |   0
 app/owlca/templates/owlca/cert_item.html      | 111 ++++++++++++++++++
 .../templates/owlca/certificate_detail.html   |  41 +++++++
 .../templates/owlca/certificate_list.html     |   9 ++
 .../templates/owlca/certificate_pickup.html   |  51 ++++++++
 .../certificatesigningrequest_detail.html     |  68 +++++++++++
 .../owlca/certificatesigningrequest_list.html |   9 ++
 .../certificatesigningrequest_retrieve.html   |   0
 .../owlca/certificatesignrequest_create.html  |  57 +++++++++
 .../owlca/certificationauthority_create.html  |  25 ++++
 .../owlca/certificationauthority_detail.html  |  73 ++++++++++++
 .../owlca/certificationauthority_list.html    |  54 +++++++++
 app/owlca/{owlca => }/tests.py                |   0
 app/owlca/{owlca => }/urls.py                 |   0
 app/owlca/{owlca => }/views.py                |   2 +-
 app/pgp/templates/pgp/publickey_list.html     |   2 +-
 47 files changed, 557 insertions(+), 307 deletions(-)
 delete mode 100644 .gitmodules
 delete mode 100644 app/owlca/.gitignore
 rename app/owlca/{owlca => }/__init__.py (100%)
 rename app/owlca/{owlca => }/admin.py (100%)
 rename app/owlca/{owlca => }/apps.py (100%)
 rename app/owlca/{owlca => }/forms.py (100%)
 rename app/owlca/{owlca => }/migrations/0001_initial.py (100%)
 rename app/owlca/{owlca => }/migrations/0002_certificationauthority_title.py (100%)
 rename app/owlca/{owlca => }/migrations/0003_auto_20210131_2224.py (100%)
 rename app/owlca/{owlca => }/migrations/0004_auto_20210214_1130.py (100%)
 rename app/owlca/{owlca => }/migrations/0005_auto_20210217_1823.py (100%)
 rename app/owlca/{owlca => }/migrations/0006_auto_20210217_2125.py (100%)
 rename app/owlca/{owlca => }/migrations/0007_remove_certificationauthority_public.py (100%)
 rename app/owlca/{owlca => }/migrations/0008_auto_20210218_1332.py (100%)
 rename app/owlca/{owlca => }/migrations/0009_auto_20210218_1334.py (100%)
 rename app/owlca/{owlca => }/migrations/0010_auto_20210218_2056.py (100%)
 rename app/owlca/{owlca => }/migrations/0011_remove_certificate_serial_number.py (100%)
 rename app/owlca/{owlca => }/migrations/__init__.py (100%)
 rename app/owlca/{owlca => }/models.py (84%)
 delete mode 100644 app/owlca/owlca/templates/owlca/certificate_detail.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificate_pickup.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificatesigningrequest_detail.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificatesignrequest_create.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificationauthority_create.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificationauthority_detail.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificationauthority_form.html
 delete mode 100644 app/owlca/owlca/templates/owlca/certificationauthority_list.html
 delete mode 100644 app/owlca/requirements.txt
 delete mode 100644 app/owlca/setup.py
 rename app/owlca/{owlca => }/templates/owlca/base.html (100%)
 create mode 100644 app/owlca/templates/owlca/cert_item.html
 create mode 100644 app/owlca/templates/owlca/certificate_detail.html
 rename app/owlca/{owlca => }/templates/owlca/certificate_list.html (60%)
 create mode 100644 app/owlca/templates/owlca/certificate_pickup.html
 create mode 100644 app/owlca/templates/owlca/certificatesigningrequest_detail.html
 rename app/owlca/{owlca => }/templates/owlca/certificatesigningrequest_list.html (62%)
 rename app/owlca/{owlca => }/templates/owlca/certificatesigningrequest_retrieve.html (100%)
 create mode 100644 app/owlca/templates/owlca/certificatesignrequest_create.html
 create mode 100644 app/owlca/templates/owlca/certificationauthority_create.html
 create mode 100644 app/owlca/templates/owlca/certificationauthority_detail.html
 create mode 100644 app/owlca/templates/owlca/certificationauthority_list.html
 rename app/owlca/{owlca => }/tests.py (100%)
 rename app/owlca/{owlca => }/urls.py (100%)
 rename app/owlca/{owlca => }/views.py (99%)

diff --git a/.gitignore b/.gitignore
index 091123f..0ad3222 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 __pycache__/
 *.sqlite3
 CACHE
+.env
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a479604..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "owlca"]
-	path = app/owlca
-	url = https://gitlab.sauerburger.com/frank/owl-ca.git
diff --git a/app/Dockerfile b/app/Dockerfile
index e678f5f..791cae8 100644
--- a/app/Dockerfile
+++ b/app/Dockerfile
@@ -9,7 +9,7 @@ COPY run.sh manage.py /app/
 COPY hkp /app/hkp
 COPY keys /app/keys
 COPY keys_home /app/keys_home
-COPY owlca/owlca /app/owlca
+COPY owlca /app/owlca
 COPY pgp /app/pgp
 COPY ssh /app/ssh
 COPY wkd /app/wkd
diff --git a/app/owlca/.gitignore b/app/owlca/.gitignore
deleted file mode 100644
index aa8e69d..0000000
--- a/app/owlca/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-__pycache__/
-*.egg-info/
-*.pem
diff --git a/app/owlca/owlca/__init__.py b/app/owlca/__init__.py
similarity index 100%
rename from app/owlca/owlca/__init__.py
rename to app/owlca/__init__.py
diff --git a/app/owlca/owlca/admin.py b/app/owlca/admin.py
similarity index 100%
rename from app/owlca/owlca/admin.py
rename to app/owlca/admin.py
diff --git a/app/owlca/owlca/apps.py b/app/owlca/apps.py
similarity index 100%
rename from app/owlca/owlca/apps.py
rename to app/owlca/apps.py
diff --git a/app/owlca/owlca/forms.py b/app/owlca/forms.py
similarity index 100%
rename from app/owlca/owlca/forms.py
rename to app/owlca/forms.py
diff --git a/app/owlca/owlca/migrations/0001_initial.py b/app/owlca/migrations/0001_initial.py
similarity index 100%
rename from app/owlca/owlca/migrations/0001_initial.py
rename to app/owlca/migrations/0001_initial.py
diff --git a/app/owlca/owlca/migrations/0002_certificationauthority_title.py b/app/owlca/migrations/0002_certificationauthority_title.py
similarity index 100%
rename from app/owlca/owlca/migrations/0002_certificationauthority_title.py
rename to app/owlca/migrations/0002_certificationauthority_title.py
diff --git a/app/owlca/owlca/migrations/0003_auto_20210131_2224.py b/app/owlca/migrations/0003_auto_20210131_2224.py
similarity index 100%
rename from app/owlca/owlca/migrations/0003_auto_20210131_2224.py
rename to app/owlca/migrations/0003_auto_20210131_2224.py
diff --git a/app/owlca/owlca/migrations/0004_auto_20210214_1130.py b/app/owlca/migrations/0004_auto_20210214_1130.py
similarity index 100%
rename from app/owlca/owlca/migrations/0004_auto_20210214_1130.py
rename to app/owlca/migrations/0004_auto_20210214_1130.py
diff --git a/app/owlca/owlca/migrations/0005_auto_20210217_1823.py b/app/owlca/migrations/0005_auto_20210217_1823.py
similarity index 100%
rename from app/owlca/owlca/migrations/0005_auto_20210217_1823.py
rename to app/owlca/migrations/0005_auto_20210217_1823.py
diff --git a/app/owlca/owlca/migrations/0006_auto_20210217_2125.py b/app/owlca/migrations/0006_auto_20210217_2125.py
similarity index 100%
rename from app/owlca/owlca/migrations/0006_auto_20210217_2125.py
rename to app/owlca/migrations/0006_auto_20210217_2125.py
diff --git a/app/owlca/owlca/migrations/0007_remove_certificationauthority_public.py b/app/owlca/migrations/0007_remove_certificationauthority_public.py
similarity index 100%
rename from app/owlca/owlca/migrations/0007_remove_certificationauthority_public.py
rename to app/owlca/migrations/0007_remove_certificationauthority_public.py
diff --git a/app/owlca/owlca/migrations/0008_auto_20210218_1332.py b/app/owlca/migrations/0008_auto_20210218_1332.py
similarity index 100%
rename from app/owlca/owlca/migrations/0008_auto_20210218_1332.py
rename to app/owlca/migrations/0008_auto_20210218_1332.py
diff --git a/app/owlca/owlca/migrations/0009_auto_20210218_1334.py b/app/owlca/migrations/0009_auto_20210218_1334.py
similarity index 100%
rename from app/owlca/owlca/migrations/0009_auto_20210218_1334.py
rename to app/owlca/migrations/0009_auto_20210218_1334.py
diff --git a/app/owlca/owlca/migrations/0010_auto_20210218_2056.py b/app/owlca/migrations/0010_auto_20210218_2056.py
similarity index 100%
rename from app/owlca/owlca/migrations/0010_auto_20210218_2056.py
rename to app/owlca/migrations/0010_auto_20210218_2056.py
diff --git a/app/owlca/owlca/migrations/0011_remove_certificate_serial_number.py b/app/owlca/migrations/0011_remove_certificate_serial_number.py
similarity index 100%
rename from app/owlca/owlca/migrations/0011_remove_certificate_serial_number.py
rename to app/owlca/migrations/0011_remove_certificate_serial_number.py
diff --git a/app/owlca/owlca/migrations/__init__.py b/app/owlca/migrations/__init__.py
similarity index 100%
rename from app/owlca/owlca/migrations/__init__.py
rename to app/owlca/migrations/__init__.py
diff --git a/app/owlca/owlca/models.py b/app/owlca/models.py
similarity index 84%
rename from app/owlca/owlca/models.py
rename to app/owlca/models.py
index a3aa842..263281e 100644
--- a/app/owlca/owlca/models.py
+++ b/app/owlca/models.py
@@ -213,11 +213,32 @@ class CertificateSigningRequest(models.Model):
         return self.decoded.subject.rfc4514_string()
 
     def extensions(self):
+        """Return the subject of the certificate"""
+        return [(
+            e.oid._name,
+            e.value,
+            e.critical
+        ) for e in self.decoded.extensions]
+
+    def extensions_human(self):
         """Return the subject of the certificate"""
         if not self._decode():
             return None
 
-        return [(e.oid._name, e.value, e.critical) for e in self.decoded.extensions]
+        def remove_prefix(text, prefix):
+            if text.startswith(prefix):
+                return text[len(prefix):]
+            return text
+
+        def encode(d):
+            pieces = [f"{remove_prefix(k, '_')}={v}" for k, v in vars(d).items()]
+            return ", ".join(pieces)
+
+        return [(
+            v.__class__.__name__,
+            encode(v),
+            v
+        ) for n, v, c in self.extensions()]
 
 class Certificate(models.Model):
     ca = models.ForeignKey(
@@ -255,9 +276,14 @@ class Certificate(models.Model):
         if not self._decode():
             return None
 
-
         return self.decoded.serial_number.to_bytes(20, 'big')
 
+    def serial_number_human(self):
+        """Return the subject of the certificate"""
+        serial = self.serial_number().hex()
+        return ":".join(a + b for a, b in zip(serial[::2], serial[1::2]))
+
+
     def subject(self):
         """Return the subject of the certificate"""
         if not self._decode():
@@ -290,12 +316,38 @@ class Certificate(models.Model):
             return None
         return self.decoded.fingerprint(hashes.SHA256())
 
+    def fingerprint_human(self):
+        """Return the fingerprint of the certificate"""
+        fingerprint = self.fingerprint().hex()
+        return fingerprint
+
     def extensions(self):
+        """Return the subject of the certificate"""
+        return [(
+            e.oid._name,
+            e.value,
+            e.critical
+        ) for e in self.decoded.extensions]
+
+    def extensions_human(self):
         """Return the subject of the certificate"""
         if not self._decode():
             return None
 
-        return [(e.oid._name, e.value, e.critical) for e in self.decoded.extensions]
+        def remove_prefix(text, prefix):
+            if text.startswith(prefix):
+                return text[len(prefix):]
+            return text
+
+        def encode(d):
+            pieces = [f"{remove_prefix(k, '_')}={v}" for k, v in vars(d).items()]
+            return ", ".join(pieces)
+
+        return [(
+            v.__class__.__name__,
+            encode(v),
+            v
+        ) for n, v, c in self.extensions()]
 
     def __str__(self):
         return f"{self.ca}: {self.serial_number().hex()}"
diff --git a/app/owlca/owlca/templates/owlca/certificate_detail.html b/app/owlca/owlca/templates/owlca/certificate_detail.html
deleted file mode 100644
index d423450..0000000
--- a/app/owlca/owlca/templates/owlca/certificate_detail.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>Certificate</h2>
-<p>Serial: {{ certificate.serial_number.hex }}</p>
-<p>Fingerprint: {{ certificate.fingerprint.hex }}</p>
-<p>CA: <a href="{% url 'ca-detail' certificate.ca.pk %}">{{ certificate.ca }}</a></p>
-<p>Subject: {{ certificate.subject }}</p>
-<p>Issuer: {{ certificate.issuer }}</p>
-<p>Not valid before: {{ certificate.not_valid_before }}</p>
-<p>Not valid after: {{ certificate.not_valid_after }}</p>
-{% if certificate.extensions %}
-<p>Extensions:
-<ul>
-{% for name, value, crit in certificate.extensions %}
-    <li>
-    {% if crit %}<b>{% endif %}
-    {{name}}: {{value}}
-    {% if crit %} (critical)</b>{% endif %}
-    </li>
-{% endfor %}
-</ul>
-</p>
-{% endif %}
-{% if certificate.request %}
-<p>CSR: <a href="{% url 'csr-detail' certificate.request.pk %}">{{ certificate.request }}</a></p>
-{% endif %}
-
-<p><a href="{% url 'cert-download' certificate.pk %}">Download cert.pem</a></p>
-<pre>{{ certificate.pem}}</pre>
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificate_pickup.html b/app/owlca/owlca/templates/owlca/certificate_pickup.html
deleted file mode 100644
index 5e14406..0000000
--- a/app/owlca/owlca/templates/owlca/certificate_pickup.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>Certificate Pickup</h2>
-
-<p>
-  Certification authority:
-  <a href="{% url 'ca-detail' certificatesigningrequest.ca.pk %}">
-    {{ certificatesigningrequest.ca }}
-  </a>
-</p>
-<p>Verification status: {{ certificatesigningrequest.verification_text }}</p>
-
-{% if certificatesigningrequest.verification == 10 %}
-<ul>
-    {% for cert in certificatesigningrequest.certificates.all %}
-    <li>
-        Serial: {{ cert.serial_number.hex }}<br />
-        Fingerprint: {{ cert.fingerprint.hex }}<br />
-        Subject: {{ cert.subject }}<br />
-        Issuer: {{ cert.issuer }}<br />
-        Not valid before: {{ cert.not_valid_before }}<br />
-        Not valid after: {{ cert.not_valid_after }}<br />
-        {% if cert.extensions %}
-        Extensions:
-        <ul>
-        {% for name, value, crit in cert.extensions %}
-            <li>
-            {% if crit %}<b>{% endif %}
-            {{name}}: {{value}}
-            {% if crit %} (critical)</b>{% endif %}
-            </li>
-        {% endfor %}
-        </ul>
-        {% endif %}
-        <a href="{% url 'cert-pickup-download' certificatesigningrequest.pickup_code cert.serial_number.hex %}">Download cert.pem</a>
-        <pre>{{ cert.pem }}</pre>
-    </li>
-    {% endfor %}
-</ul>
-{% elif certificatesigningrequest.verification == 0 %}
-<p>You certificate will be available at
-<a href="{% url 'cert-pickup' certificatesigningrequest.pickup_code %}">
-{{ request.scheme }}://{{ request.get_host }}{% url 'cert-pickup' certificatesigningrequest.pickup_code %}
-</a> once it has been approved.
-{% endif %}
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificatesigningrequest_detail.html b/app/owlca/owlca/templates/owlca/certificatesigningrequest_detail.html
deleted file mode 100644
index e7cfa8f..0000000
--- a/app/owlca/owlca/templates/owlca/certificatesigningrequest_detail.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>Certificate Signing Request</h2>
-
-<p>
-  Certification authority:
-  <a href="{% url 'ca-detail' certificatesigningrequest.ca.pk %}">
-    {{ certificatesigningrequest.ca }}
-  </a>
-</p>
-<p>Verification status: {{ certificatesigningrequest.verification_text }}</p>
-<p>Subject: {{ certificatesigningrequest.subject }}</p>
-{% if certificatesigningrequest.extensions %}
-<p>Extensions:
-<ul>
-{% for name, value, crit in certificatesigningrequest.extensions %}
-    <li>
-    {% if crit %}<b>{% endif %}
-    {{name}}: {{value}}
-    {% if crit %} (critical)</b>{% endif %}
-    </li>
-{% endfor %}
-</ul>
-</p>
-{% endif %}
-<p>Pickup:
-<a href="{% url 'cert-pickup' certificatesigningrequest.pickup_code %}">
-{% url 'cert-pickup' certificatesigningrequest.pickup_code %}
-</a>
-</p>
-<pre>{{ certificatesigningrequest.pem }}</pre>
-
-<form action="" method="post">
-  {% csrf_token %}
-  {{ form.as_p }}
-  <button type="submit" name="approve">Approve</button>
-  <button type="submit" name="reject">Reject</button>
-</form>
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificatesignrequest_create.html b/app/owlca/owlca/templates/owlca/certificatesignrequest_create.html
deleted file mode 100644
index 18b43c6..0000000
--- a/app/owlca/owlca/templates/owlca/certificatesignrequest_create.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>Request certificate signing</h2>
-<pre>
-openssl genrsa -aes256 -out key.pem 2048
-openssl req -new -key key.pem -out csr.pem
-</pre>
-
-<form action="" method="post" enctype="multipart/form-data">
-  {% csrf_token %}
-  {{ form.as_p }}
-  <button type="submit">Request</button>
-</form>
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificationauthority_create.html b/app/owlca/owlca/templates/owlca/certificationauthority_create.html
deleted file mode 100644
index 8c2c362..0000000
--- a/app/owlca/owlca/templates/owlca/certificationauthority_create.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>New CA</h2>
-<form action="" method="post">
-  {% csrf_token %}
-  {{ form.as_p }}
-  <button type="submit">Create</button>
-</form>
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificationauthority_detail.html b/app/owlca/owlca/templates/owlca/certificationauthority_detail.html
deleted file mode 100644
index 03b4228..0000000
--- a/app/owlca/owlca/templates/owlca/certificationauthority_detail.html
+++ /dev/null
@@ -1,76 +0,0 @@
-{% extends 'owlca/base.html' %}
-{% load guardian_tags %}
-
-{% block content %}
-{% get_obj_perms request.user for certificationauthority as "ca_perms" %}
-
-<h2>CA: {{ certificationauthority.title }}</h2>
-<ul>
-    {% if "request_certificate" in ca_perms %}
-    <li><a href="{% url 'csr-create' certificationauthority.pk %}">Request signature</a></li>
-    {% endif %}
-</ul>
-<p>{{ certificationauthority.comment }}</p>
-
-
-<h3>Self-signed certificate</h3>
-{% if certificationauthority.cert %}
-<p>Serial number: {{ certificationauthority.cert.serial_number.hex }}</p>
-<p>Fingerprint: {{ certificationauthority.cert.fingerprint.hex }}</p>
-<p>Subject: {{ certificationauthority.cert.subject }}</p>
-<p>Issuer: {{ certificationauthority.cert.issuer }}</p>
-<p>Not valid before: {{ certificationauthority.cert.not_valid_before }}</p>
-<p>Not valid after: {{ certificationauthority.cert.not_valid_after }}</p>
-{% if certificationauthority.cert.extensions %}
-<p>Extensions:
-<ul>
-{% for name, value, crit in certificationauthority.cert.extensions %}
-    <li>
-    {% if crit %}<b>{% endif %}
-    {{name}}: {{value}}
-    {% if crit %} (critical)</b>{% endif %}
-    </li>
-{% endfor %}
-</ul>
-</p>
-{% endif %}
-<p><a href="{% url 'cert-download' certificationauthority.cert.pk %}">Download ca.pem</a></p>
-<pre>{{ certificationauthority.cert.pem }}</pre>
-{% else %}
-<p>No self-signed certificate!</p>
-{% endif %}
-
-
-{% if "manage_certificationauthority" in ca_perms %}
-
-<h3>CSR</h3>
-{% if certificationauthority.requests %}
-<ul>
-{% for csr in certificationauthority.requests.all %}
-  <li>
-    <a href="{% url 'csr-detail' csr.pk %}">{{ csr }}</a></li>
-{% endfor %}
-</ul>
-{% else %}
-<p>CA has not issued any certificates</p>
-{% endif %}
-
-
-<h3>Certificates</h3>
-{% if certificationauthority.issued_certificates.count %}
-<ul>
-{% for cert in certificationauthority.issued_certificates.all %}
-  <li>
-    <a href="{% url 'cert-detail' cert.pk %}">
-      {{ cert.serial_number.hex }}
-    </a>
-  </li>
-{% endfor %}
-</ul>
-{% else %}
-<p>CA has not issued any certificates</p>
-{% endif %}
-
-{% endif %}
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificationauthority_form.html b/app/owlca/owlca/templates/owlca/certificationauthority_form.html
deleted file mode 100644
index 8c2c362..0000000
--- a/app/owlca/owlca/templates/owlca/certificationauthority_form.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>New CA</h2>
-<form action="" method="post">
-  {% csrf_token %}
-  {{ form.as_p }}
-  <button type="submit">Create</button>
-</form>
-
-{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificationauthority_list.html b/app/owlca/owlca/templates/owlca/certificationauthority_list.html
deleted file mode 100644
index 52dc11f..0000000
--- a/app/owlca/owlca/templates/owlca/certificationauthority_list.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends 'owlca/base.html' %}
-
-{% block content %}
-<h2>Certification Authorities</h2>
-{% if certificationauthority_list %}
-<ul>
-{% for ca in certificationauthority_list %}
-  <li><a href="{% url 'ca-detail' ca.pk %}">{{ ca }}</a></li>
-{% endfor %}
-</ul>
-{% else %}
-<p>There are no Certification Authorities visible to you.</p>
-{% endif %}
-
-{% if perms.owlca.add_certificationauthority %}
-<a href="{% url 'ca-create' %}">Create new CA</a>
-{% endif %}
-
-{% endblock %}
diff --git a/app/owlca/requirements.txt b/app/owlca/requirements.txt
deleted file mode 100644
index 41567ab..0000000
--- a/app/owlca/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-guardian
diff --git a/app/owlca/setup.py b/app/owlca/setup.py
deleted file mode 100644
index 07087e9..0000000
--- a/app/owlca/setup.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-This script is used to install uhepplot and all its dependencies. Run
-
-    python setup.py install
-or
-    python3 setup.py install
-
-to install the package.
-"""
-
-# Copyright (C) 2021 Frank Sauerburger
-
-from setuptools import setup
-
-def load_long_description(filename):
-    """
-    Loads the given file and returns its content.
-    """
-    with open(filename) as readme_file:
-        content = readme_file.read()
-        return content
-
-setup(name='uhepp',
-      version='0.0.1',  # Also change in module and docs
-      packages=["owlca"],
-      install_requires=["django",
-                        "cryptography",
-                        ],  # Also add in requirements.txt
-      description='Online web ca',
-      # long_description=load_long_description("README.md"),
-      # long_description_content_type='text/markdown',
-      url="https://gitlab.sauerburger.com/frank/owl-ca",
-      author="Frank Sauerburger",
-      author_email="frank@sauerburger.com",
-      classifiers=[
-        "License :: OSI Approved :: MIT License",
-        "Programming Language :: Python :: 3 :: Only",
-      ],
-      license="MIT")
diff --git a/app/owlca/owlca/templates/owlca/base.html b/app/owlca/templates/owlca/base.html
similarity index 100%
rename from app/owlca/owlca/templates/owlca/base.html
rename to app/owlca/templates/owlca/base.html
diff --git a/app/owlca/templates/owlca/cert_item.html b/app/owlca/templates/owlca/cert_item.html
new file mode 100644
index 0000000..1e4154d
--- /dev/null
+++ b/app/owlca/templates/owlca/cert_item.html
@@ -0,0 +1,111 @@
+<li class="pki-item">
+  <h3>
+    <i class="fas fa-certificate pki-type-icon"></i>
+    {% if csr %}
+      Certificate Signing Request
+    {% else  %}
+      {% if selfsigned %}Self-signed {% endif %}Certificate
+    {% endif %}
+  </h3>
+
+  <div class="pki-info">
+    <div class="pki-info-title">
+      <i class="fas fa-id-card"></i> Subject
+    </div>
+
+    <div class="pki-info-body-prefix">
+      <div><span>RFC4514</span></div>
+      <input type="text" readonly value="{{ cert.subject }}" />
+    </div>
+  </div>
+
+  {% if not csr %}
+  <div class="pki-info-secondary">
+    <div class="pki-info-title">
+      <i class="fas fa-id-card"></i> Issuer
+    </div>
+
+    <div class="pki-info-body-prefix">
+      <div><span>RFC4514</span></div>
+      <input type="text" readonly value="{{ cert.issuer }}" />
+    </div>
+  </div>
+
+  <div class="pki-info-secondary">
+    <div class="pki-info-title">
+      <i class="fas fa-calendar-check"></i> Validity period
+    </div>
+
+    <div class="pki-info-body-prefix">
+      <div><span>Not before</span></div>
+      <input type="text" readonly
+        value="{{ cert.not_valid_before|date:'Y-m-d H:i:s' }}" />
+    </div>
+    <div class="pki-info-body-prefix">
+      <div><span>Not after</span></div>
+      <input type="text" readonly
+        value="{{ cert.not_valid_after|date:'Y-m-d H:i:s' }}" />
+    </div>
+  </div>
+  {% endif %}
+
+  {% if cert.extensions %}
+  <div class="pki-info-secondary">
+    <div class="pki-info-title">
+      <i class="fas fa-plus-square"></i> Extensions
+    </div>
+
+    {% for name, value, crit in cert.extensions_human %}
+      <div class="pki-info-body-prefix">
+        <div><span{% if crit %} class="font-weight-bold"{% endif %}>{{ name }}</span></div>
+        <input type="text" readonly
+          value="{{ value }}{% if crit %} (critial){% endif %}" />
+      </div>
+    {% endfor %}
+  </div>
+  {% endif %}
+
+  {% if not csr %}
+  <div class="pki-info">
+    <div class="pki-info-title">
+      <i class="fas fa-tag"></i> Serial number
+    </div>
+
+    <div class="pki-info-body">
+      <input type="text" readonly value="{{ cert.serial_number_human }}" />
+      <div><span>Hex</span></div>
+    </div>
+    <div class="pki-info-title">
+      <i class="fas fa-fingerprint"></i> Fingerprint
+    </div>
+
+    <div class="pki-info-body">
+      <input type="text" readonly value="{{ cert.fingerprint_human }}" />
+      <div><span>Hex â—¦ SHA256</span></div>
+    </div>
+  </div>
+  {% endif %}
+
+  <div class="sshkey-content">
+    <div class="sshkey-content-title">
+      <div>
+        <i class="fas fa-certificate"></i>
+        {% if csr %}
+          X.509 Certificate Signing Request
+        {% else %}
+          X.509 Certificate
+        {% endif  %}
+      </div>
+
+      {% if not csr %}
+      <div>
+        <a href="{% url 'cert-download' cert.pk %}">
+          <i class="fas fa-download"></i>
+          {{ request.scheme }}://{{ request.get_host}}{% url 'cert-download' cert.pk %}
+        </a>
+      </div>
+      {% endif  %}
+    </div>
+    <pre>{{ cert.pem }}</pre>
+  </div>
+</li>
diff --git a/app/owlca/templates/owlca/certificate_detail.html b/app/owlca/templates/owlca/certificate_detail.html
new file mode 100644
index 0000000..fdc8696
--- /dev/null
+++ b/app/owlca/templates/owlca/certificate_detail.html
@@ -0,0 +1,41 @@
+{% extends 'owlca/base.html' %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'cert-list' %}">Certificates</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'cert-detail' certificate.pk %}">
+      Certificate
+    </a>
+  </li>
+</ol>
+</nav>
+
+<h2>Certificate</h2>
+<p>
+  Certification authority:
+  <a href="{% url 'ca-detail' certificate.ca.pk %}">
+    <i class="fas fa-stamp"></i>
+    {{ certificate.ca }}
+  </a>
+</p>
+
+{% if certificate.request %}
+<p>
+  Certificate Signing Request:
+  <a href="{% url 'csr-detail' certificate.request.pk %}">
+    {{ certificate.request }}
+  </a>
+</p>
+{% endif %}
+
+<ul class="list-unstyled">
+  {% include 'owlca/cert_item.html' with cert=certificate %}
+</ul>
+
+{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificate_list.html b/app/owlca/templates/owlca/certificate_list.html
similarity index 60%
rename from app/owlca/owlca/templates/owlca/certificate_list.html
rename to app/owlca/templates/owlca/certificate_list.html
index 98b82a8..9049f02 100644
--- a/app/owlca/owlca/templates/owlca/certificate_list.html
+++ b/app/owlca/templates/owlca/certificate_list.html
@@ -1,6 +1,15 @@
 {% extends 'owlca/base.html' %}
 
 {% block content %}
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'cert-list' %}">Certificates</a>
+  </li>
+</ol>
+</nav>
+
 <h2>Certificates</h2>
 {% if certificate_list %}
 <ul>
diff --git a/app/owlca/templates/owlca/certificate_pickup.html b/app/owlca/templates/owlca/certificate_pickup.html
new file mode 100644
index 0000000..0c31aca
--- /dev/null
+++ b/app/owlca/templates/owlca/certificate_pickup.html
@@ -0,0 +1,51 @@
+{% extends 'owlca/base.html' %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'ca-list' %}">Certification Authorities</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'ca-detail' certificatesigningrequest.ca.pk %}">{{ certificatesigningrequest.ca.title }}</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'cert-pickup' certificatesigningrequest.pickup_code %}">
+      Certificate Pickup
+    </a>
+  </li>
+</ol>
+</nav>
+
+<h2>Certificate Pickup</h2>
+
+<p>
+  Certification authority:
+  <a href="{% url 'ca-detail' certificatesigningrequest.ca.pk %}">
+    <i class="fas fa-stamp"></i>
+    {{ certificatesigningrequest.ca }}
+  </a>
+</p>
+<p>
+  Verification status:
+  <strong>{{ certificatesigningrequest.verification_text }}</strong>
+</p>
+
+{% if certificatesigningrequest.verification == 10 %}
+<ul class="list-unstyled">
+    {% for cert in certificatesigningrequest.certificates.all %}
+      {% include 'owlca/cert_item.html' %}
+    {% endfor %}
+</ul>
+{% elif certificatesigningrequest.verification == 0 %}
+<p class="alert alert-info">
+  You certificate will be available at
+  <a class="alert-link" href="{% url 'cert-pickup' certificatesigningrequest.pickup_code %}">
+    {{ request.scheme }}://{{ request.get_host }}{% url 'cert-pickup' certificatesigningrequest.pickup_code %}
+  </a>
+  once it has been approved.
+{% endif %}
+
+{% endblock %}
diff --git a/app/owlca/templates/owlca/certificatesigningrequest_detail.html b/app/owlca/templates/owlca/certificatesigningrequest_detail.html
new file mode 100644
index 0000000..8cd0517
--- /dev/null
+++ b/app/owlca/templates/owlca/certificatesigningrequest_detail.html
@@ -0,0 +1,68 @@
+{% extends 'owlca/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'csr-list' %}">Certificate Signing Requests</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'csr-detail' certificatesigningrequest.pk %}">
+      Request
+    </a>
+  </li>
+</ol>
+</nav>
+
+<h2>Certificate Signing Request</h2>
+
+<p>
+  Certification authority:
+  <a href="{% url 'ca-detail' certificatesigningrequest.ca.pk %}">
+    <i class="fas fa-stamp"></i>
+    {{ certificatesigningrequest.ca }}
+  </a>
+</p>
+<p>
+  Pickup:
+  <a href="{% url 'cert-pickup' certificatesigningrequest.pickup_code %}">
+  {% url 'cert-pickup' certificatesigningrequest.pickup_code %}
+  </a>
+</p>
+<p>
+  Verification status:
+  <strong>{{ certificatesigningrequest.verification_text }}</strong>
+</p>
+
+
+
+<ul class="list-unstyled">
+  {% include 'owlca/cert_item.html' with cert=certificatesigningrequest csr=True %}
+</ul>
+
+<div class="card">
+  <h3 class="card-header">
+    <i class="fas fa-signature"></i>
+    Sign Certificate
+  </h3>
+  <div class="card-body">
+    <form action="" method="post">
+      {% csrf_token %}
+      {{ form|crispy }}
+      <button class="btn btn-primary" type="submit" name="approve">
+        <i class="fas fa-signature"></i>
+        Approve
+      </button>
+      <button class="btn btn-outline-primary" type="submit" name="reject">
+        <i class="fas fa-times"></i>
+        Reject
+      </button>
+    </form>
+  </div>
+</div>
+
+
+{% endblock %}
diff --git a/app/owlca/owlca/templates/owlca/certificatesigningrequest_list.html b/app/owlca/templates/owlca/certificatesigningrequest_list.html
similarity index 62%
rename from app/owlca/owlca/templates/owlca/certificatesigningrequest_list.html
rename to app/owlca/templates/owlca/certificatesigningrequest_list.html
index 7611d43..91a457f 100644
--- a/app/owlca/owlca/templates/owlca/certificatesigningrequest_list.html
+++ b/app/owlca/templates/owlca/certificatesigningrequest_list.html
@@ -1,6 +1,15 @@
 {% extends 'owlca/base.html' %}
 
 {% block content %}
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'csr-list' %}">Certificate Signing Requests</a>
+  </li>
+</ol>
+</nav>
+
 <h2>Certificate Signing Requests</h2>
 {% if certificatesigningrequest_list %}
 <ul>
diff --git a/app/owlca/owlca/templates/owlca/certificatesigningrequest_retrieve.html b/app/owlca/templates/owlca/certificatesigningrequest_retrieve.html
similarity index 100%
rename from app/owlca/owlca/templates/owlca/certificatesigningrequest_retrieve.html
rename to app/owlca/templates/owlca/certificatesigningrequest_retrieve.html
diff --git a/app/owlca/templates/owlca/certificatesignrequest_create.html b/app/owlca/templates/owlca/certificatesignrequest_create.html
new file mode 100644
index 0000000..cd858a1
--- /dev/null
+++ b/app/owlca/templates/owlca/certificatesignrequest_create.html
@@ -0,0 +1,57 @@
+{% extends 'owlca/base.html' %}
+
+{% load crispy_forms_tags %}
+{% block content %}
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'ca-list' %}">Certification Authorities</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'ca-detail' certificationauthority.pk %}">{{ certificationauthority.title }}</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'csr-create' certificationauthority.pk %}">New CSR</a>
+  </li>
+</ol>
+</nav>
+
+<h2>Create Certificate Signing Request</h2>
+
+<p>Request a certificate from
+<a href="{% url 'ca-detail' certificationauthority.pk %}">
+<i class="fas fa-stamp"></i> {{ certificationauthority.title }}</a>:</p>
+<p class="text-muted">{{ certificationauthority.comment }}</p>
+
+<div class="row mt-3">
+  <div class="col-lg-6">
+    <div class="card">
+      <h3 class="card-header">1. Create key and CSR</h3>
+      <div class="card-body">
+        <p>Generate a new private key and create a certificate signing request
+        (CSR). For example, execute the following command and enter the
+        prompted information.</p>
+        
+        <pre>openssl genrsa -aes256 -out key.pem 2048
+openssl req -new -key key.pem -out csr.pem</pre>
+      </div>
+    </div>
+  </div>
+
+  <div class="col-lg-6">
+    <div class="card">
+      <h3 class="card-header">2. Submit CSR</h3>
+      <div class="card-body">
+        <p>Upload the certificate signing request in PEM format.</p>
+        <form action="" method="post" enctype="multipart/form-data">
+          {% csrf_token %}
+          {{ form|crispy }}
+          <button class="btn btn-primary" type="submit">Submit request</button>
+        </form>
+      </div>
+    </div>
+  </div>
+</div>
+
+{% endblock %}
diff --git a/app/owlca/templates/owlca/certificationauthority_create.html b/app/owlca/templates/owlca/certificationauthority_create.html
new file mode 100644
index 0000000..c74fc80
--- /dev/null
+++ b/app/owlca/templates/owlca/certificationauthority_create.html
@@ -0,0 +1,25 @@
+{% extends 'owlca/base.html' %}
+{% load crispy_forms_tags %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'ca-list' %}">Certification Authorities</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'ca-create' %}">Create</a>
+  </li>
+</ol>
+</nav>
+
+<h2>Create new Certification Authority</h2>
+<form action="" method="post">
+  {% csrf_token %}
+  {{ form|crispy }}
+  <button class="btn btn-primary" type="submit">Create</button>
+</form>
+
+{% endblock %}
diff --git a/app/owlca/templates/owlca/certificationauthority_detail.html b/app/owlca/templates/owlca/certificationauthority_detail.html
new file mode 100644
index 0000000..0e5db15
--- /dev/null
+++ b/app/owlca/templates/owlca/certificationauthority_detail.html
@@ -0,0 +1,73 @@
+{% extends 'owlca/base.html' %}
+{% load guardian_tags %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'ca-list' %}">Certification Authorities</a>
+  </li>
+  <li aria-current="page">
+    <a href="{% url 'ca-detail' certificationauthority.pk %}">{{ certificationauthority.title }}</a>
+  </li>
+</ol>
+</nav>
+
+{% get_obj_perms request.user for certificationauthority as "ca_perms" %}
+<h2 class="h-control">
+  <div>
+    <i class="fas fa-stamp"></i>
+    {{ certificationauthority.title }}
+  </div>
+  {% if "request_certificate" in ca_perms %}
+  <a class="btn btn-outline-primary" href="{% url 'csr-create' certificationauthority.pk %}">
+    <i class="fas fa-certificate"></i> Request certificate
+  </a>
+  {% endif %}
+</h2>
+
+<p class="lead">{{ certificationauthority.comment }}</p>
+
+{% if certificationauthority.cert %}
+<ul class="list-unstyled">
+    {% include 'owlca/cert_item.html' with selfsigned=True cert=certificationauthority.cert %}
+<ul>
+{% else %}
+<p>No self-signed certificate!</p>
+{% endif %}
+
+
+{% if "manage_certificationauthority" in ca_perms %}
+<h3>CSR</h3>
+{% if certificationauthority.requests %}
+<ul>
+{% for csr in certificationauthority.requests.all %}
+  <li>
+    <a href="{% url 'csr-detail' csr.pk %}">{{ csr }}</a></li>
+{% endfor %}
+</ul>
+{% else %}
+<p>CA has not issued any certificates</p>
+{% endif %}
+
+
+<h3>Certificates</h3>
+{% if certificationauthority.issued_certificates.count %}
+<ul>
+{% for cert in certificationauthority.issued_certificates.all %}
+  <li>
+    <a href="{% url 'cert-detail' cert.pk %}">
+      {{ cert.serial_number.hex }}
+    </a>
+  </li>
+{% endfor %}
+</ul>
+{% else %}
+<p>CA has not issued any certificates</p>
+{% endif %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/app/owlca/templates/owlca/certificationauthority_list.html b/app/owlca/templates/owlca/certificationauthority_list.html
new file mode 100644
index 0000000..4e86385
--- /dev/null
+++ b/app/owlca/templates/owlca/certificationauthority_list.html
@@ -0,0 +1,54 @@
+{% extends 'owlca/base.html' %}
+
+{% block content %}
+
+<nav class="nav-breadcrumb" aria-label="breadcrumb">
+<ol>
+  <li><a href="{% url 'home' %}">Home</a></li>
+  <li aria-current="page">
+    <a href="{% url 'ca-list' %}">Certification Authorities</a>
+  </li>
+</ol>
+</nav>
+
+<h2 class="h-control">
+  <div>Certification Authorities</div>
+  {% if perms.owlca.add_certificationauthority %}
+  <a class="btn btn-outline-primary" href="{% url 'ca-create' %}">
+    <i class="fas fa-plus"></i>
+  </a>
+  {% endif %}
+</h2>
+
+{% if certificationauthority_list %}
+<ul class="list-unstyled row row-cols-lg-3 row-cols-md-2 row-cols-1">
+{% for ca in certificationauthority_list %}
+  <li class="col">
+    <div class="card my-2">
+       <div class="card-header bg-dark text-light">
+          <h3 class="mb-0">
+            <i class="fas fa-stamp mr-2"></i>
+            <a class="stretched-link text-light" href="{% url 'ca-detail' ca.pk %}">
+              {{ ca }}
+            </a>
+          </h3>
+       </div>
+
+       <div class="card-body">
+         {{ ca.comment }}
+       </div>
+       {% if ca.common_name %}
+         <div class="card-footer">
+           <code class="text-muted"><i class="fas fa-id-card"></i>
+          CN={{ ca.common_name }}</code>
+        </div>
+      {% endif %}
+		</div>
+  </li>
+{% endfor %}
+</ul>
+{% else %}
+<p>There are no Certification Authorities visible to you.</p>
+{% endif %}
+
+{% endblock %}
diff --git a/app/owlca/owlca/tests.py b/app/owlca/tests.py
similarity index 100%
rename from app/owlca/owlca/tests.py
rename to app/owlca/tests.py
diff --git a/app/owlca/owlca/urls.py b/app/owlca/urls.py
similarity index 100%
rename from app/owlca/owlca/urls.py
rename to app/owlca/urls.py
diff --git a/app/owlca/owlca/views.py b/app/owlca/views.py
similarity index 99%
rename from app/owlca/owlca/views.py
rename to app/owlca/views.py
index 2f8006d..2fd99d2 100644
--- a/app/owlca/owlca/views.py
+++ b/app/owlca/views.py
@@ -197,7 +197,7 @@ def csr_create(request, pk):
 
     return render(request,
                   'owlca/certificatesignrequest_create.html',
-                  {'form': form})
+                  {'form': form, 'certificationauthority': ca})
 
 @permission_required("view_certificatesigningrequest",
                      (models.CertificateSigningRequest, "pk", "pk"))
diff --git a/app/pgp/templates/pgp/publickey_list.html b/app/pgp/templates/pgp/publickey_list.html
index a821f63..3068cad 100644
--- a/app/pgp/templates/pgp/publickey_list.html
+++ b/app/pgp/templates/pgp/publickey_list.html
@@ -13,7 +13,7 @@
 
 <h2 class="h-control">
   <div>OpenPGP Public Keys</div>
-  {% if perms.pgp.add_ublickey %}
+  {% if perms.pgp.add_publickey %}
   <a class="btn btn-outline-primary" href="{% url 'publickey-create' %}">
     <i class="fas fa-plus"></i>
   </a>
-- 
GitLab