💾 Archived View for mozz.us › journal › 2019-08-21_transient_tls_certs.gmi captured on 2022-04-29 at 12:21:09. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
Published 2019-08-21
I've spent a fair amount of time now trying to get my python server to accept transient TLS client certificates, and have come to the unfortunate conclusion that it's simply not possible using the standard library python bindings for OpenSSL.
Python's ssl module provides three verification modes that can be used when setting up an SSL context for a server socket [1]. These mirror the flags that are defined in the underlying OpenSSL library.
1. ssl.CERT_NONE
No certificate is requested from the
client.
2. ssl.CERT_OPTIONAL
A client certificate request is sent
to the client. The client may either
ignore the request or send a
certificate in order perform TLS
client cert authentication. If the
client chooses to send a certificate,
it is verified. Any verification
error immediately aborts the TLS
handshake.
3. ssl.CERT_REQUIRED
This mode provides mandatory TLS
client cert authentication. A client
certificate request is sent to the
client and the client must provide a
valid and trusted certificate.
None of these verification modes are sufficient for using self-signed client certificates. I can optionally request the cert from the client, but if the client provides a cert that isn't recognized by my CA chain, the handshake will immediately abort. What I really need is a modified version of CERT_OPTIONAL that sends a client certificate request, but does not verify the certificate.
The OpenSSL library itself provides a work-around for this by exposing a hook into the certificate verification process using SSL_CTX_set_cert_verify_callback [2]. If was writing my server in C, I could set this method to a dummy function that always returned true. And that would allow me to accept unverified client certificates. Unfortunately this function is not exposed at the python level, for some unknown reason (probably because nobody ever bothered to implement it).
I asked Sean how he accomplished this on his gemini server's demo TLS page [3].
It turns out he is using Lua bindings for LibreSSL, which is a fork of OpenSSL. LibreSSL provides this handy new setting called config_insecure_noverifycert [4] that allows client certificates to be received without verifying them. This setting is essentially a cleaner way to accomplish what I described above of short-circuiting the verify_callback to always return true. I couldn't find docs for LibreSSL, but the code itself is pretty easy to follow along with [5].
So my conclusion is that accepting unverified client certificates is...
There might be other ways forward in python by using an alternate ssl library like pyOpenSSL [6]. But this is getting way too complicated for me. I would rather keep Jetforce as an easy to setup, zero-dependency server and simply say that transient client certificates are a feature that I can't support.
[1] https://docs.python.org/3/library/ssl.html#constants
[2] https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_cert_verify_callback.html