[PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule
flat view
WARM3d
REVIEWED: 1 (1M)
From: Martin Belanger via B4 Relay <devnull+martin.nvme.dev.gmail.com@kernel.org>
Date: 2026-10-28 13:52:56
Also in:
b4-sent, linux-doc, linux-kselftest, linux-nvme, lkml
Subsystem:
networking [general], networking [tls], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, John Fastabend, Sabrina Dubroca, Linus Torvalds
Status: Reviewed.
1 review trailer.
From: Martin Belanger <redacted> 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 <redacted> --- 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