-- Leo's gemini proxy

-- Connecting to mozz.us:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en

Transient TLS client certificates - Part 2


Published 2020-05-06


There was some recent discussion on the gemini mailing list that lead to me re-think my whole authentication scheme for Astrobotany. Here's the new plan that I outlined, which uses certificate pinning instead of issuing trusted client certificates:


The client requests "gemini://astrobotany.mozz.us/" and receives a "62 AUTHORISED CERTIFICATE REQUIRED" response.

The client generates a self-signed certificate and makes a second request to the same endpoint. This certificate generation could be done through the gemini client software itself, or the user could provide their own cert.

The server accepts the self-signed client certificate and saves a hashed fingerprint of the peer cert. This fingerprint is now associated with that user and represents a unique UUID that nobody else can spoof.

We kick the can down the road concerning the user needing to update or re-associate their client certificate. We hope that they set an expiration date far enough in the future for this to never matter.


I have decided that I would really like to try this out. Unfortunately I'm running into some... hurdles.


Untrusted client certificates


The first hurdle is that the python standard library OpenSSL bindings don't support untrusted client certificates. This is outlined in a previous gemini post.


=>/journal/2019-08-21_transient_tls_certs.gmi


pyOpenSSL


Reaching outside the standard library, the only significant python TLS library is pyOpenSSL.


=>https://www.pyopenssl.org/


I started looking into pyOpenSSL and it turns out to have a long, interesting history behind it. It's an old library (dating back to python 2.1). It evolved alongside python in order to address ongoing "limitations" in the standard library's wrapper. Over time, pretty much every major python networking library has reached for pyOpenSSL because they needed *something* that wasn't available elsewhere. requests/urllib3 needed SNI support for python 2, twisted needed the memory BIO interface. Even now, the python standard library SSL still doesn't support OSCP stapling and has buggy TLS v1.3 support.


=>https://github.com/psf/requests/issues/749

=>https://github.com/openssl/openssl/issues/7967

=>https://www.python.org/dev/peps/pep-0546/


Current development of pyOpenSSL appears to be iffy. They can't exactly abandon it because so many bedrock python projects depend on the bindings. But there's no cooperate backing and no one really wants to maintain it anymore.


> Yeah, we'd love to kill pyOpenSSL, because the code is grody and sad-making and the APIs aren't very good. But it's still maintained and supported, so I don't have an opinion on this.


=>https://github.com/python-trio/trio/issues/1145#issuecomment-513041312


cryptography


pyOpenSSL doesn't interface to libssl C library directly. A while ago, it was updated to use the `pyca/cryptography` backend, which in turn creates CFFI bindings around libssl.


=>https://cffi.readthedocs.io/en/latest/

=>https://cryptography.io/en/latest/


This transitive dependency on pyca/cryptography is a bonus as far as I'm concerned. Cryptography is a rock-solid python package that exposes primitives for working with x509 certificates. For example, I could use it to generate ad-hoc server certificates entirely in-memory, without needing to shell out to the openssl command-line tool like I do now.


asyncio


On the surface, pyOpenSSL appears to be API compatible with the standard library SSL module. They both use objects called "Sessions" and "Contexts". Most of their methods even share the same names. pyOpenSSL might have some extra bells and whistles, but surely, I figured, it would be a simple drop-in-replacement in my code.


It was not a simple drop-in-replacement.


Asyncio (the standard library TCP server framework that jetforce uses) is very complex. It uses an ssl.SSLContext method called ```wrapbio()`` that was introduced in Python 3.5.


=>http://blog.povilasb.com/posts/python-ssl-memorybio-usage/


Memory BIOs are a low-level concept that I don't fully grasp yet. What I do know is that they're often used in async libraries, and they have something to do with efficiency of not needing to call select/poll on sockets.


=>https://www.python.org/dev/peps/pep-0546/


PyOpenSSL also supports memory BIOs. In fact, it has supported them for a much longer time. But it doesn't use the `context.wrapbio()` method; it has its own incompatible interface.


=>https://www.pyopenssl.org/en/stable/api/ssl.html?highlight=bio#OpenSSL.SSL.Connection.bio_read


The asyncio internals are a rats nest of winding, complicated code with no hooks for implementing any custom TLS behavior. The only way forward with asyncio + pyOpenSSL would be to write my own custom wrapper around the pyOpenSSL context to make it look & behave just like the ssl.SSLContext equivalent. I found some tickets where other people have tried this, and the road looks scary and full of dragons. Definitely not something that want to attempt.


curio and trio


So if I can't use asyncio, what other options do I have for async TCP frameworks? There are two general purpose async libraries that I have found.


=>https://github.com/dabeaz/curio

=>https://github.com/python-trio/trio


Both are delightful, well-written libraries and I would have no qualms using either of them for jetforce.


Curio does not support pyOpenSSL.

Trio is discussing supporting pyOpenSSL at the time of the post, but the implementation is still in-progress.


=>https://github.com/python-trio/trio/issues/1145


Dropping async


Next up on my list is to look into what TCP server frameworks *do* work well with pyOpenSSL. This will likely mean dropping async and moving to thread pooling or something else.


Oh well. I was never really using async to its full potential anyway. I have a pattern of trying to get fancy with concurrency, and it always ends up coming back to bite me in the ass.


Final thoughts


I'll end with a link to the source file for trio's SSL module.


=>https://github.com/python-trio/trio/blob/master/trio/_ssl.py


This code is a work of art. I aspire to someday write code comments as clear and meaningful as this.

-- Response ended

-- Page fetched on Sat Apr 27 18:40:25 2024