In my day job I spend a lot of time dealing with sketchy TLS setups. The error messages provided by OpenSSL tend to be quite opaque – fortunately there’s tools to help you.
In the concrete case I needed to send a client certificate and the connections failed with
SSLV3_ALERT_CERTIFICATE_UNKNOWN. I have already disabled all client-side verification, but it kept happening, so I suspected it’s a server-side problem. A server that I don’t control.
To verify, I reached out to my friend and tireless
ssl maintainer Christian Heimes and he taught me two new-ish debugging techniques for cases like this.
First, it’s surprisingly easy to debug SSL/TLS handshake errors in Wireshark. Capture the failing session and then filter out the packages that went between you and the TLS peer.
For instance if your peer’s IP is
188.8.131.52, you’d add filter like this:
ip.addr == 184.108.40.206 and tls
And this is how I verified my suspicion:
As suspected, the server was refusing my client certificate. But now I could send it to the other party to help them debug it on their end.
While my particular troubles ended here, let’s look further what else you can do! For instance, for troubleshooting handshakes this is all good, sometimes you need to look into the encrypted traffic and that’s where it gets interesting.
Bonus: Peeking Into Encrypted TLS Traffic
While encryption is there to exactly not do that, you actually can snoop into encrypted traffic.
This is well-described in Everything curl for curl, but it works with every Go program and Python script too:
Set the well-known environment variable
SSLKEYLOGFILE to a file name and software that supports it will write the connection-specific secret keys into it.
Then you can use that file to decrypt captured TLS traffic by loading it in Wireshark:
Preferences → Protocols → TLS → (Pre)-Master Secret log filename
SSL added and removed here! 😀
ssl.SSLContext has a Message Callback
In Python 3.8 and later, you can set a super secret callback attribute on
_msg_callback. This is something the
ssl maintainers added for themselves as a private debugging tool. Thus you can absolutely not rely on its future existence and behavior. However, when in a pickle, it can be very useful.
It gets called with 6 arguments on each TLS message:
- The connection object (either
- The direction (
- The protocol message version (
ssl.TLSVersion). This is not the version of the current connection and varies depending on the message type.
- The content type which is a private
- The message type that can be – depending on the type of the message – one of the three private enums:
- The decrypted payload as bytes.
You can use that to either log them out or to put a
pdb.set_trace() into it. This kind of introspection is groundbreaking compared to what we used to have!