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
RSA =
PrivateKeyAlg.RSA
ECDSA =
PrivateKeyAlg.ECDSA
ED25519 =
PrivateKeyAlg.ED25519
ED448 =
PrivateKeyAlg.ED448
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
alg: PrivateKeyAlg
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