mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
@ 2026-10-28 13:52 Martin Belanger via B4 Relay
  2026-10-28 13:52 ` [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
                   ` (5 more replies)
  0 siblings, 6 replies; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

kTLS does not currently handle TLS 1.3 KeyUpdate. It never sends a
KeyUpdate. And when it receives one, it blocks the socket until the
application installs the next key. kTLS also does not enforce the key
usage limit on transmit.

This applies to TLS libraries in user space as well as kernel users such
as NVMe/TCP or sunrpc:

- GnuTLS sends a KeyUpdate after 2^24 records when it runs its own
  record layer. With kTLS, the record layer is handled in the kernel and
  GnuTLS therefore never runs the check. OpenSSL never sends a KeyUpdate
  on its own.
- NVMe/TCP and sunrpc don't have a mechanism to install a new key. A
  conforming peer that sends a KeyUpdate stalls their connection.

RFC 9846 (July 2026), which obsoletes RFC 8446, makes the limit
mandatory (section 5.5): "Implementations MUST either close the
connection or do a key update as described in Section 4.7.3 prior to
reaching these limits." Long-lived connections with small records reach
the limit quickly. One NVMe/TCP queue sends at least one record per
command, and more with large I/O. Depending on the load and link speed,
the 2^24 limit can be reached in less than 10 minutes in many cases.

With this series, kTLS handles KeyUpdate in both directions for a socket
that registers its traffic secrets. A socket that does not register them
behaves as before. A user can opt in by passing the secrets after the
handshake. GnuTLS and OpenSSL install the kTLS keys themselves, so they
could register the secrets at the same time. tlshd gets the secrets from
GnuTLS through a callback it uses for QUIC.

Why the record layer
====================

KeyUpdate must be handled where the records are built.

The 2^24 limit is a record count, and only the record layer can see the
count precisely as it changes. A user sitting above kTLS can only read
the count with getsockopt(TLS_TX) after each write. A kTLS user writes
bytes, and the kernel splits and merges those bytes into records. I
counted send calls in an NVMe/TCP prototype trying to guesstimate the
2^24 limit. The count was correct with 4 KB I/O, but was 21 times too
low with 1 MB I/O. Each send call translates into a different number of
records. It's simply not possible to precisely guess the number of
records based on the number of send calls.

The KeyUpdate must also go at a precise place in the record stream
(section 4.7.3). It is encrypted with the old key, and every record
after it with the new key. When the peer sets update_requested, our
KeyUpdate must go out before the next Application Data record. The key
can only change between two records. Only net/tls has control over that.
Fixing this in one driver or library would leave other drivers or
libraries to re-implement their own (imperfect) solution.

The in-kernel QUIC series under review on netdev splits the work the
same way as this proposed fix. The handshake stays in user space, the
secrets are passed in with a socket option, and the kernel derives each
key update with HKDF-Expand-Label (quic_crypto_key_update()).

What the series does
====================

- Two new socket options, TLS_TX_TRAFFIC_SECRET and
  TLS_RX_TRAFFIC_SECRET, are used to register the application traffic
  secrets. The secret is passed as raw bytes. The kernel derives the
  hash (SHA-256 or SHA-384) from the cipher installed with TLS_TX or
  TLS_RX. The TX secret must be set first because the RX secret needs
  the TX secret in case a received KeyUpdate asks for a reply.
- A third socket option, TLS_TX_KEY_UPDATE, can be used to send a
  KeyUpdate now. It can also optionally ask the peer to update too.
- Receive: kTLS consumes the KeyUpdate, installs the next key and
  continues. This works for recvmsg, splice and read_sock.
- Transmit: kTLS sends a KeyUpdate after 2^24 records, a margin below
  the 2^24.5 limit of RFC 9846 section 5.5, and in reply to
  update_requested. ChaCha20-Poly1305 has no record limit.
- kTLS aborts the connection when a peer sends more than 8 KeyUpdates in
  one second, or an invalid KeyUpdate. GnuTLS uses the same rate limit.
- Software path only. Device offload is unchanged.
- SM4-GCM and SM4-CCM are not supported: the kernel has no HMAC-SM3.

Testing
=======

- NVMe/TCP host and target over loopback, with the record limit and the
  rate limit lowered for the test: more than 1000 KeyUpdates in each
  direction under I/O, with AES-128-GCM and AES-256-GCM.
- Interoperability with OpenSSL in user space, which derives the keys on
  its own, including the reply to update_requested.
- New selftests in tools/testing/selftests/net/tls.c for every TLS 1.3
  cipher kTLS supports. The full tls selftest passes.

The matching tlshd change for ktls-utils is a small patch to be
submitted at a later time if this series is accepted.

Questions
=========

This is an RFC for feedback on the design and the new socket options, in
particular:

1. I used one socket option per direction, like TLS_TX and TLS_RX. Is
   that the right form? Would one option that sets secrets for both
   directions be better?
2. When the application installs a key itself with TLS_TX or TLS_RX
   after the secrets have already been registered, the series removes
   the registered secrets (TLS_TX removes both, TLS_RX only the RX
   secret) and gives KeyUpdate back to the application. Is that right,
   or should the key change be refused while a secret is registered?
3. TLS_TX_KEY_UPDATE (patch 4) is not needed for the record limit. I can
   move it to a separate series if you prefer a smaller first step.

Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
Changes in v2:
- Patch 2: check that the installed key and IV come from the
  registered secret. (Hannes)
- Patch 3: free the KeyUpdate record when its processing fails.
  (Sashiko)
- Patch 3: after a rekey, wake pollers only if a pending KeyUpdate held
  them back. (Sashiko)
- Patch 4: keep MSG_NOSIGNAL when sending a KeyUpdate. setsockopt()
  never raises SIGPIPE. (Sashiko)
- Patch 4: clear the KeyUpdate request state when the application
  installs a key with TLS_TX or TLS_RX. (Sashiko)
- Patch 5: derive the test keys from the secrets with AF_ALG. Add tests
  for the changes above.
- Not changed: if TCP fails to send the KeyUpdate after the record was
  queued, TLS_TX_KEY_UPDATE can return 0, as sendmsg() returns the
  queued length. The next call reports the error. (Sashiko)
- Link to v1: https://patch.msgid.link/20261025-keyupdate-rfc-v1-0-60b7756eccbd@gmail.com

---
Martin Belanger (5):
      tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
      tls: add socket options to register the traffic secrets
      tls: update the RX key when a KeyUpdate is received
      tls: send a KeyUpdate at the record limit and on request
      selftests/tls: add KeyUpdate tests

 Documentation/networking/tls.rst   |  43 ++++
 include/net/tls.h                  |  23 +++
 include/uapi/linux/tls.h           |   3 +
 net/tls/Kconfig                    |   2 +
 net/tls/Makefile                   |   3 +-
 net/tls/tls.h                      |   5 +
 net/tls/tls_hkdf.c                 |  82 ++++++++
 net/tls/tls_keyupdate.c            | 155 ++++++++++++++
 net/tls/tls_main.c                 | 175 +++++++++++++++-
 net/tls/tls_sw.c                   | 193 ++++++++++++++++-
 tools/testing/selftests/net/config |   4 +
 tools/testing/selftests/net/tls.c  | 412 +++++++++++++++++++++++++++++++++++++
 12 files changed, 1091 insertions(+), 9 deletions(-)
---
base-commit: 1a2c35c49cc7c6be860038b8442b52424a8f123c
change-id: 20261025-keyupdate-rfc-94d3c7e84077

Best regards,
--  
Martin Belanger <martin.nvme.dev@gmail.com>



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
@ 2026-10-28 13:52 ` Martin Belanger via B4 Relay
  2026-10-28 13:52 ` [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

From: Martin Belanger <martin.belanger@dell.com>

A TLS 1.3 KeyUpdate derives the next traffic secret, key and IV with
HKDF-Expand-Label (RFC 9846, section 7.2). Add the helper for the
kernel to do this itself.

Only the part of the key schedule after the handshake is needed. The
input is a traffic secret, which is already a pseudorandom key, so
HKDF-Extract is not needed. Each output is at most Hash.length bytes,
so each derivation is a single HMAC with the library SHA-256 or SHA-384
functions.

Assisted-by: LLM
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 net/tls/Kconfig    |  2 ++
 net/tls/Makefile   |  2 +-
 net/tls/tls.h      |  2 ++
 net/tls/tls_hkdf.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/net/tls/Kconfig b/net/tls/Kconfig
index 4f4d5973a28f..6183ebe954ee 100644
--- a/net/tls/Kconfig
+++ b/net/tls/Kconfig
@@ -8,6 +8,8 @@ config TLS
 	select CRYPTO
 	select CRYPTO_AES
 	select CRYPTO_GCM
+	select CRYPTO_LIB_SHA256
+	select CRYPTO_LIB_SHA512
 	select NET_SOCK_MSG
 	default n
 	help
diff --git a/net/tls/Makefile b/net/tls/Makefile
index 4c7d296081ee..6edcf1271b75 100644
--- a/net/tls/Makefile
+++ b/net/tls/Makefile
@@ -7,6 +7,6 @@ CFLAGS_trace.o := -I$(src)
 
 obj-$(CONFIG_TLS) += tls.o
 
-tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
+tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o
 
 tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 60a37bdaaa25..b2061f6bf065 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -147,6 +147,8 @@ void tls_strp_abort_strp(struct tls_strparser *strp, int err);
 int init_prot_info(struct tls_prot_info *prot,
 		   const struct tls_crypto_info *crypto_info,
 		   const struct tls_cipher_desc *cipher_desc);
+int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
+			  const char *label, u8 *out, size_t out_len);
 int tls_set_sw_offload(struct sock *sk, int tx,
 		       struct tls_crypto_info *new_crypto_info);
 void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_hkdf.c b/net/tls/tls_hkdf.c
new file mode 100644
index 000000000000..49e33146ac7e
--- /dev/null
+++ b/net/tls/tls_hkdf.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
+ *
+ * TLS 1.3 HKDF-Expand-Label, RFC 9846 section 7.1.
+ *
+ * TLS 1.3 derives all of its secrets and keys with HKDF (RFC 5869). The
+ * TLS handshake runs in user space and gives the kernel one application
+ * traffic secret for each direction. For a KeyUpdate, the kernel derives
+ * the next traffic secret from the current one (section 7.2), and the
+ * new key and IV from the next traffic secret (section 7.3). Both steps
+ * use only HKDF-Expand-Label, so this file implements only that
+ * function, not the full key schedule of the handshake.
+ *
+ * The input is always a traffic secret. It is already a random key of
+ * the hash length, so HKDF-Extract is not needed. No output is longer
+ * than the hash, so HKDF-Expand is a single HMAC.
+ */
+
+#include <crypto/sha2.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "tls.h"
+
+#define TLS_HKDF_LABEL_PREFIX		"tls13 "
+#define TLS_HKDF_LABEL_PREFIX_LEN	(sizeof(TLS_HKDF_LABEL_PREFIX) - 1)
+#define TLS_HKDF_LABEL_MAX		32
+
+/**
+ * tls_hkdf_expand_label - HKDF-Expand-Label with an empty context
+ * @secret: the secret
+ * @secret_len: length of @secret, which selects the hash: SHA-256 for
+ *	SHA256_DIGEST_SIZE, SHA-384 for SHA384_DIGEST_SIZE
+ * @label: the label, without the "tls13 " prefix
+ * @out: output buffer
+ * @out_len: number of bytes to derive, at most @secret_len
+ *
+ * Return: 0 on success, -EINVAL if a length is not valid.
+ */
+int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
+			  const char *label, u8 *out, size_t out_len)
+{
+	u8 info[2 + 1 + TLS_HKDF_LABEL_PREFIX_LEN + TLS_HKDF_LABEL_MAX + 1 + 1];
+	size_t label_len = strlen(label);
+	u8 tmp[SHA384_DIGEST_SIZE];
+	size_t pos = 0;
+
+	if (secret_len != SHA256_DIGEST_SIZE &&
+	    secret_len != SHA384_DIGEST_SIZE)
+		return -EINVAL;
+	if (!out_len || out_len > secret_len || label_len > TLS_HKDF_LABEL_MAX)
+		return -EINVAL;
+
+	/* struct {
+	 *	uint16 length;
+	 *	opaque label<7..255>;
+	 *	opaque context<0..255>;
+	 * } HkdfLabel;
+	 *
+	 * followed by the HKDF-Expand counter for the first output block.
+	 */
+	put_unaligned_be16(out_len, info);
+	pos += 2;
+	info[pos++] = TLS_HKDF_LABEL_PREFIX_LEN + label_len;
+	memcpy(info + pos, TLS_HKDF_LABEL_PREFIX, TLS_HKDF_LABEL_PREFIX_LEN);
+	pos += TLS_HKDF_LABEL_PREFIX_LEN;
+	memcpy(info + pos, label, label_len);
+	pos += label_len;
+	info[pos++] = 0;	/* empty context */
+	info[pos++] = 1;	/* counter */
+
+	if (secret_len == SHA256_DIGEST_SIZE)
+		hmac_sha256_usingrawkey(secret, secret_len, info, pos, tmp);
+	else
+		hmac_sha384_usingrawkey(secret, secret_len, info, pos, tmp);
+
+	memcpy(out, tmp, out_len);
+	memzero_explicit(tmp, sizeof(tmp));
+
+	return 0;
+}

