ktls now under the rustls org
What's a ktls
I started work on ktls and ktls-sys, a pair of crates exposing Kernel TLS offload to Rust, about two years ago.
kTLS lets the kernel (and, in turn, any network interface that supports it) take care of encryption, framing, etc., for the entire duration of a TLS connection... as soon as you have a TLS connection.
For the handshake itself (hellos, change cipher, encrypted extensions, certificate verification, etc.), you still have to use a userland TLS implementation.
The Illustrated TLS 1.3 Connection shows the handshake in great detail.
In Rust, the natural choice is rustls, which got a very favorable audit in 2020, and so that's why the higher-level of my two crates, ktls, has a dependency on rustls.
This posed a challenge early on: for the kernel to take over encryption, continuing where rustls left off, we
have to, well, not only deal with any data rustls may have already decrypted (TLS frames, TCP segments,
and whatever read
/recvmsg
writes into your buffer don't necessarily align), but also to exfiltrate the
session keys (and sequence numbers), something that rustls keeps well-hidden by design.
The exact data being exported varies depending on which cipher is used, and it was a bit of work to find an API design both the rustls maintainers and myself were happy with — that work took place two years ago and landed in rustls in a little under a month.
The ktls API today
Today, ktls
's API takes a rustls ClientConnection
or ServerConnection
(kTLS works in either direction)
and gives you back a TcpStream
and a Vec<u8>
of already-decrypted data.
Here's an excerpt of a code sample from loona, my HTTP/1+2 implementation:
// use a self-signed certificate for testing let certified_key = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap(); let crt = certified_key.cert.der(); let key = certified_key.key_pair.serialize_der(); let mut server_config = ServerConfig::builder() .with_no_client_auth() .with_single_cert( vec![crt.clone()], PrivatePkcs8KeyDer::from(key.clone()).into(), ) .unwrap(); // not strictly needed for kTLS, but useful for debugging: // this reads the SSLKEYLOGFILE environment variable, and writes // the secrets to it server_config.key_log = Arc::new(rustls::KeyLogFile::new()); // we'll need to extract secrets to give them to the kernel — the // rustls API has us explicitly opt into this server_config.enable_secret_extraction = true; server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(server_config)); let stream: TcpStream = todo!("TCP listen/accept logic goes here"); // this wrapper is able to stop reading at the boundary between // two TLS messages — which is done in `config_ktls_{client,server}` // further down, once the handshake is done. let stream = CorkStream::new(stream); // this is the usual way for a server to establish a TLS connection // asynchronously with `tokio-rustls` let stream = acceptor.accept(stream).await?; // as usual, we get access to the rustls session, including // the negotiated ALPN protocol let sc = stream.get_ref().1; let alpn_proto = sc.alpn_protocol().and_then(|p| std::str::from_utf8(p).ok().map(|s| s.to_string())); debug!(?alpn_proto, "Performed TLS handshake"); // this extracts the secrets, configures kTLS as the "ULP" // (upper-layer protocol), and returns a `TcpStream` that // transparently does TLS. let stream = ktls::config_ktls_server(stream).await?; debug!("kTLS successfully set up"); let (drained, stream) = stream.into_raw(); let drained = drained.unwrap_or_default(); debug!("{} bytes already decoded by rustls", drained.len());
Coordinating & collaborating
But rustls
occasionally evolves, and undergoes APIs changes (whenever better — safer, more correct, more flexible — interfaces
are found).
And whenever a new version comes out, crates like tokio-rustls
and ktls
must be updated to be compatible with it.
In the past, I've been irritated at the several months lag between a rustls
update and the corresponding
tokio-rustls
release — I recently complained about it online and the maintainers let me know that this
is unlikely to happen again in the future because both packages are now under the same GitHub organization,
https://github.com/rustls — they also offered to adopt ktls
, so that it would also stay in-sync, and
so we could easily orchestrate simultaneous releases of rustls
, tokio-rustls
, and ktls
.
I'd like to thank Dirkjan for offering to adopt ktls
— even though I'll be
around too, to deal with whatever comes up :)
Thanks to my sponsors: Marco Carmosino, Chris Emery, Dylan Anthony, Shane Lillie, Timothée Gerber, WeblWabl, callym, Ben Mitchell, Ives van Hoorne, genny, Steven McGuire, Valentin Mariette, SeniorMars, Neil Blakey-Milner, Chris Sims, Andy Gocke, Scott Sanderson, clement, budrick, you got maiL and 235 more
If you liked what you saw, please support my work!
Here's another article just for you:
Some bugs are merely fun. Others are simply delicious!
Today's pick is the latter.
Reproducing the issue, part 1
(It may be tempting to skip that section, but reproducing an issue is an important part of figuring it out, so.)
I've never used Emacs before, so let's install it. I do most of my computing on an era-appropriate Ubuntu, today it's Ubuntu 22.10, so I just need to: