From 44e4f2e56116c3bc66bc87466921784b54604d11 Mon Sep 17 00:00:00 2001
From: Jens L <jens.langhammer@beryju.org>
Date: Sat, 1 Oct 2022 00:06:00 +0200
Subject: [PATCH] crypto: make certificate parsing optional for crypto api
 (#3711)

---
 authentik/crypto/api.py                       | 47 +++++++++++++++++--
 authentik/crypto/tests.py                     | 23 +++++++++
 internal/outpost/ak/crypto.go                 |  2 +-
 schema.yml                                    |  9 ++++
 .../outposts/ServiceConnectionDockerForm.ts   |  2 +
 .../admin/providers/ldap/LDAPProviderForm.ts  |  1 +
 .../providers/oauth2/OAuth2ProviderForm.ts    |  3 +-
 .../providers/proxy/ProxyProviderForm.ts      |  1 +
 .../admin/providers/saml/SAMLProviderForm.ts  |  2 +
 web/src/admin/sources/ldap/LDAPSourceForm.ts  |  1 +
 web/src/admin/sources/saml/SAMLSourceForm.ts  |  1 +
 web/src/admin/tenants/TenantForm.ts           |  1 +
 12 files changed, 88 insertions(+), 5 deletions(-)

diff --git a/authentik/crypto/api.py b/authentik/crypto/api.py
index e71199fee..ff3391645 100644
--- a/authentik/crypto/api.py
+++ b/authentik/crypto/api.py
@@ -1,4 +1,5 @@
 """Crypto API Views"""
+from datetime import datetime
 from typing import Optional
 
 from cryptography.hazmat.backends import default_backend
@@ -13,7 +14,7 @@ from drf_spectacular.types import OpenApiTypes
 from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
 from rest_framework.decorators import action
 from rest_framework.exceptions import ValidationError
-from rest_framework.fields import CharField, DateTimeField, IntegerField, SerializerMethodField
+from rest_framework.fields import CharField, IntegerField, SerializerMethodField
 from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.serializers import ModelSerializer
@@ -34,7 +35,10 @@ LOGGER = get_logger()
 class CertificateKeyPairSerializer(ModelSerializer):
     """CertificateKeyPair Serializer"""
 
-    cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True)
+    fingerprint_sha256 = SerializerMethodField()
+    fingerprint_sha1 = SerializerMethodField()
+
+    cert_expiry = SerializerMethodField()
     cert_subject = SerializerMethodField()
     private_key_available = SerializerMethodField()
     private_key_type = SerializerMethodField()
@@ -42,8 +46,35 @@ class CertificateKeyPairSerializer(ModelSerializer):
     certificate_download_url = SerializerMethodField()
     private_key_download_url = SerializerMethodField()
 
-    def get_cert_subject(self, instance: CertificateKeyPair) -> str:
+    @property
+    def _should_include_details(self) -> bool:
+        request: Request = self.context.get("request", None)
+        if not request:
+            return True
+        return str(request.query_params.get("include_details", "true")).lower() == "true"
+
+    def get_fingerprint_sha256(self, instance: CertificateKeyPair) -> Optional[str]:
+        "Get certificate Hash (SHA256)"
+        if not self._should_include_details:
+            return None
+        return instance.fingerprint_sha256
+
+    def get_fingerprint_sha1(self, instance: CertificateKeyPair) -> Optional[str]:
+        "Get certificate Hash (SHA1)"
+        if not self._should_include_details:
+            return None
+        return instance.fingerprint_sha1
+
+    def get_cert_expiry(self, instance: CertificateKeyPair) -> Optional[datetime]:
+        "Get certificate expiry"
+        if not self._should_include_details:
+            return None
+        return instance.certificate.not_valid_after
+
+    def get_cert_subject(self, instance: CertificateKeyPair) -> Optional[str]:
         """Get certificate subject as full rfc4514"""
+        if not self._should_include_details:
+            return None
         return instance.certificate.subject.rfc4514_string()
 
     def get_private_key_available(self, instance: CertificateKeyPair) -> bool:
@@ -52,6 +83,8 @@ class CertificateKeyPairSerializer(ModelSerializer):
 
     def get_private_key_type(self, instance: CertificateKeyPair) -> Optional[str]:
         """Get the private key's type, if set"""