-- 
2.53.0



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
  2026-10-28 13:52 ` [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
@ 2026-10-28 13:52 ` Martin Belanger via B4 Relay
  2026-10-28 14:13   ` Hannes Reinecke
  2026-10-28 13:52 ` [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

From: Martin Belanger <martin.belanger@dell.com>

kTLS holds the key and IV of each direction, but not the traffic secret
they were derived from. The derivation is one-way, so the kernel cannot
compute the next generation for a KeyUpdate on its own.

Add TLS_TX_TRAFFIC_SECRET and TLS_RX_TRAFFIC_SECRET. The owner of the
socket passes the TLS 1.3 application traffic secret of a direction after
installing its key. The cipher of the installed key selects the hash, so
the option takes only the secret. A socket without a registered secret
behaves as before.

The secret must give the installed key and IV, or the option fails with
-EINVAL. A wrong secret would otherwise fail only at the first
KeyUpdate.

The TX secret can be registered alone. The RX secret needs the TX secret
first: a received KeyUpdate can ask for one in reply, and since the
kernel consumes the record, only the kernel can send that reply.

Installing a new key with TLS_TX or TLS_RX removes the secrets that no
longer apply. The secrets are cleared when the context is freed and
cannot be read back.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 Documentation/networking/tls.rst |  26 ++++++++
 include/net/tls.h                |  13 ++++
 include/uapi/linux/tls.h         |   2 +
 net/tls/tls_main.c               | 139 ++++++++++++++++++++++++++++++++++++++-
 4 files changed, 179 insertions(+), 1 deletion(-)

diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 980c442d7161..2e011bd1ce4a 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -300,6 +300,32 @@ extra byte used by the ContentType field.
 
 [1] https://datatracker.ietf.org/doc/html/rfc8449
 
+TLS_TX_TRAFFIC_SECRET and TLS_RX_TRAFFIC_SECRET
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``TLS_TX_TRAFFIC_SECRET`` registers the TLS 1.3 application traffic secret
+of the transmit direction, ``TLS_RX_TRAFFIC_SECRET`` that of the receive
+direction. With a registered secret, the kernel performs KeyUpdate for that
+direction itself (RFC 9846, section 4.7.3). The value is the secret itself.
+TLS 1.3 only, software path only.
+
+Install the key of the direction first, with the ``TLS_TX`` or ``TLS_RX``
+socket option described above. The cipher of that key selects the hash, and
+the secret must have the length of that hash: 32 bytes for SHA-256
+(AES-128-GCM, AES-128-CCM, ChaCha20-Poly1305) or 48 bytes for SHA-384
+(AES-256-GCM). Other ciphers return ``EOPNOTSUPP``.
+
+Each secret can be registered once. ``TLS_RX_TRAFFIC_SECRET`` needs
+``TLS_TX_TRAFFIC_SECRET`` first: a received KeyUpdate can ask for one in
+reply, and only the kernel sees it. ``TLS_TX_TRAFFIC_SECRET`` alone is
+allowed.
+
+If the application installs a new key itself with ``TLS_TX``, both secrets
+are removed. With ``TLS_RX``, the RX secret is removed. KeyUpdate for that
+direction is then left to the application again.
+
+The secrets cannot be read back.
+
 Statistics
 ==========
 
diff --git a/include/net/tls.h b/include/net/tls.h
index e57bef58851e..aacc6d12226e 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -65,6 +65,9 @@ struct tls_rec;
 
 #define TLS_AAD_SPACE_SIZE		13
 
+/* SHA-384, the longest hash of a TLS 1.3 cipher suite kTLS supports */
+#define TLS_MAX_TRAFFIC_SECRET_SIZE	48
+
 #define TLS_MAX_IV_SIZE			16
 #define TLS_MAX_SALT_SIZE		4
 #define TLS_TAG_SIZE			16
@@ -224,6 +227,12 @@ struct tls_prot_info {
 	u16 tail_size;
 };
 
+/* A TLS 1.3 application traffic secret. len is 0 if none is registered. */
+struct tls_traffic_secret {
+	u8 data[TLS_MAX_TRAFFIC_SECRET_SIZE];
+	u8 len;
+};
+
 struct tls_context {
 	/* read-only cache line */
 	struct tls_prot_info prot_info;
@@ -266,6 +275,10 @@ struct tls_context {
 	union tls_crypto_context crypto_send;
 	union tls_crypto_context crypto_recv;
 
+	/* Registered for KeyUpdate, protected by the socket lock */
+	struct tls_traffic_secret tx_secret;
+	struct tls_traffic_secret rx_secret;
+
 	struct list_head list;
 	refcount_t refcount;
 	struct rcu_head rcu;
diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
index 1245ab38afc1..d1b136ecea96 100644
--- a/include/uapi/linux/tls.h
+++ b/include/uapi/linux/tls.h
@@ -42,6 +42,8 @@
 #define TLS_TX_ZEROCOPY_RO	3	/* TX zerocopy (only sendfile now) */
 #define TLS_RX_EXPECT_NO_PAD	4	/* Attempt opportunistic zero-copy */
 #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
+#define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
+#define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
 
 /* Supported versions */
 #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8bdb78718a8d..8bb1b2773f3f 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -31,6 +31,8 @@
  * SOFTWARE.
  */
 
+#include <crypto/sha2.h>
+#include <crypto/utils.h>
 #include <linux/module.h>
 
 #include <net/tcp.h>
@@ -325,6 +327,8 @@ void tls_ctx_free(struct sock *sk, struct tls_context *ctx)
 
 	memzero_explicit(&ctx->crypto_send, sizeof(ctx->crypto_send));
 	memzero_explicit(&ctx->crypto_recv, sizeof(ctx->crypto_recv));
+	memzero_explicit(&ctx->tx_secret, sizeof(ctx->tx_secret));
+	memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
 	mutex_destroy(&ctx->tx_lock);
 
 	if (sk)
@@ -757,8 +761,16 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
 		ctx->rx_conf = conf;
 	update_sk_prot(sk, ctx);
 
-	if (update)
+	if (update) {
+		/* The registered secret no longer matches the key. The RX
+		 * secret also needs the TX one, see do_tls_setsockopt_secret().
+		 */
+		if (tx)
+			memzero_explicit(&ctx->tx_secret,
+					 sizeof(ctx->tx_secret));
+		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
 		return 0;
+	}
 
 	if (tx) {
 		ctx->sk_write_space = sk->sk_write_space;
@@ -857,6 +869,124 @@ static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval,
 	return 0;
 }
 
+/* Length of the TLS 1.3 traffic secret for @cipher_type, the size of
+ * its hash. 0 if KeyUpdate is not supported for the cipher.
+ */
+static int tls_traffic_secret_len(u16 cipher_type)
+{
+	switch (cipher_type) {
+	case TLS_CIPHER_AES_GCM_128:
+	case TLS_CIPHER_AES_CCM_128:
+	case TLS_CIPHER_CHACHA20_POLY1305:
+		return SHA256_DIGEST_SIZE;
+	case TLS_CIPHER_AES_GCM_256:
+		return SHA384_DIGEST_SIZE;
+	default:
+		return 0;
+	}
+}
+
+/* The installed key and IV must be the ones that @secret gives. Each
+ * KeyUpdate derives the next key from the secret, so a wrong secret
+ * fails only at the first KeyUpdate, with no clear cause.
+ */
+static int tls_check_traffic_secret(struct tls_crypto_info *crypto_info,
+				    const u8 *secret, int len)
+{
+	const struct tls_cipher_desc *cipher_desc;
+	u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
+	u8 key[TLS_CIPHER_AES_GCM_256_KEY_SIZE];
+	int rc;
+
+	cipher_desc = get_cipher_desc(crypto_info->cipher_type);
+	if (!cipher_desc || cipher_desc->key > sizeof(key) ||
+	    cipher_desc->salt + cipher_desc->iv > sizeof(iv))
+		return -EINVAL;
+
+	rc = tls_hkdf_expand_label(secret, len, "key", key, cipher_desc->key);
+	if (rc)
+		goto out;
+
+	rc = tls_hkdf_expand_label(secret, len, "iv", iv,
+				   cipher_desc->salt + cipher_desc->iv);
+	if (rc)
+		goto out;
+
+	if (crypto_memneq(key, crypto_info_key(crypto_info, cipher_desc),
+			  cipher_desc->key) ||
+	    crypto_memneq(iv, crypto_info_salt(crypto_info, cipher_desc),
+			  cipher_desc->salt) ||
+	    crypto_memneq(iv + cipher_desc->salt,
+			  crypto_info_iv(crypto_info, cipher_desc),
+			  cipher_desc->iv))
+		rc = -EINVAL;
+
+out:
+	memzero_explicit(key, sizeof(key));
+	memzero_explicit(iv, sizeof(iv));
+
+	return rc;
+}
+
+static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
+				    unsigned int optlen, int tx)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	struct tls_crypto_info *crypto_info;
+	struct tls_traffic_secret *secret;
+	u8 data[TLS_MAX_TRAFFIC_SECRET_SIZE];
+	int len, rc;
+
+	if (tx) {
+		crypto_info = &ctx->crypto_send.info;
+		secret = &ctx->tx_secret;
+	} else {
+		crypto_info = &ctx->crypto_recv.info;
+		secret = &ctx->rx_secret;
+	}
+
+	/* The key must be installed first: its cipher selects the hash */
+	if (!TLS_CRYPTO_INFO_READY(crypto_info) ||
+	    crypto_info->version != TLS_1_3_VERSION)
+		return -EINVAL;
+
+	if ((tx ? ctx->tx_conf : ctx->rx_conf) != TLS_SW)
+		return -EOPNOTSUPP;
+
+	len = tls_traffic_secret_len(crypto_info->cipher_type);
+	if (!len)
+		return -EOPNOTSUPP;
+
+	if (sockptr_is_null(optval) || optlen != len)
+		return -EINVAL;
+
+	/* A received KeyUpdate may ask for one in reply. The kernel
+	 * consumes the record, so only the kernel can send the reply.
+	 */
+	if (!tx && !ctx->tx_secret.len)
+		return -EINVAL;
+
+	if (secret->len)
+		return -EBUSY;
+
+	if (copy_from_sockptr(data, optval, len)) {
+		rc = -EFAULT;
+		goto out;
+	}
+
+	rc = tls_check_traffic_secret(crypto_info, data, len);
+	if (rc)
+		goto out;
+
+	memcpy(secret->data, data, len);
+	secret->len = len;
+
+out:
+	memzero_explicit(data, sizeof(data));
+
+	return rc;
+}
+
 static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 			     unsigned int optlen)
 {
@@ -883,6 +1013,13 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		rc = do_tls_setsockopt_tx_payload_len(sk, optval, optlen);
 		release_sock(sk);
 		break;
+	case TLS_TX_TRAFFIC_SECRET:
+	case TLS_RX_TRAFFIC_SECRET:
+		lock_sock(sk);
+		rc = do_tls_setsockopt_secret(sk, optval, optlen,
+					      optname == TLS_TX_TRAFFIC_SECRET);
+		release_sock(sk);
+		break;
 	default:
 		rc = -ENOPROTOOPT;
 		break;

-- 
2.53.0



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
  2026-10-28 13:52 ` [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
  2026-10-28 13:52 ` [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
@ 2026-10-28 13:52 ` Martin Belanger via B4 Relay
  2026-10-28 14:19   ` Hannes Reinecke
  2026-10-28 13:52 ` [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

From: Martin Belanger <martin.belanger@dell.com>

When a KeyUpdate arrives, kTLS blocks reads with -EKEYEXPIRED until
the user installs the next key. A kernel user such as nvme-tcp or
sunrpc never does, so the connection stalls.

For a socket with a registered RX secret, derive the next key and
install it when the KeyUpdate is decrypted. The record is not passed to
the user, so recvmsg(), splice() and read_sock() go on to the next
record. Without a registered secret the behavior is unchanged.

A KeyUpdate that is not alone in its record, has a wrong length or an
invalid request_update value ends the connection, as RFC 9846 section
4.7.3 requires. So does a peer that sends more than 8 KeyUpdates in one
second, the same limit as GnuTLS.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 include/net/tls.h       |   7 +++
 net/tls/Makefile        |   3 +-
 net/tls/tls.h           |   2 +
 net/tls/tls_keyupdate.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++
 net/tls/tls_sw.c        |  43 ++++++++++++--
 5 files changed, 198 insertions(+), 7 deletions(-)

diff --git a/include/net/tls.h b/include/net/tls.h
index aacc6d12226e..a5bc28a7d1f5 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -63,6 +63,11 @@ struct tls_rec;
 
 #define TLS_HANDSHAKE_KEYUPDATE		24	/* rfc8446 B.3: Key update */
 
+/* KeyUpdate: msg_type, 24-bit length, request_update (RFC 9846 4.7.3) */
+#define TLS_KEYUPDATE_MSG_SIZE		5
+#define TLS_KEY_UPDATE_NOT_REQUESTED	0
+#define TLS_KEY_UPDATE_REQUESTED	1
+
 #define TLS_AAD_SPACE_SIZE		13
 
 /* SHA-384, the longest hash of a TLS 1.3 cipher suite kTLS supports */
@@ -142,6 +147,8 @@ struct tls_sw_context_rx {
 	u8 zc_capable:1;
 	u8 reader_contended:1;
 	bool key_update_pending;
+	u8 key_update_count;		/* received in this window */
+	unsigned long key_update_window;	/* jiffies */
 
 	struct tls_strparser strp;
 
diff --git a/net/tls/Makefile b/net/tls/Makefile
index 6edcf1271b75..ce8ac6a59f8c 100644
--- a/net/tls/Makefile
+++ b/net/tls/Makefile
@@ -7,6 +7,7 @@ CFLAGS_trace.o := -I$(src)
 
 obj-$(CONFIG_TLS) += tls.o
 
-tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o
+tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o \
+	 tls_keyupdate.o
 
 tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
diff --git a/net/tls/tls.h b/net/tls/tls.h
index b2061f6bf065..63ece8074985 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -149,6 +149,8 @@ int init_prot_info(struct tls_prot_info *prot,
 		   const struct tls_cipher_desc *cipher_desc);
 int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
 			  const char *label, u8 *out, size_t out_len);
+int tls_keyupdate_rekey(struct sock *sk, int tx);
+int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
 int tls_set_sw_offload(struct sock *sk, int tx,
 		       struct tls_crypto_info *new_crypto_info);
 void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
new file mode 100644
index 000000000000..543bf57599c7
--- /dev/null
+++ b/net/tls/tls_keyupdate.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
+ *
+ * TLS 1.3 KeyUpdate, RFC 9846 sections 4.7.3 and 7.2.
+ *
+ * A TLS 1.3 peer can change the key of the direction it sends in at any
+ * time. It sends a KeyUpdate message, then encrypts all later records
+ * with the next key. A sender must also change its key before the key
+ * has protected too many records (section 5.5).
+ *
+ * The next key is derived from the application traffic secret, not from
+ * the current key. User space gives the kernel only the key and IV, so
+ * the kernel cannot derive the next key on its own. Without the secret,
+ * a received KeyUpdate goes to the application, and the kernel stops
+ * decrypting until the application installs the next key with TLS_RX.
+ *
+ * With TLS_TX_TRAFFIC_SECRET or TLS_RX_TRAFFIC_SECRET, user space also
+ * gives the kernel the secret of that direction. The kernel then handles
+ * KeyUpdate itself. This file derives and installs the next key, and
+ * processes a received KeyUpdate. tls_sw.c sends KeyUpdate messages.
+ */
+
+#include <linux/jiffies.h>
+#include <linux/skbuff.h>
+#include <net/strparser.h>
+#include <net/tls.h>
+
+#include "tls.h"
+
+/* A peer that sends more KeyUpdates than this in one second ends the
+ * connection. Each one costs three HMACs and a new key, and carries no
+ * data.
+ */
+#define TLS_KEYUPDATE_RX_MAX_PER_SEC	8
+
+/**
+ * tls_keyupdate_rekey - install the next key of one direction
+ * @sk: socket
+ * @tx: non-zero for the transmit direction
+ *
+ * Derive the next application traffic secret from the registered one,
+ * then its key and IV, and install them. The record sequence number
+ * restarts at zero.
+ *
+ * Return: 0 on success, or a negative errno. On error the installed key
+ * and the registered secret do not change.
+ */
+int tls_keyupdate_rekey(struct sock *sk, int tx)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	struct tls_traffic_secret *secret;
+	const struct tls_cipher_desc *cipher_desc;
+	u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
+	u8 next[TLS_MAX_TRAFFIC_SECRET_SIZE];
+	union tls_crypto_context tmp = {};
+	struct tls_crypto_info *info = &tmp.info;
+	int rc;
+
+	secret = tx ? &ctx->tx_secret : &ctx->rx_secret;
+	cipher_desc = get_cipher_desc(ctx->prot_info.cipher_type);
+	if (!cipher_desc || !secret->len ||
+	    cipher_desc->salt + cipher_desc->iv > sizeof(iv))
+		return -EINVAL;
+
+	/* version and cipher_type; the rest of tmp stays zero */
+	*info = tx ? ctx->crypto_send.info : ctx->crypto_recv.info;
+
+	rc = tls_hkdf_expand_label(secret->data, secret->len, "traffic upd",
+				   next, secret->len);
+	if (rc)
+		goto out;
+
+	rc = tls_hkdf_expand_label(next, secret->len, "key",
+				   crypto_info_key(info, cipher_desc),
+				   cipher_desc->key);
+	if (rc)
+		goto out;
+
+	rc = tls_hkdf_expand_label(next, secret->len, "iv", iv,
+				   cipher_desc->salt + cipher_desc->iv);
+	if (rc)
+		goto out;
+
+	memcpy(crypto_info_salt(info, cipher_desc), iv, cipher_desc->salt);
+	memcpy(crypto_info_iv(info, cipher_desc), iv + cipher_desc->salt,
+	       cipher_desc->iv);
+
+	rc = tls_set_sw_offload(sk, tx, info);
+	if (rc)
+		goto out;
+
+	memcpy(secret->data, next, secret->len);
+
+out:
+	memzero_explicit(next, sizeof(next));
+	memzero_explicit(iv, sizeof(iv));
+	memzero_explicit(&tmp, sizeof(tmp));
+
+	return rc;
+}
+
+/**
+ * tls_keyupdate_rx - process a received KeyUpdate
+ * @sk: socket
+ * @skb: the decrypted record, which starts with a KeyUpdate message
+ *
+ * Called with the socket locked, for a direction with a registered
+ * secret. The caller does not pass the record to the user.
+ *
+ * Return: 0 on success, or a negative errno if the connection must end.
+ */
+int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	struct tls_sw_context_rx *rx_ctx = tls_sw_ctx_rx(ctx);
+	const struct strp_msg *rxm = strp_msg(skb);
+	u8 msg[TLS_KEYUPDATE_MSG_SIZE];
+	int rc;
+
+	/* A KeyUpdate must end its record (section 5.1). Another message
+	 * in the same record is not supported.
+	 */
+	if (rxm->full_len != sizeof(msg) ||
+	    skb_copy_bits(skb, rxm->offset, msg, sizeof(msg)))
+		return -EBADMSG;
+
+	/* msg_type, a 24-bit length of 1, then request_update */
+	if (msg[1] || msg[2] || msg[3] != 1)
+		return -EBADMSG;
+	if (msg[4] != TLS_KEY_UPDATE_NOT_REQUESTED &&
+	    msg[4] != TLS_KEY_UPDATE_REQUESTED)
+		return -EBADMSG;
+
+	if (!rx_ctx->key_update_count ||
+	    time_after(jiffies, rx_ctx->key_update_window + HZ)) {
+		rx_ctx->key_update_window = jiffies;
+		rx_ctx->key_update_count = 0;
+	}
+	if (++rx_ctx->key_update_count > TLS_KEYUPDATE_RX_MAX_PER_SEC)
+		return -EBADMSG;
+
+	rc = tls_keyupdate_rekey(sk, 0);
+	if (rc) {
+		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYERROR);
+		return rc;
+	}
+	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
+
+	return 0;
+}
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 312e51270f29..cc0177320e7e 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1551,15 +1551,28 @@ static int tls_check_pending_rekey(struct sock *sk, struct tls_context *ctx,
 	if (hs_type == TLS_HANDSHAKE_KEYUPDATE) {
 		struct tls_sw_context_rx *rx_ctx = ctx->priv_ctx_rx;
 
-		WRITE_ONCE(rx_ctx->key_update_pending, true);
 		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYRECEIVED);
+
+		if (ctx->rx_secret.len) {
+			err = tls_keyupdate_rx(sk, skb);
+			if (err) {
+				tls_err_abort(sk, err);
+				/* callers free the record only on success */
+				consume_skb(skb);
+				return err;
+			}
+			return 1;
+		}
+
+		WRITE_ONCE(rx_ctx->key_update_pending, true);
 	}
 
 	return 0;
 }
 
 /* On decrypt failure the connection is aborted (sk_err set) before
- * returning a negative errno.
+ * returning a negative errno. Return 1 for a record the kernel consumed
+ * (a KeyUpdate): the caller frees it and does not pass it to the user.
  */
 static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,
 			     struct tls_decrypt_arg *darg)
@@ -1883,6 +1896,11 @@ int tls_sw_recvmsg(struct sock *sk,
 		err = tls_rx_one_record(sk, msg, &darg);
 		if (err < 0)
 			goto recv_end;
+		if (err > 0) {
+			tls_rx_rec_done(ctx);
+			consume_skb(darg.skb);
+			continue;
+		}
 
 		async |= darg.async;
 
@@ -2019,9 +2037,8 @@ ssize_t tls_sw_splice_read(struct socket *sock,  loff_t *ppos,
 	if (err)
 		goto splice_read_end;
 
-	if (!skb_queue_empty(&ctx->rx_list)) {
-		skb = __skb_dequeue(&ctx->rx_list);
-	} else {
+	skb = __skb_dequeue(&ctx->rx_list);
+	while (!skb) {
 		struct tls_decrypt_arg darg;
 
 		err = tls_rx_rec_wait(sk, flags & SPLICE_F_NONBLOCK,
@@ -2036,7 +2053,10 @@ ssize_t tls_sw_splice_read(struct socket *sock,  loff_t *ppos,
 			goto splice_read_end;
 
 		tls_rx_rec_done(ctx);
-		skb = darg.skb;
+		if (err > 0)
+			consume_skb(darg.skb);
+		else
+			skb = darg.skb;
 	}
 
 	rxm = strp_msg(skb);
@@ -2118,6 +2138,11 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
 			err = tls_rx_one_record(sk, NULL, &darg);
 			if (err < 0)
 				goto read_sock_end;
+			if (err > 0) {
+				tls_rx_rec_done(ctx);
+				consume_skb(darg.skb);
+				continue;
+			}
 
 			released = tls_read_flush_backlog(sk, prot, INT_MAX,
 							  0, decrypted,
@@ -2517,6 +2542,12 @@ static void tls_finish_key_update(struct sock *sk, struct tls_context *tls_ctx)
 {
 	struct tls_sw_context_rx *ctx = tls_ctx->priv_ctx_rx;
 
+	/* Only a pending KeyUpdate holds poll() back. The in-kernel rekey
+	 * runs while the KeyUpdate record is still queued.
+	 */
+	if (!READ_ONCE(ctx->key_update_pending))
+		return;
+
 	WRITE_ONCE(ctx->key_update_pending, false);
 	/* wake-up pre-existing poll() */
 	ctx->saved_data_ready(sk);

-- 
2.53.0



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
                   ` (2 preceding siblings ...)
  2026-10-28 13:52 ` [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
@ 2026-10-28 13:52 ` Martin Belanger via B4 Relay
  2026-10-28 14:23   ` Hannes Reinecke
  2026-10-28 13:52 ` [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
  2026-10-28 14:21 ` [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Chuck Lever
  5 siblings, 1 reply; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

From: Martin Belanger <martin.belanger@dell.com>

RFC 9846 section 5.5 requires a key update before the key reaches its
usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
records, and only the record layer knows where records start and end.
Section 4.7.3 also requires a reply to a KeyUpdate with
update_requested, sent before the next Application Data record. Both
need a KeyUpdate at an exact position in the record stream.

For a socket with a registered TX secret, send a KeyUpdate before the
next record when the key has protected 2^24 records, as GnuTLS does,
or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
The KeyUpdate is sent as a handshake record, which closes the open data
record first. Pending encryptions are then completed, and only then is
the next key installed.

Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
peer to update too. It can only bring a KeyUpdate forward: the record
limit is enforced either way. Following section 4.7.3, a new request
to the peer waits for the answer to the previous one, and no more than
2^48-1 epochs are used.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 Documentation/networking/tls.rst |  17 +++++
 include/net/tls.h                |   5 +-
 include/uapi/linux/tls.h         |   1 +
 net/tls/tls.h                    |   1 +
 net/tls/tls_keyupdate.c          |   5 ++
 net/tls/tls_main.c               |  38 +++++++++-
 net/tls/tls_sw.c                 | 150 ++++++++++++++++++++++++++++++++++++++-
 7 files changed, 214 insertions(+), 3 deletions(-)

diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 2e011bd1ce4a..69752a6d67df 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -326,6 +326,23 @@ direction is then left to the application again.
 
 The secrets cannot be read back.
 
+With a registered TX secret, the kernel sends a KeyUpdate before the key
+reaches its record limit (RFC 9846, section 5.5): after 2^24 records, except
+for ChaCha20-Poly1305, which has no limit. It also sends one before the next
+record when a received KeyUpdate asks for it. With a registered RX secret,
+the kernel processes a received KeyUpdate and does not pass the record to
+the application. A peer that sends more than 8 KeyUpdates in one second ends
+the connection.
+
+TLS_TX_KEY_UPDATE
+~~~~~~~~~~~~~~~~~
+
+TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
+registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 1 to
+also ask the peer to update its key. Asking the peer needs a registered RX
+secret, and fails with ``EBUSY`` until the peer has answered the previous
+request.
+
 Statistics
 ==========
 
diff --git a/include/net/tls.h b/include/net/tls.h
index a5bc28a7d1f5..d3123b17030c 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -282,9 +282,12 @@ struct tls_context {
 	union tls_crypto_context crypto_send;
 	union tls_crypto_context crypto_recv;
 
-	/* Registered for KeyUpdate, protected by the socket lock */
+	/* KeyUpdate state, protected by the socket lock */
 	struct tls_traffic_secret tx_secret;
 	struct tls_traffic_secret rx_secret;
+	u64 tx_key_updates;		/* KeyUpdates sent */
+	bool tx_key_update_reply;	/* peer set update_requested */
+	bool tx_key_update_asked;	/* we set it, no answer yet */
 
 	struct list_head list;
 	refcount_t refcount;
diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
index d1b136ecea96..c85408cb6ce7 100644
--- a/include/uapi/linux/tls.h
+++ b/include/uapi/linux/tls.h
@@ -44,6 +44,7 @@
 #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
 #define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
 #define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
+#define TLS_TX_KEY_UPDATE	8	/* Send a TLS 1.3 KeyUpdate now */
 
 /* Supported versions */
 #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 63ece8074985..d12c9278c814 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
 			  const char *label, u8 *out, size_t out_len);
 int tls_keyupdate_rekey(struct sock *sk, int tx);
 int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
+int tls_sw_key_update(struct sock *sk, int request);
 int tls_set_sw_offload(struct sock *sk, int tx,
 		       struct tls_crypto_info *new_crypto_info);
 void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
index 543bf57599c7..851d5b54b336 100644
--- a/net/tls/tls_keyupdate.c
+++ b/net/tls/tls_keyupdate.c
@@ -146,5 +146,10 @@ int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
 	}
 	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
 
+	/* Any KeyUpdate from the peer answers our update_requested */
+	ctx->tx_key_update_asked = false;
+	if (msg[4] == TLS_KEY_UPDATE_REQUESTED)
+		ctx->tx_key_update_reply = true;
+
 	return 0;
 }
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8bb1b2773f3f..d71de99d8890 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
 		/* The registered secret no longer matches the key. The RX
 		 * secret also needs the TX one, see do_tls_setsockopt_secret().
 		 */
-		if (tx)
+		if (tx) {
 			memzero_explicit(&ctx->tx_secret,
 					 sizeof(ctx->tx_secret));
+			ctx->tx_key_update_reply = false;
+		}
 		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
+		/* the answer now goes to user space */
+		ctx->tx_key_update_asked = false;
 		return 0;
 	}
 
@@ -987,6 +991,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
 	return rc;
 }
 
+static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t optval,
+					unsigned int optlen)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int value, rc;
+
+	if (sockptr_is_null(optval) || optlen != sizeof(value))
+		return -EINVAL;
+
+	if (copy_from_sockptr(&value, optval, sizeof(value)))
+		return -EFAULT;
+
+	if (value != 0 && value != 1)
+		return -EINVAL;
+
+	rc = mutex_lock_interruptible(&ctx->tx_lock);
+	if (rc)
+		return rc;
+	lock_sock(sk);
+	if (ctx->tx_conf == TLS_SW)
+		rc = tls_sw_key_update(sk, value);
+	else
+		rc = -EINVAL;
+	release_sock(sk);
+	mutex_unlock(&ctx->tx_lock);
+
+	return rc;
+}
+
 static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 			     unsigned int optlen)
 {
@@ -1020,6 +1053,9 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 					      optname == TLS_TX_TRAFFIC_SECRET);
 		release_sock(sk);
 		break;
+	case TLS_TX_KEY_UPDATE:
+		rc = do_tls_setsockopt_key_update(sk, optval, optlen);
+		break;
 	default:
 		rc = -ENOPROTOOPT;
 		break;
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index cc0177320e7e..4fe78624859d 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -40,6 +40,7 @@
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/splice.h>
+#include <linux/unaligned.h>
 #include <crypto/aead.h>
 
 #include <net/strparser.h>
@@ -1005,6 +1006,151 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	return copied > 0 ? copied : ret;
 }
 
+/* RFC 9846 section 5.5 allows 2^24.5 full-size records per AES-GCM key.
+ * Send the KeyUpdate a little earlier.
+ */
+#define TLS_KEYUPDATE_TX_RECORDS	BIT_ULL(24)
+
+/* Section 4.7.3: the epoch must not exceed 2^48-1. The application
+ * traffic secrets start at epoch 3.
+ */
+#define TLS_KEYUPDATE_MAX		(BIT_ULL(48) - 4)
+
+/* Send a KeyUpdate with the current key, then install the next key.
+ * Called with tx_lock held and the socket locked.
+ */
+static int tls_tx_key_update(struct sock *sk, u8 request, int flags)
+{
+	u8 body[TLS_KEYUPDATE_MSG_SIZE] = {
+		TLS_HANDSHAKE_KEYUPDATE, 0, 0, 1, request
+	};
+	struct tls_context *tls_ctx = tls_get_ctx(sk);
+	struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx);
+	u8 record_type = TLS_RECORD_TYPE_HANDSHAKE;
+	u8 buf[CMSG_SPACE(sizeof(record_type))];
+	struct msghdr msg = {};
+	struct cmsghdr *cmsg;
+	struct kvec iov;
+	int ret;
+
+	memset(buf, 0, sizeof(buf));
+	msg.msg_control = buf;
+	msg.msg_controllen = sizeof(buf);
+	msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_TLS;
+	cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
+	memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
+
+	iov.iov_base = body;
+	iov.iov_len = sizeof(body);
+	iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
+
+	/* A control record closes the open record first, so all data
+	 * written so far goes out with the current key, ahead of the
+	 * KeyUpdate.
+	 */
+	ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
+	if (ret < 0)
+		return ret;
+
+	/* Nothing may be encrypted with the next key before the KeyUpdate
+	 * is encrypted with the current one.
+	 */
+	ret = tls_encrypt_async_wait(ctx);
+	if (!ret)
+		ret = tls_keyupdate_rekey(sk, 1);
+	if (ret) {
+		/* The peer expects the next key: the connection is lost */
+		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
+		tls_err_abort(sk, ret);
+		return ret;
+	}
+
+	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+	tls_ctx->tx_key_updates++;
+	if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
+		tls_ctx->tx_key_update_reply = false;
+
+	return 0;
+}
+
+/* Send a KeyUpdate if one is due: in reply to the peer's
+ * update_requested (RFC 9846 section 4.7.3), or before the key reaches
+ * its record limit (section 5.5). ChaCha20-Poly1305 has no record limit:
+ * the sequence number would wrap first.
+ */
+static int tls_tx_key_update_if_due(struct sock *sk, int flags)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	bool limit;
+
+	if (likely(!ctx->tx_secret.len))
+		return 0;
+
+	limit = ctx->prot_info.cipher_type != TLS_CIPHER_CHACHA20_POLY1305 &&
+		get_unaligned_be64(ctx->tx.rec_seq) >= TLS_KEYUPDATE_TX_RECORDS;
+	if (!limit && !ctx->tx_key_update_reply)
+		return 0;
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX) {
+		/* Ignore update_requested. At the record limit the
+		 * connection must end.
+		 */
+		ctx->tx_key_update_reply = false;
+		return limit ? -EKEYEXPIRED : 0;
+	}
+
+	return tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED, flags);
+}
+
+/**
+ * tls_sw_key_update - send a KeyUpdate now, for TLS_TX_KEY_UPDATE
+ * @sk: socket
+ * @request: non-zero to also ask the peer to update its key
+ *
+ * Called with tx_lock held and the socket locked.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int tls_sw_key_update(struct sock *sk, int request)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int ret;
+
+	if (!ctx->tx_secret.len)
+		return -EINVAL;
+
+	if (request) {
+		/* Only the kernel sees the answer */
+		if (!ctx->rx_secret.len)
+			return -EINVAL;
+		/* Section 4.7.3: wait for the answer to the previous one */
+		if (ctx->tx_key_update_asked)
+			return -EBUSY;
+	}
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX - 1)
+		return -EKEYEXPIRED;
+
+	/* A reply must carry update_not_requested */
+	if (request && ctx->tx_key_update_reply) {
+		ret = tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED,
+					MSG_NOSIGNAL);
+		if (ret)
+			return ret;
+	}
+
+	ret = tls_tx_key_update(sk, request ? TLS_KEY_UPDATE_REQUESTED :
+				TLS_KEY_UPDATE_NOT_REQUESTED, MSG_NOSIGNAL);
+	if (!ret && request)
+		ctx->tx_key_update_asked = true;
+
+	return ret;
+}
+
 int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
@@ -1019,7 +1165,9 @@ int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	if (ret)
 		return ret;
 	lock_sock(sk);
-	ret = tls_sw_sendmsg_locked(sk, msg, size);
+	ret = tls_tx_key_update_if_due(sk, msg->msg_flags);
+	if (!ret)
+		ret = tls_sw_sendmsg_locked(sk, msg, size);
 	release_sock(sk);
 	mutex_unlock(&tls_ctx->tx_lock);
 	return ret;

-- 
2.53.0



^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
                   ` (3 preceding siblings ...)
  2026-10-28 13:52 ` [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
@ 2026-10-28 13:52 ` Martin Belanger via B4 Relay
  2026-10-28 14:24   ` Hannes Reinecke
  2026-10-28 14:21 ` [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Chuck Lever
  5 siblings, 1 reply; 17+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-28 13:52 UTC (permalink / raw)
  To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

From: Martin Belanger <martin.belanger@dell.com>

Test the in-kernel KeyUpdate on a bidirectional connection with both
traffic secrets registered on each socket:

- registration errors: wrong length, missing key, TLS 1.2, SM4, the
  RX secret before the TX secret, and a secret that does not give the
  installed key;
- KeyUpdates in both directions with data around them;
- a data record, a KeyUpdate and another data record queued before
  the first read;
- update_requested: the peer answers before its next data, and a
  second request waits for the answer;
- splice() across a KeyUpdate;
- a key installed with TLS_TX removes the registered secrets;
- after a key installed with TLS_RX, a new update_requested is
  accepted;
- TLS_TX_KEY_UPDATE on a shut down socket does not raise SIGPIPE;
- more than 8 KeyUpdates in one second, and an invalid
  request_update value, end the connection.

The keys are derived from the secrets with HKDF-Expand-Label, with the
HMAC of the kernel through AF_ALG.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 tools/testing/selftests/net/config |   4 +
 tools/testing/selftests/net/tls.c  | 412 +++++++++++++++++++++++++++++++++++++
 2 files changed, 416 insertions(+)

diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index cb8cb4696df7..0de4011cfabd 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -10,8 +10,12 @@ CONFIG_CAN_VXCAN=m
 CONFIG_CGROUPS=y
 CONFIG_CRYPTO_ARIA=y
 CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_HMAC=y
 CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_SHA512=y
 CONFIG_CRYPTO_SM4_GENERIC=y
+CONFIG_CRYPTO_USER_API_HASH=y
 CONFIG_DEBUG_INFO_BTF=y
 CONFIG_DEBUG_INFO_BTF_MODULES=n
 CONFIG_DUMMY=y
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 9d3cd4fff062..775d347d54ea 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -11,6 +11,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <linux/if_alg.h>
 #include <linux/tls.h>
 #include <linux/tcp.h>
 #include <linux/socket.h>
@@ -2390,6 +2391,417 @@ TEST_F(tls, rekey_poll_delay)
 	}
 }
 
+#ifndef SOL_ALG
+#define SOL_ALG 279
+#endif
+
+/* RFC 9846 section 7.1, HKDF-Expand-Label with an empty context, for an
+ * output no longer than the hash. The HMAC comes from the kernel.
+ */
+static bool hkdf_expand_label(const unsigned char *secret, int secret_len,
+			      const char *label, unsigned char *out,
+			      int out_len)
+{
+	struct sockaddr_alg sa = {
+		.salg_family = AF_ALG,
+		.salg_type = "hash",
+	};
+	unsigned char info[2 + 1 + 255 + 1 + 1], md[48];
+	int tfm, op, n = 0, label_len = 6 + strlen(label);
+	bool ok = false;
+
+	strcpy((char *)sa.salg_name,
+	       secret_len == 48 ? "hmac(sha384)" : "hmac(sha256)");
+
+	info[n++] = out_len >> 8;
+	info[n++] = out_len;
+	info[n++] = label_len;
+	memcpy(info + n, "tls13 ", 6);
+	memcpy(info + n + 6, label, label_len - 6);
+	n += label_len;
+	info[n++] = 0;	/* context */
+	info[n++] = 1;	/* HKDF-Expand counter */
+
+	tfm = socket(AF_ALG, SOCK_SEQPACKET, 0);
+	if (tfm < 0)
+		return false;
+	if (bind(tfm, (struct sockaddr *)&sa, sizeof(sa)) ||
+	    setsockopt(tfm, SOL_ALG, ALG_SET_KEY, secret, secret_len))
+		goto out;
+
+	op = accept(tfm, NULL, 0);
+	if (op < 0)
+		goto out;
+	ok = write(op, info, n) == n && read(op, md, secret_len) == secret_len;
+	close(op);
+	if (ok)
+		memcpy(out, md, out_len);
+out:
+	close(tfm);
+	return ok;
+}
+
+/* Fill @keys with the key and IV that @secret gives, record number 0 */
+static bool keyupdate_keys(uint16_t cipher_type, const unsigned char *secret,
+			   int secret_len, struct tls_crypto_info_keys *keys)
+{
+	unsigned char *key, *salt, *iv;
+	int key_len, salt_len, iv_len;
+	unsigned char nonce[12];
+
+	tls_crypto_info_init(TLS_1_3_VERSION, cipher_type, keys, 0);
+
+	switch (cipher_type) {
+	case TLS_CIPHER_AES_GCM_128:
+		key = keys->aes128.key;
+		salt = keys->aes128.salt;
+		iv = keys->aes128.iv;
+		key_len = sizeof(keys->aes128.key);
+		salt_len = sizeof(keys->aes128.salt);
+		iv_len = sizeof(keys->aes128.iv);
+		break;
+	case TLS_CIPHER_AES_GCM_256:
+		key = keys->aesgcm256.key;
+		salt = keys->aesgcm256.salt;
+		iv = keys->aesgcm256.iv;
+		key_len = sizeof(keys->aesgcm256.key);
+		salt_len = sizeof(keys->aesgcm256.salt);
+		iv_len = sizeof(keys->aesgcm256.iv);
+		break;
+	case TLS_CIPHER_AES_CCM_128:
+		key = keys->aesccm128.key;
+		salt = keys->aesccm128.salt;
+		iv = keys->aesccm128.iv;
+		key_len = sizeof(keys->aesccm128.key);
+		salt_len = sizeof(keys->aesccm128.salt);
+		iv_len = sizeof(keys->aesccm128.iv);
+		break;
+	case TLS_CIPHER_CHACHA20_POLY1305:
+		key = keys->chacha20.key;
+		salt = keys->chacha20.salt;
+		iv = keys->chacha20.iv;
+		key_len = sizeof(keys->chacha20.key);
+		salt_len = 0;
+		iv_len = sizeof(keys->chacha20.iv);
+		break;
+	default:
+		return false;
+	}
+
+	if (!hkdf_expand_label(secret, secret_len, "key", key, key_len) ||
+	    !hkdf_expand_label(secret, secret_len, "iv", nonce,
+			       salt_len + iv_len))
+		return false;
+
+	memcpy(salt, nonce, salt_len);
+	memcpy(iv, nonce + salt_len, iv_len);
+	return true;
+}
+
+/* In-kernel KeyUpdate: fd and cfd both register the TX and RX traffic
+ * secrets of a bidirectional connection. Returns false if the variant
+ * does not support it.
+ */
+static bool keyupdate_setup(struct __test_metadata *_metadata,
+			    FIXTURE_DATA(tls) *self,
+			    const FIXTURE_VARIANT(tls) *variant)
+{
+	struct tls_crypto_info_keys keys_a, keys_b;
+	unsigned char secret_a[48], secret_b[48];
+	int len;
+
+	if (self->notls || variant->tls_version != TLS_1_3_VERSION)
+		return false;
+
+	switch (variant->cipher_type) {
+	case TLS_CIPHER_AES_GCM_128:
+	case TLS_CIPHER_AES_CCM_128:
+	case TLS_CIPHER_CHACHA20_POLY1305:
+		len = 32;
+		break;
+	case TLS_CIPHER_AES_GCM_256:
+		len = 48;
+		break;
+	default:
+		return false;
+	}
+
+	memset(secret_a, 0xa5, sizeof(secret_a));
+	memset(secret_b, 0x5a, sizeof(secret_b));
+
+	/* the keys must be the ones that the secrets give */
+	ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret_a, len,
+				   &keys_a));
+	ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret_b, len,
+				   &keys_b));
+
+	/* fd -> cfd: replace the keys of the fixture */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &keys_a,
+			     keys_a.len), 0);
+	EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &keys_a,
+			     keys_a.len), 0);
+	/* cfd -> fd */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX, &keys_b,
+			     keys_b.len), 0);
+	EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_TX, &keys_b,
+			     keys_b.len), 0);
+
+	/* the RX secret needs the TX secret first */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+			     secret_b, len), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+			     secret_a, len), 0);
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+			     secret_b, len), 0);
+	EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+			     secret_b, len), 0);
+	EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+			     secret_a, len), 0);
+
+	return true;
+}
+
+static void keyupdate_send(struct __test_metadata *_metadata, int fd,
+			   int request)
+{
+	EXPECT_EQ(setsockopt(fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+			     &request, sizeof(request)), 0);
+}
+
+static void send_recv_str(struct __test_metadata *_metadata, int from, int to,
+			  const char *str)
+{
+	int len = strlen(str) + 1;
+	char buf[64];
+
+	EXPECT_EQ(send(from, str, len, 0), len);
+	EXPECT_EQ(recv(to, buf, sizeof(buf), 0), len);
+	EXPECT_EQ(memcmp(buf, str, len), 0);
+}
+
+TEST_F(tls, keyupdate_secret_errors)
+{
+	unsigned char secret[48] = {};
+	int one = 1;
+
+	if (self->notls)
+		SKIP(return, "no TLS support");
+
+	/* no secret registered */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+			     &one, sizeof(one)), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	if (variant->tls_version != TLS_1_3_VERSION) {
+		EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+				     secret, 32), -1);
+		EXPECT_EQ(errno, EINVAL);
+		return;
+	}
+
+	if (variant->cipher_type == TLS_CIPHER_SM4_GCM ||
+	    variant->cipher_type == TLS_CIPHER_SM4_CCM) {
+		EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+				     secret, 32), -1);
+		EXPECT_EQ(errno, EOPNOTSUPP);
+		return;
+	}
+
+	/* the RX key is not installed on fd */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+			     secret, 32), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	/* wrong length */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+			     secret, 16), -1);
+	EXPECT_EQ(errno, EINVAL);
+
+	/* the installed key does not come from this secret */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET, secret,
+			     variant->cipher_type == TLS_CIPHER_AES_GCM_256 ?
+			     48 : 32), -1);
+	EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(tls, keyupdate)
+{
+	int i;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	send_recv_str(_metadata, self->fd, self->cfd, "before");
+
+	/* stay below the limit of 8 received KeyUpdates per second */
+	for (i = 0; i < 4; i++) {
+		keyupdate_send(_metadata, self->fd, 0);
+		send_recv_str(_metadata, self->fd, self->cfd, "after");
+		keyupdate_send(_metadata, self->cfd, 0);
+		send_recv_str(_metadata, self->cfd, self->fd, "reverse");
+	}
+}
+
+TEST_F(tls, keyupdate_queued)
+{
+	const char *str = "queued behind a KeyUpdate";
+	int len = strlen(str) + 1;
+	char buf[64];
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	/* the record before the KeyUpdate, the KeyUpdate and the record
+	 * after it are all queued before the first read
+	 */
+	EXPECT_EQ(send(self->fd, str, len, MSG_MORE), len);
+	keyupdate_send(_metadata, self->fd, 0);
+	EXPECT_EQ(send(self->fd, str, len, 0), len);
+
+	EXPECT_EQ(recv(self->cfd, buf, len, MSG_WAITALL), len);
+	EXPECT_EQ(memcmp(buf, str, len), 0);
+	EXPECT_EQ(recv(self->cfd, buf, len, MSG_WAITALL), len);
+	EXPECT_EQ(memcmp(buf, str, len), 0);
+}
+
+TEST_F(tls, keyupdate_request)
+{
+	int one = 1;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	keyupdate_send(_metadata, self->fd, 1);
+
+	/* no answer yet */
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+			     &one, sizeof(one)), -1);
+	EXPECT_EQ(errno, EBUSY);
+
+	/* cfd processes the request, then answers ahead of its data */
+	send_recv_str(_metadata, self->fd, self->cfd, "request");
+	send_recv_str(_metadata, self->cfd, self->fd, "answer");
+
+	/* answered */
+	keyupdate_send(_metadata, self->fd, 1);
+	send_recv_str(_metadata, self->fd, self->cfd, "request");
+	send_recv_str(_metadata, self->cfd, self->fd, "answer");
+}
+
+TEST_F(tls, keyupdate_splice)
+{
+	const char *str = "spliced after a KeyUpdate";
+	int len = strlen(str) + 1;
+	char buf[64];
+	int p[2];
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	ASSERT_GE(pipe(p), 0);
+
+	keyupdate_send(_metadata, self->fd, 0);
+	EXPECT_EQ(send(self->fd, str, len, 0), len);
+
+	EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, len, 0), len);
+	EXPECT_EQ(read(p[0], buf, len), len);
+	EXPECT_EQ(memcmp(buf, str, len), 0);
+
+	close(p[0]);
+	close(p[1]);
+}
+
+TEST_F(tls, keyupdate_manual_rekey)
+{
+	struct tls_crypto_info_keys tls12;
+	int zero = 0;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	/* a key installed by the user removes the registered secrets */
+	tls_crypto_info_init(variant->tls_version, variant->cipher_type,
+			     &tls12, 2);
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &tls12, tls12.len), 0);
+
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+			     &zero, sizeof(zero)), -1);
+	EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(tls, keyupdate_manual_rekey_request)
+{
+	struct tls_crypto_info_keys keys;
+	unsigned char secret[48];
+	int len;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	keyupdate_send(_metadata, self->fd, 1);
+
+	/* the answer to the request now goes to user space */
+	len = variant->cipher_type == TLS_CIPHER_AES_GCM_256 ? 48 : 32;
+	memset(secret, 0x3c, sizeof(secret));
+	ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret, len, &keys));
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX, &keys, keys.len), 0);
+
+	EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+			     secret, len), 0);
+
+	keyupdate_send(_metadata, self->fd, 1);
+}
+
+TEST_F(tls, keyupdate_nosignal)
+{
+	int zero = 0;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	/* The default action of SIGPIPE ends the test. The result is not
+	 * checked: as for sendmsg(), the error can show up only on the
+	 * next call.
+	 */
+	EXPECT_EQ(shutdown(self->fd, SHUT_WR), 0);
+	setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE, &zero, sizeof(zero));
+}
+
+TEST_F(tls, keyupdate_rate_limit)
+{
+	char buf[64];
+	int i;
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	/* the ninth KeyUpdate in one second ends the connection */
+	for (i = 0; i < 9; i++)
+		keyupdate_send(_metadata, self->fd, 0);
+	EXPECT_EQ(send(self->fd, "x", 1, 0), 1);
+
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
+TEST_F(tls, keyupdate_invalid)
+{
+	/* key_update, length 1, request_update 2 */
+	static const char bad[] = "\x18\x00\x00\x01\x02";
+	char buf[64];
+
+	if (!keyupdate_setup(_metadata, self, variant))
+		SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+	EXPECT_EQ(tls_send_cmsg(self->fd, TLS_RECORD_TYPE_HANDSHAKE,
+				(char *)bad, 5, 0), 5);
+
+	EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+	EXPECT_EQ(errno, EBADMSG);
+}
+
 struct raw_rec {
 	unsigned int plain_len;
 	unsigned char plain_data[100];

-- 
2.53.0



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets
  2026-10-28 13:52 ` [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
@ 2026-10-28 14:13   ` Hannes Reinecke
  0 siblings, 0 replies; 17+ messages in thread
From: Hannes Reinecke @ 2026-10-28 14:13 UTC (permalink / raw)
  To: martin.nvme.dev, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
> From: Martin Belanger <martin.belanger@dell.com>
> 
> kTLS holds the key and IV of each direction, but not the traffic secret
> they were derived from. The derivation is one-way, so the kernel cannot
> compute the next generation for a KeyUpdate on its own.
> 
> Add TLS_TX_TRAFFIC_SECRET and TLS_RX_TRAFFIC_SECRET. The owner of the
> socket passes the TLS 1.3 application traffic secret of a direction after
> installing its key. The cipher of the installed key selects the hash, so
> the option takes only the secret. A socket without a registered secret
> behaves as before.
> 
> The secret must give the installed key and IV, or the option fails with
> -EINVAL. A wrong secret would otherwise fail only at the first
> KeyUpdate.
> 
> The TX secret can be registered alone. The RX secret needs the TX secret
> first: a received KeyUpdate can ask for one in reply, and since the
> kernel consumes the record, only the kernel can send that reply.
> 
> Installing a new key with TLS_TX or TLS_RX removes the secrets that no
> longer apply. The secrets are cleared when the context is freed and
> cannot be read back.
> 
> Assisted-by: LLM
> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
> ---
>   Documentation/networking/tls.rst |  26 ++++++++
>   include/net/tls.h                |  13 ++++
>   include/uapi/linux/tls.h         |   2 +
>   net/tls/tls_main.c               | 139 ++++++++++++++++++++++++++++++++++++++-
>   4 files changed, 179 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
> index 980c442d7161..2e011bd1ce4a 100644
> --- a/Documentation/networking/tls.rst
> +++ b/Documentation/networking/tls.rst
> @@ -300,6 +300,32 @@ extra byte used by the ContentType field.
>   
>   [1] https://datatracker.ietf.org/doc/html/rfc8449
>   
> +TLS_TX_TRAFFIC_SECRET and TLS_RX_TRAFFIC_SECRET
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +``TLS_TX_TRAFFIC_SECRET`` registers the TLS 1.3 application traffic secret
> +of the transmit direction, ``TLS_RX_TRAFFIC_SECRET`` that of the receive
> +direction. With a registered secret, the kernel performs KeyUpdate for that
> +direction itself (RFC 9846, section 4.7.3). The value is the secret itself.
> +TLS 1.3 only, software path only.
> +
> +Install the key of the direction first, with the ``TLS_TX`` or ``TLS_RX``
> +socket option described above. The cipher of that key selects the hash, and
> +the secret must have the length of that hash: 32 bytes for SHA-256
> +(AES-128-GCM, AES-128-CCM, ChaCha20-Poly1305) or 48 bytes for SHA-384
> +(AES-256-GCM). Other ciphers return ``EOPNOTSUPP``.
> +
> +Each secret can be registered once. ``TLS_RX_TRAFFIC_SECRET`` needs
> +``TLS_TX_TRAFFIC_SECRET`` first: a received KeyUpdate can ask for one in
> +reply, and only the kernel sees it. ``TLS_TX_TRAFFIC_SECRET`` alone is
> +allowed.
> +
> +If the application installs a new key itself with ``TLS_TX``, both secrets
> +are removed. With ``TLS_RX``, the RX secret is removed. KeyUpdate for that
> +direction is then left to the application again.
> +
> +The secrets cannot be read back.
> +
>   Statistics
>   ==========
>   
> diff --git a/include/net/tls.h b/include/net/tls.h
> index e57bef58851e..aacc6d12226e 100644
> --- a/include/net/tls.h
> +++ b/include/net/tls.h
> @@ -65,6 +65,9 @@ struct tls_rec;
>   
>   #define TLS_AAD_SPACE_SIZE		13
>   
> +/* SHA-384, the longest hash of a TLS 1.3 cipher suite kTLS supports */
> +#define TLS_MAX_TRAFFIC_SECRET_SIZE	48
> +
>   #define TLS_MAX_IV_SIZE			16
>   #define TLS_MAX_SALT_SIZE		4
>   #define TLS_TAG_SIZE			16
> @@ -224,6 +227,12 @@ struct tls_prot_info {
>   	u16 tail_size;
>   };
>   
> +/* A TLS 1.3 application traffic secret. len is 0 if none is registered. */
> +struct tls_traffic_secret {
> +	u8 data[TLS_MAX_TRAFFIC_SECRET_SIZE];
> +	u8 len;
> +};
> +
>   struct tls_context {
>   	/* read-only cache line */
>   	struct tls_prot_info prot_info;
> @@ -266,6 +275,10 @@ struct tls_context {
>   	union tls_crypto_context crypto_send;
>   	union tls_crypto_context crypto_recv;
>   
> +	/* Registered for KeyUpdate, protected by the socket lock */
> +	struct tls_traffic_secret tx_secret;
> +	struct tls_traffic_secret rx_secret;
> +
>   	struct list_head list;
>   	refcount_t refcount;
>   	struct rcu_head rcu;
> diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
> index 1245ab38afc1..d1b136ecea96 100644
> --- a/include/uapi/linux/tls.h
> +++ b/include/uapi/linux/tls.h
> @@ -42,6 +42,8 @@
>   #define TLS_TX_ZEROCOPY_RO	3	/* TX zerocopy (only sendfile now) */
>   #define TLS_RX_EXPECT_NO_PAD	4	/* Attempt opportunistic zero-copy */
>   #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
> +#define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
> +#define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
>   
>   /* Supported versions */
>   #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
> diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
> index 8bdb78718a8d..8bb1b2773f3f 100644
> --- a/net/tls/tls_main.c
> +++ b/net/tls/tls_main.c
> @@ -31,6 +31,8 @@
>    * SOFTWARE.
>    */
>   
> +#include <crypto/sha2.h>
> +#include <crypto/utils.h>
>   #include <linux/module.h>
>   
>   #include <net/tcp.h>
> @@ -325,6 +327,8 @@ void tls_ctx_free(struct sock *sk, struct tls_context *ctx)
>   
>   	memzero_explicit(&ctx->crypto_send, sizeof(ctx->crypto_send));
>   	memzero_explicit(&ctx->crypto_recv, sizeof(ctx->crypto_recv));
> +	memzero_explicit(&ctx->tx_secret, sizeof(ctx->tx_secret));
> +	memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
>   	mutex_destroy(&ctx->tx_lock);
>   
>   	if (sk)
> @@ -757,8 +761,16 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
>   		ctx->rx_conf = conf;
>   	update_sk_prot(sk, ctx);
>   
> -	if (update)
> +	if (update) {
> +		/* The registered secret no longer matches the key. The RX
> +		 * secret also needs the TX one, see do_tls_setsockopt_secret().
> +		 */
> +		if (tx)
> +			memzero_explicit(&ctx->tx_secret,
> +					 sizeof(ctx->tx_secret));
> +		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
>   		return 0;
> +	}
>   
>   	if (tx) {
>   		ctx->sk_write_space = sk->sk_write_space;
> @@ -857,6 +869,124 @@ static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval,
>   	return 0;
>   }
>   
> +/* Length of the TLS 1.3 traffic secret for @cipher_type, the size of
> + * its hash. 0 if KeyUpdate is not supported for the cipher.
> + */
> +static int tls_traffic_secret_len(u16 cipher_type)
> +{
> +	switch (cipher_type) {
> +	case TLS_CIPHER_AES_GCM_128:
> +	case TLS_CIPHER_AES_CCM_128:
> +	case TLS_CIPHER_CHACHA20_POLY1305:
> +		return SHA256_DIGEST_SIZE;
> +	case TLS_CIPHER_AES_GCM_256:
> +		return SHA384_DIGEST_SIZE;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +/* The installed key and IV must be the ones that @secret gives. Each
> + * KeyUpdate derives the next key from the secret, so a wrong secret
> + * fails only at the first KeyUpdate, with no clear cause.
> + */
> +static int tls_check_traffic_secret(struct tls_crypto_info *crypto_info,
> +				    const u8 *secret, int len)
> +{
> +	const struct tls_cipher_desc *cipher_desc;
> +	u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
> +	u8 key[TLS_CIPHER_AES_GCM_256_KEY_SIZE];
> +	int rc;
> +
> +	cipher_desc = get_cipher_desc(crypto_info->cipher_type);
> +	if (!cipher_desc || cipher_desc->key > sizeof(key) ||
> +	    cipher_desc->salt + cipher_desc->iv > sizeof(iv))
> +		return -EINVAL;
> +
> +	rc = tls_hkdf_expand_label(secret, len, "key", key, cipher_desc->key);
> +	if (rc)
> +		goto out;
> +
> +	rc = tls_hkdf_expand_label(secret, len, "iv", iv,
> +				   cipher_desc->salt + cipher_desc->iv);
> +	if (rc)
> +		goto out;
> +

You could add a comment that this follows the rules for key derivation
from RFC 8446, to be clear what we are doing here.

But aside from the:

Reviewed-by: Hannes Reinecke <hare@kernel.org>

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received
  2026-10-28 13:52 ` [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
@ 2026-10-28 14:19   ` Hannes Reinecke
  2026-10-28 17:13     ` Martin Belanger
  0 siblings, 1 reply; 17+ messages in thread
From: Hannes Reinecke @ 2026-10-28 14:19 UTC (permalink / raw)
  To: martin.nvme.dev, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
> From: Martin Belanger <martin.belanger@dell.com>
> 
> When a KeyUpdate arrives, kTLS blocks reads with -EKEYEXPIRED until
> the user installs the next key. A kernel user such as nvme-tcp or
> sunrpc never does, so the connection stalls.
> 
> For a socket with a registered RX secret, derive the next key and
> install it when the KeyUpdate is decrypted. The record is not passed to
> the user, so recvmsg(), splice() and read_sock() go on to the next
> record. Without a registered secret the behavior is unchanged.
> 
> A KeyUpdate that is not alone in its record, has a wrong length or an
> invalid request_update value ends the connection, as RFC 9846 section
> 4.7.3 requires. So does a peer that sends more than 8 KeyUpdates in one
> second, the same limit as GnuTLS.
> 
> Assisted-by: LLM
> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
> ---
>   include/net/tls.h       |   7 +++
>   net/tls/Makefile        |   3 +-
>   net/tls/tls.h           |   2 +
>   net/tls/tls_keyupdate.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++
>   net/tls/tls_sw.c        |  43 ++++++++++++--
>   5 files changed, 198 insertions(+), 7 deletions(-)
> 
> diff --git a/include/net/tls.h b/include/net/tls.h
> index aacc6d12226e..a5bc28a7d1f5 100644
> --- a/include/net/tls.h
> +++ b/include/net/tls.h
> @@ -63,6 +63,11 @@ struct tls_rec;
>   
>   #define TLS_HANDSHAKE_KEYUPDATE		24	/* rfc8446 B.3: Key update */
>   
> +/* KeyUpdate: msg_type, 24-bit length, request_update (RFC 9846 4.7.3) */
> +#define TLS_KEYUPDATE_MSG_SIZE		5
> +#define TLS_KEY_UPDATE_NOT_REQUESTED	0
> +#define TLS_KEY_UPDATE_REQUESTED	1
> +
>   #define TLS_AAD_SPACE_SIZE		13
>   
>   /* SHA-384, the longest hash of a TLS 1.3 cipher suite kTLS supports */
> @@ -142,6 +147,8 @@ struct tls_sw_context_rx {
>   	u8 zc_capable:1;
>   	u8 reader_contended:1;
>   	bool key_update_pending;
> +	u8 key_update_count;		/* received in this window */
> +	unsigned long key_update_window;	/* jiffies */
>   
>   	struct tls_strparser strp;
>   
> diff --git a/net/tls/Makefile b/net/tls/Makefile
> index 6edcf1271b75..ce8ac6a59f8c 100644
> --- a/net/tls/Makefile
> +++ b/net/tls/Makefile
> @@ -7,6 +7,7 @@ CFLAGS_trace.o := -I$(src)
>   
>   obj-$(CONFIG_TLS) += tls.o
>   
> -tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o
> +tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o \
> +	 tls_keyupdate.o
>   
>   tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
> diff --git a/net/tls/tls.h b/net/tls/tls.h
> index b2061f6bf065..63ece8074985 100644
> --- a/net/tls/tls.h
> +++ b/net/tls/tls.h
> @@ -149,6 +149,8 @@ int init_prot_info(struct tls_prot_info *prot,
>   		   const struct tls_cipher_desc *cipher_desc);
>   int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
>   			  const char *label, u8 *out, size_t out_len);
> +int tls_keyupdate_rekey(struct sock *sk, int tx);
> +int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
>   int tls_set_sw_offload(struct sock *sk, int tx,
>   		       struct tls_crypto_info *new_crypto_info);
>   void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
> diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
> new file mode 100644
> index 000000000000..543bf57599c7
> --- /dev/null
> +++ b/net/tls/tls_keyupdate.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
> + *
> + * TLS 1.3 KeyUpdate, RFC 9846 sections 4.7.3 and 7.2.
> + *
> + * A TLS 1.3 peer can change the key of the direction it sends in at any
> + * time. It sends a KeyUpdate message, then encrypts all later records
> + * with the next key. A sender must also change its key before the key
> + * has protected too many records (section 5.5).
> + *
> + * The next key is derived from the application traffic secret, not from
> + * the current key. User space gives the kernel only the key and IV, so
> + * the kernel cannot derive the next key on its own. Without the secret,
> + * a received KeyUpdate goes to the application, and the kernel stops
> + * decrypting until the application installs the next key with TLS_RX.
> + *
> + * With TLS_TX_TRAFFIC_SECRET or TLS_RX_TRAFFIC_SECRET, user space also
> + * gives the kernel the secret of that direction. The kernel then handles
> + * KeyUpdate itself. This file derives and installs the next key, and
> + * processes a received KeyUpdate. tls_sw.c sends KeyUpdate messages.
> + */
> +
> +#include <linux/jiffies.h>
> +#include <linux/skbuff.h>
> +#include <net/strparser.h>
> +#include <net/tls.h>
> +
> +#include "tls.h"
> +
> +/* A peer that sends more KeyUpdates than this in one second ends the
> + * connection. Each one costs three HMACs and a new key, and carries no
> + * data.
> + */
> +#define TLS_KEYUPDATE_RX_MAX_PER_SEC	8
> +
> +/**
> + * tls_keyupdate_rekey - install the next key of one direction
> + * @sk: socket
> + * @tx: non-zero for the transmit direction
> + *
> + * Derive the next application traffic secret from the registered one,
> + * then its key and IV, and install them. The record sequence number
> + * restarts at zero.
> + *
> + * Return: 0 on success, or a negative errno. On error the installed key
> + * and the registered secret do not change.
> + */
> +int tls_keyupdate_rekey(struct sock *sk, int tx)
> +{
> +	struct tls_context *ctx = tls_get_ctx(sk);
> +	struct tls_traffic_secret *secret;
> +	const struct tls_cipher_desc *cipher_desc;
> +	u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
> +	u8 next[TLS_MAX_TRAFFIC_SECRET_SIZE];
> +	union tls_crypto_context tmp = {};
> +	struct tls_crypto_info *info = &tmp.info;
> +	int rc;
> +
> +	secret = tx ? &ctx->tx_secret : &ctx->rx_secret;
> +	cipher_desc = get_cipher_desc(ctx->prot_info.cipher_type);
> +	if (!cipher_desc || !secret->len ||
> +	    cipher_desc->salt + cipher_desc->iv > sizeof(iv))
> +		return -EINVAL;
> +
> +	/* version and cipher_type; the rest of tmp stays zero */
> +	*info = tx ? ctx->crypto_send.info : ctx->crypto_recv.info;
> +
> +	rc = tls_hkdf_expand_label(secret->data, secret->len, "traffic upd",
> +				   next, secret->len);
> +	if (rc)
> +		goto out;
> +
> +	rc = tls_hkdf_expand_label(next, secret->len, "key",
> +				   crypto_info_key(info, cipher_desc),
> +				   cipher_desc->key);
> +	if (rc)
> +		goto out;
> +
> +	rc = tls_hkdf_expand_label(next, secret->len, "iv", iv,
> +				   cipher_desc->salt + cipher_desc->iv);
> +	if (rc)
> +		goto out;
> +
> +	memcpy(crypto_info_salt(info, cipher_desc), iv, cipher_desc->salt);
> +	memcpy(crypto_info_iv(info, cipher_desc), iv + cipher_desc->salt,
> +	       cipher_desc->iv);
> +
> +	rc = tls_set_sw_offload(sk, tx, info);
> +	if (rc)
> +		goto out;
> +
> +	memcpy(secret->data, next, secret->len);
> +
> +out:
> +	memzero_explicit(next, sizeof(next));
> +	memzero_explicit(iv, sizeof(iv));
> +	memzero_explicit(&tmp, sizeof(tmp));
> +
> +	return rc;
> +}
> +
> +/**
> + * tls_keyupdate_rx - process a received KeyUpdate
> + * @sk: socket
> + * @skb: the decrypted record, which starts with a KeyUpdate message
> + *
> + * Called with the socket locked, for a direction with a registered
> + * secret. The caller does not pass the record to the user.
> + *
> + * Return: 0 on success, or a negative errno if the connection must end.
> + */
> +int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
> +{
> +	struct tls_context *ctx = tls_get_ctx(sk);
> +	struct tls_sw_context_rx *rx_ctx = tls_sw_ctx_rx(ctx);
> +	const struct strp_msg *rxm = strp_msg(skb);
> +	u8 msg[TLS_KEYUPDATE_MSG_SIZE];
> +	int rc;
> +
> +	/* A KeyUpdate must end its record (section 5.1). Another message
> +	 * in the same record is not supported.
> +	 */
> +	if (rxm->full_len != sizeof(msg) ||
> +	    skb_copy_bits(skb, rxm->offset, msg, sizeof(msg)))
> +		return -EBADMSG;
> +
> +	/* msg_type, a 24-bit length of 1, then request_update */
> +	if (msg[1] || msg[2] || msg[3] != 1)
> +		return -EBADMSG;
> +	if (msg[4] != TLS_KEY_UPDATE_NOT_REQUESTED &&
> +	    msg[4] != TLS_KEY_UPDATE_REQUESTED)
> +		return -EBADMSG;
> +
> +	if (!rx_ctx->key_update_count ||
> +	    time_after(jiffies, rx_ctx->key_update_window + HZ)) {
> +		rx_ctx->key_update_window = jiffies;
> +		rx_ctx->key_update_count = 0;
> +	}
> +	if (++rx_ctx->key_update_count > TLS_KEYUPDATE_RX_MAX_PER_SEC)

Hmm. I see the rationale, but the actual number falls a bit out of
thin air. And closing the connection with no indiation is bound
to confuse users.
Please add a WARN_ON() statemen there such that the admin is informed,
and I _think_ we could just leave the connection running instead of
closing.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
  2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
                   ` (4 preceding siblings ...)
  2026-10-28 13:52 ` [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
@ 2026-10-28 14:21 ` Chuck Lever
  2026-10-28 14:42   ` Hannes Reinecke
  5 siblings, 1 reply; 17+ messages in thread
From: Chuck Lever @ 2026-10-28 14:21 UTC (permalink / raw)
  To: martin.nvme.dev, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger



On Wed, Oct 28, 2026, at 9:52 AM, Martin Belanger via B4 Relay wrote:
> kTLS does not currently handle TLS 1.3 KeyUpdate. It never sends a
> KeyUpdate. And when it receives one, it blocks the socket until the
> application installs the next key. kTLS also does not enforce the key
> usage limit on transmit.

No argument here.


> This applies to TLS libraries in user space as well as kernel users such
> as NVMe/TCP or sunrpc:
>
> - GnuTLS sends a KeyUpdate after 2^24 records when it runs its own
>   record layer. With kTLS, the record layer is handled in the kernel and
>   GnuTLS therefore never runs the check. OpenSSL never sends a KeyUpdate
>   on its own.
> - NVMe/TCP and sunrpc don't have a mechanism to install a new key. A
>   conforming peer that sends a KeyUpdate stalls their connection.

The design intention of these consumers is that they are supposed to break
the connection and create a new session, rather than stall. If that is not
happening, that's a bug. KeyUpdate is complicated enough that backporting
to LTS is not really an option, so that bug needs to be addressed first,
IMHO.

The argument that was made that put tlshd in user space is that we do
not want the kernel to handle something that the libraries are already
good at (IOW, mechanisms that have been soaked in commodity usage by many
consumers).

Is that rationale different for handling TLS KeyUpdate? I see that the
series adds back the HKDF functionality to the kernel. That seems like
an indication that KeyUpdate should be handled via an upcall.

Are there policy decisions that need to be made during KeyUpdate (like,
has the peer's certificate expired, or has it been put on a revocation
list in the meantime) ?


-- 
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request
  2026-10-28 13:52 ` [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
@ 2026-10-28 14:23   ` Hannes Reinecke
  2026-10-28 17:18     ` Martin Belanger
  0 siblings, 1 reply; 17+ messages in thread
From: Hannes Reinecke @ 2026-10-28 14:23 UTC (permalink / raw)
  To: martin.nvme.dev, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
> From: Martin Belanger <martin.belanger@dell.com>
> 
> RFC 9846 section 5.5 requires a key update before the key reaches its
> usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
> records, and only the record layer knows where records start and end.
> Section 4.7.3 also requires a reply to a KeyUpdate with
> update_requested, sent before the next Application Data record. Both
> need a KeyUpdate at an exact position in the record stream.
> 
> For a socket with a registered TX secret, send a KeyUpdate before the
> next record when the key has protected 2^24 records, as GnuTLS does,
> or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
> The KeyUpdate is sent as a handshake record, which closes the open data
> record first. Pending encryptions are then completed, and only then is
> the next key installed.
> 
> Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
> peer to update too. It can only bring a KeyUpdate forward: the record
> limit is enforced either way. Following section 4.7.3, a new request
> to the peer waits for the answer to the previous one, and no more than
> 2^48-1 epochs are used.
> 
> Assisted-by: LLM
> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
> ---
>   Documentation/networking/tls.rst |  17 +++++
>   include/net/tls.h                |   5 +-
>   include/uapi/linux/tls.h         |   1 +
>   net/tls/tls.h                    |   1 +
>   net/tls/tls_keyupdate.c          |   5 ++
>   net/tls/tls_main.c               |  38 +++++++++-
>   net/tls/tls_sw.c                 | 150 ++++++++++++++++++++++++++++++++++++++-
>   7 files changed, 214 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
> index 2e011bd1ce4a..69752a6d67df 100644
> --- a/Documentation/networking/tls.rst
> +++ b/Documentation/networking/tls.rst
> @@ -326,6 +326,23 @@ direction is then left to the application again.
>   
>   The secrets cannot be read back.
>   
> +With a registered TX secret, the kernel sends a KeyUpdate before the key
> +reaches its record limit (RFC 9846, section 5.5): after 2^24 records, except
> +for ChaCha20-Poly1305, which has no limit. It also sends one before the next
> +record when a received KeyUpdate asks for it. With a registered RX secret,
> +the kernel processes a received KeyUpdate and does not pass the record to
> +the application. A peer that sends more than 8 KeyUpdates in one second ends
> +the connection.
> +
> +TLS_TX_KEY_UPDATE
> +~~~~~~~~~~~~~~~~~
> +
> +TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
> +registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 1 to
> +also ask the peer to update its key. Asking the peer needs a registered RX
> +secret, and fails with ``EBUSY`` until the peer has answered the previous
> +request.
> +
>   Statistics
>   ==========
>   
> diff --git a/include/net/tls.h b/include/net/tls.h
> index a5bc28a7d1f5..d3123b17030c 100644
> --- a/include/net/tls.h
> +++ b/include/net/tls.h
> @@ -282,9 +282,12 @@ struct tls_context {
>   	union tls_crypto_context crypto_send;
>   	union tls_crypto_context crypto_recv;
>   
> -	/* Registered for KeyUpdate, protected by the socket lock */
> +	/* KeyUpdate state, protected by the socket lock */
>   	struct tls_traffic_secret tx_secret;
>   	struct tls_traffic_secret rx_secret;
> +	u64 tx_key_updates;		/* KeyUpdates sent */
> +	bool tx_key_update_reply;	/* peer set update_requested */
> +	bool tx_key_update_asked;	/* we set it, no answer yet */
>   
>   	struct list_head list;
>   	refcount_t refcount;
> diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
> index d1b136ecea96..c85408cb6ce7 100644
> --- a/include/uapi/linux/tls.h
> +++ b/include/uapi/linux/tls.h
> @@ -44,6 +44,7 @@
>   #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
>   #define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
>   #define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
> +#define TLS_TX_KEY_UPDATE	8	/* Send a TLS 1.3 KeyUpdate now */
>   
>   /* Supported versions */
>   #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
> diff --git a/net/tls/tls.h b/net/tls/tls.h
> index 63ece8074985..d12c9278c814 100644
> --- a/net/tls/tls.h
> +++ b/net/tls/tls.h
> @@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
>   			  const char *label, u8 *out, size_t out_len);
>   int tls_keyupdate_rekey(struct sock *sk, int tx);
>   int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
> +int tls_sw_key_update(struct sock *sk, int request);
>   int tls_set_sw_offload(struct sock *sk, int tx,
>   		       struct tls_crypto_info *new_crypto_info);
>   void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
> diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
> index 543bf57599c7..851d5b54b336 100644
> --- a/net/tls/tls_keyupdate.c
> +++ b/net/tls/tls_keyupdate.c
> @@ -146,5 +146,10 @@ int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
>   	}
>   	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
>   
> +	/* Any KeyUpdate from the peer answers our update_requested */
> +	ctx->tx_key_update_asked = false;
> +	if (msg[4] == TLS_KEY_UPDATE_REQUESTED)
> +		ctx->tx_key_update_reply = true;
> +
>   	return 0;
>   }
> diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
> index 8bb1b2773f3f..d71de99d8890 100644
> --- a/net/tls/tls_main.c
> +++ b/net/tls/tls_main.c
> @@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
>   		/* The registered secret no longer matches the key. The RX
>   		 * secret also needs the TX one, see do_tls_setsockopt_secret().
>   		 */
> -		if (tx)
> +		if (tx) {
>   			memzero_explicit(&ctx->tx_secret,
>   					 sizeof(ctx->tx_secret));
> +			ctx->tx_key_update_reply = false;
> +		}
>   		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
> +		/* the answer now goes to user space */
> +		ctx->tx_key_update_asked = false;
>   		return 0;
>   	}
>   
> @@ -987,6 +991,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
>   	return rc;
>   }
>   
> +static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t optval,
> +					unsigned int optlen)
> +{
> +	struct tls_context *ctx = tls_get_ctx(sk);
> +	int value, rc;
> +
> +	if (sockptr_is_null(optval) || optlen != sizeof(value))
> +		return -EINVAL;
> +
> +	if (copy_from_sockptr(&value, optval, sizeof(value)))
> +		return -EFAULT;
> +
> +	if (value != 0 && value != 1)
> +		return -EINVAL;
> +
> +	rc = mutex_lock_interruptible(&ctx->tx_lock);
> +	if (rc)
> +		return rc;
> +	lock_sock(sk);
> +	if (ctx->tx_conf == TLS_SW)
> +		rc = tls_sw_key_update(sk, value);
> +	else
> +		rc = -EINVAL;
> +	release_sock(sk);
> +	mutex_unlock(&ctx->tx_lock);
> +
> +	return rc;
> +}
> +
>   static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
>   			     unsigned int optlen)
>   {
> @@ -1020,6 +1053,9 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
>   					      optname == TLS_TX_TRAFFIC_SECRET);
>   		release_sock(sk);
>   		break;
> +	case TLS_TX_KEY_UPDATE:
> +		rc = do_tls_setsockopt_key_update(sk, optval, optlen);
> +		break;
>   	default:
>   		rc = -ENOPROTOOPT;
>   		break;
> diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
> index cc0177320e7e..4fe78624859d 100644
> --- a/net/tls/tls_sw.c
> +++ b/net/tls/tls_sw.c
> @@ -40,6 +40,7 @@
>   #include <linux/module.h>
>   #include <linux/kernel.h>
>   #include <linux/splice.h>
> +#include <linux/unaligned.h>
>   #include <crypto/aead.h>
>   
>   #include <net/strparser.h>
> @@ -1005,6 +1006,151 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
>   	return copied > 0 ? copied : ret;
>   }
>   
> +/* RFC 9846 section 5.5 allows 2^24.5 full-size records per AES-GCM key.
> + * Send the KeyUpdate a little earlier.
> + */
> +#define TLS_KEYUPDATE_TX_RECORDS	BIT_ULL(24)
> +
> +/* Section 4.7.3: the epoch must not exceed 2^48-1. The application
> + * traffic secrets start at epoch 3.
> + */
> +#define TLS_KEYUPDATE_MAX		(BIT_ULL(48) - 4)
> +
> +/* Send a KeyUpdate with the current key, then install the next key.
> + * Called with tx_lock held and the socket locked.
> + */
> +static int tls_tx_key_update(struct sock *sk, u8 request, int flags)
> +{
> +	u8 body[TLS_KEYUPDATE_MSG_SIZE] = {
> +		TLS_HANDSHAKE_KEYUPDATE, 0, 0, 1, request
> +	};
> +	struct tls_context *tls_ctx = tls_get_ctx(sk);
> +	struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx);
> +	u8 record_type = TLS_RECORD_TYPE_HANDSHAKE;
> +	u8 buf[CMSG_SPACE(sizeof(record_type))];
> +	struct msghdr msg = {};
> +	struct cmsghdr *cmsg;
> +	struct kvec iov;
> +	int ret;
> +
> +	memset(buf, 0, sizeof(buf));
> +	msg.msg_control = buf;
> +	msg.msg_controllen = sizeof(buf);
> +	msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
> +
> +	cmsg = CMSG_FIRSTHDR(&msg);
> +	cmsg->cmsg_level = SOL_TLS;
> +	cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
> +	cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
> +	memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
> +
> +	iov.iov_base = body;
> +	iov.iov_len = sizeof(body);
> +	iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
> +
> +	/* A control record closes the open record first, so all data
> +	 * written so far goes out with the current key, ahead of the
> +	 * KeyUpdate.
> +	 */
> +	ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Nothing may be encrypted with the next key before the KeyUpdate
> +	 * is encrypted with the current one.
> +	 */
> +	ret = tls_encrypt_async_wait(ctx);
> +	if (!ret)
> +		ret = tls_keyupdate_rekey(sk, 1);
> +	if (ret) {
> +		/* The peer expects the next key: the connection is lost */
> +		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
> +		tls_err_abort(sk, ret);
> +		return ret;
> +	}
> +
> +	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
> +	tls_ctx->tx_key_updates++;
> +	if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
> +		tls_ctx->tx_key_update_reply = false;
> +
> +	return 0;
> +}
> +
> +/* Send a KeyUpdate if one is due: in reply to the peer's
> + * update_requested (RFC 9846 section 4.7.3), or before the key reaches
> + * its record limit (section 5.5). ChaCha20-Poly1305 has no record limit:
> + * the sequence number would wrap first.
> + */
> +static int tls_tx_key_update_if_due(struct sock *sk, int flags)

