From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=pass header.oldest-pass=0 smtp.remote-ip=100.103.45.18 ARC-Seal: i=2; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=pass; t=1793638685; b=AA6ZP1bMDhVh4mntKji9NzouFDR/ppougKukMlFB7iOfOfdZaxeBBGr6fFYkOj7wr8IM soikmsD9jCqRS1/STUr1LBM7SzaT4+RlkkuHmFy40ZhpylpnOlLZWaACFnzEEIp+EQxMg u+kJ3A0ywd12jXLdYB3ZZJTEkEfKllfk1popZl+94acIoOflkyLfIx8NWqfxzP3y1FH2d Pz9tMUZJ61aowN9sICiKiBdnecWW3NtiJU+FQz22M5OnOR+05tEVZbYcLX/Tabx26u+Y+ hnveAFsTEmKddJ38qOdJMh11oHEMsNL32MRfiWMyPZ1oxEwOwwLoIPVsRBbI5gyHBHg== ARC-Message-Signature: i=2; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793638685; h=Received:DKIM-Signature:Received:From:Date:Subject:MIME-Version: Content-Type:Content-Transfer-Encoding:Message-Id:References: In-Reply-To:To:Cc:X-Mailer:X-Developer-Signature:X-Developer-Key: X-Endpoint-Received:X-Original-From:Reply-To; bh=Jp8DX7/QhrlYZH534G3CZnxJ2bnt3NbST8IeVr5PZM8=; b=iQvK81iiWVIO+rXDushH49gbLZzV7eiN224PLaE49YPP2wmld1o3hivbqP+r7T9GJ97V Ko94VmFx3C6GxwKRMQUZMwIw6vS7VYF6ET6D75wx2DptUzdbFt9NIbqfZk/G9vSZWW/W5 n+a+b7k7rcFeFZ+pFTKw9Y90jN/GNB5bLEKB+IVBxoG2mpEgOZ58+TVAK0ka3E9Pw4vQe W4QmLZBXU3hPiV4TU1/2XC6SLyXCnu5mP5uT5gATuwqGu8DCitR4KMZo5256wry2lkqeM 3W6SmFkoZneV8/PdRQwUvRVXgDOpHI7D/j1v3BBy+F5vcsNRZU5IlBNEN1WqEzxsgEw== ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b="I/ICoZ6w"; arc=pass header.oldest-pass=0 smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5C5656074A44; Mon, 2 Nov 2026 16:58:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=I/ICoZ6w Authentication-Results: smtp.kernel.org; arc=none smtp.remote-ip=100.80.115.77 ARC-Seal: i=1; d=kernel.org; s=arc20260519; a=rsa-sha256; cv=none; t=1793638685; b=plOmO91dtLQ/QPGXCNTXqlgiaXHjMysP8yWvJYadHqHZK6WiqY5IOKidf7Dp9hl6S/zp +Hg2lohdfrawF2uyZmoBmLcklYK9MXwjfxwTe8UjHdugBu9lNVfiTOpxh9s3y0hreAo42 16kpDA4TK3BdQTQOLoYBP+9W4PS4BkD6oT1NuS0nnr1+Kk3VyXbHM9jtAEcq5d1dzOJch HFMddRUwoVCuF/ixOxiBy+N5pyAwB1+T7NmWEtGHxezEgmc4CF2FZyJ1nqWdx9qKj/K5A 52GO8Dge5kZk0PsnoK+V6f05V3VvfJnUy2bH8vEi002iuaF12MVnV/RlCFtoUyrF0Yg== ARC-Message-Signature: i=1; d=kernel.org; s=arc20260519; a=rsa-sha256; c=relaxed/relaxed; t=1793638685; h=DKIM-Signature:Received:From:Date:Subject:MIME-Version:Content-Type: Content-Transfer-Encoding:Message-Id:References:In-Reply-To:To:Cc: X-Mailer:X-Developer-Signature:X-Developer-Key:X-Endpoint-Received: X-Original-From:Reply-To; bh=Jp8DX7/QhrlYZH534G3CZnxJ2bnt3NbST8IeVr5PZM8=; b=1D19kbYqe9Bm/+Ed6OD8xBvIe+N9Vv6crWteqiFtWEl1/oKbY5q8VxjJoRqWKqeOq7hz 24PXWV2bDnUS4hTO8wyQ8pj3/Y0O+Fzdknqtm5Xf6OO1pOO3zQ8owaQQy0HIWVOxP/cnU QlSnWxth7Q/fI0ZcSHNaivLEHuwQTR6HjHT7NCiodj/6pI1Py0v2bZ9q4ruMf1BJL1d6a NTzDNiVD+qJk03Hq3nZgM3x+6EQfjy3v10DHvzmJSWzdai7g9kJ4aE8YvJkx0q9dDWc9o nyQ65rrXSLto6XvZzOwbI6fXon7i4b1hBpCgJJF6VAr1D6bQKrXk677+2Au5rX1gimg== ARC-Authentication-Results: i=1; smtp.kernel.org; arc=none smtp.remote-ip=100.80.115.77 Received: from lkml.kernel.org (nexus.subspace.kernel.org [100.80.115.77]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.kernel.org (Postfix) with ESMTPS id 20E431F00A20; Mon, 2 Nov 2026 16:58:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1793638685; bh=Jp8DX7/QhrlYZH534G3CZnxJ2bnt3NbST8IeVr5PZM8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To; b=I/ICoZ6wO2O5DiuG52uxhf7MbGs5Ch9NFYAkOdUGJjT5x/UtYhc02ZYp0vDuwcrhK 0mKxZasle01IPkE6i4iaE8/QI/3B3+A2BpVoCO/tRSSFga1bxpYyLD2ZAzVumqnB6y kUL1hVsXmMuRV4wyuOXbVRzAMxUxjL6wdiIIhBUvOH3BwldWlVOmK+Fe6ZZPaXEs9D cYqHOHF4+cNbitXT4tPFkeFfPObLt0suD8W1Uvb7EWLi2zVIypMIxZ+8glP29KX5x5 Tb9SsQs6Z3DZpwujQW6PiUxl9UAE3QfXiiXuEvV91z5wIIpz5Tf1MX7Oe6VH7oPx4O 9T8f2OBNlvJpw== Received: from nexus.subspace.kernel.org (localhost [IPv6:::1]) by lkml.kernel.org (Postfix) with ESMTP id 95187465AB; Mon, 2 Nov 2026 16:58:01 +0000 (UTC) From: Martin Belanger via B4 Relay Date: Mon, 02 Nov 2026 11:57:59 -0500 Subject: [PATCH net-next v4 2/5] tls: add socket options to register the traffic secrets X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261102-keyupdate-rfc-v4-2-f17419972b9f@gmail.com> References: <20261102-keyupdate-rfc-v4-0-f17419972b9f@gmail.com> In-Reply-To: <20261102-keyupdate-rfc-v4-0-f17419972b9f@gmail.com> 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@vger.kernel.org, netdev@vger.kernel.org, linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, Hannes Reinecke , Chuck Lever , kernel-tls-handshake@lists.linux.dev, linux-nvme@lists.infradead.org, Alistair Francis , Martin Belanger X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1793638681; l=10158; i=martin.nvme.dev@gmail.com; s=20261014; h=from:subject:message-id; bh=XULSW2ld30PktMPOL3DYioR7rw0P37WP71UH+9VxP6I=; b=WwVgmtXXlJlEl8MIYHMW3W3iYeVPJI2PzQOp6qivYUh8naBzW1xxYpldmcF12doSxahBqr0yC /67g4aLaOdMDszqTjTneEdgaIyURuADyVc9n2aCFzXAT72+A1dCMqNw X-Developer-Key: i=martin.nvme.dev@gmail.com; a=ed25519; pk=4K+XXetGK35w2LzRRddd/bbHawZU222BN+YZRuSpN+k= X-Endpoint-Received: by B4 Relay for martin.nvme.dev@gmail.com/20261014 with auth_id=1176 X-Original-From: Martin Belanger Reply-To: martin.nvme.dev@gmail.com From: Martin Belanger 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. Reviewed-by: Hannes Reinecke Signed-off-by: Martin Belanger --- 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 b8d5a843f761..3ab71e98527a 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -31,6 +31,8 @@ * SOFTWARE. */ +#include +#include #include #include @@ -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; @@ -868,6 +880,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) { @@ -901,6 +1037,13 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval, release_sock(sk); mutex_unlock(&ctx->tx_lock); 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