Package: x509middleware
x509middleware: Working with (client) certificatesThis Python package contains middleware classes for working with
certificates. Currently it only contains support for extracting client
certificates from the ASGI scope and from headers set by a reverse
proxy.Note: The package is rather small but solves an immediate need for some
projectes of mine while possibly being generally helpful to other
people. As I chose a rather generic package name, patches or pull
requests with support for other protocols than ASGI as well as
additional functionallty are very welcome.Installation$pipinstallx509middlewareRequirementsPython v3.3+ (only 3.6+ is beingtested).Theasn1cryptopackage.Contentsx509middleware.asgi.ClientCertificateMiddlewareThis middlware class will try to find and parse a client certificate for
a ASGI http or websocket request. It supports the proposedtls extensionto the ASGI
protocol as well as pulling the certificate from a header supplied by a
reverse proxy your app is deployed behind.It will set the keyscope['client_cert']to aasn1crypto.x509.Certificateobject orNoneif it can't find or parse
a certificate.Parametersapp(async callable)– ...use_tls_extension(bool=True)– ...proxy_header(str=None)– Name of the header to try parsing as a
client certificate orNoneto disable proxy header support. The
header value can be line-folded, URL/percent-encoded or base64 encoded
as well as in PEM or DER format. The correct way to decode will be
detected automatically.Configuring your reverse proxyYou will need to choose a header name when configuring your reverse proxy
as well as the middlware class. Commonly used names are:CLIENT-CERT–IETF Draft.X-SSL-CLIENT-CERTX-CLIENT-CERTX-CLIENT-CERTIFICATEX-CLIENT-CRTX-SSL-CERTSSLClientCertb64– F5 products seem to use or suggest this.Apache / mod_proxy & mod_headersProxyPass/http://127.0.0.1:8000/RequestHeaderunsetCLIENT-CERTRequestHeadersetCLIENT-CERT"%{SSL_CLIENT_CERT}s"NGINXlocation/{proxy_passhttp://127.0.0.1:8000/;proxy_set_headerCLIENT-CERT$ssl_client_escaped_cert;}HAProxySupport for passing the certificate as a DER blob is available starting
version 1.6-dev1. Since DER is a binary format this needs to be base64
encoded.backend back
  mode http
  server app 127.0.0.1:8000
  http-request set-header CLIENT-CERT %[ssl_c_der,base64]Using it in Starlettefromstarlette.applicationsimportStarlettefromstarlette.configimportConfigfromstarlette.middlewareimportMiddlewarefromstarlette.responsesimportResponsefromstarlette.routingimportRoutefromx509middleware.asgiimportClientCertificateMiddlewareasyncdefhello_common_name(request):client_cert=request.scope.get('client_cert')ifclient_cert:cn=client_cert.subject.native['common_name']else:cn='unknown'returnResponse(f'Hello,{cn}!',media_type='text/plain')config=Config('.env')app=Starlette(routes=[Route('/',hello_common_name)],middleware=[Middleware(ClientCertificateMiddleware,proxy_header=config('CLIENT_CERT_HEADER',default=None))])Using it directlyfromosimportenvironfromx509middleware.asgiimportClientCertificateMiddlewareasyncdefhello_common_name(scope,receive,send):client_cert=scope.get('client_cert')ifclient_cert:cn=client_cert.subject.native['common_name']else:cn='unknown'awaitsend({'type':'http.response.start','status':200,'headers':[(b'content-type',b'text/plain; charset=utf-8'),]})awaitsend({'type':'http.response.body','body':f'Hello,{cn}!'.encode('utf8'),})app=ClientCertificateMiddleware(hello_common_name,proxy_header=environ.get('CLIENT_CERT_HEADER'),)