ktls now under the rustls org
Thanks to my sponsors: Guillaume E, Lyssieth, Tiziano Santoro, Max Heaton, teor, Aleksandre Khokhiashvili, Em Sharnoff, Thehbadger, James Brown, Sawyer Knoblich, Isak Sunde Singh, Michał Bartoszkiewicz, Philipp Hatt, xales, dataphract, Elnath, Marc-Andre Giroux, Ben Mitchell, James Leitch, Mason Ginter and 279 more
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 :)
Here's another article just for you:
crates.io phishing attempt
Earlier this week, an npm supply chain attack.
It’s turn for crates.io, the main public repository for Rust crates (packages).
The phishing e-mail looks like this:
And it leads to a GitHub login page that looks like this:
Several maintainers received it — the issue is being discussed on GitHub.
The crates.io team has acknowledged the attack and said they’d see if they can do something about it.