Skip to content

Instantly share code, notes, and snippets.

@nebulak
Last active February 2, 2024 20:44
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nebulak/6d865ddd768fb905a562d6026cdd508a to your computer and use it in GitHub Desktop.
Save nebulak/6d865ddd768fb905a562d6026cdd508a to your computer and use it in GitHub Desktop.
python mutual tls for client certificate validation
# sources:
# https://kb.op5.com/pages/viewpage.action?pageId=19073746#sthash.9gTMRKM1.dpbs
# https://stackoverflow.com/a/26093147
# https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html
# additional ressource: https://gist.github.com/Soarez/9688998
# TODO: renew certificates and ca, add capability for authentication to client cert
# TODO ressources: https://gist.github.com/richieforeman/3166387

HKP_PATH=""
CLIENT_PATH=""

# Create CA-key and CA-cert
openssl genrsa -out FlexCA.key 2048
openssl req -x509 -new -nodes -key FlexCA.key -sha256 -days 1024 -out FlexCA.pem -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=example.com"

# Create client key & cert signing request
openssl genrsa -out FlexClient.key 2048
openssl req -new -key FlexClient.key -out FlexClient.csr -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=example.com"

# Generate Client certificate
openssl x509 -req -in FlexClient.csr -CA FlexCA.pem -CAkey FlexCA.key -CAcreateserial -out FlexClient.pem -days 1024 -sha256

# //TODO: restart hkp-server
# //TODO: for readme: how to create ca & client-certs:
# // ---> https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html
# source: https://www.ajg.id.au/2018/01/01/mutual-tls-with-python-flask-and-werkzeug/
from flask import Flask, render_template, request
import werkzeug.serving
import ssl
import OpenSSL
class PeerCertWSGIRequestHandler( werkzeug.serving.WSGIRequestHandler ):
"""
We subclass this class so that we can gain access to the connection
property. self.connection is the underlying client socket. When a TLS
connection is established, the underlying socket is an instance of
SSLSocket, which in turn exposes the getpeercert() method.
The output from that method is what we want to make available elsewhere
in the application.
"""
def make_environ(self):
"""
The superclass method develops the environ hash that eventually
forms part of the Flask request object.
We allow the superclass method to run first, then we insert the
peer certificate into the hash. That exposes it to us later in
the request variable that Flask provides
"""
environ = super(PeerCertWSGIRequestHandler, self).make_environ()
x509_binary = self.connection.getpeercert(True)
x509 = OpenSSL.crypto.load_certificate( OpenSSL.crypto.FILETYPE_ASN1, x509_binary )
environ['peercert'] = x509
return environ
app = Flask(__name__)
# to establish an SSL socket we need the private key and certificate that
# we want to serve to users.
#
# app_key_password here is None, because the key isn't password protected,
# but if yours is protected here's where you place it.
app_key = './app.key'
app_key_password = None
app_cert = './app.crt'
# in order to verify client certificates we need the certificate of the
# CA that issued the client's certificate. In this example I have a
# single certificate, but this could also be a bundle file.
ca_cert = './ca.crt'
# create_default_context establishes a new SSLContext object that
# aligns with the purpose we provide as an argument. Here we provide
# Purpose.CLIENT_AUTH, so the SSLContext is set up to handle validation
# of client certificates.
ssl_context = ssl.create_default_context( purpose=ssl.Purpose.CLIENT_AUTH,
cafile=ca_cert )
# load in the certificate and private key for our server to provide to clients.
# force the client to provide a certificate.
ssl_context.load_cert_chain( certfile=app_cert, keyfile=app_key, password=app_key_password )
ssl_context.verify_mode = ssl.CERT_REQUIRED
# now we get into the regular Flask details, except we're passing in the peer certificate
# as a variable to the template.
@app.route('/')
def hello_world():
return render_template('helloworld.html', client_cert=request.environ['peercert'])
# start our webserver!
if __name__ == "__main__":
app.run( ssl_context=ssl_context, request_handler=PeerCertWSGIRequestHandler )
@vvipVikash
Copy link

How i decode OpenSSL.crypto.x509 object inside template??

@joaopedrolourencoaffonso

In these lines:

# force the client to provide a certificate.
ssl_context.load_cert_chain( certfile=app_cert, keyfile=app_key, password=app_key_password )
ssl_context.verify_mode = ssl.CERT_REQUIRED

This means that python checks the client signature, i.e., it checks if the client actually has the private key in question. Right?

@nebulak
Copy link
Author

nebulak commented Dec 11, 2021

Yes, that's right. You may also check the source which contains a blog post with explanations here:
https://www.ajg.id.au/2018/01/01/mutual-tls-with-python-flask-and-werkzeug/

@joaopedrolourencoaffonso

WOW! this was everything I needed! Thanks!

@kot9pko
Copy link

kot9pko commented Jul 13, 2022

@icsboyx
Copy link

icsboyx commented Nov 11, 2022

Hi, sorry for boring you, is it possible to print out client certificate to the web page?

        We subclass this class so that we can gain access to the connection
        property. self.connection is the underlying client socket. When a TLS
        connection is established, the underlying socket is an instance of
        SSLSocket, which in turn exposes the getpeercert() method.
        The output from that method is what we want to make available elsewhere
        in the application.
 

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment