django-cats/cats/authentication.py
Jan Dittberner 3f4e005cf3 Implement client certificate authentication
- add a cats.authentication.ClientCertificateBackend authentication
  backend implementation that extracts the used fields from a client
  certificate
- configure the AUTHENTICATION_BACKENDS setting to use the
  ClientCertificateBackend
- add cryptography dependency to parse certificate data
- add gunicorn as production dependency
- add a development configuration for Gunicorn
- document how to pass client certificate information via nginx reverse
  proxy
- add a certificate_login view and a basic home_page view and add
  corresponding URL patterns
- ignore PEM encoded files and temporary gunicorn files
2024-09-20 12:42:44 +02:00

42 lines
1.7 KiB
Python

from urllib.parse import unquote_to_bytes
from cryptography import x509
from cryptography.x509 import load_pem_x509_certificate, SubjectAlternativeName
from django.contrib.auth.backends import BaseBackend
class ClientCertificateUser:
def __init__(self, serial_number, issuer_name, subject_name, emails):
self.serial_number = serial_number
self.issuer_name = issuer_name
self.subject_name = subject_name
self.emails = emails
def __str__(self):
return (f"<ClientCertificateUser[serial_number={self.serial_number}, issuer_name={self.issuer_name},"
f" subject_name={self.subject_name}, emails={self.emails}]>")
class ClientCertificateBackend(BaseBackend):
def authenticate(self, request, encoded_certificate=None):
"""
encoded_certificate is expected to be a URL encoded PEM certificate as sent by nginx when using the
$ssl_client_escaped_cert
"""
if encoded_certificate is None:
return None
pem_data = unquote_to_bytes(encoded_certificate)
certificate_data = load_pem_x509_certificate(pem_data)
subject_alternative_name = certificate_data.extensions.get_extension_for_class(SubjectAlternativeName)
emails = subject_alternative_name.value.get_values_for_type(x509.RFC822Name)
serial_number = f"{certificate_data.serial_number:X}"
if len(serial_number) % 2 == 1:
serial_number = "0" + serial_number
issuer_name = certificate_data.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
subject_name = certificate_data.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value
return ClientCertificateUser(serial_number, issuer_name, subject_name, emails)