This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/authentik/crypto/models.py

103 lines
3.6 KiB
Python

"""authentik crypto models"""
from binascii import hexlify
from hashlib import md5
from typing import Optional
from uuid import uuid4
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes, PublicKeyTypes
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.blueprints.models import ManagedModel
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
LOGGER = get_logger()
class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
is set, otherwise it can be used to verify remote data."""
kp_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField(unique=True)
certificate_data = models.TextField(help_text=_("PEM-encoded Certificate data"))
key_data = models.TextField(
help_text=_(
"Optional Private Key. If this is set, you can use this keypair for encryption."
),
blank=True,
default="",
)
_cert: Optional[Certificate] = None
_private_key: Optional[PrivateKeyTypes] = None
_public_key: Optional[PublicKeyTypes] = None
@property
def serializer(self) -> Serializer:
from authentik.crypto.api import CertificateKeyPairSerializer
return CertificateKeyPairSerializer
@property
def certificate(self) -> Certificate:
"""Get python cryptography Certificate instance"""
if not self._cert:
self._cert = load_pem_x509_certificate(
self.certificate_data.encode("utf-8"), default_backend()
)
return self._cert
@property
def public_key(self) -> Optional[PublicKeyTypes]:
"""Get public key of the private key"""
if not self._public_key:
self._public_key = self.private_key.public_key()
return self._public_key
@property
def private_key(
self,
) -> Optional[PrivateKeyTypes]:
"""Get python cryptography PrivateKey instance"""
if not self._private_key and self.key_data != "":
try:
self._private_key = load_pem_private_key(
str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])),
password=None,
backend=default_backend(),
)
except ValueError as exc:
LOGGER.warning(exc)
return None
return self._private_key
@property
def fingerprint_sha256(self) -> str:
"""Get SHA256 Fingerprint of certificate_data"""
return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode("utf-8")
@property
def fingerprint_sha1(self) -> str:
"""Get SHA1 Fingerprint of certificate_data"""
return hexlify(self.certificate.fingerprint(hashes.SHA1()), ":").decode("utf-8") # nosec
@property
def kid(self):
"""Get Key ID used for JWKS"""
return md5(self.key_data.encode("utf-8")).hexdigest() if self.key_data else "" # nosec
def __str__(self) -> str:
return f"Certificate-Key Pair {self.name}"
class Meta:
verbose_name = _("Certificate-Key Pair")
verbose_name_plural = _("Certificate-Key Pairs")