authentik.crypto.builder

Create self-signed certificates

  1"""Create self-signed certificates"""
  2
  3import datetime
  4import uuid
  5
  6from cryptography import x509
  7from cryptography.hazmat.backends import default_backend
  8from cryptography.hazmat.primitives import hashes, serialization
  9from cryptography.hazmat.primitives.asymmetric import ec, rsa
 10from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
 11from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 12from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
 13from cryptography.x509.oid import NameOID
 14from django.db import models
 15from django.utils.translation import gettext_lazy as _
 16
 17from authentik import authentik_version
 18from authentik.crypto.models import CertificateKeyPair
 19
 20
 21class PrivateKeyAlg(models.TextChoices):
 22    """Algorithm to create private key with"""
 23
 24    RSA = "rsa", _("rsa")
 25    ECDSA = "ecdsa", _("ecdsa")
 26    ED25519 = "ed25519", _("Ed25519")
 27    ED448 = "ed448", _("Ed448")
 28
 29
 30class CertificateBuilder:
 31    """Build self-signed certificates"""
 32
 33    common_name: str
 34    alg: PrivateKeyAlg
 35
 36    def __init__(self, name: str):
 37        self.alg = PrivateKeyAlg.RSA
 38        self.__public_key = None
 39        self.__private_key = None
 40        self.__builder = None
 41        self.__certificate = None
 42        self.common_name = name
 43        self.cert = CertificateKeyPair()
 44
 45    def save(self) -> CertificateKeyPair:
 46        """Save generated certificate as model"""
 47        if not self.__certificate:
 48            raise ValueError("Certificated hasn't been built yet")
 49        self.cert.name = self.common_name
 50        self.cert.certificate_data = self.certificate
 51        self.cert.key_data = self.private_key
 52        self.cert.save()
 53        return self.cert
 54
 55    def generate_private_key(self) -> PrivateKeyTypes:
 56        """Generate private key"""
 57        if self.alg == PrivateKeyAlg.ECDSA:
 58            return ec.generate_private_key(curve=ec.SECP256R1())
 59        if self.alg == PrivateKeyAlg.RSA:
 60            return rsa.generate_private_key(
 61                public_exponent=65537, key_size=4096, backend=default_backend()
 62            )
 63        if self.alg == PrivateKeyAlg.ED25519:
 64            return Ed25519PrivateKey.generate()
 65        if self.alg == PrivateKeyAlg.ED448:
 66            return Ed448PrivateKey.generate()
 67        raise ValueError(f"Invalid alg: {self.alg}")
 68
 69    def build(
 70        self,
 71        validity_days: int = 365,
 72        subject_alt_names: list[str] | None = None,
 73    ):
 74        """Build self-signed certificate"""
 75        one_day = datetime.timedelta(1, 0, 0)
 76        self.__private_key = self.generate_private_key()
 77        self.__public_key = self.__private_key.public_key()
 78        alt_names: list[x509.GeneralName] = []
 79        for alt_name in subject_alt_names or []:
 80            if alt_name.strip() != "":
 81                alt_names.append(x509.DNSName(alt_name))
 82        self.__builder = (
 83            x509.CertificateBuilder()
 84            .subject_name(
 85                x509.Name(
 86                    [
 87                        x509.NameAttribute(NameOID.COMMON_NAME, self.common_name[:64]),
 88                        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "authentik"),
 89                        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Self-signed"),
 90                    ]
 91                )
 92            )
 93            .issuer_name(
 94                x509.Name(
 95                    [
 96                        x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {authentik_version()}"),
 97                    ]
 98                )
 99            )
100            .not_valid_before(datetime.datetime.today() - one_day)
101            .not_valid_after(datetime.datetime.today() + datetime.timedelta(days=validity_days))
102            .serial_number(int(uuid.uuid4()))
103            .public_key(self.__public_key)
104        )
105        if alt_names:
106            self.__builder = self.__builder.add_extension(
107                x509.SubjectAlternativeName(alt_names), critical=True
108            )
109        algo = hashes.SHA256()
110        # EdDSA doesn't take a hash algorithm
111        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
112            algo = None
113        self.__certificate = self.__builder.sign(
114            private_key=self.__private_key,
115            algorithm=algo,
116            backend=default_backend(),
117        )
118
119    @property
120    def private_key(self):
121        """Return private key in PEM format"""
122        format = serialization.PrivateFormat.TraditionalOpenSSL
123        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
124            format = serialization.PrivateFormat.PKCS8
125        return self.__private_key.private_bytes(
126            encoding=serialization.Encoding.PEM,
127            format=format,
128            encryption_algorithm=serialization.NoEncryption(),
129        ).decode("utf-8")
130
131    @property
132    def certificate(self):
133        """Return certificate in PEM format"""
134        return self.__certificate.public_bytes(
135            encoding=serialization.Encoding.PEM,
136        ).decode("utf-8")
class PrivateKeyAlg(django.db.models.enums.TextChoices):
22class PrivateKeyAlg(models.TextChoices):
23    """Algorithm to create private key with"""
24
25    RSA = "rsa", _("rsa")
26    ECDSA = "ecdsa", _("ecdsa")
27    ED25519 = "ed25519", _("Ed25519")
28    ED448 = "ed448", _("Ed448")