'_if_due()' ? Hmm. 'tls_tx_should_update_key()'?

But otherwise looks good.

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests
  2026-10-28 13:52 ` [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
@ 2026-10-28 14:24   ` Hannes Reinecke
  0 siblings, 0 replies; 17+ messages in thread
From: Hannes Reinecke @ 2026-10-28 14:24 UTC (permalink / raw)
  To: martin.nvme.dev, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
> From: Martin Belanger <martin.belanger@dell.com>
> 
> Test the in-kernel KeyUpdate on a bidirectional connection with both
> traffic secrets registered on each socket:
> 
> - registration errors: wrong length, missing key, TLS 1.2, SM4, the
>    RX secret before the TX secret, and a secret that does not give the
>    installed key;
> - KeyUpdates in both directions with data around them;
> - a data record, a KeyUpdate and another data record queued before
>    the first read;
> - update_requested: the peer answers before its next data, and a
>    second request waits for the answer;
> - splice() across a KeyUpdate;
> - a key installed with TLS_TX removes the registered secrets;
> - after a key installed with TLS_RX, a new update_requested is
>    accepted;
> - TLS_TX_KEY_UPDATE on a shut down socket does not raise SIGPIPE;
> - more than 8 KeyUpdates in one second, and an invalid
>    request_update value, end the connection.
> 
> The keys are derived from the secrets with HKDF-Expand-Label, with the
> HMAC of the kernel through AF_ALG.
> 
> Assisted-by: LLM
> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
> ---
>   tools/testing/selftests/net/config |   4 +
>   tools/testing/selftests/net/tls.c  | 412 +++++++++++++++++++++++++++++++++++++
>   2 files changed, 416 insertions(+)
> 

