mbedtls icon indicating copy to clipboard operation
mbedtls copied to clipboard

Public API for TLS 1.3 KTLS Integration

Open HaniAmmar opened this issue 3 months ago • 4 comments

Hello,

Thank you for maintaining mbedTLS and providing a high-performance, lightweight TLS library.

I am currently rewriting a private C++ web server/framework for public release, leveraging Linux io_uring and KTLS to enable zero-copy send and receive operations. While mbedTLS’s read/write callbacks integrate well with registered buffers, filling the fields in KTLS tls12_crypto_info_* requires additional steps, some of which currently rely on internal helpers.

For TLS 1.2, the key and IV/salt can be derived using the public mbedtls_ssl_tls_prf function, which is straightforward. TLS 1.3, however, provides no public API to derive record keys from the application traffic secret. In my prototype, I had to rely on the internal mbedtls_ssl_tls13_make_traffic_keys function, declared extern, to obtain the AEAD key and IV required by the kernel.

Using mbedtls_ssl_tls13_make_traffic_keys also requires calling mbedtls_md_psa_alg_from_type and populating an mbedtls_ssl_key_set structure. While these obstacles can be worked around, obtaining the correct sequence numbers for TX and RX is more complex. For TX, the outgoing sequence can be read from mbedtls_ssl_context::cur_out_ctr (if I am not mistaken). For RX, I have not explored the sources in depth, but it appears to reside in mbedtls_ssl_transform.

While it is possible to invoke private/internal helpers for TLS 1.3 and compute sequence numbers inside the read/write callbacks, doing so makes the code fragile and difficult to maintain, and risks compatibility or licensing problems.

mbedTLS could be made KTLS-friendly by leveraging the existing functions: with a simple wrapper around mbedtls_ssl_tls13_make_traffic_keys and mbedtls_ssl_key_set, and a getter for the TX/RX sequence numbers, the hand-off to the kernel can be performed at any time — even if the kernel reads data frames after the handshake. This scenario occurs, for example, with an HTTP GET request, in which case I use the mbedTLS read function to drain the buffer before enabling KTLS.

Thank you for considering this enhancement.

HaniAmmar avatar Sep 27 '25 17:09 HaniAmmar

Hi Hani! Thanks for your interest in Mbed TLS and for getting in touch.

Indeed relying on internal functions and private fields in structures is fragile as those are susceptible to be changed without notice in any minor version, so your request for a public API supporting this use case makes complete sense.

However, at the moment we don't have a lot of bandwidth for TLS improvements, so to be honest it seems unlikely we're going to be working on this in the near future. If you have working code for this (relying on internals) and you feel like turning it into a PR, that is probably your best chance of seeing this feature in a release in the foreseeable future. Note however that we require unit tests for all new features, which in this case may require some work (probably not actually using kTLS, but making sure the correct information is exported).

Again, thanks for your input! (And if others are interested in this, please comment on this issue so that we know about it.)

mpg avatar Oct 02 '25 07:10 mpg

Hi Manuel,

I’d be happy to contribute to MbedTLS and provide both tests and a working example demonstrating how KTLS hand-over can be achieved. Before I begin, I would like to ask about the level of implementation that would be considered acceptable for MbedTLS:

Simple: Provide only a wrapper around the necessary TLS 1.3 functions. This would keep the addition minimal, but the burden on the user side would be significant, as it requires a deep understanding of TLS internals and MbedTLS.

Advanced: Add a full enable_ktls() function that configures everything automatically. Personally, I am against this approach, as it would tie this to Linux-only functionality, require kernel feature checks, and involve kernel-specific programming knowledge. This would make the code harder to maintain and less portable.

Medium: Introduce three functions — one to fill an mbedtls_ssl_key_set struct, plus two getters (or one) for the inbound and outbound sequence numbers. This seems like the sweet spot to me. It keeps the implementation generic and agnostic, making it usable not only for KTLS but also for other technologies that require key/IV/salt and sequence numbers — for example, the upcoming kernel QUIC project

I would appreciate your guidance on which direction aligns best with MbedTLS’s goals, and I also welcome any input from others in the community who may have an interest in this.

HaniAmmar avatar Oct 02 '25 15:10 HaniAmmar

Hi Hani,

Thanks for discussing the general strategy, that's very helpful. I tend to agree with your analysis and favour the "medium" approach as well.

Simple: Provide only a wrapper around the necessary TLS 1.3 functions. This would keep the addition minimal, but the burden on the user side would be significant, as it requires a deep understanding of TLS internals and MbedTLS.

Another problem with this approach, if I understand it correctly, is that too much of the internals would be exposed in the public API, which would severely limit our ability to refactor or evolve internal code in the future. There is a short term win with this approach (fewer additions) but a long-term cost on both users and maintainers.

Advanced: Add a full enable_ktls() function that configures everything automatically. Personally, I am against this approach, as it would tie this to Linux-only functionality, require kernel feature checks, and involve kernel-specific programming knowledge. This would make the code harder to maintain and less portable.

Indeed, that wouldn't be aligned with the project's general philosophy regarding portability. We try to limit platform-dependant code to a few well-defined abstractions. This approach would have a very large platform-dependant surface and significantly complicate maintenance and testing (needing to test with several specific kernel versions would be a new requirement on our CI).

Medium: Introduce three functions — one to fill an mbedtls_ssl_key_set struct, plus two getters (or one) for the inbound and outbound sequence numbers. This seems like the sweet spot to me. It keeps the implementation generic and agnostic, making it usable not only for KTLS but also for other technologies that require key/IV/salt and sequence numbers — for example, the upcoming kernel QUIC project

Indeed that looks like the sweet spot, and aligned with our general philosophy.

mpg avatar Oct 03 '25 11:10 mpg

Hi Manuel,

I’ve finished the draft implementation and an example demonstrating how it can be used. I’ve been testing it within my web framework, and so far it’s working correctly for both the client and server sides.

I’m now at the stage of writing the tests for it, and before I begin, I’d like to get your input on the best approach.

This function doesn’t retrieve the keys directly from the session; instead, it uses the material provided by the callback set via mbedtls_ssl_set_export_keys_cb(). It therefore depends on mbedtls_ssl_tls13_make_traffic_keys() and mbedtls_ssl_cipher_to_psa().

I’ve been considering two possible testing strategies:

  1. Deterministic approach – use predefined secrets and verify the derived keys and IVs.
  2. Handshake-based approach – perform a full handshake, then reinsert the exported keys into a new session, and compare the encoded/decoded records between the two sessions.

Which approach would you prefer for this case?

If you’d like, I can attach the example here showing the new function I added and how it’s being used.

Thank you.

HaniAmmar avatar Oct 12 '25 21:10 HaniAmmar