Algorithm to create private key with

class CertificateBuilder:
 31class CertificateBuilder:
 32    """Build self-signed certificates"""
 33
 34    common_name: str
 35    alg: PrivateKeyAlg
 36
 37    def __init__(self, name: str):
 38        self.alg = PrivateKeyAlg.RSA
 39        self.__public_key = None
 40        self.__private_key = None
 41        self.__builder = None
 42        self.__certificate = None
 43        self.common_name = name
 44        self.cert = CertificateKeyPair()
 45
 46    def save(self) -> CertificateKeyPair:
 47        """Save generated certificate as model"""
 48        if not self.__certificate:
 49            raise ValueError("Certificated hasn't been built yet")
 50        self.cert.name = self.common_name
 51        self.cert.certificate_data = self.certificate
 52        self.cert.key_data = self.private_key
 53        self.cert.save()
 54        return self.cert
 55
 56    def generate_private_key(self) -> PrivateKeyTypes:
 57        """Generate private key"""
 58        if self.alg == PrivateKeyAlg.ECDSA:
 59            return ec.generate_private_key(curve=ec.SECP256R1())
 60        if self.alg == PrivateKeyAlg.RSA:
 61            return rsa.generate_private_key(
 62                public_exponent=65537, key_size=4096, backend=default_backend()
 63            )
 64        if self.alg == PrivateKeyAlg.ED25519:
 65            return Ed25519PrivateKey.generate()
 66        if self.alg == PrivateKeyAlg.ED448:
 67            return Ed448PrivateKey.generate()
 68        raise ValueError(f"Invalid alg: {self.alg}")
 69
 70    def build(
 71        self,
 72        validity_days: int = 365,
 73        subject_alt_names: list[str] | None = None,
 74    ):
 75        """Build self-signed certificate"""
 76        one_day = datetime.timedelta(1, 0, 0)
 77        self.__private_key = self.generate_private_key()
 78        self.__public_key = self.__private_key.public_key()
 79        alt_names: list[x509.GeneralName] = []
 80        for alt_name in subject_alt_names or []:
 81            if alt_name.strip() != "":
 82                alt_names.append(x509.DNSName(alt_name))
 83        self.__builder = (
 84            x509.CertificateBuilder()
 85            .subject_name(
 86                x509.Name(
 87                    [
 88                        x509.NameAttribute(NameOID.COMMON_NAME, self.common_name[:64]),
 89                        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "authentik"),
 90                        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Self-signed"),
 91                    ]
 92                )
 93            )
 94            .issuer_name(
 95                x509.Name(
 96                    [
 97                        x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {authentik_version()}"),
 98                    ]
 99                )
100            )
101            .not_valid_before(datetime.datetime.today() - one_day)
102            .not_valid_after(datetime.datetime.today() + datetime.timedelta(days=validity_days))
103            .serial_number(int(uuid.uuid4()))
104            .public_key(self.__public_key)
105        )
106        if alt_names:
107            self.__builder = self.__builder.add_extension(
108                x509.SubjectAlternativeName(alt_names), critical=True
109            )
110        algo = hashes.SHA256()
111        # EdDSA doesn't take a hash algorithm
112        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
113            algo = None
114        self.__certificate = self.__builder.sign(
115            private_key=self.__private_key,
116            algorithm=algo,
117            backend=default_backend(),
118        )
119
120    @property
121    def private_key(self):
122        """Return private key in PEM format"""
123        format = serialization.PrivateFormat.TraditionalOpenSSL
124        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
125            format = serialization.PrivateFormat.PKCS8
126        return self.__private_key.private_bytes(
127            encoding=serialization.Encoding.PEM,
128            format=format,
129            encryption_algorithm=serialization.NoEncryption(),
130        ).decode("utf-8")
131
132    @property
133    def certificate(self):
134        """Return certificate in PEM format"""
135        return self.__certificate.public_bytes(
136            encoding=serialization.Encoding.PEM,
137        ).decode("utf-8")