Reviewed-by: Hannes Reinecke <hare@kernel.org>
Cheers,

Hannes

-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
  2026-10-28 14:21 ` [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Chuck Lever
@ 2026-10-28 14:42   ` Hannes Reinecke
       [not found]     ` <CAPG1HMsnUDa=Q5O4fwZOU=Pd+m7ahzu574Cs+mWoB_9KUT5ASA@mail.gmail.com>
  2026-10-28 15:04     ` Martin Belanger
  0 siblings, 2 replies; 17+ messages in thread
From: Hannes Reinecke @ 2026-10-28 14:42 UTC (permalink / raw)
  To: Chuck Lever, martin.nvme.dev, John Fastabend, Jakub Kicinski,
	Sabrina Dubroca, David S. Miller, Eric Dumazet, Paolo Abeni,
	Simon Horman, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger

On 10/28/26 3:21 PM, Chuck Lever wrote:
> 
> 
> On Wed, Oct 28, 2026, at 9:52 AM, Martin Belanger via B4 Relay wrote:
>> kTLS does not currently handle TLS 1.3 KeyUpdate. It never sends a
>> KeyUpdate. And when it receives one, it blocks the socket until the
>> application installs the next key. kTLS also does not enforce the key
>> usage limit on transmit.
> 
> No argument here.
> 
> 
>> This applies to TLS libraries in user space as well as kernel users such
>> as NVMe/TCP or sunrpc:
>>
>> - GnuTLS sends a KeyUpdate after 2^24 records when it runs its own
>>    record layer. With kTLS, the record layer is handled in the kernel and
>>    GnuTLS therefore never runs the check. OpenSSL never sends a KeyUpdate
>>    on its own.
>> - NVMe/TCP and sunrpc don't have a mechanism to install a new key. A
>>    conforming peer that sends a KeyUpdate stalls their connection.
> 
> The design intention of these consumers is that they are supposed to break
> the connection and create a new session, rather than stall. If that is not
> happening, that's a bug. KeyUpdate is complicated enough that backporting
> to LTS is not really an option, so that bug needs to be addressed first,
> IMHO.
> 
Yes. But Martin send already a patchset for that (well, for NVMe, at least).

