* [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
2026-10-26 0:36 [PATCH RFC net-next 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
@ 2026-10-26 0:36 ` Martin Belanger via B4 Relay
2026-10-26 7:27 ` Hannes Reinecke
2026-10-26 0:36 ` [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-26 0:36 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
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 related [flat|nested] 12+ messages in thread* Re: [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
2026-10-26 0:36 ` [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
@ 2026-10-26 7:27 ` Hannes Reinecke
2026-10-26 11:25 ` Martin Belanger
0 siblings, 1 reply; 12+ messages in thread
From: Hannes Reinecke @ 2026-10-26 7:27 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/26/26 1:36 AM, Martin Belanger via B4 Relay wrote:
> 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
> 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(-)
>
And another HKDF implementation. Maybe it's time to split if off (again).
But anyway.
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] 12+ messages in thread
* Re: [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
2026-10-26 7:27 ` Hannes Reinecke
@ 2026-10-26 11:25 ` Martin Belanger
0 siblings, 0 replies; 12+ messages in thread
From: Martin Belanger @ 2026-10-26 11:25 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/26/26 03:27, Hannes Reinecke wrote:
> On 10/26/26 1:36 AM, Martin Belanger via B4 Relay wrote:
>> 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
>> 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(-)
>>
> And another HKDF implementation. Maybe it's time to split if off (again).
> But anyway.
>
Yes. crypto/hkdf.c went away again in 6d888db2cfd9 ("crypto: remove
HKDF library"), when fscrypt and nvme-auth moved to the HMAC library.
tls_hkdf.c follows that pattern. It needs only HKDF-Expand-Label, which
is one HMAC here. If a shared helper is wanted again, I would do it in
a separate series.
Thanks for the review.
Martin
> Reviewed-by: Hannes Reinecke <hare@kernel.org>
>
> Cheers,
>
> Hannes
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets
2026-10-26 0:36 [PATCH RFC net-next 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-26 0:36 ` [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
@ 2026-10-26 0:36 ` Martin Belanger via B4 Relay
2026-10-26 7:37 ` Hannes Reinecke
2026-10-26 0:36 ` [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
` (2 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-26 0:36 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 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 | 86 +++++++++++++++++++++++++++++++++++++++-
4 files changed, 126 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..1c6e993ec012 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -31,6 +31,7 @@
* SOFTWARE.
*/
+#include <crypto/sha2.h>
#include <linux/module.h>
#include <net/tcp.h>
@@ -325,6 +326,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 +760,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 +868,72 @@ 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;
+ }
+}
+
+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;
+ int len;
+
+ 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(secret->data, optval, len)) {
+ memzero_explicit(secret->data, sizeof(secret->data));
+ return -EFAULT;
+ }
+ secret->len = len;
+
+ return 0;
+}
+
static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
unsigned int optlen)
{
@@ -883,6 +960,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 related [flat|nested] 12+ messages in thread* Re: [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets
2026-10-26 0:36 ` [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
@ 2026-10-26 7:37 ` Hannes Reinecke
2026-10-26 11:27 ` Martin Belanger
0 siblings, 1 reply; 12+ messages in thread
From: Hannes Reinecke @ 2026-10-26 7:37 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/26/26 1:36 AM, 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 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 | 86 +++++++++++++++++++++++++++++++++++++++-
> 4 files changed, 126 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..1c6e993ec012 100644
> --- a/net/tls/tls_main.c
> +++ b/net/tls/tls_main.c
> @@ -31,6 +31,7 @@
> * SOFTWARE.
> */
>
> +#include <crypto/sha2.h>
> #include <linux/module.h>
>
> #include <net/tcp.h>
> @@ -325,6 +326,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 +760,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().
> + */
What is a bit unfortunate that we don't have a direct link between the
key and the traffic secret. So a user might set a key which is _not_
derived from the traffic secret, rendering the KeyUpdate non-workable.
Can we add a check here?
It should be possible to calculate the key from the traffic secret, no?
> + 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 +868,72 @@ 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;
> + }
> +}
> +
> +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;
> + int len;
> +
> + 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;
Hmm? What is this? Doesn't that imply that you first have to set the
TX key before setting the RX key?
The documentation says we can do either way ...
> +
> + if (secret->len)
> + return -EBUSY;
> +
> + if (copy_from_sockptr(secret->data, optval, len)) {
> + memzero_explicit(secret->data, sizeof(secret->data));
> + return -EFAULT;
> + }
> + secret->len = len;
> +
> + return 0;
> +}
> +
> static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
> unsigned int optlen)
> {
> @@ -883,6 +960,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;
>
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] 12+ messages in thread* Re: [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets
2026-10-26 7:37 ` Hannes Reinecke
@ 2026-10-26 11:27 ` Martin Belanger
0 siblings, 0 replies; 12+ messages in thread
From: Martin Belanger @ 2026-10-26 11:27 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/26/26 03:37, Hannes Reinecke wrote:
> On 10/26/26 1:36 AM, 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 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 | 86
>> +++++++++++++++++++++++++++++++++++++++-
>> 4 files changed, 126 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..1c6e993ec012 100644
>> --- a/net/tls/tls_main.c
>> +++ b/net/tls/tls_main.c
>> @@ -31,6 +31,7 @@
>> * SOFTWARE.
>> */
>> +#include <crypto/sha2.h>
>> #include <linux/module.h>
>> #include <net/tcp.h>
>> @@ -325,6 +326,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 +760,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().
>> + */
>
> What is a bit unfortunate that we don't have a direct link between the
> key and the traffic secret. So a user might set a key which is _not_
> derived from the traffic secret, rendering the KeyUpdate non-workable.
>
> Can we add a check here?
> It should be possible to calculate the key from the traffic secret, no?
>
Yes. The key is HKDF-Expand-Label(secret, "key") and the IV is
HKDF-Expand-Label(secret, "iv"). When the secret is registered, the
kernel can derive both and compare them with the installed key and IV.
A secret that does not match fails with EINVAL. I will add this in v2.
>> + 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 +868,72 @@ 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;
>> + }
>> +}
>> +
>> +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;
>> + int len;
>> +
>> + 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;
>
> Hmm? What is this? Doesn't that imply that you first have to set the
> TX key before setting the RX key?
> The documentation says we can do either way ...
The order applies only to the secrets. TLS_TX and TLS_RX can still be
set in either order. tls.rst says "TLS_RX_TRAFFIC_SECRET needs
TLS_TX_TRAFFIC_SECRET first".
The check refuses an RX secret without a TX secret. A received
KeyUpdate can have update_requested set. The kernel consumes the
record, so only the kernel can send the reply, and the reply needs the
TX secret. An RX secret alone could never answer it.
An application normally sets both secrets one after the other, right
after the handshake. A KeyUpdate is processed only when the socket is
read, so it cannot arrive between the two calls unless another thread
reads the socket.
Question 1 of the cover letter, one option for both secrets, would
remove the order. I will wait for other opinions on it.
Martin
>
>> +
>> + if (secret->len)
>> + return -EBUSY;
>> +
>> + if (copy_from_sockptr(secret->data, optval, len)) {
>> + memzero_explicit(secret->data, sizeof(secret->data));
>> + return -EFAULT;
>> + }
>> + secret->len = len;
>> +
>> + return 0;
>> +}
>> +
>> static int do_tls_setsockopt(struct sock *sk, int optname,
>> sockptr_t optval,
>> unsigned int optlen)
>> {
>> @@ -883,6 +960,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;
>>
> Cheers,
>
> Hannes
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received
2026-10-26 0:36 [PATCH RFC net-next 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-26 0:36 ` [PATCH RFC net-next 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
2026-10-26 0:36 ` [PATCH RFC net-next 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
@ 2026-10-26 0:36 ` Martin Belanger via B4 Relay
2026-10-26 7:53 ` Hannes Reinecke
2026-10-26 0:36 ` [PATCH RFC net-next 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
2026-10-26 0:36 ` [PATCH RFC net-next 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
4 siblings, 1 reply; 12+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-26 0:36 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 | 35 +++++++++--
5 files changed, 190 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..a314fa638dc3 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1551,15 +1551,26 @@ 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);
+ 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 +1894,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 +2035,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 +2051,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 +2136,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,
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received
2026-10-26 0:36 ` [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
@ 2026-10-26 7:53 ` Hannes Reinecke
2026-10-26 11:28 ` Martin Belanger
0 siblings, 1 reply; 12+ messages in thread
From: Hannes Reinecke @ 2026-10-26 7:53 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/26/26 1:36 AM, 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 | 35 +++++++++--
> 5 files changed, 190 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;
> +}
> +
Hmm. So this calculates 'iv' and 'key' from the traffic secret.
But as it does, why can't we use it to calculate the _initial_ 'iv' and
'key' from the traffic secret?
IE wouldn't it make sense to allow for _just_ the (initial) traffic
secrets to be set, and calculate 'iv' and 'key' on the fly _always?
That would remove the possible admin error where the admin sets a
set of 'IV' and 'key' values which do _not_ match the traffic secrets.
Hmm?
> +/**
> + * 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..a314fa638dc3 100644
> --- a/net/tls/tls_sw.c
> +++ b/net/tls/tls_sw.c
> @@ -1551,15 +1551,26 @@ 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);
> + 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 +1894,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 +2035,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 +2051,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 +2136,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,
>
The rest looks okay.
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] 12+ messages in thread* Re: [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received
2026-10-26 7:53 ` Hannes Reinecke
@ 2026-10-26 11:28 ` Martin Belanger
0 siblings, 0 replies; 12+ messages in thread
From: Martin Belanger @ 2026-10-26 11:28 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/26/26 03:53, Hannes Reinecke wrote:
> On 10/26/26 1:36 AM, 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 | 35 +++++++++--
>> 5 files changed, 190 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;
>> +}
>> +
> Hmm. So this calculates 'iv' and 'key' from the traffic secret.
> But as it does, why can't we use it to calculate the _initial_ 'iv'
> and 'key' from the traffic secret?
> IE wouldn't it make sense to allow for _just_ the (initial) traffic
> secrets to be set, and calculate 'iv' and 'key' on the fly _always?
> That would remove the possible admin error where the admin sets a
> set of 'IV' and 'key' values which do _not_ match the traffic secrets.
>
> Hmm?
It can. I kept TLS_TX and TLS_RX as they are for two reasons:
- Every kTLS user (OpenSSL, GnuTLS, tlshd) already derives the key and
IV and installs them with TLS_TX and TLS_RX. The secret is an
optional addition for KeyUpdate. Code that does not use KeyUpdate
does not change.
- The crypto_info of TLS_TX and TLS_RX also holds the version, the
cipher and the record sequence number. An option that takes only the
secret needs a new struct for these.
With the check from patch 2, the kernel refuses a secret that does not
match the installed key and IV. So the error you describe cannot
happen.
Martin
>
>> +/**
>> + * 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..a314fa638dc3 100644
>> --- a/net/tls/tls_sw.c
>> +++ b/net/tls/tls_sw.c
>> @@ -1551,15 +1551,26 @@ 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);
>> + 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 +1894,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 +2035,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 +2051,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 +2136,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,
>>
>
> The rest looks okay.
>
> Cheers,
>
> Hannes
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH RFC net-next 4/5] tls: send a KeyUpdate at the record limit and on request
2026-10-26 0:36 [PATCH RFC net-next 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
` (2 preceding siblings ...)
2026-10-26 0:36 ` [PATCH RFC net-next 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
@ 2026-10-26 0:36 ` Martin Belanger via B4 Relay
2026-10-26 0:36 ` [PATCH RFC net-next 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
4 siblings, 0 replies; 12+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-26 0:36 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 | 32 +++++++++
net/tls/tls_sw.c | 149 ++++++++++++++++++++++++++++++++++++++-
7 files changed, 208 insertions(+), 2 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 1c6e993ec012..0bce7df279a5 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -934,6 +934,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
return 0;
}
+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)
{
@@ -967,6 +996,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 a314fa638dc3..4acedddfb01e 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,150 @@ 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;
+
+ 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, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = tls_tx_key_update(sk, request ? TLS_KEY_UPDATE_REQUESTED :
+ TLS_KEY_UPDATE_NOT_REQUESTED, 0);
+ 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 +1164,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 related [flat|nested] 12+ messages in thread* [PATCH RFC net-next 5/5] selftests/tls: add KeyUpdate tests
2026-10-26 0:36 [PATCH RFC net-next 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
` (3 preceding siblings ...)
2026-10-26 0:36 ` [PATCH RFC net-next 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
@ 2026-10-26 0:36 ` Martin Belanger via B4 Relay
4 siblings, 0 replies; 12+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-26 0:36 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, and
the RX secret before the TX secret;
- 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;
- more than 8 KeyUpdates in one second, and an invalid
request_update value, end the connection.
Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
tools/testing/selftests/net/tls.c | 249 ++++++++++++++++++++++++++++++++++++++
1 file changed, 249 insertions(+)
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 9d3cd4fff062..838030d09cd3 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -2390,6 +2390,255 @@ TEST_F(tls, rekey_poll_delay)
}
}
+/* 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 tls12;
+ 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;
+ }
+
+ /* cfd -> fd */
+ tls_crypto_info_init(variant->tls_version, variant->cipher_type,
+ &tls12, 1);
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX, &tls12, tls12.len), 0);
+ EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_TX, &tls12, tls12.len), 0);
+
+ memset(secret_a, 0xa5, sizeof(secret_a));
+ memset(secret_b, 0x5a, sizeof(secret_b));
+
+ /* 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);
+}
+
+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_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 related [flat|nested] 12+ messages in thread