Build self-signed certificates

CertificateBuilder(name: str)
37    def __init__(self, name: str):
38        self.alg = PrivateKeyAlg.RSA
39        self.__public_key = None
40        self.__private_key = None
41        self.__builder = None
42        self.__certificate = None
43        self.common_name = name
44        self.cert = CertificateKeyPair()
common_name: str
cert
def save(self) -> authentik.crypto.models.CertificateKeyPair:
46    def save(self) -> CertificateKeyPair:
47        """Save generated certificate as model"""
48        if not self.__certificate:
49            raise ValueError("Certificated hasn't been built yet")
50        self.cert.name = self.common_name
51        self.cert.certificate_data = self.certificate
52        self.cert.key_data = self.private_key
53        self.cert.save()
54        return self.cert

Save generated certificate as model

def generate_private_key( self) -> cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey | cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey | cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey | cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey | cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey | cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey | cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey | cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey:
56    def generate_private_key(self) -> PrivateKeyTypes:
57        """Generate private key"""
58        if self.alg == PrivateKeyAlg.ECDSA:
59            return ec.generate_private_key(curve=ec.SECP256R1())
60        if self.alg == PrivateKeyAlg.RSA:
61            return rsa.generate_private_key(
62                public_exponent=65537, key_size=4096, backend=default_backend()
63            )
64        if self.alg == PrivateKeyAlg.ED25519:
65            return Ed25519PrivateKey.generate()
66        if self.alg == PrivateKeyAlg.ED448:
67            return Ed448PrivateKey.generate()
68        raise ValueError(f"Invalid alg: {self.alg}")

Generate private key

def build( self, validity_days: int = 365, subject_alt_names: list[str] | None = None):
 70    def build(
 71        self,
 72        validity_days: int = 365,
 73        subject_alt_names: list[str] | None = None,
 74    ):
 75        """Build self-signed certificate"""
 76        one_day = datetime.timedelta(1, 0, 0)
 77        self.__private_key = self.generate_private_key()
 78        self.__public_key = self.__private_key.public_key()
 79        alt_names: list[x509.GeneralName] = []
 80        for alt_name in subject_alt_names or []:
 81            if alt_name.strip() != "":
 82                alt_names.append(x509.DNSName(alt_name))
 83        self.__builder = (
 84            x509.CertificateBuilder()
 85            .subject_name(
 86                x509.Name(
 87                    [
 88                        x509.NameAttribute(NameOID.COMMON_NAME, self.common_name[:64]),
 89                        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "authentik"),
 90                        x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Self-signed"),
 91                    ]
 92                )
 93            )
 94            .issuer_name(
 95                x509.Name(
 96                    [
 97                        x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {authentik_version()}"),
 98                    ]
 99                )
100            )
101            .not_valid_before(datetime.datetime.today() - one_day)
102            .not_valid_after(datetime.datetime.today() + datetime.timedelta(days=validity_days))
103            .serial_number(int(uuid.uuid4()))
104            .public_key(self.__public_key)
105        )
106        if alt_names:
107            self.__builder = self.__builder.add_extension(
108                x509.SubjectAlternativeName(alt_names), critical=True
109            )
110        algo = hashes.SHA256()
111        # EdDSA doesn't take a hash algorithm
112        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
113            algo = None
114        self.__certificate = self.__builder.sign(
115            private_key=self.__private_key,
116            algorithm=algo,
117            backend=default_backend(),
118        )

Build self-signed certificate

private_key
120    @property
121    def private_key(self):
122        """Return private key in PEM format"""
123        format = serialization.PrivateFormat.TraditionalOpenSSL
124        if isinstance(self.__private_key, (Ed25519PrivateKey | Ed448PrivateKey)):
125            format = serialization.PrivateFormat.PKCS8
126        return self.__private_key.private_bytes(
127            encoding=serialization.Encoding.PEM,
128            format=format,
129            encryption_algorithm=serialization.NoEncryption(),
130        ).decode("utf-8")

Return private key in PEM format

certificate
132    @property
133    def certificate(self):
134        """Return certificate in PEM format"""
135        return self.__certificate.public_bytes(
136            encoding=serialization.Encoding.PEM,
137        ).decode("utf-8")

Return certificate in PEM format