> The argument that was made that put tlshd in user space is that we do
> not want the kernel to handle something that the libraries are already
> good at (IOW, mechanisms that have been soaked in commodity usage by many
> consumers).
> 
> Is that rationale different for handling TLS KeyUpdate? I see that the
> series adds back the HKDF functionality to the kernel. That seems like
> an indication that KeyUpdate should be handled via an upcall.
> 
Yes. TLS KeyUpdate requires that all records _after_ the KeyUpdate 
message are encrypted with the next key; idea is here that one can
(and should) do this in-line without stopping/resetting the connection.
_And_ TLS KeyUpdate is done independently on the RX and the TX side.

This makes it extremely hard to implement via tlshd; we would need
to pass the KeyUpdate message to tlshd (which is easy), but then
we would need to stop processing incoming packets until we get
a response from tlshd indicating that it has installed the new
secret.

Which makes the whole idea of KeyUpdate (namely that you can
do it _without_ stopping packet processing) a bit pointless.

And I haven't even mentioned that you would need to be able to
stop RX and TX _independently_, which I'm not sure the various
ULPs can do.

Plus there is the question why ULPs need to be burdened with
protocol specific knowledge; why would I need to code hooks/
handling for an extremely protocol-specific know?
It's far better be handled by the protocol itself.

