In case anyone is concerned, this is not a security vulnerability in SwiftNIO, TLS, or anything else. This is merely a debugging feature I wanted to make more obvious.
I’ve been somewhat fascinated with SwiftNIO lately. There’s something magical about getting code to work at the wire protocol level, and SwiftNIO makes it easy to do it safely and efficiently. I’ll save talking about it in much depth here, as I’m still very much figuring that out as I go, but I wanted to share a tip I had to figure out that I couldn’t easily find an answer for without some creative searching. Note: This post will be generally useless to you unless you’re already using
I came across Project Gemini recently, a simple hypertext protocol without all the tons and tons of baggage that the web has built up over the last few decades, and decided to give it a spin with SwiftNIO. Within a day, I was able to get a basic client implementation up and running, but doing so required some debugging.
Much like HTTP, Gemini runs with a request/response model, and I ran into a problem where I wasn’t sure if the server was receiving my requests, sending a response, or if something in my SwiftNIO code was not working properly. Normally when doing something at the wire protocol level, you would use a tool like Wireshark, but the trick is that Gemini requires ALL traffic to be TLS-encrypted. This means that, on its own, Wireshark can’t break the encryption (which is a very good thing!).
Fortunately, I’m not the only one who has needed to break TLS encryption for debugging reasons. TLS encryption requires both the client and sender to agree on encryption secrets, and if you have those,, Wireshark can decrypt the data. Many tools like
curl and some browsers support writing these secrets to a file using the environment variable
SSLKEYLOGFILE, but I don’t think SwiftNIO’s TLS library supports this by default.
Instead, SwiftNIO exposes an API to access the important secrets as data. When setting up your
TLSConfiguration.forClient(), you can pass a closure to
keyLogCallback. This will get called periodically as part of TLS negotiations, and is equivalnet to the SSKEYLOGFILE output you’d get from
curl and friends. Just write this to a file somewhere using whatever API you like, e.g.
FileManager().createFile(...) if running on a Mac and make sure you’re appending and not overwriting. Make sure not to ship this!
Once this file is written somewhere, open Wireshark and go to Preferences. Open the Protocols section and scroll all the way down to TLS. At the bottom of that pane will be a textbox that says something like
(Pre)-Master-Secret log filename, and here you put the path to the file you wrote out earlier. Wireshark will automatically monitor that file for updates, and with any luck, you should see decrypted traffic start to show up in Wireshark’s logs.
In the end, my bug was that when I was writing the request to the SwiftNIO Channel, it wasn’t flushing it. So ultimately I just had to change
channel.writeAndFlush(...) and everything started working. Once Wireshark was decrypting the data, I was able to see that the request was simply never making it to the server I was connecting to, and that it started working just fine with that code change.
Update: Johannes Weiss of the SwiftNIO core team has pointed out another approach for capturing packets: NIOWritePCAPHandler. If you add this to your channel pipeline after the SSL handler, you can have it write out .pcap files which can be read by Wireshark or tcpdump. An example of this can be found in the swift-nio-extras repository. I completely missed this when hunting for answers, and this looks like it can be similarly helpful for debugging TLS-encrypted network traffic.