+        if not self._should_include_details:
+            return None
         key = instance.private_key
         if key:
             return key.__class__.__name__.replace("_", "").lower().replace("privatekey", "")
@@ -171,6 +204,14 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
     ordering = ["name"]
     search_fields = ["name"]
 
+    @extend_schema(
+        parameters=[
+            OpenApiParameter("include_details", bool, default=True),
+        ]
+    )
+    def list(self, request, *args, **kwargs):
+        return super().list(request, *args, **kwargs)
+
     @permission_required(None, ["authentik_crypto.add_certificatekeypair"])
     @extend_schema(
         request=CertificateGenerationSerializer(),
diff --git a/authentik/crypto/tests.py b/authentik/crypto/tests.py
index 04ff429a3..3231f277b 100644
--- a/authentik/crypto/tests.py
+++ b/authentik/crypto/tests.py
@@ -1,5 +1,6 @@
 """Crypto tests"""
 import datetime
+from json import loads
 from os import makedirs
 from tempfile import TemporaryDirectory
 
@@ -86,13 +87,35 @@ class TestCrypto(APITestCase):
 
     def test_list(self):
         """Test API List"""
+        cert = create_test_cert()
         self.client.force_login(create_test_admin_user())
         response = self.client.get(
             reverse(
                 "authentik_api:certificatekeypair-list",
             )
+            + f"?name={cert.name}"
         )
         self.assertEqual(200, response.status_code)
+        body = loads(response.content.decode())
+        api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
+        self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
+        self.assertEqual(api_cert["fingerprint_sha256"], cert.fingerprint_sha256)
+
+    def test_list_without_details(self):
+        """Test API List (no details)"""
+        cert = create_test_cert()
+        self.client.force_login(create_test_admin_user())
+        response = self.client.get(
+            reverse(
+                "authentik_api:certificatekeypair-list",
+            )
+            + f"?name={cert.name}&include_details=false"
+        )
+        self.assertEqual(200, response.status_code)
+        body = loads(response.content.decode())
+        api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
+        self.assertEqual(api_cert["fingerprint_sha1"], None)
+        self.assertEqual(api_cert["fingerprint_sha256"], None)
 
     def test_certificate_download(self):
         """Test certificate export (download)"""
diff --git a/internal/outpost/ak/crypto.go b/internal/outpost/ak/crypto.go
index 16042d7e1..d6d988e23 100644
--- a/internal/outpost/ak/crypto.go
+++ b/internal/outpost/ak/crypto.go
@@ -48,7 +48,7 @@ func (cs *CryptoStore) getFingerprint(uuid string) string {
 		cs.log.WithField("uuid", uuid).WithError(err).Warning("Failed to fetch certificate's fingerprint")
 		return ""
 	}
-	return kp.FingerprintSha256
+	return kp.GetFingerprintSha256()
 }
 
 func (cs *CryptoStore) Fetch(uuid string) error {
diff --git a/schema.yml b/schema.yml
index fece7d2fd..9e495ae95 100644
--- a/schema.yml
+++ b/schema.yml
@@ -4923,6 +4923,11 @@ paths:
         schema:
           type: boolean
         description: Only return certificate-key pairs with keys
+      - in: query
+        name: include_details
+        schema:
+          type: boolean
+          default: true
       - in: query
         name: managed
         schema:
@@ -25924,16 +25929,20 @@ components:
           type: string
         fingerprint_sha256:
           type: string
+          nullable: true
           readOnly: true
         fingerprint_sha1:
           type: string
+          nullable: true
           readOnly: true
         cert_expiry:
           type: string
           format: date-time
+          nullable: true
           readOnly: true
         cert_subject:
           type: string
+          nullable: true
           readOnly: true
         private_key_available:
           type: boolean
diff --git a/web/src/admin/outposts/ServiceConnectionDockerForm.ts b/web/src/admin/outposts/ServiceConnectionDockerForm.ts
index 860339fe3..cfaf32237 100644
--- a/web/src/admin/outposts/ServiceConnectionDockerForm.ts
+++ b/web/src/admin/outposts/ServiceConnectionDockerForm.ts
@@ -87,6 +87,7 @@ export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnecti
                         new CryptoApi(DEFAULT_CONFIG)
                             .cryptoCertificatekeypairsList({
                                 ordering: "name",
+                                includeDetails: false,
                             })
                             .then((certs) => {
                                 return certs.results.map((cert) => {
@@ -122,6 +123,7 @@ export class ServiceConnectionDockerForm extends ModelForm<DockerServiceConnecti
                         new CryptoApi(DEFAULT_CONFIG)
                             .cryptoCertificatekeypairsList({
                                 ordering: "name",
+                                includeDetails: false,
                             })
                             .then((certs) => {
                                 return certs.results.map((cert) => {
diff --git a/web/src/admin/providers/ldap/LDAPProviderForm.ts b/web/src/admin/providers/ldap/LDAPProviderForm.ts
index 106de1349..21ac5f7b9 100644
--- a/web/src/admin/providers/ldap/LDAPProviderForm.ts
+++ b/web/src/admin/providers/ldap/LDAPProviderForm.ts
@@ -182,6 +182,7 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
                                         hasKey: true,
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts
index 92f2e5ea0..879be1798 100644
--- a/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts
+++ b/web/src/admin/providers/oauth2/OAuth2ProviderForm.ts
@@ -189,6 +189,7 @@ ${this.instance?.redirectUris}</textarea
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
                                         hasKey: true,
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
@@ -200,7 +201,7 @@ ${this.instance?.redirectUris}</textarea
                                                 value=${ifDefined(key.pk)}
                                                 ?selected=${selected}
                                             >
-                                                ${key.name} (${key.privateKeyType?.toUpperCase()})
+                                                ${key.name}
                                             </option>`;
                                         });
                                     }),
diff --git a/web/src/admin/providers/proxy/ProxyProviderForm.ts b/web/src/admin/providers/proxy/ProxyProviderForm.ts
index 71acf99aa..a4d6af167 100644
--- a/web/src/admin/providers/proxy/ProxyProviderForm.ts
+++ b/web/src/admin/providers/proxy/ProxyProviderForm.ts
@@ -346,6 +346,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
                                         hasKey: true,
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
diff --git a/web/src/admin/providers/saml/SAMLProviderForm.ts b/web/src/admin/providers/saml/SAMLProviderForm.ts
index 5d34d3b3d..756b38835 100644
--- a/web/src/admin/providers/saml/SAMLProviderForm.ts
+++ b/web/src/admin/providers/saml/SAMLProviderForm.ts
@@ -158,6 +158,7 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
                                         hasKey: true,
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
@@ -196,6 +197,7 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
                                 new CryptoApi(DEFAULT_CONFIG)
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
diff --git a/web/src/admin/sources/ldap/LDAPSourceForm.ts b/web/src/admin/sources/ldap/LDAPSourceForm.ts
index 5509ec339..30970b244 100644
--- a/web/src/admin/sources/ldap/LDAPSourceForm.ts
+++ b/web/src/admin/sources/ldap/LDAPSourceForm.ts
@@ -160,6 +160,7 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
                                 new CryptoApi(DEFAULT_CONFIG)
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
diff --git a/web/src/admin/sources/saml/SAMLSourceForm.ts b/web/src/admin/sources/saml/SAMLSourceForm.ts
index 9f1ebfcb7..431a0cebf 100644
--- a/web/src/admin/sources/saml/SAMLSourceForm.ts
+++ b/web/src/admin/sources/saml/SAMLSourceForm.ts
@@ -151,6 +151,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
                                 new CryptoApi(DEFAULT_CONFIG)
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {
diff --git a/web/src/admin/tenants/TenantForm.ts b/web/src/admin/tenants/TenantForm.ts
index 1cfb5ac0b..f3aa0dead 100644
--- a/web/src/admin/tenants/TenantForm.ts
+++ b/web/src/admin/tenants/TenantForm.ts
@@ -366,6 +366,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
                                     .cryptoCertificatekeypairsList({
                                         ordering: "name",
                                         hasKey: true,
+                                        includeDetails: false,
                                     })
                                     .then((keys) => {
                                         return keys.results.map((key) => {