> Are there policy decisions that need to be made during KeyUpdate (like,
> has the peer's certificate expired, or has it been put on a revocation
> list in the meantime) ?
> 
No. This is not about the certificate, it's about traffic secrets.
If a certificate is expired you need to reset the connection
to install a new certificate; TLS 1.3 does not allow you to do
that while the connection is established.
And handling of expired certificates is an admin choice, not
something one should do automatically.

(The last thing you want is your root fs becoming inaccessible
just because a lazy admin forgot to renew the certificate ...)

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
hare@suse.de                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
       [not found]     ` <CAPG1HMsnUDa=Q5O4fwZOU=Pd+m7ahzu574Cs+mWoB_9KUT5ASA@mail.gmail.com>
@ 2026-10-28 15:01       ` Chuck Lever
  2026-10-28 15:12         ` Martin Belanger
  0 siblings, 1 reply; 17+ messages in thread
From: Chuck Lever @ 2026-10-28 15:01 UTC (permalink / raw)
  To: Martin Belanger, Hannes Reinecke
  Cc: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan, linux-kernel, netdev,
	linux-doc, linux-kselftest, Hannes Reinecke,
	kernel-tls-handshake, linux-nvme, Alistair Francis,
	Martin Belanger



On Wed, Oct 28, 2026, at 10:49 AM, Martin Belanger wrote:
> On Wed, Oct 28, 2026 at 10:42 AM Hannes Reinecke <hare@suse.de> wrote:
>
> Thanks, Hannes. Two points to add.
>
> On 10/28/26 3:21 PM, Chuck Lever wrote:

>> >> This applies to TLS libraries in user space as well as kernel users such
>> >> as NVMe/TCP or sunrpc:
>> >>
>> >> - GnuTLS sends a KeyUpdate after 2^24 records when it runs its own
>> >>    record layer. With kTLS, the record layer is handled in the kernel
>> and
>> >>    GnuTLS therefore never runs the check. OpenSSL never sends a
>> KeyUpdate
>> >>    on its own.
>> >> - NVMe/TCP and sunrpc don't have a mechanism to install a new key. A
>> >>    conforming peer that sends a KeyUpdate stalls their connection.
>> >
>
>
>> > The design intention of these consumers is that they are supposed to
>> break
>> > the connection and create a new session, rather than stall. If that is
>> not
>> > happening, that's a bug. KeyUpdate is complicated enough that backporting
>> > to LTS is not really an option, so that bug needs to be addressed first,
>> > IMHO.
>> >
>
>
> A reconnect is also a valid answer to the record limit: RFC 9846 section
> 5.5 allows
> closing the connection. The cost is the problem. Depending on the load and
> link
> speed, the 2^24 limit can be reached in less than 10 minutes in many cases.
> Each
> reconnect stops the I/O of the queue and needs a new handshake upcall, for
> every
> queue of every controller. A peer that sends KeyUpdate on its own causes
> the same
> reconnects. With this series, the connection stays up.

I get that dropping the connection is bad; the current weak-ass
implementation is meant to handle KeyUpdate until we have a proper
spec-conformant implementation.

My concern here is if the current design is malfunctioning in LTS
kernels, the only mechanism we have is to fix it upstream and backport
the fix. I don't believe stable@ would be happy absorbing this full
series.

Your commit message claims the connection stalls rather than getting
replaced. If that's true, that needs to be fixed before this series
gets merged. Can you confirm? I don't expect such a fix would be large,
but the ordering matters.


-- 
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
  2026-10-28 14:42   ` Hannes Reinecke
       [not found]     ` <CAPG1HMsnUDa=Q5O4fwZOU=Pd+m7ahzu574Cs+mWoB_9KUT5ASA@mail.gmail.com>
@ 2026-10-28 15:04     ` Martin Belanger
  1 sibling, 0 replies; 17+ messages in thread
