* [PATCH RFC net-next v3 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
2026-10-30 11:18 [PATCH RFC net-next v3 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
@ 2026-10-30 11:18 ` Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-30 11:18 UTC (permalink / raw)
To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Shuah Khan
Cc: linux-kernel, netdev, linux-doc, linux-kselftest, Hannes Reinecke,
Chuck Lever, kernel-tls-handshake, linux-nvme, Alistair Francis,
Martin Belanger
From: Martin Belanger <martin.belanger@dell.com>
A TLS 1.3 KeyUpdate derives the next traffic secret, key and IV with
HKDF-Expand-Label (RFC 9846, section 7.2). Add the helper for the
kernel to do this itself.
Only the part of the key schedule after the handshake is needed. The
input is a traffic secret, which is already a pseudorandom key, so
HKDF-Extract is not needed. Each output is at most Hash.length bytes,
so each derivation is a single HMAC with the library SHA-256 or SHA-384
functions.
Assisted-by: LLM
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
net/tls/Kconfig | 2 ++
net/tls/Makefile | 2 +-
net/tls/tls.h | 2 ++
net/tls/tls_hkdf.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/net/tls/Kconfig b/net/tls/Kconfig
index 4f4d5973a28f..6183ebe954ee 100644
--- a/net/tls/Kconfig
+++ b/net/tls/Kconfig
@@ -8,6 +8,8 @@ config TLS
select CRYPTO
select CRYPTO_AES
select CRYPTO_GCM
+ select CRYPTO_LIB_SHA256
+ select CRYPTO_LIB_SHA512
select NET_SOCK_MSG
default n
help
diff --git a/net/tls/Makefile b/net/tls/Makefile
index 4c7d296081ee..6edcf1271b75 100644
--- a/net/tls/Makefile
+++ b/net/tls/Makefile
@@ -7,6 +7,6 @@ CFLAGS_trace.o := -I$(src)
obj-$(CONFIG_TLS) += tls.o
-tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
+tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o tls_hkdf.o
tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 60a37bdaaa25..b2061f6bf065 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -147,6 +147,8 @@ void tls_strp_abort_strp(struct tls_strparser *strp, int err);
int init_prot_info(struct tls_prot_info *prot,
const struct tls_crypto_info *crypto_info,
const struct tls_cipher_desc *cipher_desc);
+int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
+ const char *label, u8 *out, size_t out_len);
int tls_set_sw_offload(struct sock *sk, int tx,
struct tls_crypto_info *new_crypto_info);
void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_hkdf.c b/net/tls/tls_hkdf.c
new file mode 100644
index 000000000000..49e33146ac7e
--- /dev/null
+++ b/net/tls/tls_hkdf.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Dell Technologies Inc. or its subsidiaries.
+ *
+ * TLS 1.3 HKDF-Expand-Label, RFC 9846 section 7.1.
+ *
+ * TLS 1.3 derives all of its secrets and keys with HKDF (RFC 5869). The
+ * TLS handshake runs in user space and gives the kernel one application
+ * traffic secret for each direction. For a KeyUpdate, the kernel derives
+ * the next traffic secret from the current one (section 7.2), and the
+ * new key and IV from the next traffic secret (section 7.3). Both steps
+ * use only HKDF-Expand-Label, so this file implements only that
+ * function, not the full key schedule of the handshake.
+ *
+ * The input is always a traffic secret. It is already a random key of
+ * the hash length, so HKDF-Extract is not needed. No output is longer
+ * than the hash, so HKDF-Expand is a single HMAC.
+ */
+
+#include <crypto/sha2.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+
+#include "tls.h"
+
+#define TLS_HKDF_LABEL_PREFIX "tls13 "
+#define TLS_HKDF_LABEL_PREFIX_LEN (sizeof(TLS_HKDF_LABEL_PREFIX) - 1)
+#define TLS_HKDF_LABEL_MAX 32
+
+/**
+ * tls_hkdf_expand_label - HKDF-Expand-Label with an empty context
+ * @secret: the secret
+ * @secret_len: length of @secret, which selects the hash: SHA-256 for
+ * SHA256_DIGEST_SIZE, SHA-384 for SHA384_DIGEST_SIZE
+ * @label: the label, without the "tls13 " prefix
+ * @out: output buffer
+ * @out_len: number of bytes to derive, at most @secret_len
+ *
+ * Return: 0 on success, -EINVAL if a length is not valid.
+ */
+int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
+ const char *label, u8 *out, size_t out_len)
+{
+ u8 info[2 + 1 + TLS_HKDF_LABEL_PREFIX_LEN + TLS_HKDF_LABEL_MAX + 1 + 1];
+ size_t label_len = strlen(label);
+ u8 tmp[SHA384_DIGEST_SIZE];
+ size_t pos = 0;
+
+ if (secret_len != SHA256_DIGEST_SIZE &&
+ secret_len != SHA384_DIGEST_SIZE)
+ return -EINVAL;
+ if (!out_len || out_len > secret_len || label_len > TLS_HKDF_LABEL_MAX)
+ return -EINVAL;
+
+ /* struct {
+ * uint16 length;
+ * opaque label<7..255>;
+ * opaque context<0..255>;
+ * } HkdfLabel;
+ *
+ * followed by the HKDF-Expand counter for the first output block.
+ */
+ put_unaligned_be16(out_len, info);
+ pos += 2;
+ info[pos++] = TLS_HKDF_LABEL_PREFIX_LEN + label_len;
+ memcpy(info + pos, TLS_HKDF_LABEL_PREFIX, TLS_HKDF_LABEL_PREFIX_LEN);
+ pos += TLS_HKDF_LABEL_PREFIX_LEN;
+ memcpy(info + pos, label, label_len);
+ pos += label_len;
+ info[pos++] = 0; /* empty context */
+ info[pos++] = 1; /* counter */
+
+ if (secret_len == SHA256_DIGEST_SIZE)
+ hmac_sha256_usingrawkey(secret, secret_len, info, pos, tmp);
+ else
+ hmac_sha384_usingrawkey(secret, secret_len, info, pos, tmp);
+
+ memcpy(out, tmp, out_len);
+ memzero_explicit(tmp, sizeof(tmp));
+
+ return 0;
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH RFC net-next v3 2/5] tls: add socket options to register the traffic secrets
2026-10-30 11:18 [PATCH RFC net-next v3 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
@ 2026-10-30 11:18 ` Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-30 11:18 UTC (permalink / raw)
To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Shuah Khan
Cc: linux-kernel, netdev, linux-doc, linux-kselftest, Hannes Reinecke,
Chuck Lever, kernel-tls-handshake, linux-nvme, Alistair Francis,
Martin Belanger
From: Martin Belanger <martin.belanger@dell.com>
kTLS holds the key and IV of each direction, but not the traffic secret
they were derived from. The derivation is one-way, so the kernel cannot
compute the next generation for a KeyUpdate on its own.
Add TLS_TX_TRAFFIC_SECRET and TLS_RX_TRAFFIC_SECRET. The owner of the
socket passes the TLS 1.3 application traffic secret of a direction after
installing its key. The cipher of the installed key selects the hash, so
the option takes only the secret. A socket without a registered secret
behaves as before.
The secret must give the installed key and IV, or the option fails with
-EINVAL. A wrong secret would otherwise fail only at the first
KeyUpdate.
The TX secret can be registered alone. The RX secret needs the TX secret
first: a received KeyUpdate can ask for one in reply, and since the
kernel consumes the record, only the kernel can send that reply.
Installing a new key with TLS_TX or TLS_RX removes the secrets that no
longer apply. The secrets are cleared when the context is freed and
cannot be read back.
Assisted-by: LLM
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 | 145 ++++++++++++++++++++++++++++++++++++++-
4 files changed, 185 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..c46dc399af16 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -31,6 +31,8 @@
* SOFTWARE.
*/
+#include <crypto/sha2.h>
+#include <crypto/utils.h>
#include <linux/module.h>
#include <net/tcp.h>
@@ -325,6 +327,8 @@ void tls_ctx_free(struct sock *sk, struct tls_context *ctx)
memzero_explicit(&ctx->crypto_send, sizeof(ctx->crypto_send));
memzero_explicit(&ctx->crypto_recv, sizeof(ctx->crypto_recv));
+ memzero_explicit(&ctx->tx_secret, sizeof(ctx->tx_secret));
+ memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
mutex_destroy(&ctx->tx_lock);
if (sk)
@@ -757,8 +761,16 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
ctx->rx_conf = conf;
update_sk_prot(sk, ctx);
- if (update)
+ if (update) {
+ /* The registered secret no longer matches the key. The RX
+ * secret also needs the TX one, see do_tls_setsockopt_secret().
+ */
+ if (tx)
+ memzero_explicit(&ctx->tx_secret,
+ sizeof(ctx->tx_secret));
+ memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
return 0;
+ }
if (tx) {
ctx->sk_write_space = sk->sk_write_space;
@@ -857,6 +869,130 @@ static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval,
return 0;
}
+/* Length of the TLS 1.3 traffic secret for @cipher_type, the size of
+ * its hash. 0 if KeyUpdate is not supported for the cipher.
+ */
+static int tls_traffic_secret_len(u16 cipher_type)
+{
+ switch (cipher_type) {
+ case TLS_CIPHER_AES_GCM_128:
+ case TLS_CIPHER_AES_CCM_128:
+ case TLS_CIPHER_CHACHA20_POLY1305:
+ return SHA256_DIGEST_SIZE;
+ case TLS_CIPHER_AES_GCM_256:
+ return SHA384_DIGEST_SIZE;
+ default:
+ return 0;
+ }
+}
+
+/* The installed key and IV must be the ones that @secret gives
+ * (RFC 9846 section 7.3):
+ *
+ * key = HKDF-Expand-Label(secret, "key", "", key_length)
+ * iv = HKDF-Expand-Label(secret, "iv", "", iv_length)
+ *
+ * crypto_info holds the iv as salt followed by iv. Each KeyUpdate
+ * derives the next key from the secret, so a wrong secret fails only
+ * at the first KeyUpdate, with no clear cause.
+ */
+static int tls_check_traffic_secret(struct tls_crypto_info *crypto_info,
+ const u8 *secret, int len)
+{
+ const struct tls_cipher_desc *cipher_desc;
+ u8 iv[TLS_MAX_SALT_SIZE + TLS_MAX_IV_SIZE];
+ u8 key[TLS_CIPHER_AES_GCM_256_KEY_SIZE];
+ int rc;
+
+ cipher_desc = get_cipher_desc(crypto_info->cipher_type);
+ if (!cipher_desc || cipher_desc->key > sizeof(key) ||
+ cipher_desc->salt + cipher_desc->iv > sizeof(iv))
+ return -EINVAL;
+
+ rc = tls_hkdf_expand_label(secret, len, "key", key, cipher_desc->key);
+ if (rc)
+ goto out;
+
+ rc = tls_hkdf_expand_label(secret, len, "iv", iv,
+ cipher_desc->salt + cipher_desc->iv);
+ if (rc)
+ goto out;
+
+ if (crypto_memneq(key, crypto_info_key(crypto_info, cipher_desc),
+ cipher_desc->key) ||
+ crypto_memneq(iv, crypto_info_salt(crypto_info, cipher_desc),
+ cipher_desc->salt) ||
+ crypto_memneq(iv + cipher_desc->salt,
+ crypto_info_iv(crypto_info, cipher_desc),
+ cipher_desc->iv))
+ rc = -EINVAL;
+
+out:
+ memzero_explicit(key, sizeof(key));
+ memzero_explicit(iv, sizeof(iv));
+
+ return rc;
+}
+
+static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
+ unsigned int optlen, int tx)
+{
+ struct tls_context *ctx = tls_get_ctx(sk);
+ struct tls_crypto_info *crypto_info;
+ struct tls_traffic_secret *secret;
+ u8 data[TLS_MAX_TRAFFIC_SECRET_SIZE];
+ int len, rc;
+
+ if (tx) {
+ crypto_info = &ctx->crypto_send.info;
+ secret = &ctx->tx_secret;
+ } else {
+ crypto_info = &ctx->crypto_recv.info;
+ secret = &ctx->rx_secret;
+ }
+
+ /* The key must be installed first: its cipher selects the hash */
+ if (!TLS_CRYPTO_INFO_READY(crypto_info) ||
+ crypto_info->version != TLS_1_3_VERSION)
+ return -EINVAL;
+
+ if ((tx ? ctx->tx_conf : ctx->rx_conf) != TLS_SW)
+ return -EOPNOTSUPP;
+
+ len = tls_traffic_secret_len(crypto_info->cipher_type);
+ if (!len)
+ return -EOPNOTSUPP;
+
+ if (sockptr_is_null(optval) || optlen != len)
+ return -EINVAL;
+
+ /* A received KeyUpdate may ask for one in reply. The kernel
+ * consumes the record, so only the kernel can send the reply.
+ */
+ if (!tx && !ctx->tx_secret.len)
+ return -EINVAL;
+
+ if (secret->len)
+ return -EBUSY;
+
+ if (copy_from_sockptr(data, optval, len)) {
+ rc = -EFAULT;
+ goto out;
+ }
+
+ rc = tls_check_traffic_secret(crypto_info, data, len);
+ if (rc)
+ goto out;
+
+ memcpy(secret->data, data, len);
+ secret->len = len;
+
+out:
+ memzero_explicit(data, sizeof(data));
+
+ return rc;
+}
+
static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
unsigned int optlen)
{
@@ -883,6 +1019,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] 6+ messages in thread* [PATCH RFC net-next v3 3/5] tls: update the RX key when a KeyUpdate is received
2026-10-30 11:18 [PATCH RFC net-next v3 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
@ 2026-10-30 11:18 ` Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-30 11:18 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. That case is counted as
TlsRxRekeyError and logged, rate limited.
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 | 154 ++++++++++++++++++++++++++++++++++++++++++++++++
net/tls/tls_sw.c | 43 ++++++++++++--
5 files changed, 202 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..281c7b13eb82
--- /dev/null
+++ b/net/tls/tls_keyupdate.c
@@ -0,0 +1,154 @@
+// 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) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYERROR);
+ net_warn_ratelimited("tls: peer sent more than %d KeyUpdates in one second\n",
+ TLS_KEYUPDATE_RX_MAX_PER_SEC);
+ return -EBADMSG;
+ }
+
+ rc = tls_keyupdate_rekey(sk, 0);
+ if (rc) {
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYERROR);
+ return rc;
+ }
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
+
+ return 0;
+}
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 312e51270f29..cc0177320e7e 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1551,15 +1551,28 @@ static int tls_check_pending_rekey(struct sock *sk, struct tls_context *ctx,
if (hs_type == TLS_HANDSHAKE_KEYUPDATE) {
struct tls_sw_context_rx *rx_ctx = ctx->priv_ctx_rx;
- WRITE_ONCE(rx_ctx->key_update_pending, true);
TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYRECEIVED);
+
+ if (ctx->rx_secret.len) {
+ err = tls_keyupdate_rx(sk, skb);
+ if (err) {
+ tls_err_abort(sk, err);
+ /* callers free the record only on success */
+ consume_skb(skb);
+ return err;
+ }
+ return 1;
+ }
+
+ WRITE_ONCE(rx_ctx->key_update_pending, true);
}
return 0;
}
/* On decrypt failure the connection is aborted (sk_err set) before
- * returning a negative errno.
+ * returning a negative errno. Return 1 for a record the kernel consumed
+ * (a KeyUpdate): the caller frees it and does not pass it to the user.
*/
static int tls_rx_one_record(struct sock *sk, struct msghdr *msg,
struct tls_decrypt_arg *darg)
@@ -1883,6 +1896,11 @@ int tls_sw_recvmsg(struct sock *sk,
err = tls_rx_one_record(sk, msg, &darg);
if (err < 0)
goto recv_end;
+ if (err > 0) {
+ tls_rx_rec_done(ctx);
+ consume_skb(darg.skb);
+ continue;
+ }
async |= darg.async;
@@ -2019,9 +2037,8 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
if (err)
goto splice_read_end;
- if (!skb_queue_empty(&ctx->rx_list)) {
- skb = __skb_dequeue(&ctx->rx_list);
- } else {
+ skb = __skb_dequeue(&ctx->rx_list);
+ while (!skb) {
struct tls_decrypt_arg darg;
err = tls_rx_rec_wait(sk, flags & SPLICE_F_NONBLOCK,
@@ -2036,7 +2053,10 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos,
goto splice_read_end;
tls_rx_rec_done(ctx);
- skb = darg.skb;
+ if (err > 0)
+ consume_skb(darg.skb);
+ else
+ skb = darg.skb;
}
rxm = strp_msg(skb);
@@ -2118,6 +2138,11 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc,
err = tls_rx_one_record(sk, NULL, &darg);
if (err < 0)
goto read_sock_end;
+ if (err > 0) {
+ tls_rx_rec_done(ctx);
+ consume_skb(darg.skb);
+ continue;
+ }
released = tls_read_flush_backlog(sk, prot, INT_MAX,
0, decrypted,
@@ -2517,6 +2542,12 @@ static void tls_finish_key_update(struct sock *sk, struct tls_context *tls_ctx)
{
struct tls_sw_context_rx *ctx = tls_ctx->priv_ctx_rx;
+ /* Only a pending KeyUpdate holds poll() back. The in-kernel rekey
+ * runs while the KeyUpdate record is still queued.
+ */
+ if (!READ_ONCE(ctx->key_update_pending))
+ return;
+
WRITE_ONCE(ctx->key_update_pending, false);
/* wake-up pre-existing poll() */
ctx->saved_data_ready(sk);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH RFC net-next v3 4/5] tls: send a KeyUpdate at the record limit and on request
2026-10-30 11:18 [PATCH RFC net-next v3 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
` (2 preceding siblings ...)
2026-10-30 11:18 ` [PATCH RFC net-next v3 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
@ 2026-10-30 11:18 ` Martin Belanger via B4 Relay
2026-10-30 11:18 ` [PATCH RFC net-next v3 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-30 11:18 UTC (permalink / raw)
To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Shuah Khan
Cc: linux-kernel, netdev, linux-doc, linux-kselftest, Hannes Reinecke,
Chuck Lever, kernel-tls-handshake, linux-nvme, Alistair Francis,
Martin Belanger
From: Martin Belanger <martin.belanger@dell.com>
RFC 9846 section 5.5 requires a key update before the key reaches its
usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
records, and only the record layer knows where records start and end.
Section 4.7.3 also requires a reply to a KeyUpdate with
update_requested, sent before the next Application Data record. Both
need a KeyUpdate at an exact position in the record stream.
For a socket with a registered TX secret, send a KeyUpdate before the
next record when the key has protected 2^24 records, as GnuTLS does,
or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
The KeyUpdate is sent as a handshake record, which closes the open data
record first. Pending encryptions are then completed, and only then is
the next key installed.
Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
peer to update too. It can only bring a KeyUpdate forward: the record
limit is enforced either way. Following section 4.7.3, a new request
to the peer waits for the answer to the previous one, and no more than
2^48-1 epochs are used.
Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
Documentation/networking/tls.rst | 17 ++++
include/net/tls.h | 5 +-
include/uapi/linux/tls.h | 1 +
net/tls/tls.h | 1 +
net/tls/tls_keyupdate.c | 5 ++
net/tls/tls_main.c | 38 ++++++++-
net/tls/tls_sw.c | 164 ++++++++++++++++++++++++++++++++++++++-
7 files changed, 228 insertions(+), 3 deletions(-)
diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 2e011bd1ce4a..69752a6d67df 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -326,6 +326,23 @@ direction is then left to the application again.
The secrets cannot be read back.
+With a registered TX secret, the kernel sends a KeyUpdate before the key
+reaches its record limit (RFC 9846, section 5.5): after 2^24 records, except
+for ChaCha20-Poly1305, which has no limit. It also sends one before the next
+record when a received KeyUpdate asks for it. With a registered RX secret,
+the kernel processes a received KeyUpdate and does not pass the record to
+the application. A peer that sends more than 8 KeyUpdates in one second ends
+the connection.
+
+TLS_TX_KEY_UPDATE
+~~~~~~~~~~~~~~~~~
+
+TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
+registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 1 to
+also ask the peer to update its key. Asking the peer needs a registered RX
+secret, and fails with ``EBUSY`` until the peer has answered the previous
+request.
+
Statistics
==========
diff --git a/include/net/tls.h b/include/net/tls.h
index a5bc28a7d1f5..d3123b17030c 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -282,9 +282,12 @@ struct tls_context {
union tls_crypto_context crypto_send;
union tls_crypto_context crypto_recv;
- /* Registered for KeyUpdate, protected by the socket lock */
+ /* KeyUpdate state, protected by the socket lock */
struct tls_traffic_secret tx_secret;
struct tls_traffic_secret rx_secret;
+ u64 tx_key_updates; /* KeyUpdates sent */
+ bool tx_key_update_reply; /* peer set update_requested */
+ bool tx_key_update_asked; /* we set it, no answer yet */
struct list_head list;
refcount_t refcount;
diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
index d1b136ecea96..c85408cb6ce7 100644
--- a/include/uapi/linux/tls.h
+++ b/include/uapi/linux/tls.h
@@ -44,6 +44,7 @@
#define TLS_TX_MAX_PAYLOAD_LEN 5 /* Maximum plaintext size */
#define TLS_TX_TRAFFIC_SECRET 6 /* TLS 1.3 TX traffic secret */
#define TLS_RX_TRAFFIC_SECRET 7 /* TLS 1.3 RX traffic secret */
+#define TLS_TX_KEY_UPDATE 8 /* Send a TLS 1.3 KeyUpdate now */
/* Supported versions */
#define TLS_VERSION_MINOR(ver) ((ver) & 0xFF)
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 63ece8074985..d12c9278c814 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
const char *label, u8 *out, size_t out_len);
int tls_keyupdate_rekey(struct sock *sk, int tx);
int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
+int tls_sw_key_update(struct sock *sk, int request);
int tls_set_sw_offload(struct sock *sk, int tx,
struct tls_crypto_info *new_crypto_info);
void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
index 281c7b13eb82..58cd426cc94b 100644
--- a/net/tls/tls_keyupdate.c
+++ b/net/tls/tls_keyupdate.c
@@ -150,5 +150,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 c46dc399af16..66bd5217aa67 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
/* The registered secret no longer matches the key. The RX
* secret also needs the TX one, see do_tls_setsockopt_secret().
*/
- if (tx)
+ if (tx) {
memzero_explicit(&ctx->tx_secret,
sizeof(ctx->tx_secret));
+ ctx->tx_key_update_reply = false;
+ }
memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
+ /* the answer now goes to user space */
+ ctx->tx_key_update_asked = false;
return 0;
}
@@ -993,6 +997,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
return rc;
}
+static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t optval,
+ unsigned int optlen)
+{
+ struct tls_context *ctx = tls_get_ctx(sk);
+ int value, rc;
+
+ if (sockptr_is_null(optval) || optlen != sizeof(value))
+ return -EINVAL;
+
+ if (copy_from_sockptr(&value, optval, sizeof(value)))
+ return -EFAULT;
+
+ if (value != 0 && value != 1)
+ return -EINVAL;
+
+ rc = mutex_lock_interruptible(&ctx->tx_lock);
+ if (rc)
+ return rc;
+ lock_sock(sk);
+ if (ctx->tx_conf == TLS_SW)
+ rc = tls_sw_key_update(sk, value);
+ else
+ rc = -EINVAL;
+ release_sock(sk);
+ mutex_unlock(&ctx->tx_lock);
+
+ return rc;
+}
+
static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
unsigned int optlen)
{
@@ -1026,6 +1059,9 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
optname == TLS_TX_TRAFFIC_SECRET);
release_sock(sk);
break;
+ case TLS_TX_KEY_UPDATE:
+ rc = do_tls_setsockopt_key_update(sk, optval, optlen);
+ break;
default:
rc = -ENOPROTOOPT;
break;
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index cc0177320e7e..f46cfbd95a6f 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>
@@ -763,6 +764,8 @@ static int tls_sw_sendmsg_splice(struct sock *sk, struct msghdr *msg,
return 0;
}
+static int tls_tx_maybe_update_key(struct sock *sk, int flags);
+
static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
size_t size)
{
@@ -805,6 +808,16 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
goto send_end;
}
+ /* Check before each new record: one call can write many
+ * records, and a KeyUpdate reply must go before the next
+ * one. The KeyUpdate itself is a handshake record.
+ */
+ if (!ctx->open_rec && record_type == TLS_RECORD_TYPE_DATA) {
+ ret = tls_tx_maybe_update_key(sk, msg->msg_flags);
+ if (ret)
+ goto send_end;
+ }
+
if (ctx->open_rec)
rec = ctx->open_rec;
else
@@ -1005,6 +1018,153 @@ 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;
+ union {
+ struct cmsghdr cmsg;
+ u8 buf[CMSG_SPACE(sizeof(record_type))];
+ } ctrl = {};
+ struct msghdr msg = {};
+ struct cmsghdr *cmsg;
+ struct kvec iov;
+ int ret;
+
+ msg.msg_control = &ctrl;
+ msg.msg_controllen = sizeof(ctrl);
+ msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_TLS;
+ cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
+ memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
+
+ iov.iov_base = body;
+ iov.iov_len = sizeof(body);
+ iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
+
+ /* A control record closes the open record first, so all data
+ * written so far goes out with the current key, ahead of the
+ * KeyUpdate.
+ */
+ ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
+ if (ret < 0)
+ return ret;
+
+ /* Nothing may be encrypted with the next key before the KeyUpdate
+ * is encrypted with the current one.
+ */
+ ret = tls_encrypt_async_wait(ctx);
+ if (!ret)
+ ret = tls_keyupdate_rekey(sk, 1);
+ if (ret) {
+ /* The peer expects the next key: the connection is lost */
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
+ tls_err_abort(sk, ret);
+ return ret;
+ }
+
+ TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+ tls_ctx->tx_key_updates++;
+ if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
+ tls_ctx->tx_key_update_reply = false;
+
+ return 0;
+}
+
+/* Send a KeyUpdate if one is due: in reply to the peer's
+ * update_requested (RFC 9846 section 4.7.3), or before the key reaches
+ * its record limit (section 5.5). ChaCha20-Poly1305 has no record limit:
+ * the sequence number would wrap first.
+ */
+static int tls_tx_maybe_update_key(struct sock *sk, int flags)
+{
+ struct tls_context *ctx = tls_get_ctx(sk);
+ bool limit;
+
+ if (likely(!ctx->tx_secret.len))
+ return 0;
+
+ limit = ctx->prot_info.cipher_type != TLS_CIPHER_CHACHA20_POLY1305 &&
+ get_unaligned_be64(ctx->tx.rec_seq) >= TLS_KEYUPDATE_TX_RECORDS;
+ if (!limit && !ctx->tx_key_update_reply)
+ return 0;
+
+ if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX) {
+ /* Ignore update_requested. At the record limit the
+ * connection must end.
+ */
+ ctx->tx_key_update_reply = false;
+ return limit ? -EKEYEXPIRED : 0;
+ }
+
+ return tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED, flags);
+}
+
+/**
+ * tls_sw_key_update - send a KeyUpdate now, for TLS_TX_KEY_UPDATE
+ * @sk: socket
+ * @request: non-zero to also ask the peer to update its key
+ *
+ * Called with tx_lock held and the socket locked.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int tls_sw_key_update(struct sock *sk, int request)
+{
+ struct tls_context *ctx = tls_get_ctx(sk);
+ int ret;
+
+ if (!ctx->tx_secret.len)
+ return -EINVAL;
+
+ if (request) {
+ /* Only the kernel sees the answer */
+ if (!ctx->rx_secret.len)
+ return -EINVAL;
+ /* Section 4.7.3: wait for the answer to the previous one */
+ if (ctx->tx_key_update_asked)
+ return -EBUSY;
+ }
+
+ if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX - 1)
+ return -EKEYEXPIRED;
+
+ /* A reply must carry update_not_requested */
+ if (request && ctx->tx_key_update_reply) {
+ ret = tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED,
+ MSG_NOSIGNAL);
+ if (ret)
+ return ret;
+ }
+
+ ret = tls_tx_key_update(sk, request ? TLS_KEY_UPDATE_REQUESTED :
+ TLS_KEY_UPDATE_NOT_REQUESTED, MSG_NOSIGNAL);
+ if (!ret && request)
+ ctx->tx_key_update_asked = true;
+
+ return ret;
+}
+
int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
struct tls_context *tls_ctx = tls_get_ctx(sk);
@@ -1019,7 +1179,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_maybe_update_key(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] 6+ messages in thread* [PATCH RFC net-next v3 5/5] selftests/tls: add KeyUpdate tests
2026-10-30 11:18 [PATCH RFC net-next v3 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
` (3 preceding siblings ...)
2026-10-30 11:18 ` [PATCH RFC net-next v3 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger via B4 Relay
@ 2026-10-30 11:18 ` Martin Belanger via B4 Relay
4 siblings, 0 replies; 6+ messages in thread
From: Martin Belanger via B4 Relay @ 2026-10-30 11:18 UTC (permalink / raw)
To: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Shuah Khan
Cc: linux-kernel, netdev, linux-doc, linux-kselftest, Hannes Reinecke,
Chuck Lever, kernel-tls-handshake, linux-nvme, Alistair Francis,
Martin Belanger
From: Martin Belanger <martin.belanger@dell.com>
Test the in-kernel KeyUpdate on a bidirectional connection with both
traffic secrets registered on each socket:
- registration errors: wrong length, missing key, TLS 1.2, SM4, the
RX secret before the TX secret, and a secret that does not give the
installed key;
- KeyUpdates in both directions with data around them;
- a data record, a KeyUpdate and another data record queued before
the first read;
- update_requested: the peer answers before its next data, and a
second request waits for the answer;
- splice() across a KeyUpdate;
- one write that crosses the record limit gets a KeyUpdate in the
middle;
- a key installed with TLS_TX removes the registered secrets;
- after a key installed with TLS_RX, a new update_requested is
accepted;
- TLS_TX_KEY_UPDATE on a shut down socket does not raise SIGPIPE;
- more than 8 KeyUpdates in one second, and an invalid
request_update value, end the connection.
The keys are derived from the secrets with HKDF-Expand-Label, with the
HMAC of the kernel through AF_ALG.
Assisted-by: LLM
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
tools/testing/selftests/net/config | 4 +
tools/testing/selftests/net/tls.c | 461 +++++++++++++++++++++++++++++++++++++
2 files changed, 465 insertions(+)
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index cb8cb4696df7..0de4011cfabd 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -10,8 +10,12 @@ CONFIG_CAN_VXCAN=m
CONFIG_CGROUPS=y
CONFIG_CRYPTO_ARIA=y
CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_HMAC=y
CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_SHA512=y
CONFIG_CRYPTO_SM4_GENERIC=y
+CONFIG_CRYPTO_USER_API_HASH=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=n
CONFIG_DUMMY=y
diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c
index 9d3cd4fff062..abc543a5e3d9 100644
--- a/tools/testing/selftests/net/tls.c
+++ b/tools/testing/selftests/net/tls.c
@@ -11,6 +11,7 @@
#include <stdlib.h>
#include <unistd.h>
+#include <linux/if_alg.h>
#include <linux/tls.h>
#include <linux/tcp.h>
#include <linux/socket.h>
@@ -2390,6 +2391,466 @@ TEST_F(tls, rekey_poll_delay)
}
}
+#ifndef SOL_ALG
+#define SOL_ALG 279
+#endif
+
+/* RFC 9846 section 7.1, HKDF-Expand-Label with an empty context, for an
+ * output no longer than the hash. The HMAC comes from the kernel.
+ */
+static bool hkdf_expand_label(const unsigned char *secret, int secret_len,
+ const char *label, unsigned char *out,
+ int out_len)
+{
+ struct sockaddr_alg sa = {
+ .salg_family = AF_ALG,
+ .salg_type = "hash",
+ };
+ unsigned char info[2 + 1 + 255 + 1 + 1], md[48];
+ int tfm, op, n = 0, label_len = 6 + strlen(label);
+ bool ok = false;
+
+ strcpy((char *)sa.salg_name,
+ secret_len == 48 ? "hmac(sha384)" : "hmac(sha256)");
+
+ info[n++] = out_len >> 8;
+ info[n++] = out_len;
+ info[n++] = label_len;
+ memcpy(info + n, "tls13 ", 6);
+ memcpy(info + n + 6, label, label_len - 6);
+ n += label_len;
+ info[n++] = 0; /* context */
+ info[n++] = 1; /* HKDF-Expand counter */
+
+ tfm = socket(AF_ALG, SOCK_SEQPACKET, 0);
+ if (tfm < 0)
+ return false;
+ if (bind(tfm, (struct sockaddr *)&sa, sizeof(sa)) ||
+ setsockopt(tfm, SOL_ALG, ALG_SET_KEY, secret, secret_len))
+ goto out;
+
+ op = accept(tfm, NULL, 0);
+ if (op < 0)
+ goto out;
+ ok = write(op, info, n) == n && read(op, md, secret_len) == secret_len;
+ close(op);
+ if (ok)
+ memcpy(out, md, out_len);
+out:
+ close(tfm);
+ return ok;
+}
+
+/* Fill @keys with the key and IV that @secret gives, record number 0 */
+static bool keyupdate_keys(uint16_t cipher_type, const unsigned char *secret,
+ int secret_len, struct tls_crypto_info_keys *keys)
+{
+ unsigned char *key, *salt, *iv;
+ int key_len, salt_len, iv_len;
+ unsigned char nonce[12];
+
+ tls_crypto_info_init(TLS_1_3_VERSION, cipher_type, keys, 0);
+
+ switch (cipher_type) {
+ case TLS_CIPHER_AES_GCM_128:
+ key = keys->aes128.key;
+ salt = keys->aes128.salt;
+ iv = keys->aes128.iv;
+ key_len = sizeof(keys->aes128.key);
+ salt_len = sizeof(keys->aes128.salt);
+ iv_len = sizeof(keys->aes128.iv);
+ break;
+ case TLS_CIPHER_AES_GCM_256:
+ key = keys->aesgcm256.key;
+ salt = keys->aesgcm256.salt;
+ iv = keys->aesgcm256.iv;
+ key_len = sizeof(keys->aesgcm256.key);
+ salt_len = sizeof(keys->aesgcm256.salt);
+ iv_len = sizeof(keys->aesgcm256.iv);
+ break;
+ case TLS_CIPHER_AES_CCM_128:
+ key = keys->aesccm128.key;
+ salt = keys->aesccm128.salt;
+ iv = keys->aesccm128.iv;
+ key_len = sizeof(keys->aesccm128.key);
+ salt_len = sizeof(keys->aesccm128.salt);
+ iv_len = sizeof(keys->aesccm128.iv);
+ break;
+ case TLS_CIPHER_CHACHA20_POLY1305:
+ key = keys->chacha20.key;
+ salt = keys->chacha20.salt;
+ iv = keys->chacha20.iv;
+ key_len = sizeof(keys->chacha20.key);
+ salt_len = 0;
+ iv_len = sizeof(keys->chacha20.iv);
+ break;
+ default:
+ return false;
+ }
+
+ if (!hkdf_expand_label(secret, secret_len, "key", key, key_len) ||
+ !hkdf_expand_label(secret, secret_len, "iv", nonce,
+ salt_len + iv_len))
+ return false;
+
+ memcpy(salt, nonce, salt_len);
+ memcpy(iv, nonce + salt_len, iv_len);
+ return true;
+}
+
+/* In-kernel KeyUpdate: fd and cfd both register the TX and RX traffic
+ * secrets of a bidirectional connection. fd -> cfd starts at record
+ * number @seq. Returns false if the variant does not support it.
+ */
+static bool keyupdate_setup_seq(struct __test_metadata *_metadata,
+ FIXTURE_DATA(tls) *self,
+ const FIXTURE_VARIANT(tls) *variant,
+ uint64_t seq)
+{
+ struct tls_crypto_info_keys keys_a, keys_b;
+ unsigned char secret_a[48], secret_b[48];
+ unsigned char *rec_seq;
+ int len, i;
+
+ if (self->notls || variant->tls_version != TLS_1_3_VERSION)
+ return false;
+
+ switch (variant->cipher_type) {
+ case TLS_CIPHER_AES_GCM_128:
+ case TLS_CIPHER_AES_CCM_128:
+ case TLS_CIPHER_CHACHA20_POLY1305:
+ len = 32;
+ break;
+ case TLS_CIPHER_AES_GCM_256:
+ len = 48;
+ break;
+ default:
+ return false;
+ }
+
+ memset(secret_a, 0xa5, sizeof(secret_a));
+ memset(secret_b, 0x5a, sizeof(secret_b));
+
+ /* the keys must be the ones that the secrets give */
+ ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret_a, len,
+ &keys_a));
+ ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret_b, len,
+ &keys_b));
+
+ /* rec_seq is the last member of each crypto_info */
+ rec_seq = (unsigned char *)&keys_a + keys_a.len - 8;
+ for (i = 0; i < 8; i++)
+ rec_seq[i] = seq >> (56 - 8 * i);
+
+ /* fd -> cfd: replace the keys of the fixture */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &keys_a,
+ keys_a.len), 0);
+ EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX, &keys_a,
+ keys_a.len), 0);
+ /* cfd -> fd */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX, &keys_b,
+ keys_b.len), 0);
+ EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_TX, &keys_b,
+ keys_b.len), 0);
+
+ /* the RX secret needs the TX secret first */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+ secret_b, len), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+ secret_a, len), 0);
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+ secret_b, len), 0);
+ EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+ secret_b, len), 0);
+ EXPECT_EQ(setsockopt(self->cfd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+ secret_a, len), 0);
+
+ return true;
+}
+
+static bool keyupdate_setup(struct __test_metadata *_metadata,
+ FIXTURE_DATA(tls) *self,
+ const FIXTURE_VARIANT(tls) *variant)
+{
+ return keyupdate_setup_seq(_metadata, self, variant, 0);
+}
+
+static void keyupdate_send(struct __test_metadata *_metadata, int fd,
+ int request)
+{
+ EXPECT_EQ(setsockopt(fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+ &request, sizeof(request)), 0);
+}
+
+static void send_recv_str(struct __test_metadata *_metadata, int from, int to,
+ const char *str)
+{
+ int len = strlen(str) + 1;
+ char buf[64];
+
+ EXPECT_EQ(send(from, str, len, 0), len);
+ EXPECT_EQ(recv(to, buf, sizeof(buf), 0), len);
+ EXPECT_EQ(memcmp(buf, str, len), 0);
+}
+
+TEST_F(tls, keyupdate_secret_errors)
+{
+ unsigned char secret[48] = {};
+ int one = 1;
+
+ if (self->notls)
+ SKIP(return, "no TLS support");
+
+ /* no secret registered */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+ &one, sizeof(one)), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ if (variant->tls_version != TLS_1_3_VERSION) {
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+ secret, 32), -1);
+ EXPECT_EQ(errno, EINVAL);
+ return;
+ }
+
+ if (variant->cipher_type == TLS_CIPHER_SM4_GCM ||
+ variant->cipher_type == TLS_CIPHER_SM4_CCM) {
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+ secret, 32), -1);
+ EXPECT_EQ(errno, EOPNOTSUPP);
+ return;
+ }
+
+ /* the RX key is not installed on fd */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+ secret, 32), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* wrong length */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET,
+ secret, 16), -1);
+ EXPECT_EQ(errno, EINVAL);
+
+ /* the installed key does not come from this secret */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_TRAFFIC_SECRET, secret,
+ variant->cipher_type == TLS_CIPHER_AES_GCM_256 ?
+ 48 : 32), -1);
+ EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(tls, keyupdate)
+{
+ int i;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ send_recv_str(_metadata, self->fd, self->cfd, "before");
+
+ /* stay below the limit of 8 received KeyUpdates per second */
+ for (i = 0; i < 4; i++) {
+ keyupdate_send(_metadata, self->fd, 0);
+ send_recv_str(_metadata, self->fd, self->cfd, "after");
+ keyupdate_send(_metadata, self->cfd, 0);
+ send_recv_str(_metadata, self->cfd, self->fd, "reverse");
+ }
+}
+
+TEST_F(tls, keyupdate_queued)
+{
+ const char *str = "queued behind a KeyUpdate";
+ int len = strlen(str) + 1;
+ char buf[64];
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ /* the record before the KeyUpdate, the KeyUpdate and the record
+ * after it are all queued before the first read
+ */
+ EXPECT_EQ(send(self->fd, str, len, MSG_MORE), len);
+ keyupdate_send(_metadata, self->fd, 0);
+ EXPECT_EQ(send(self->fd, str, len, 0), len);
+
+ EXPECT_EQ(recv(self->cfd, buf, len, MSG_WAITALL), len);
+ EXPECT_EQ(memcmp(buf, str, len), 0);
+ EXPECT_EQ(recv(self->cfd, buf, len, MSG_WAITALL), len);
+ EXPECT_EQ(memcmp(buf, str, len), 0);
+}
+
+TEST_F(tls, keyupdate_request)
+{
+ int one = 1;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ keyupdate_send(_metadata, self->fd, 1);
+
+ /* no answer yet */
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+ &one, sizeof(one)), -1);
+ EXPECT_EQ(errno, EBUSY);
+
+ /* cfd processes the request, then answers ahead of its data */
+ send_recv_str(_metadata, self->fd, self->cfd, "request");
+ send_recv_str(_metadata, self->cfd, self->fd, "answer");
+
+ /* answered */
+ keyupdate_send(_metadata, self->fd, 1);
+ send_recv_str(_metadata, self->fd, self->cfd, "request");
+ send_recv_str(_metadata, self->cfd, self->fd, "answer");
+}
+
+TEST_F(tls, keyupdate_splice)
+{
+ const char *str = "spliced after a KeyUpdate";
+ int len = strlen(str) + 1;
+ char buf[64];
+ int p[2];
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ ASSERT_GE(pipe(p), 0);
+
+ keyupdate_send(_metadata, self->fd, 0);
+ EXPECT_EQ(send(self->fd, str, len, 0), len);
+
+ EXPECT_EQ(splice(self->cfd, NULL, p[1], NULL, len, 0), len);
+ EXPECT_EQ(read(p[0], buf, len), len);
+ EXPECT_EQ(memcmp(buf, str, len), 0);
+
+ close(p[0]);
+ close(p[1]);
+}
+
+TEST_F(tls, keyupdate_manual_rekey)
+{
+ struct tls_crypto_info_keys tls12;
+ int zero = 0;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ /* a key installed by the user removes the registered secrets */
+ tls_crypto_info_init(variant->tls_version, variant->cipher_type,
+ &tls12, 2);
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX, &tls12, tls12.len), 0);
+
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE,
+ &zero, sizeof(zero)), -1);
+ EXPECT_EQ(errno, EINVAL);
+}
+
+TEST_F(tls, keyupdate_manual_rekey_request)
+{
+ struct tls_crypto_info_keys keys;
+ unsigned char secret[48];
+ int len;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ keyupdate_send(_metadata, self->fd, 1);
+
+ /* the answer to the request now goes to user space */
+ len = variant->cipher_type == TLS_CIPHER_AES_GCM_256 ? 48 : 32;
+ memset(secret, 0x3c, sizeof(secret));
+ ASSERT_TRUE(keyupdate_keys(variant->cipher_type, secret, len, &keys));
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX, &keys, keys.len), 0);
+
+ EXPECT_EQ(setsockopt(self->fd, SOL_TLS, TLS_RX_TRAFFIC_SECRET,
+ secret, len), 0);
+
+ keyupdate_send(_metadata, self->fd, 1);
+}
+
+TEST_F(tls, keyupdate_nosignal)
+{
+ int zero = 0;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ /* The default action of SIGPIPE ends the test. The result is not
+ * checked: as for sendmsg(), the error can show up only on the
+ * next call.
+ */
+ EXPECT_EQ(shutdown(self->fd, SHUT_WR), 0);
+ setsockopt(self->fd, SOL_TLS, TLS_TX_KEY_UPDATE, &zero, sizeof(zero));
+}
+
+TEST_F(tls, keyupdate_rate_limit)
+{
+ char buf[64];
+ int i;
+
+ if (!keyupdate_setup(_metadata, self, variant))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ /* the ninth KeyUpdate in one second ends the connection */
+ for (i = 0; i < 9; i++)
+ keyupdate_send(_metadata, self->fd, 0);
+ EXPECT_EQ(send(self->fd, "x", 1, 0), 1);
+
+ EXPECT_EQ(recv(self->cfd, buf, sizeof(buf), 0), -1);
+ EXPECT_EQ(errno, EBADMSG);
+}
+
+TEST_F(tls, keyupdate_record_limit)
+{
+ struct tls_crypto_info_keys get;
+ char sbuf[1024], rbuf[1024];
+ socklen_t optlen;
+ unsigned char *rec_seq;
+ __u16 limit = 128;
+ int i;
+
+ if (variant->cipher_type == TLS_CIPHER_CHACHA20_POLY1305)
+ SKIP(return, "no record limit for ChaCha20-Poly1305");
+ if (!keyupdate_setup_seq(_metadata, self, variant, (1 << 24) - 2))
+ SKIP(return, "no in-kernel KeyUpdate for this variant");
+
+ ASSERT_EQ(setsockopt(self->fd, SOL_TLS, TLS_TX_MAX_PAYLOAD_LEN,
+ &limit, sizeof(limit)), 0);
+ for (i = 0; i < sizeof(sbuf); i++)
+ sbuf[i] = i;
+
+ /* one call writes 8 records or more, past the record limit */
+ EXPECT_EQ(send(self->fd, sbuf, sizeof(sbuf), 0), sizeof(sbuf));
+ EXPECT_EQ(recv(self->cfd, rbuf, sizeof(rbuf), MSG_WAITALL),
+ sizeof(rbuf));
+ EXPECT_EQ(memcmp(sbuf, rbuf, sizeof(sbuf)), 0);
+
+ /* the KeyUpdate in the middle of the call restarted at 0 */
+ tls_crypto_info_init(variant->tls_version, variant->cipher_type,
+ &get, 0);
+ optlen = get.len;
+ ASSERT_EQ(getsockopt(self->fd, SOL_TLS, TLS_TX, &get, &optlen), 0);
+ rec_seq = (unsigned char *)&get + optlen - 8;
+ for (i = 0; i < 5; i++)
+ EXPECT_EQ(rec_seq[i], 0);
+}
+
+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] 6+ messages in thread