From: Martin Belanger @ 2026-10-28 15:04 UTC (permalink / raw)
  To: Hannes Reinecke, Chuck Lever, John Fastabend, Jakub Kicinski,
	Sabrina Dubroca, David S. Miller, Eric Dumazet, Paolo Abeni,
	Simon Horman, Jonathan Corbet, Shuah Khan, Randy Dunlap,
	Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger



On 10/28/26 10:42, Hannes Reinecke wrote:
> On 10/28/26 3:21 PM, Chuck Lever wrote:
>>
>>
>> On Wed, Oct 28, 2026, at 9:52 AM, Martin Belanger via B4 Relay wrote:
>>> kTLS does not currently handle TLS 1.3 KeyUpdate. It never sends a
>>> KeyUpdate. And when it receives one, it blocks the socket until the
>>> application installs the next key. kTLS also does not enforce the key
>>> usage limit on transmit.
>>
>> No argument here.
>>
>>
>>> This applies to TLS libraries in user space as well as kernel users such
>>> as NVMe/TCP or sunrpc:
>>>
>>> - GnuTLS sends a KeyUpdate after 2^24 records when it runs its own
>>>    record layer. With kTLS, the record layer is handled in the kernel 
>>> and
>>>    GnuTLS therefore never runs the check. OpenSSL never sends a 
>>> KeyUpdate
>>>    on its own.
>>> - NVMe/TCP and sunrpc don't have a mechanism to install a new key. A
>>>    conforming peer that sends a KeyUpdate stalls their connection.
>>
>> The design intention of these consumers is that they are supposed to 
>> break
>> the connection and create a new session, rather than stall. If that is 
>> not
>> happening, that's a bug. KeyUpdate is complicated enough that backporting
>> to LTS is not really an option, so that bug needs to be addressed first,
>> IMHO.
>>
> Yes. But Martin send already a patchset for that (well, for NVMe, at 
> least).

Thanks, Hannes. Two points to add.

A reconnect is also a valid answer to the record limit: RFC 9846
section 5.5 allows closing the connection. The cost is the problem.
Depending on the load and link speed, the 2^24 limit can be reached in
less than 10 minutes in many cases. Each reconnect stops the I/O of the
queue and needs a new handshake upcall, for every queue of every
controller. A peer that sends KeyUpdate on its own causes the same
reconnects. With this series, the connection stays up.

> 
>> The argument that was made that put tlshd in user space is that we do
>> not want the kernel to handle something that the libraries are already
>> good at (IOW, mechanisms that have been soaked in commodity usage by many
>> consumers).
>>
>> Is that rationale different for handling TLS KeyUpdate? I see that the
>> series adds back the HKDF functionality to the kernel. That seems like
>> an indication that KeyUpdate should be handled via an upcall.
>>

The series does not add back the HKDF library. It computes the HMACs
inline with the HMAC library, as the NVMe PSK derivation in
drivers/nvme/common/auth.c does. That is why commit 6d888db2cfd9
removed the library. The QUIC series also derives its key updates in
the kernel.

GnuTLS does handle a received KeyUpdate with kTLS since kernel 6.14,
but only when user space keeps the GnuTLS session. tlshd does not: it
hands the socket to the kernel after the handshake. So the library
cannot help nvme-tcp or sunrpc here.

Martin.

> Yes. TLS KeyUpdate requires that all records _after_ the KeyUpdate 
> message are encrypted with the next key; idea is here that one can
> (and should) do this in-line without stopping/resetting the connection.
> _And_ TLS KeyUpdate is done independently on the RX and the TX side.
> 
> This makes it extremely hard to implement via tlshd; we would need
> to pass the KeyUpdate message to tlshd (which is easy), but then
> we would need to stop processing incoming packets until we get
> a response from tlshd indicating that it has installed the new
> secret.
> 
> Which makes the whole idea of KeyUpdate (namely that you can
> do it _without_ stopping packet processing) a bit pointless.
> 
> And I haven't even mentioned that you would need to be able to
> stop RX and TX _independently_, which I'm not sure the various
> ULPs can do.
> 
> Plus there is the question why ULPs need to be burdened with
> protocol specific knowledge; why would I need to code hooks/
> handling for an extremely protocol-specific know?
> It's far better be handled by the protocol itself.
> 
>> Are there policy decisions that need to be made during KeyUpdate (like,
>> has the peer's certificate expired, or has it been put on a revocation
>> list in the meantime) ?
>>
> No. This is not about the certificate, it's about traffic secrets.
> If a certificate is expired you need to reset the connection
> to install a new certificate; TLS 1.3 does not allow you to do
> that while the connection is established.
> And handling of expired certificates is an admin choice, not
> something one should do automatically.
> 
> (The last thing you want is your root fs becoming inaccessible
> just because a lazy admin forgot to renew the certificate ...)
> 
> Cheers,
> 
> Hannes


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel
  2026-10-28 15:01       ` Chuck Lever
@ 2026-10-28 15:12         ` Martin Belanger
  0 siblings, 0 replies; 17+ messages in thread
From: Martin Belanger @ 2026-10-28 15:12 UTC (permalink / raw)
  To: Chuck Lever, Hannes Reinecke
  Cc: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
	Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
	Shuah Khan, Randy Dunlap, Shuah Khan, linux-kernel, netdev,
	linux-doc, linux-kselftest, Hannes Reinecke,
	kernel-tls-handshake, linux-nvme, Alistair Francis,
	Martin Belanger



On 10/28/26 11:01, Chuck Lever wrote:

> 
> I get that dropping the connection is bad; the current weak-ass
> implementation is meant to handle KeyUpdate until we have a proper
> spec-conformant implementation.
> 
> My concern here is if the current design is malfunctioning in LTS
> kernels, the only mechanism we have is to fix it upstream and backport
> the fix. I don't believe stable@ would be happy absorbing this full
> series.
> 
> Your commit message claims the connection stalls rather than getting
> replaced. If that's true, that needs to be fixed before this series
> gets merged. Can you confirm? I don't expect such a fix would be large,
> but the ordering matters.
> 

Yes, for nvme-tcp. Since 6.7, tls_sw_read_sock() returns -EINVAL for a 
KeyUpdate record. nvme_tcp_io_work() then stops without error recovery, 
and the queue stalls until a command times out. Only the timeout handler 
replaces the connection.

The fix is small: "nvme-tcp: start error recovery when read_sock fails". 
I posted it on Monday, and Keith applied v2 to nvme-7.4 yesterday:
https://lists.infradead.org/pipermail/linux-nvme/2026-October/066937.html

The queue now starts error recovery at once and reconnects. It has no 
Fixes: tag. I will ask Keith to add Fixes: be8e82caa685 ("nvme-tcp: 
enable TLS handshake upcall") and Cc: stable, so it reaches the LTS 
kernels before this series.

I have not checked sunrpc. Does xprtsock replace the connection when 
tls_sw_read_sock() fails, or does it stall too?

Martin

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received
  2026-10-28 14:19   ` Hannes Reinecke
@ 2026-10-28 17:13     ` Martin Belanger
  0 siblings, 0 replies; 17+ messages in thread
From: Martin Belanger @ 2026-10-28 17:13 UTC (permalink / raw)
  To: Hannes Reinecke, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger



On 10/28/26 10:19, Hannes Reinecke wrote:
> On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
>> From: Martin Belanger <martin.belanger@dell.com>
>>
>> When a KeyUpdate arrives, kTLS blocks reads with -EKEYEXPIRED until
>> the user installs the next key. A kernel user such as nvme-tcp or
>> sunrpc never does, so the connection stalls.
>>
>> For a socket with a registered RX secret, derive the next key and
>> install it when the KeyUpdate is decrypted. The record is not passed to
>> the user, so recvmsg(), splice() and read_sock() go on to the next
>> record. Without a registered secret the behavior is unchanged.
>>
>> A KeyUpdate that is not alone in its record, has a wrong length or an
>> invalid request_update value ends the connection, as RFC 9846 section
>> 4.7.3 requires. So does a peer that sends more than 8 KeyUpdates in one
>> second, the same limit as GnuTLS.
>>
>> Assisted-by: LLM
>> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
>> ---
>>   include/net/tls.h       |   7 +++
>>   net/tls/Makefile        |   3 +-
>>   net/tls/tls.h           |   2 +
>>   net/tls/tls_keyupdate.c | 150 ++++++++++++++++++++++++++++++++++++++ 
>> ++++++++++
>>   net/tls/tls_sw.c        |  43 ++++++++++++--
>>   5 files changed, 198 insertions(+), 7 deletions(-)
>>
>> diff --git a/include/net/tls.h b/include/net/tls.h
>> index aacc6d12226e..a5bc28a7d1f5 100644
>> --- a/include/net/tls.h
>> +++ b/include/net/tls.h
>> @@ -63,6 +63,11 @@ struct tls_rec;
>>   #define TLS_HANDSHAKE_KEYUPDATE        24    /* rfc8446 B.3: Key 
>> update */
>> +/* KeyUpdate: msg_type, 24-bit length, request_update (RFC 9846 
>> 4.7.3) */
>> +#define TLS_KEYUPDATE_MSG_SIZE        5
>> +#define TLS_KEY_UPDATE_NOT_REQUESTED    0
>> +#define TLS_KEY_UPDATE_REQUESTED    1
>> +
>>   #define TLS_AAD_SPACE_SIZE        13
>>   /* SHA-384, the longest hash of a TLS 1.3 cipher suite kTLS supports */
>> @@ -142,6 +147,8 @@ struct tls_sw_context_rx {
>>       u8 zc_capable:1;
>>       u8 reader_contended:1;
>>       bool key_update_pending;
>> +    u8 key_update_count;        /* received in this window */
>> +    unsigned long key_update_window;    /* jiffies */
>>       struct tls_strparser strp;
>> diff --git a/net/tls/Makefile b/net/tls/Makefile
>> index 6edcf1271b75..ce8ac6a59f8c 100644
>> --- a/net/tls/Makefile
>> +++ b/net/tls/Makefile
>> @@ -7,6 +7,7 @@ CFLAGS_trace.o := -I$(src)
>>   obj-$(CONFIG_TLS) += tls.o
>> -tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o
>> +tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o \
>> +     tls_keyupdate.o
>>   tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
>> diff --git a/net/tls/tls.h b/net/tls/tls.h
>> index b2061f6bf065..63ece8074985 100644
>> --- a/net/tls/tls.h
>> +++ b/net/tls/tls.h
>> @@ -149,6 +149,8 @@ int init_prot_info(struct tls_prot_info *prot,
>>              const struct tls_cipher_desc *cipher_desc);
>>   int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
>>                 const char *label, u8 *out, size_t out_len);
>> +int tls_keyupdate_rekey(struct sock *sk, int tx);
>> +int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
>>   int tls_set_sw_offload(struct sock *sk, int tx,
>>                  struct tls_crypto_info *new_crypto_info);
>>   void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
>> diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
>> new file mode 100644
>> index 000000000000..543bf57599c7
>> --- /dev/null
>> +++ b/net/tls/tls_keyupdate.c
>> @@ -0,0 +1,150 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/* Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
>> + *
>> + * TLS 1.3 KeyUpdate, RFC 9846 sections 4.7.3 and 7.2.
>> + *
>> + * A TLS 1.3 peer can change the key of the direction it sends in at any
>> + * time. It sends a KeyUpdate message, then encrypts all later records
>> + * with the next key. A sender must also change its key before the key
>> + * has protected too many records (section 5.5).
>> + *
>> + * The next key is derived from the application traffic secret, not from
>> + * the current key. User space gives the kernel only the key and IV, so
>> + * the kernel cannot derive the next key on its own. Without the secret,
>> + * a received KeyUpdate goes to the application, and the kernel stops
>> + * decrypting until the application installs the next key with TLS_RX.
>> + *
>> + * With TLS_TX_TRAFFIC_SECRET or TLS_RX_TRAFFIC_SECRET, user space also
>> + * gives the kernel the secret of that direction. The kernel then 
>> handles
>> + * KeyUpdate itself. This file derives and installs the next key, and
>> + * processes a received KeyUpdate. tls_sw.c sends KeyUpdate messages.
>> + */
>> +
>> +#include <linux/jiffies.h>
>> +#include <linux/skbuff.h>
>> +#include <net/strparser.h>
>> +#include <net/tls.h>
>> +
>> +#include "tls.h"
>> +
>> +/* A peer that sends more KeyUpdates than this in one second ends the
>> + * connection. Each one costs three HMACs and a new key, and carries no
>> + * data.
>> + */
>> +#define TLS_KEYUPDATE_RX_MAX_PER_SEC    8
>> +
>> +/**
>> + * tls_keyupdate_rekey - install the next key of one direction
>> + * @sk: socket
>> + * @tx: non-zero for the transmit direction
>> + *
>> + * Derive the next application traffic secret from the registered one,
>> + * then its key and IV, and install them. The record sequence number
>> + * restarts at zero.
>> + *
>> + * Return: 0 on success, or a negative errno. On error the installed key
>> + * and the registered secret do not change.
>> + */
>> +int tls_keyupdate_rekey(struct sock *sk, int tx)
>> +{
>> +    struct tls_context *ctx = tls_get_ctx(sk);
>> +    struct tls_traffic_secret *secret;
>> +    const struct tls_cipher_desc *cipher_desc;
>> +    u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
>> +    u8 next[TLS_MAX_TRAFFIC_SECRET_SIZE];
>> +    union tls_crypto_context tmp = {};
>> +    struct tls_crypto_info *info = &tmp.info;
>> +    int rc;
>> +
>> +    secret = tx ? &ctx->tx_secret : &ctx->rx_secret;
>> +    cipher_desc = get_cipher_desc(ctx->prot_info.cipher_type);
>> +    if (!cipher_desc || !secret->len ||
>> +        cipher_desc->salt + cipher_desc->iv > sizeof(iv))
>> +        return -EINVAL;
>> +
>> +    /* version and cipher_type; the rest of tmp stays zero */
>> +    *info = tx ? ctx->crypto_send.info : ctx->crypto_recv.info;
>> +
>> +    rc = tls_hkdf_expand_label(secret->data, secret->len, "traffic upd",
>> +                   next, secret->len);
>> +    if (rc)
>> +        goto out;
>> +
>> +    rc = tls_hkdf_expand_label(next, secret->len, "key",
>> +                   crypto_info_key(info, cipher_desc),
>> +                   cipher_desc->key);
>> +    if (rc)
>> +        goto out;
>> +
>> +    rc = tls_hkdf_expand_label(next, secret->len, "iv", iv,
>> +                   cipher_desc->salt + cipher_desc->iv);
>> +    if (rc)
>> +        goto out;
>> +
>> +    memcpy(crypto_info_salt(info, cipher_desc), iv, cipher_desc->salt);
>> +    memcpy(crypto_info_iv(info, cipher_desc), iv + cipher_desc->salt,
>> +           cipher_desc->iv);
>> +
>> +    rc = tls_set_sw_offload(sk, tx, info);
>> +    if (rc)
>> +        goto out;
>> +
>> +    memcpy(secret->data, next, secret->len);
>> +
>> +out:
>> +    memzero_explicit(next, sizeof(next));
>> +    memzero_explicit(iv, sizeof(iv));
>> +    memzero_explicit(&tmp, sizeof(tmp));
>> +
>> +    return rc;
>> +}
>> +
>> +/**
>> + * tls_keyupdate_rx - process a received KeyUpdate
>> + * @sk: socket
>> + * @skb: the decrypted record, which starts with a KeyUpdate message
>> + *
>> + * Called with the socket locked, for a direction with a registered
>> + * secret. The caller does not pass the record to the user.
>> + *
>> + * Return: 0 on success, or a negative errno if the connection must end.
>> + */
>> +int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
>> +{
>> +    struct tls_context *ctx = tls_get_ctx(sk);
>> +    struct tls_sw_context_rx *rx_ctx = tls_sw_ctx_rx(ctx);
>> +    const struct strp_msg *rxm = strp_msg(skb);
>> +    u8 msg[TLS_KEYUPDATE_MSG_SIZE];
>> +    int rc;
>> +
>> +    /* A KeyUpdate must end its record (section 5.1). Another message
>> +     * in the same record is not supported.
>> +     */
>> +    if (rxm->full_len != sizeof(msg) ||
>> +        skb_copy_bits(skb, rxm->offset, msg, sizeof(msg)))
>> +        return -EBADMSG;
>> +
>> +    /* msg_type, a 24-bit length of 1, then request_update */
>> +    if (msg[1] || msg[2] || msg[3] != 1)
>> +        return -EBADMSG;
>> +    if (msg[4] != TLS_KEY_UPDATE_NOT_REQUESTED &&
>> +        msg[4] != TLS_KEY_UPDATE_REQUESTED)
>> +        return -EBADMSG;
>> +
>> +    if (!rx_ctx->key_update_count ||
>> +        time_after(jiffies, rx_ctx->key_update_window + HZ)) {
>> +        rx_ctx->key_update_window = jiffies;
>> +        rx_ctx->key_update_count = 0;
>> +    }
>> +    if (++rx_ctx->key_update_count > TLS_KEYUPDATE_RX_MAX_PER_SEC)
> 
> Hmm. I see the rationale, but the actual number falls a bit out of
> thin air. And closing the connection with no indiation is bound
> to confuse users.

It's just to prevent a flood of KeyUpdates.

The number comes from GnuTLS: lib/tls13/key_update.c has
KEY_UPDATES_PER_WINDOW 8 in a window of 1000 ms, and the ninth
KeyUpdate ends the session with GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS.
GnuTLS logs it only at debug level. OpenSSL has no limit.

> Please add a WARN_ON() statemen there such that the admin is informed,
> and I _think_ we could just leave the connection running instead of
> closing.

The peer controls this condition, so a WARN_ON() would let a remote
peer trigger kernel warnings, and a panic with panic_on_warn. I would
use net_warn_ratelimited() and a MIB counter instead.

Leaving the connection running is fine with me. The peer is
authenticated, the cost per KeyUpdate is three HMACs and a key change,
and OpenSSL accepts any number. In v3, I will drop the limit and the
close, and count and log a peer that sends more than 8 KeyUpdates in
one second. Does that work for you?

> 
> Cheers,
> 
> Hannes


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request
  2026-10-28 14:23   ` Hannes Reinecke
@ 2026-10-28 17:18     ` Martin Belanger
  0 siblings, 0 replies; 17+ messages in thread
From: Martin Belanger @ 2026-10-28 17:18 UTC (permalink / raw)
  To: Hannes Reinecke, John Fastabend, Jakub Kicinski, Sabrina Dubroca,
	David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
	Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan
  Cc: linux-kernel, netdev, linux-doc, linux-kselftest,
	Hannes Reinecke, Chuck Lever, kernel-tls-handshake, linux-nvme,
	Alistair Francis, Martin Belanger



On 10/28/26 10:23, Hannes Reinecke wrote:
> On 10/28/26 2:52 PM, Martin Belanger via B4 Relay wrote:
>> From: Martin Belanger <martin.belanger@dell.com>
>>
>> RFC 9846 section 5.5 requires a key update before the key reaches its
>> usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
>> records, and only the record layer knows where records start and end.
>> Section 4.7.3 also requires a reply to a KeyUpdate with
>> update_requested, sent before the next Application Data record. Both
>> need a KeyUpdate at an exact position in the record stream.
>>
>> For a socket with a registered TX secret, send a KeyUpdate before the
>> next record when the key has protected 2^24 records, as GnuTLS does,
>> or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
>> The KeyUpdate is sent as a handshake record, which closes the open data
>> record first. Pending encryptions are then completed, and only then is
>> the next key installed.
>>
>> Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
>> peer to update too. It can only bring a KeyUpdate forward: the record
>> limit is enforced either way. Following section 4.7.3, a new request
>> to the peer waits for the answer to the previous one, and no more than
>> 2^48-1 epochs are used.
>>
>> Assisted-by: LLM
>> Signed-off-by: Martin Belanger <martin.belanger@dell.com>
>> ---
>>   Documentation/networking/tls.rst |  17 +++++
>>   include/net/tls.h                |   5 +-
>>   include/uapi/linux/tls.h         |   1 +
>>   net/tls/tls.h                    |   1 +
>>   net/tls/tls_keyupdate.c          |   5 ++
>>   net/tls/tls_main.c               |  38 +++++++++-
>>   net/tls/tls_sw.c                 | 150 +++++++++++++++++++++++++++++ 
>> +++++++++-
>>   7 files changed, 214 insertions(+), 3 deletions(-)
>>
>> diff --git a/Documentation/networking/tls.rst b/Documentation/ 
>> networking/tls.rst
>> index 2e011bd1ce4a..69752a6d67df 100644
>> --- a/Documentation/networking/tls.rst
>> +++ b/Documentation/networking/tls.rst
>> @@ -326,6 +326,23 @@ direction is then left to the application again.
>>   The secrets cannot be read back.
>> +With a registered TX secret, the kernel sends a KeyUpdate before the key
>> +reaches its record limit (RFC 9846, section 5.5): after 2^24 records, 
>> except
>> +for ChaCha20-Poly1305, which has no limit. It also sends one before 
>> the next
>> +record when a received KeyUpdate asks for it. With a registered RX 
>> secret,
>> +the kernel processes a received KeyUpdate and does not pass the 
>> record to
>> +the application. A peer that sends more than 8 KeyUpdates in one 
>> second ends
>> +the connection.
>> +
>> +TLS_TX_KEY_UPDATE
>> +~~~~~~~~~~~~~~~~~
>> +
>> +TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
>> +registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 
>> 1 to
>> +also ask the peer to update its key. Asking the peer needs a 
>> registered RX
>> +secret, and fails with ``EBUSY`` until the peer has answered the 
>> previous
>> +request.
>> +
>>   Statistics
>>   ==========
>> diff --git a/include/net/tls.h b/include/net/tls.h
>> index a5bc28a7d1f5..d3123b17030c 100644
>> --- a/include/net/tls.h
>> +++ b/include/net/tls.h
>> @@ -282,9 +282,12 @@ struct tls_context {
>>       union tls_crypto_context crypto_send;
>>       union tls_crypto_context crypto_recv;
>> -    /* Registered for KeyUpdate, protected by the socket lock */
>> +    /* KeyUpdate state, protected by the socket lock */
>>       struct tls_traffic_secret tx_secret;
>>       struct tls_traffic_secret rx_secret;
>> +    u64 tx_key_updates;        /* KeyUpdates sent */
>> +    bool tx_key_update_reply;    /* peer set update_requested */
>> +    bool tx_key_update_asked;    /* we set it, no answer yet */
>>       struct list_head list;
>>       refcount_t refcount;
>> diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
>> index d1b136ecea96..c85408cb6ce7 100644
>> --- a/include/uapi/linux/tls.h
>> +++ b/include/uapi/linux/tls.h
>> @@ -44,6 +44,7 @@
>>   #define TLS_TX_MAX_PAYLOAD_LEN    5    /* Maximum plaintext size */
>>   #define TLS_TX_TRAFFIC_SECRET    6    /* TLS 1.3 TX traffic secret */
>>   #define TLS_RX_TRAFFIC_SECRET    7    /* TLS 1.3 RX traffic secret */
>> +#define TLS_TX_KEY_UPDATE    8    /* Send a TLS 1.3 KeyUpdate now */
>>   /* Supported versions */
>>   #define TLS_VERSION_MINOR(ver)    ((ver) & 0xFF)
>> diff --git a/net/tls/tls.h b/net/tls/tls.h
>> index 63ece8074985..d12c9278c814 100644
>> --- a/net/tls/tls.h
>> +++ b/net/tls/tls.h
>> @@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t 
>> secret_len,
>>                 const char *label, u8 *out, size_t out_len);
>>   int tls_keyupdate_rekey(struct sock *sk, int tx);
>>   int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
>> +int tls_sw_key_update(struct sock *sk, int request);
>>   int tls_set_sw_offload(struct sock *sk, int tx,
>>                  struct tls_crypto_info *new_crypto_info);
>>   void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
>> diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
>> index 543bf57599c7..851d5b54b336 100644
>> --- a/net/tls/tls_keyupdate.c
>> +++ b/net/tls/tls_keyupdate.c
>> @@ -146,5 +146,10 @@ int tls_keyupdate_rx(struct sock *sk, struct 
>> sk_buff *skb)
>>       }
>>       TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
>> +    /* Any KeyUpdate from the peer answers our update_requested */
>> +    ctx->tx_key_update_asked = false;
>> +    if (msg[4] == TLS_KEY_UPDATE_REQUESTED)
>> +        ctx->tx_key_update_reply = true;
>> +
>>       return 0;
>>   }
>> diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
>> index 8bb1b2773f3f..d71de99d8890 100644
>> --- a/net/tls/tls_main.c
>> +++ b/net/tls/tls_main.c
>> @@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock 
>> *sk, sockptr_t optval,
>>           /* The registered secret no longer matches the key. The RX
>>            * secret also needs the TX one, see 
>> do_tls_setsockopt_secret().
>>            */
>> -        if (tx)
>> +        if (tx) {
>>               memzero_explicit(&ctx->tx_secret,
>>                        sizeof(ctx->tx_secret));
>> +            ctx->tx_key_update_reply = false;
>> +        }
>>           memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
>> +        /* the answer now goes to user space */
>> +        ctx->tx_key_update_asked = false;
>>           return 0;
>>       }
>> @@ -987,6 +991,35 @@ static int do_tls_setsockopt_secret(struct sock 
>> *sk, sockptr_t optval,
>>       return rc;
>>   }
>> +static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t 
>> optval,
>> +                    unsigned int optlen)
>> +{
>> +    struct tls_context *ctx = tls_get_ctx(sk);
>> +    int value, rc;
>> +
>> +    if (sockptr_is_null(optval) || optlen != sizeof(value))
>> +        return -EINVAL;
>> +
>> +    if (copy_from_sockptr(&value, optval, sizeof(value)))
>> +        return -EFAULT;
>> +
>> +    if (value != 0 && value != 1)
>> +        return -EINVAL;
>> +
>> +    rc = mutex_lock_interruptible(&ctx->tx_lock);
>> +    if (rc)
>> +        return rc;
>> +    lock_sock(sk);
>> +    if (ctx->tx_conf == TLS_SW)
>> +        rc = tls_sw_key_update(sk, value);
>> +    else
>> +        rc = -EINVAL;
>> +    release_sock(sk);
>> +    mutex_unlock(&ctx->tx_lock);
>> +
>> +    return rc;
>> +}
>> +
>>   static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t 
>> optval,
>>                    unsigned int optlen)
>>   {
>> @@ -1020,6 +1053,9 @@ static int do_tls_setsockopt(struct sock *sk, 
>> int optname, sockptr_t optval,
>>                             optname == TLS_TX_TRAFFIC_SECRET);
>>           release_sock(sk);
>>           break;
>> +    case TLS_TX_KEY_UPDATE:
>> +        rc = do_tls_setsockopt_key_update(sk, optval, optlen);
>> +        break;
>>       default:
>>           rc = -ENOPROTOOPT;
>>           break;
>> diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
>> index cc0177320e7e..4fe78624859d 100644
>> --- a/net/tls/tls_sw.c
>> +++ b/net/tls/tls_sw.c
>> @@ -40,6 +40,7 @@
>>   #include <linux/module.h>
>>   #include <linux/kernel.h>
>>   #include <linux/splice.h>
>> +#include <linux/unaligned.h>
>>   #include <crypto/aead.h>
>>   #include <net/strparser.h>
>> @@ -1005,6 +1006,151 @@ static int tls_sw_sendmsg_locked(struct sock 
>> *sk, struct msghdr *msg,
>>       return copied > 0 ? copied : ret;
>>   }
>> +/* RFC 9846 section 5.5 allows 2^24.5 full-size records per AES-GCM key.
>> + * Send the KeyUpdate a little earlier.
>> + */
>> +#define TLS_KEYUPDATE_TX_RECORDS    BIT_ULL(24)
>> +
>> +/* Section 4.7.3: the epoch must not exceed 2^48-1. The application
>> + * traffic secrets start at epoch 3.
>> + */
>> +#define TLS_KEYUPDATE_MAX        (BIT_ULL(48) - 4)
>> +
>> +/* Send a KeyUpdate with the current key, then install the next key.
>> + * Called with tx_lock held and the socket locked.
>> + */
>> +static int tls_tx_key_update(struct sock *sk, u8 request, int flags)
>> +{
>> +    u8 body[TLS_KEYUPDATE_MSG_SIZE] = {
>> +        TLS_HANDSHAKE_KEYUPDATE, 0, 0, 1, request
>> +    };
>> +    struct tls_context *tls_ctx = tls_get_ctx(sk);
>> +    struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx);
>> +    u8 record_type = TLS_RECORD_TYPE_HANDSHAKE;
>> +    u8 buf[CMSG_SPACE(sizeof(record_type))];
>> +    struct msghdr msg = {};
>> +    struct cmsghdr *cmsg;
>> +    struct kvec iov;
>> +    int ret;
>> +
>> +    memset(buf, 0, sizeof(buf));
>> +    msg.msg_control = buf;
>> +    msg.msg_controllen = sizeof(buf);
>> +    msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
>> +
>> +    cmsg = CMSG_FIRSTHDR(&msg);
>> +    cmsg->cmsg_level = SOL_TLS;
>> +    cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
>> +    cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
>> +    memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
>> +
>> +    iov.iov_base = body;
>> +    iov.iov_len = sizeof(body);
>> +    iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
>> +
>> +    /* A control record closes the open record first, so all data
>> +     * written so far goes out with the current key, ahead of the
>> +     * KeyUpdate.
>> +     */
>> +    ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    /* Nothing may be encrypted with the next key before the KeyUpdate
>> +     * is encrypted with the current one.
>> +     */
>> +    ret = tls_encrypt_async_wait(ctx);
>> +    if (!ret)
>> +        ret = tls_keyupdate_rekey(sk, 1);
>> +    if (ret) {
>> +        /* The peer expects the next key: the connection is lost */
>> +        TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
>> +        tls_err_abort(sk, ret);
>> +        return ret;
>> +    }
>> +
>> +    TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
>> +    tls_ctx->tx_key_updates++;
>> +    if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
>> +        tls_ctx->tx_key_update_reply = false;
>> +
>> +    return 0;
>> +}
>> +
>> +/* Send a KeyUpdate if one is due: in reply to the peer's
>> + * update_requested (RFC 9846 section 4.7.3), or before the key reaches
>> + * its record limit (section 5.5). ChaCha20-Poly1305 has no record 
>> limit:
>> + * the sequence number would wrap first.
>> + */
>> +static int tls_tx_key_update_if_due(struct sock *sk, int flags)
> 
> '_if_due()' ? Hmm. 'tls_tx_should_update_key()'?

A "should" name usually means a check that returns bool, like
tcp_should_autocork(). This function also sends the KeyUpdate, and it
returns -EKEYEXPIRED at the epoch limit.

net/tls already uses "maybe" for a function that acts only when needed:
tls_rx_msg_maybe_announce() in tls_strp.c. I will rename this one to
tls_tx_maybe_update_key() in v3.

> 
> But otherwise looks good.
> 
> Cheers,
> 
> Hannes


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-10-28 17:18 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-28 13:52 ` [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
2026-10-28 13:52 ` [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
2026-10-28 14:13   ` Hannes Reinecke
2026-10-28 13:52 ` [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
2026-10-28 14:19   ` Hannes Reinecke
2026-10-28 17:13     ` Martin Belanger
2026-10-28 13:52 ` [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
2026-10-28 14:23   ` Hannes Reinecke
2026-10-28 17:18     ` Martin Belanger
2026-10-28 13:52 ` [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
2026-10-28 14:24   ` Hannes Reinecke
2026-10-28 14:21 ` [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Chuck Lever
2026-10-28 14:42   ` Hannes Reinecke
     [not found]     ` <CAPG1HMsnUDa=Q5O4fwZOU=Pd+m7ahzu574Cs+mWoB_9KUT5ASA@mail.gmail.com>
2026-10-28 15:01       ` Chuck Lever
2026-10-28 15:12         ` Martin Belanger
2026-10-28 15:04     ` Martin Belanger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®