From patchwork Thu Jul 2 11:52:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5066 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550681max; Thu, 2 Jul 2026 04:53:25 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8Xzz2hDOop5/RppE0sMaL3qnhAuUSq6BS7ZjmZJoKdCLYgmH1uEcda1o+bpAt2h7iFazjpY8tAH3U=@openvpn.net X-Received: by 2002:a05:6820:1806:b0:69e:b7f4:2a14 with SMTP id 006d021491bc7-6a309a13352mr3542414eaf.26.1782993205171; Thu, 02 Jul 2026 04:53:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993205; cv=none; d=google.com; s=arc-20260327; b=ZwYYf1Opp8seNLU7ltXfE+uyBCpThiX6pCPhavU/A55RGN58W9omjgrvp+/tHXeZIG Sy/Yr3OXlXKJGypeqGH24UX93yaG14wveO+B/DEDOFKuvZ1kUdrVORHICngO4tjGopn/ qRhoYcU9xXsmIPrHauivNwmbXKnHmL+e0SONZOXazGZuQ7tn0jcmh1yUvSrxX08BFBuu yphH0FYbUStHV3M1LNTIPjBTUUrzFmmLM0yVA2vFcOeQL5tdtUmve5OSaycJ1FLj1ehk 5n+0V7FOlthnB4xi1IYN2Nf9yEmJUIt/RC9M2rt33P46H9ypTOorkvnTdz8bECKIVjkY hbYQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=XR1XHt8CdQHwi0OELWA7z5dtq0JNZIW4+cNyVevzqbQ=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=lJeoNUumaFXnRSfh9U8zrjiG6VX9baLlUXMU6y6iPqmhMUGKg3FvOyukl4gQgvKFR5 wOzB+OpFfVl2Vx0duN+xj+OVgTmNhXDb7OWxVj5nicE+eLOmYeNrwvh+8cp5cq80wWkV uZp6Qrqb5yzdZ+dKYAwdz44ksh7DUubPDJPtuHPHZ0ut3jtRlstasRjo4fqm+XlqR8Ub SYgB3/9xRP189+0mBX666RT1gfdexzyWgFptRF+QoDcmwVrieuHmUSBZHqMuBRN7V6Mp o4/7LWQAFsPFB3bDgZ2Iebq60KZkrSIB0M9XaGjuK5h9FZL2CYUWSjcmJefM8ip8Re8y aZ5A==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=BoxqGQej; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=bWYwR1RP; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="DBZbFQA/"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="n5/FKP8z"; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-6a30ffea00fsi2135716eaf.28.2026.07.02.04.53.24 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:25 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=BoxqGQej; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=bWYwR1RP; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="DBZbFQA/"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="n5/FKP8z"; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=XR1XHt8CdQHwi0OELWA7z5dtq0JNZIW4+cNyVevzqbQ=; b=BoxqGQej90kGFG4LYo8Wvkb8Nu eTaEeFM1IT/ubLqUPMoi6yHUUxgznKIW+iMk9j/AvyTuKaSUwHkg2kVVPpZGw1oKcFJEUH4e3qYY0 puCycb6vTQSMfbXcLDRgIR6xrAvkf/6E+MsJKW/wwcHt7tyaDnEmHnJIiHZwHGKXQysc=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFyo-0001gI-TZ; Thu, 02 Jul 2026 11:53:19 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFym-0001gA-OZ for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:18 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=lOOxtbNdj0/1K+eQqMh7U0u+VrjYM+S83uLDH/KN33Y=; b=bWYwR1RPgeBtbFldE6dr3ECn/r R6os5yUE1QWUArgp6b94K658X9/Z4/ouQeUptPoaaSfK3tnGUNtZf9sGNgAS0oIIlJMGCe8Z/DJ1n utqlpKVYsRPROlDYt1HJ2ojEdZ7ZdXJax8msO5tIao3emN8bWupxFl4OzXFhyqkQFlmY=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=lOOxtbNdj0/1K+eQqMh7U0u+VrjYM+S83uLDH/KN33Y=; b=DBZbFQA/aXPhtxGSM85RvpgyLL fafsoU94pmSxMU4WOjaXeh3BO1P/OHJhlMjhxRWfvt2gD30pBXmnuirMavE5zD6z9cCloMTEFVs7Y 9zoKerRipmE596hsN2g9g6G8BPKso9oiZZddYyMiMGV4vQhesL8xfVOtq2S2YhX/JPqU=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyk-0004JD-TS for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:17 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4grZyW2H80zDs65; Thu, 2 Jul 2026 13:53:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993187; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=lOOxtbNdj0/1K+eQqMh7U0u+VrjYM+S83uLDH/KN33Y=; b=n5/FKP8zwZmRwR6uA6zSEx+Nr0f7a7QKSOD9EEhsDnHO8VAglxbZGsPv4HQoGCUKSIgIII GM5bddeGhGHPk0QqrursOBl+3zm7uqNfpLya1X6syEMsC9wYOZ/3RllT9Ucw4Hu1t5Xn6G 0ws8/MUfR+yiaz0Qj5QNooAupoX39Aa+Ph5xSVCe4+RQVI99i545m2jwkDi20fjjt3IFUL +RHCa/0GNiGPWX++kPUzIHsAj5QHrt8mYwxIu34wvkR72bPR0E52lJvmVgDkpu3n1e3QvE jrnhvCIQUhCjtRme/slzNPSR1y70jP0pizGA3CKuLoQMmPnqDAfo+Jgd3TPBkg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:16 +0200 Message-ID: <6c66a38af8ea53e4fbc587f087c594573fd8ee6a.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4grZyW2H80zDs65 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: OpenVPN needs packet-ID and cipher usage thresholds to decide when a data-channel key should be renewed or stopped. The existing transmit packet-ID helper only returns the next ID and has no way to re [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFyk-0004JD-TS Subject: [Openvpn-devel] [RFC ovpn net-next v2 01/14] ovpn: prepare packet ID state for usage limits X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603882794310497 X-GMAIL-MSGID: 1869603882794310497 OpenVPN needs packet-ID and cipher usage thresholds to decide when a data-channel key should be renewed or stopped. The existing transmit packet-ID helper only returns the next ID and has no way to request a threshold notification. Teach the transmit packet-ID helper to return 1 when the fixed OpenVPN packet-ID soft threshold is crossed. The counter is monotonic, so the exact crossing is naturally reported only once. The hard packet-ID wrap check still stops TX; this only gives userspace a chance to rekey while packet-ID space is still available. Signed-off-by: Ralf Lici --- No changes since v1 https://lore.kernel.org/openvpn-devel/6c66a38af8ea53e4fbc587f087c594573fd8ee6a.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto_aead.c | 3 +++ drivers/net/ovpn/netlink.c | 4 ++-- drivers/net/ovpn/pktid.h | 31 +++++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 8f07c418622b..86b69aeddcf7 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -19,6 +19,7 @@ #include "pktid.h" #include "crypto_aead.h" #include "crypto.h" +#include "netlink.h" #include "peer.h" #include "proto.h" #include "skb.h" @@ -207,6 +208,8 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid); if (unlikely(ret < 0)) return ret; + if (unlikely(ret > 0)) + ovpn_nl_key_swap_notify(peer, ks->key_id); /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes * nonce diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 4c66c1ec497e..83d81a468b5a 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -1312,8 +1312,8 @@ int ovpn_nl_key_swap_notify(struct ovpn_peer *peer, u8 key_id) int ret = -EMSGSIZE; void *hdr; - netdev_info(peer->ovpn->dev, "peer with id %u must rekey - primary key unusable.\n", - peer->id); + netdev_info(peer->ovpn->dev, "peer with id %u should rekey key %u\n", + peer->id, key_id); msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); if (!msg) diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 21845f353bc8..82f59256b4a3 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -17,6 +17,9 @@ */ #define PKTID_RECV_EXPIRE (30 * HZ) +/* notify userspace when the packet ID space is close to wrapping */ +#define PKTID_XMIT_REKEY_NOTIFY 0xff000000U + /* Packet-ID state for transmitter */ struct ovpn_pktid_xmit { atomic_t seq_num; @@ -52,20 +55,36 @@ struct ovpn_pktid_recv { spinlock_t lock; }; -/* Get the next packet ID for xmit */ +/** + * ovpn_pktid_xmit_next - allocate a transmit packet ID + * @pid: transmit packet ID state + * @pktid: location where the generated packet ID is stored + * + * The returned packet ID becomes part of the AEAD nonce, so the helper rejects + * the packet before the 32-bit packet-ID space wraps. + * + * The packet-ID soft threshold does not reject the packet. It returns 1 once + * so the caller can notify userspace to rekey while packet-ID space remains. + * + * Return: 1 if userspace should be notified, 0 if no notification is needed, + * or a negative error code otherwise. + */ static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid) { const u32 seq_num = atomic_fetch_add_unless(&pid->seq_num, 1, 0); - /* when the 32bit space is over, we return an error because the packet - * ID is used to create the cipher IV and we do not want to reuse the - * same value more than once - */ + int ret = 0; + + /* packet IDs are used to create cipher IVs and must not wrap */ if (unlikely(!seq_num)) return -ERANGE; + /* notify userspace before the packet ID space is close to wrapping */ + if (unlikely(seq_num == PKTID_XMIT_REKEY_NOTIFY)) + ret = 1; + *pktid = seq_num; - return 0; + return ret; } /* Write 12-byte AEAD IV to dest */ From patchwork Thu Jul 2 11:52:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5067 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550682max; Thu, 2 Jul 2026 04:53:25 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/22EHFgqmikTFHgi4Zu34LOtmOYUxbEayN4m33thdb/0tBZ3Ub7J4rn9aDBnVg+3JJp+QIL8YSfu4=@openvpn.net X-Received: by 2002:a05:6820:615:b0:69d:e746:6ec9 with SMTP id 006d021491bc7-6a30996ad59mr3379688eaf.9.1782993205136; Thu, 02 Jul 2026 04:53:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993205; cv=none; d=google.com; s=arc-20260327; b=K9JKbGcG/00JCtdD6XoYUgcdT+hTyWr0uR35PqRjl9DaJmSrxui0ryj05Qfl2nS2vV an0FWRwoeljUZKvyiIRZj0wiJZqdjMfpGKWjCTcFHhEGNawIKJKlFdkwozhEmsy7BHN0 NyX3NOLoI92xUwEVyfm3tXSTJMMLbVTf/VcLKTSxc6j2m/hDdbg/cL+dxyCtQEUZI1TN f8P/WPsaKrrvZbJYG2ao7IHu4S1XtSARNOta8BqO6G5l4N5DyyrxH42DqaDVMcaQQrYm zNRlP2kSOasihva3eIVQKc90xwPabDqvqkpKELokUVjzhstMRnSWtMt7DLlznzDf6AuQ ulHQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=4VssKMSNPLrEcr45wLt/8lw1U4Q5RXyDAmx7rne0bRU=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=ct2g1bFeTp1KmS59cng4FlBppSA/BrEXLmJkxZ7SJ+Kz0dfv5OcdjcOwZcjvhEPBeU 3jXT6Rkz7pH6YeukNQ3npfgOqOrHfcZZeK1exUN9B+pnG7Q4LZk4qGMp0sVG4RePTPTh O8gY05TZY+EBKl9Wp18HmGaaXXPLm/nPjHI3+HGAV8K9nMIM6IeYd3jydtFYePYhVRoe ipqFrjQRw2sk4SYsGyaxD4XeDZiFQwkXQcy8yW5m6T11u3Fu5qDNug00IueOv5dI2zG2 2XQTfycASBGW3Fof8eGYLaPSpzbi9vRcT6QIxbitdfOQlP5z3B+LqzJ9SasPtUkRk6lt WJUg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=lN3XLs8O; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="ROeswx/X"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=WIr9MXsg; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=SKwXvqlk; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-44cbee12cd5si2220833fac.121.2026.07.02.04.53.24 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:24 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=lN3XLs8O; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="ROeswx/X"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=WIr9MXsg; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=SKwXvqlk; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=4VssKMSNPLrEcr45wLt/8lw1U4Q5RXyDAmx7rne0bRU=; b=lN3XLs8O2KOBwIhOVlaaDnHysv BGPc6csEpOb84Roypk+a6wywD/N0TtRGl9FqVkBFmdxe8mn8D4K1PXlVA2l0nMmWTuzqTGu1x/XxN 4mCLKH5nmX8fYamnjw4oDRv0RX6gdMo0/H1BesnRJLZwec0y62fjM7McLJATMcI/Bcuk=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFyu-0007sy-4I; Thu, 02 Jul 2026 11:53:21 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFys-0007sq-7A for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:19 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=mUdPcWJA54KjHwEoP5qShFb9sBFj57tZ32CCMC7tT+A=; b=ROeswx/XOkiOW57XuSwvrEOCQ6 NRoBv6ESzq72kBVsvGlT5UV6wJCSUIjAor5C6xHZGrhjXUTXCX6b2h5Nk1u1StP8QzhYqTj4bq9Yw 78IbrzPb/DWFHzV9TGR08xiwaLCzsKhyim1dg1e4tPU+v6vmvPdHhBUpMuAhvJDR4IYA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=mUdPcWJA54KjHwEoP5qShFb9sBFj57tZ32CCMC7tT+A=; b=WIr9MXsgDhyjI3v+rg04Mo5NvH 6nPf5m19u+GP6fyPMN/0CvDBWHxvwMvYcEpqBCS6D3Kvc+wXo2W917gloTUqAi0090eZLQUAR856U cEi9JFQ/i/D8izzMXoE/4feynxVYurqtsO4HNDYEmStiiQKVLj/eqIr0SB2tjL8Wd48U=; Received: from mout-b-206.mailbox.org ([195.10.208.51]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyn-0004JL-In for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:19 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-206.mailbox.org (Postfix) with ESMTPS id 4grZyY0GtNz9yMC; Thu, 2 Jul 2026 13:53:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993189; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=mUdPcWJA54KjHwEoP5qShFb9sBFj57tZ32CCMC7tT+A=; b=SKwXvqlk4JO+R73uGmK6htQ0B0FDDy1mScYKTT8dv/fCcmGxehwcGlxEIdLAmqqqwcpDlH ulTOA5lTe0XPj/RxaRBdMZK0zbuxyMKo9vGpP5LK0Rr3PvBVPXN8dCtW6Yr4NiYTcPpSC7 N45sBo3OVLHkRW4g68EoBZsOpKcAjrNEv8x2C+x0fCEbhFKu2F5hnkJQQLcjQaDdR3oKd/ vAKHqGGqqfNc5ZQ+9RD1eoQW99tPstmbb/K0LpmM59WBMn9OlzQdc+s7pOgjc+tomKoVbN RaDibgjqc1JijsxyzZ7+aeXAC8yPk2UY0magweh2MYrogDP+L7P20e+7AC640Q== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:17 +0200 Message-ID: <039bea1f79025e6374c6d3237f4f6e003060a996.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4grZyY0GtNz9yMC X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: AES-GCM has a finite per-key usage bound in terms of GCM block operations. ovpn already stops before packet-ID wrap, but it did not account for this tighter limit, so a large-volume key could exceed t [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFyn-0004JL-In Subject: [Openvpn-devel] [RFC ovpn net-next v2 02/14] ovpn: enforce AES-GCM usage limits X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603882996273308 X-GMAIL-MSGID: 1869603882996273308 AES-GCM has a finite per-key usage bound in terms of GCM block operations. ovpn already stops before packet-ID wrap, but it did not account for this tighter limit, so a large-volume key could exceed the AES-GCM usage budget without any kernel-side action. Keep the configured cipher in the key slot and convert authenticated data and plaintext length to AES block units for AES-GCM. The block helper is cipher-specific rather than based on crypto_aead_blocksize, since GCM implementations can advertise a provider block size that is not the GCM accounting block. On TX, reserve the blocks with packet ID allocation and fail once the combined invocation/block budget would be exceeded, which drives the existing key-kill notification path. Also notify userspace once when TX or RX usage crosses 7/8 of the fixed hard limit, so userspace can rekey before the hard limit is reached. RX counts only authenticated, non-replayed packets and does not drop solely because the peer crossed a local usage threshold. Signed-off-by: Ralf Lici --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/bee985b879f9e5b740921e448fa024500ac1340c.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto.h | 5 ++ drivers/net/ovpn/crypto_aead.c | 13 +++- drivers/net/ovpn/crypto_limits.h | 125 +++++++++++++++++++++++++++++++ drivers/net/ovpn/io.c | 11 +++ drivers/net/ovpn/pktid.h | 57 ++++++++++++-- 5 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 drivers/net/ovpn/crypto_limits.h diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 0e284fec3a75..09d3a945c5d9 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNCRYPTO_H_ #define _NET_OVPN_OVPNCRYPTO_H_ +#include "crypto_limits.h" #include "pktid.h" #include "proto.h" @@ -37,6 +38,8 @@ struct ovpn_peer_key_reset { struct ovpn_crypto_key_slot { u8 key_id; + enum ovpn_cipher_alg cipher_alg; + struct ovpn_limit usage_limit; struct crypto_aead *encrypt; struct crypto_aead *decrypt; @@ -44,7 +47,9 @@ struct ovpn_crypto_key_slot { u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE]; struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp; + struct ovpn_key_usage usage_recv; struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp; + struct ovpn_key_usage usage_xmit; struct kref refcount; struct rcu_head rcu; }; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 86b69aeddcf7..9b10ae6f6002 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -139,6 +139,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { const unsigned int tag_size = crypto_aead_authsize(ks->encrypt); + unsigned int plaintext_len; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; @@ -149,6 +150,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ovpn_skb_cb(skb)->peer = peer; ovpn_skb_cb(skb)->ks = ks; + plaintext_len = skb->len; /* Sample AEAD header format: * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a... @@ -205,7 +207,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* obtain packet ID, which is used both as a first * 4 bytes of nonce and last 4 bytes of associated data. */ - ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &pktid); + ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &ks->usage_xmit, + &ks->usage_limit, + ovpn_aead_limit_blocks(ks->cipher_alg, + OVPN_AAD_SIZE, + plaintext_len), + &pktid); if (unlikely(ret < 0)) return ret; if (unlikely(ret > 0)) @@ -425,6 +432,10 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc) ks->decrypt = NULL; kref_init(&ks->refcount); ks->key_id = kc->key_id; + ks->cipher_alg = kc->cipher_alg; + ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); + ovpn_key_usage_init(&ks->usage_xmit); + ovpn_key_usage_init(&ks->usage_recv); ks->encrypt = ovpn_aead_init("encrypt", alg_name, kc->encrypt.cipher_key, diff --git a/drivers/net/ovpn/crypto_limits.h b/drivers/net/ovpn/crypto_limits.h new file mode 100644 index 000000000000..2af2c3389852 --- /dev/null +++ b/drivers/net/ovpn/crypto_limits.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2026 OpenVPN, Inc. + * + * Author: Ralf Lici + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_CRYPTO_LIMITS_H_ +#define _NET_OVPN_CRYPTO_LIMITS_H_ + +#include +#include +#include +#include +#include +#include + +/* use the OpenVPN/SP 800-38D AES-GCM invocation limit */ +#define OVPN_AES_GCM_USAGE_LIMIT ((1ULL << 36) - 1) + +/* notify userspace at 7/8 of the AES-GCM hard limit */ +#define OVPN_AES_GCM_USAGE_NOTIFY (OVPN_AES_GCM_USAGE_LIMIT / 8 * 7) + +#define OVPN_KEY_USAGE_NOTIFY_BIT 0 + +struct ovpn_limit { + u64 soft; + u64 hard; +}; + +struct ovpn_key_usage { + atomic64_t blocks; + unsigned long flags; +}; + +static inline void ovpn_key_usage_init(struct ovpn_key_usage *usage) +{ + atomic64_set(&usage->blocks, 0); + usage->flags = 0; +} + +static inline void +ovpn_key_usage_limit_init(struct ovpn_limit *limit, + enum ovpn_cipher_alg cipher_alg) +{ + limit->soft = U64_MAX; + limit->hard = U64_MAX; + + switch (cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + limit->soft = OVPN_AES_GCM_USAGE_NOTIFY; + limit->hard = OVPN_AES_GCM_USAGE_LIMIT; + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + default: + break; + } +} + +static inline bool ovpn_key_usage_over_limit(u64 limit, u64 pktid, u64 blocks) +{ + return pktid > limit || blocks > limit - pktid; +} + +static inline bool ovpn_key_usage_notify_once(struct ovpn_key_usage *usage) +{ + return !test_and_set_bit(OVPN_KEY_USAGE_NOTIFY_BIT, &usage->flags); +} + +static inline int +ovpn_key_usage_xmit(struct ovpn_key_usage *usage, + const struct ovpn_limit *limit, + u64 pktid, u64 blocks, bool pktid_notify) +{ + int ret = 0; + u64 total; + + total = atomic64_add_return(blocks, &usage->blocks); + + /* tx must stop before the hard limit is crossed */ + if (unlikely(ovpn_key_usage_over_limit(limit->hard, pktid, total))) + return -ERANGE; + + /* soft limits ask userspace to rekey before tx must stop */ + if (unlikely(pktid_notify || + ovpn_key_usage_over_limit(limit->soft, pktid, total)) && + ovpn_key_usage_notify_once(usage)) + ret = 1; + + return ret; +} + +static inline bool +ovpn_key_usage_recv(struct ovpn_key_usage *usage, + const struct ovpn_limit *limit, + u64 pktid, u64 blocks) +{ + u64 total; + + total = atomic64_add_return(blocks, &usage->blocks); + + /* rx threshold crossings are reported once without dropping + * the packet + */ + return unlikely(ovpn_key_usage_over_limit(limit->soft, pktid, total)) && + ovpn_key_usage_notify_once(usage); +} + +static inline u64 ovpn_aead_limit_blocks(enum ovpn_cipher_alg cipher_alg, + unsigned int aad, + unsigned int bytes) +{ + switch (cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + return DIV_ROUND_UP_ULL(aad, AES_BLOCK_SIZE) + + DIV_ROUND_UP_ULL(bytes, AES_BLOCK_SIZE); + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + default: + return 0; + } +} + +#endif /* _NET_OVPN_CRYPTO_LIMITS_H_ */ diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index a6b777a9c2d9..31c750b9481a 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -112,6 +112,7 @@ void ovpn_decrypt_post(void *data, int ret) struct sk_buff *skb = data; struct ovpn_socket *sock; struct ovpn_peer *peer; + u64 aead_blocks; __be16 proto; __be32 *pid; @@ -141,6 +142,16 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, + OVPN_OPCODE_SIZE + + OVPN_NONCE_WIRE_SIZE, + skb->len - payload_offset); + if (unlikely(ovpn_pktid_recv_update_aead(&ks->pid_recv, + &ks->usage_recv, + &ks->usage_limit, + aead_blocks))) + ovpn_nl_key_swap_notify(peer, ks->key_id); + /* keep track of last received authenticated packet for keepalive */ WRITE_ONCE(peer->last_recv, ktime_get_real_seconds()); diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 82f59256b4a3..a85a1d160150 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNPKTID_H_ #define _NET_OVPN_OVPNPKTID_H_ +#include "crypto_limits.h" #include "proto.h" /* If no packets received for this length of time, set a backtrack floor @@ -58,35 +59,75 @@ struct ovpn_pktid_recv { /** * ovpn_pktid_xmit_next - allocate a transmit packet ID * @pid: transmit packet ID state + * @usage: key usage state + * @limit: key usage limits + * @aead_blocks: AEAD usage blocks consumed by this packet * @pktid: location where the generated packet ID is stored * * The returned packet ID becomes part of the AEAD nonce, so the helper rejects - * the packet before the 32-bit packet-ID space wraps. + * the packet before the 32-bit packet-ID space wraps. It also reserves this + * packet's AEAD usage against the key and rejects the packet before the hard + * AES-GCM usage limit would be exceeded. * - * The packet-ID soft threshold does not reject the packet. It returns 1 once - * so the caller can notify userspace to rekey while packet-ID space remains. + * Soft thresholds do not reject the packet. They return 1 once so the caller + * can notify userspace to rekey while packet-ID space remains and before the + * hard AES-GCM usage limit is reached. * * Return: 1 if userspace should be notified, 0 if no notification is needed, * or a negative error code otherwise. */ -static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u32 *pktid) +static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, + struct ovpn_key_usage *usage, + const struct ovpn_limit *limit, + u64 aead_blocks, u32 *pktid) { const u32 seq_num = atomic_fetch_add_unless(&pid->seq_num, 1, 0); - int ret = 0; + bool pktid_notify; + int ret; /* packet IDs are used to create cipher IVs and must not wrap */ if (unlikely(!seq_num)) return -ERANGE; - /* notify userspace before the packet ID space is close to wrapping */ - if (unlikely(seq_num == PKTID_XMIT_REKEY_NOTIFY)) - ret = 1; + pktid_notify = seq_num >= PKTID_XMIT_REKEY_NOTIFY; + ret = ovpn_key_usage_xmit(usage, limit, seq_num, aead_blocks, + pktid_notify); + if (unlikely(ret < 0)) + return ret; *pktid = seq_num; return ret; } +/** + * ovpn_pktid_recv_update_aead - account receive-side AEAD usage + * @pr: receive packet ID state + * @usage: key usage state + * @limit: key usage limits + * @aead_blocks: AEAD usage blocks consumed by this packet + * + * RX AEAD accounting is only informational for the local userspace process. + * The peer's packets that already passed authentication and replay checks are + * not dropped because the peer crossed a local usage threshold. + * + * Return: true if userspace should be notified, false otherwise. + */ +static inline bool +ovpn_pktid_recv_update_aead(struct ovpn_pktid_recv *pr, + struct ovpn_key_usage *usage, + const struct ovpn_limit *limit, + u64 aead_blocks) +{ + bool ret; + + spin_lock_bh(&pr->lock); + ret = ovpn_key_usage_recv(usage, limit, pr->id, aead_blocks); + spin_unlock_bh(&pr->lock); + + return ret; +} + /* Write 12-byte AEAD IV to dest */ static inline void ovpn_pktid_aead_write(const u32 pktid, const u8 nt[], From patchwork Thu Jul 2 11:52:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5068 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550749max; Thu, 2 Jul 2026 04:53:29 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/8xGMn8SCOx+Z5ra5lG+1jI7XlwTa06UbNsrntK1KDYvVY6n3UddNI+uYZEDHGWHsxR6yAakKxb2E=@openvpn.net X-Received: by 2002:a05:6830:6f45:b0:7e9:cc39:2ef0 with SMTP id 46e09a7af769-7eb4cc18739mr3960185a34.18.1782993208970; Thu, 02 Jul 2026 04:53:28 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993208; cv=none; d=google.com; s=arc-20260327; b=E2ML2wPnJXVaYJkFsj2bOTH1v665IMH25nN89zIy80MktGhjrochrDCmjEORUTLP8D ngrTGTuv4DIFl5gxVf7mGaS4E7eTpj1GWYJVnqSH/FzZkk2TrsomqM+hWG179TjmauFL a1jbHkUKX9EGlSjMCE3g85Qw3eJmuu4qstlWbdxgQ5rRkWXscR/+G1LIIg5Vl9ZgoMzK 9tydfuxJ5q/7JAqrn2Ndwr+udm0PYNeqW8U/08xT3lxAdS1EnzJyJdcxodWzkw12RWAM QhQHI8/d6w9uy1OR0A/GeAwArXO+my/R9ea33D/rJOD0+t6bjIyJPyiqQPA7u5zUhbi0 vjvA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=HE1EzEDVyOgkbHZbpYVDh1peytTj8PcbaQeOJPa/8cM=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=I3m8qw+izYkPPpm6d+iD0b74V2bONA1myJTWQ7TpAfFkzOPN8mnqc2mftfNUVm0kCd w9loC3mYE9D9VVasXRJ0fdzJUjvn6F0p5uR6FQ14UUSrK3MkNpdQyufpFAQcvTOs0blD 7BSrcAMnB39U0qVpDvEzN33V4jauCsa4YrenBrf5N04j0aEMc6O7KFs0Amzu1gJ4TaSS P+KPg+ZE7C75QWHtZfXCDOouOu+8TreqEA199YOI7mVu8VfTMDdtLgoIlMyKFujFujXf nZmcKAiZYlVEcbIQAzkMk2A7teDOihVKuoopq2R1FvsKpYn611PDMKGgZVUFNRfnWlXT EvuA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=PnSvxLYU; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=PLOXfu1i; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TkWCe2H8; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=wcRZJXgV; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-7eb542a2aa5si2354297a34.22.2026.07.02.04.53.28 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:28 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=PnSvxLYU; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=PLOXfu1i; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TkWCe2H8; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=wcRZJXgV; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=HE1EzEDVyOgkbHZbpYVDh1peytTj8PcbaQeOJPa/8cM=; b=PnSvxLYUSa3LTAE2fwAyHYMN6j Xjh7pVdFu7YvhdZ2XklkE0CTi0hoLOme3NA0WYuy5eJTbx9DWwAx9EF2xAfg7HJU+zdzEouVba7rh XylSZ+7Q5t7lG6vhe0YsTqSW8Dc99TmC+caSlWBcvDkhFUQHECyY52hF4jrjzAdKbO6I=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFyy-0007tM-Ee; Thu, 02 Jul 2026 11:53:25 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFyx-0007tF-UR for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:25 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=sedkrn1igrLYS2D5wNpQ2omlJGdpqGF+DCO29pv2lVE=; b=PLOXfu1iaYqQHsCnmzF33iZ/3V brDAVdvRoX/q2cCopvSRrIZNA+v6BVupZANu5SLNesLGiYzkz6yQ+TuDjM017cDfw9wrJbK16Tu8i p9gY6CILqeKEDVERudlb2q7LQy8TJiSNPc9cKqjcwad2CvKQ/ZyCIKMokKJm13QdoHS0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=sedkrn1igrLYS2D5wNpQ2omlJGdpqGF+DCO29pv2lVE=; b=TkWCe2H8gLxXUKxwvdggPRhgNF OsJIsvk7LK+4oQJ2ioEz+Sw8U4DNK7gg7bh04fh4SRd/eBxfA3/9J1nMW3G3snyfzekY08VdnA9W8 3tTvTaW21QXFGRbrFeYcaoj63s06EXKpIEaOcaNG/Uj0G5WO7SZJMSJ1qJcVMAIOmyXI=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyt-0004Jn-G7 for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:25 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4grZyZ53CnzDs9r; Thu, 2 Jul 2026 13:53:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993190; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=sedkrn1igrLYS2D5wNpQ2omlJGdpqGF+DCO29pv2lVE=; b=wcRZJXgVhcCk21iwxmx4EGkIvun76rUT1hX6u7h5fSXYNoKCGsOdrKl1ar3x4J2P2N11+d mX7ULbFY2Jv0+fxrwgiZqyyojUZcc4o2yZuJM8KCYAdeE1Yy0fHPY6HJ/NiVyTF+f8VjXI hla+TzJuydT+mOr/ethLsIpV5siWM///nW9L8MuNsUVDiwybYzy/julKQkfpn+A4qkeoGv 6JX8ycMXDnUFq2QPetUF8HEr5G+bKolf6Qi19uLL36I/yUoGvebGMsS8OO8CrnWLC/cCKr wAI8y40zzvXBzVKCRIrpYOx/xb2aFnhE05Hj3OIKgCHudZDWtmbMJtYjPFMqDA== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:18 +0200 Message-ID: <9ddcecb7c766c02e48f3bbe7c88eba4e195df68f.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: OpenVPN userspace tracks AEAD tag verification failures per decrypt key. It asks for a new key after more than 2^35 failures and stops attempting AEAD decryption with that key after more than 2^36 fai [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFyt-0004Jn-G7 Subject: [Openvpn-devel] [RFC ovpn net-next v2 03/14] ovpn: limit AEAD decrypt verification failures X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603887217358567 X-GMAIL-MSGID: 1869603887217358567 OpenVPN userspace tracks AEAD tag verification failures per decrypt key. It asks for a new key after more than 2^35 failures and stops attempting AEAD decryption with that key after more than 2^36 failures. Mirror that behavior in ovpn. Count only crypto_aead_decrypt authentication failures, reported by the crypto API as -EBADMSG, and notify userspace once when the soft threshold is crossed. Once the hard threshold is exceeded, reject new decrypt attempts with the same key before submitting more crypto work. This applies to all ovpn AEAD ciphers, not only AES-GCM, and is independent from the AES-GCM packet/block usage limits. Signed-off-by: Ralf Lici --- No changes since v1 https://lore.kernel.org/openvpn-devel/ff21c6997ad080e4b23541fccba120730de8e96d.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto.h | 2 ++ drivers/net/ovpn/crypto_aead.c | 24 ++++++++++++++++++++++++ drivers/net/ovpn/crypto_aead.h | 1 + drivers/net/ovpn/io.c | 6 ++++++ 4 files changed, 33 insertions(+) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 09d3a945c5d9..2be7ff54fc40 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -43,6 +43,8 @@ struct ovpn_crypto_key_slot { struct crypto_aead *encrypt; struct crypto_aead *decrypt; + atomic64_t decrypt_failures; + unsigned long decrypt_failure_flags; u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE]; u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE]; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 9b10ae6f6002..9e34a553f59a 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -29,6 +29,25 @@ #define ALG_NAME_AES "gcm(aes)" #define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" +#define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY BIT_ULL(35) +#define OVPN_AEAD_DECRYPT_FAILURE_LIMIT BIT_ULL(36) +#define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT 0 + +static bool +ovpn_aead_decrypt_failure_exceeded(const struct ovpn_crypto_key_slot *ks) +{ + return atomic64_read(&ks->decrypt_failures) > + OVPN_AEAD_DECRYPT_FAILURE_LIMIT; +} + +bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks) +{ + u64 failures = atomic64_inc_return(&ks->decrypt_failures); + + return failures > OVPN_AEAD_DECRYPT_FAILURE_NOTIFY && + !test_and_set_bit(OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT, + &ks->decrypt_failure_flags); +} static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks) { @@ -270,6 +289,9 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, if (unlikely(payload_len < 0)) return -EINVAL; + if (unlikely(ovpn_aead_decrypt_failure_exceeded(ks))) + return -EKEYREJECTED; + /* Prepare the skb data buffer to be accessed up until the auth tag. * This is required because this area is directly mapped into the sg * list. @@ -436,6 +458,8 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc) ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); ovpn_key_usage_init(&ks->usage_xmit); ovpn_key_usage_init(&ks->usage_recv); + atomic64_set(&ks->decrypt_failures, 0); + ks->decrypt_failure_flags = 0; ks->encrypt = ovpn_aead_init("encrypt", alg_name, kc->encrypt.cipher_key, diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index 65a2ff307898..e5ff0b9b5ee3 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -25,5 +25,6 @@ ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc); void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks); enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks); +bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks); #endif /* _NET_OVPN_OVPNAEAD_H_ */ diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 31c750b9481a..8085cc345c59 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -129,6 +129,12 @@ void ovpn_decrypt_post(void *data, int ret) /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); + if (unlikely(ret == -EBADMSG)) { + if (unlikely(ovpn_aead_decrypt_failure_record(ks))) + ovpn_nl_key_swap_notify(peer, ks->key_id); + goto drop; + } + if (unlikely(ret < 0)) goto drop; From patchwork Thu Jul 2 11:52:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5069 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550812max; Thu, 2 Jul 2026 04:53:32 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/cEdOVt3hQ1TfkKg1jraYrhuS8O7BKf0EnB+89pRHA7URvN3GqMJ7ECA07MpUuIyJAfgSFekvTdJk=@openvpn.net X-Received: by 2002:a05:6830:2705:b0:7e9:db7b:6d5e with SMTP id 46e09a7af769-7eb48accaccmr3967482a34.5.1782993212104; Thu, 02 Jul 2026 04:53:32 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993212; cv=none; d=google.com; s=arc-20260327; b=D/CymF4Q++14bWRFoUMoIFzpFrGSncKf+mdIfIfK40djHrMFqESRGfs6nYlQXq0lHB pqr5opc8YuxJBuchWHUFoK2ETj6tTpNnXySWRTxmFbzB6oqxQVky4TYxtgNumnqoF45D psKnf/jNvuPB+K1cFWoT8QmnsjScrTrLMJZ8NCjb4xQqaJicBDBA2NyOq7DD9LkpOcgC k0Aqql81A4JyXs27LogUkz8mGQgVhtQqv+ZfuUx998MqvG5F4D8c7Nbw839EGxh/Plv5 ZYs9lsnJjeihnzWlrH/F75Krsfl9zb9ZYYQTgwNrTVaWMZeipJCxdN5oKzMXUJjt/6o/ r2ng== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=JDNtgRqZrwXROVrQmCV2t2QTUCn2UVOLENNVCL7407A=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=nlK2AoZvILZbs1ZSYFzciyOEZjcCayesuxQD6wwQEl7q+DdNQtMBBCCIVe5wHk9EKL rvZrNbgiNLrtqws1jezVNokTx5lqOLUgVlPifuHI283g95pmCvchb6T/HypLD6Ywedrw MgT83jaPTmgzu0Oz2JrfpwpivIqcd0bHW58Gl9+op/vWxYRjmOHqHtunnWfK58dhwFRv onAPpBshr/HnSew5BuKng4nDzReKLqppaDVFAhQZFMtjgV6wDucFZ4rgOSk/PbQIvae/ vixiNMidfO7jce4M7uwGWjxRxV6cFBBoXfIkAdQeXbuS/qvMr//2j22ZcESTqMKqZgv4 vkXA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=c8fQ7F8A; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=RZoijnXk; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MuKSKxzL; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=OY+VKkiW; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-7eb5454c8c5si2263069a34.109.2026.07.02.04.53.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:32 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=c8fQ7F8A; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=RZoijnXk; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=MuKSKxzL; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=OY+VKkiW; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=JDNtgRqZrwXROVrQmCV2t2QTUCn2UVOLENNVCL7407A=; b=c8fQ7F8A0jKySGLagY0C3xoYIi L1woKTyoWUY9k3nP6oU0Ml1/uqWMkonX0U0liB9sDdaMts/40H2tumCWy5uz3y7mB1X5SxDud/xDi +rpb+Nn1phjs0jXTETie+HeKP4DXJ4K5nq6kUp+msizIQcg+7TLqjnNr2of83jAseA0k=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFyy-000072-9h; Thu, 02 Jul 2026 11:53:29 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFyw-00006o-AU for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:27 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=FjA7NA92WgIf/HRBbmq3kycqJUkNMZkfgx5cYp/zoD8=; b=RZoijnXkTcpWmDp6r7C89IgccH L17cpOEtc7/dVd/De0HKdbx/OFpNQriUcowvDGipVMy5VVx1V6QSZG/XNE4Ys4LsV1QklRHd1s3nq RM/m6ySVVB5R75jKfiJ5KcR+kdbHgH6amboi1/x1z6W64lWyJYW8lRxgR7spoTK5tQ4I=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=FjA7NA92WgIf/HRBbmq3kycqJUkNMZkfgx5cYp/zoD8=; b=MuKSKxzLNVCy7X3ELEK/5fhZGU rl4wfMs7mY4rBQmtRaKqntVvwmPVKwVbLL54d/3S94ItF5hlpb0K8FqCiwtBCuTJbPP4h1WFLbRAH /0dPhDm9hhZ6HIBu8GFwqOYjJGfoEKeVDTWgJMS1D/q8n1b7+r67fncNSPbafnTYNd3A=; Received: from mout-b-202.mailbox.org ([195.10.208.62]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyu-0004Ju-Oi for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:27 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-202.mailbox.org (Postfix) with ESMTPS id 4grZyc1mc5zDs12; Thu, 2 Jul 2026 13:53:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993192; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=FjA7NA92WgIf/HRBbmq3kycqJUkNMZkfgx5cYp/zoD8=; b=OY+VKkiWVOGG/W6hisTk58sIIUnk8R+Zov40iuYTXawmbeY7It+8F0MnQuLSKkfmx5zLMH i2GOETmPa1BJa8r254NDjWxtS6+8UTIRvd/DY4dfUd1YzOKgAZquM3PNocy1QyWfmupiX5 YwWN0CGXMc+bzNyfb64m6d49Mc0MGHOeSl2arCQjZuIE4yW8Xqm03R/DiGo9s2rDXi8kRJ nib4x9gpWDMrYoHE7GUXZlZ/6R38Cbs3kooxgzcyePcWYoZZ94Xe0n8rWy7dPNcC7CvkYA BK0wc/XBjSD4vkJCGreFRFoRRwhj3XO/4qrNz772VJumG+CASXA0vyaJbTpxkQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:19 +0200 Message-ID: <6bcc89e06e0c701ab0d7c7343c962f1b87684a6a.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Some AEAD packet layout constants are currently named as DATA_V2 helpers. DATA_V2 is the opcode used by OpenVPN data packets, but the layout represented by these constants is the direct-key AEAD layou [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFyu-0004Ju-Oi Subject: [Openvpn-devel] [RFC ovpn net-next v2 04/14] ovpn: make direct-key AEAD layout explicit X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603890448223886 X-GMAIL-MSGID: 1869603890448223886 Some AEAD packet layout constants are currently named as DATA_V2 helpers. DATA_V2 is the opcode used by OpenVPN data packets, but the layout represented by these constants is the direct-key AEAD layout: opcode, 32-bit packet ID, prepended authentication tag, and payload. Rename the helpers after the direct-key format they describe. This gives the direct layout clear scope before adding another data-channel key format that keeps the DATA_V2 opcode but uses different packet fields. Signed-off-by: Ralf Lici --- No changes since v1 https://lore.kernel.org/openvpn-devel/e120784b36e67749ead92d3ba1cdedac57738ecf.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto_aead.c | 38 +++++++++++++++------------------- drivers/net/ovpn/io.c | 10 ++++----- drivers/net/ovpn/io.h | 8 +++---- drivers/net/ovpn/proto.h | 30 +++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 9e34a553f59a..d2e3532934fb 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -24,9 +24,6 @@ #include "proto.h" #include "skb.h" -#define OVPN_AUTH_TAG_SIZE 16 -#define OVPN_AAD_SIZE (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE) - #define ALG_NAME_AES "gcm(aes)" #define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY BIT_ULL(35) @@ -51,9 +48,7 @@ bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks) static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks) { - return OVPN_OPCODE_SIZE + /* OP header size */ - sizeof(u32) + /* Packet ID */ - crypto_aead_authsize(ks->encrypt); /* Auth Tag */ + return OVPN_AEAD_DIRECT_AAD_SIZE + crypto_aead_authsize(ks->encrypt); } /** @@ -163,6 +158,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *trailer; struct scatterlist *sg; int nfrags, ret; + u64 aead_blocks; u32 pktid, op; void *tmp; u8 *iv; @@ -205,7 +201,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg = ovpn_aead_crypto_req_sg(ks->encrypt, req); /* sg table: - * 0: op, wire nonce (AD, len=OVPN_OP_SIZE_V2+OVPN_NONCE_WIRE_SIZE), + * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), * 1, 2, 3, ..., n: payload, * n+1: auth_tag (len=tag_size) */ @@ -226,12 +222,11 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* obtain packet ID, which is used both as a first * 4 bytes of nonce and last 4 bytes of associated data. */ + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, + OVPN_AEAD_DIRECT_AAD_SIZE, + plaintext_len); ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &ks->usage_xmit, - &ks->usage_limit, - ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_AAD_SIZE, - plaintext_len), - &pktid); + &ks->usage_limit, aead_blocks, &pktid); if (unlikely(ret < 0)) return ret; if (unlikely(ret > 0)) @@ -253,14 +248,14 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, *((__force __be32 *)skb->data) = htonl(op); /* AEAD Additional data */ - sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); /* setup async crypto operation */ aead_request_set_tfm(req, ks->encrypt); aead_request_set_callback(req, 0, ovpn_encrypt_post, skb); aead_request_set_crypt(req, sg, sg, skb->len - ovpn_aead_encap_overhead(ks), iv); - aead_request_set_ad(req, OVPN_AAD_SIZE); + aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); /* encrypt it */ return crypto_aead_encrypt(req); @@ -278,7 +273,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, void *tmp; u8 *iv; - payload_offset = OVPN_AAD_SIZE + tag_size; + payload_offset = ovpn_aead_direct_payload_offset(tag_size); payload_len = skb->len - payload_offset; ovpn_skb_cb(skb)->payload_offset = payload_offset; @@ -320,14 +315,14 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg = ovpn_aead_crypto_req_sg(ks->decrypt, req); /* sg table: - * 0: op, wire nonce (AD, len=OVPN_OPCODE_SIZE+OVPN_NONCE_WIRE_SIZE), + * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), * 1, 2, 3, ..., n: payload, * n+1: auth_tag (len=tag_size) */ sg_init_table(sg, nfrags + 2); /* packet op is head of additional data */ - sg_set_buf(sg, skb->data, OVPN_AAD_SIZE); + sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); /* build scatterlist to decrypt packet payload */ ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); @@ -338,10 +333,11 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, } /* append auth_tag onto scatterlist */ - sg_set_buf(sg + ret + 1, skb->data + OVPN_AAD_SIZE, tag_size); + sg_set_buf(sg + ret + 1, skb->data + OVPN_AEAD_DIRECT_TAG_OFFSET, + tag_size); /* copy nonce into IV buffer */ - memcpy(iv, skb->data + OVPN_OPCODE_SIZE, OVPN_NONCE_WIRE_SIZE); + memcpy(iv, ovpn_aead_direct_wire_nonce(skb), OVPN_NONCE_WIRE_SIZE); memcpy(iv + OVPN_NONCE_WIRE_SIZE, ks->nonce_tail_recv, OVPN_NONCE_TAIL_SIZE); @@ -350,7 +346,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_request_set_callback(req, 0, ovpn_decrypt_post, skb); aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv); - aead_request_set_ad(req, OVPN_AAD_SIZE); + aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); /* decrypt it */ return crypto_aead_decrypt(req); @@ -380,7 +376,7 @@ static struct crypto_aead *ovpn_aead_init(const char *title, goto error; } - ret = crypto_aead_setauthsize(aead, OVPN_AUTH_TAG_SIZE); + ret = crypto_aead_setauthsize(aead, OVPN_AEAD_TAG_SIZE); if (ret) { pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title, ret); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 8085cc345c59..6f3396f6f72e 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -114,7 +114,7 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_peer *peer; u64 aead_blocks; __be16 proto; - __be32 *pid; + u32 pktid; /* crypto is happening asynchronously. this function will be called * again later by the crypto callback with a proper return code @@ -138,9 +138,8 @@ void ovpn_decrypt_post(void *data, int ret) if (unlikely(ret < 0)) goto drop; - /* PID sits after the op */ - pid = (__force __be32 *)(skb->data + OVPN_OPCODE_SIZE); - ret = ovpn_pktid_recv(&ks->pid_recv, ntohl(*pid), 0); + pktid = ovpn_aead_direct_pktid(skb); + ret = ovpn_pktid_recv(&ks->pid_recv, pktid, 0); if (unlikely(ret < 0)) { net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n", netdev_name(peer->ovpn->dev), peer->id, @@ -149,8 +148,7 @@ void ovpn_decrypt_post(void *data, int ret) } aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_OPCODE_SIZE + - OVPN_NONCE_WIRE_SIZE, + OVPN_AEAD_DIRECT_AAD_SIZE, skb->len - payload_offset); if (unlikely(ovpn_pktid_recv_update_aead(&ks->pid_recv, &ks->usage_recv, diff --git a/drivers/net/ovpn/io.h b/drivers/net/ovpn/io.h index db9e10f9077c..fa795573c797 100644 --- a/drivers/net/ovpn/io.h +++ b/drivers/net/ovpn/io.h @@ -10,10 +10,10 @@ #ifndef _NET_OVPN_OVPN_H_ #define _NET_OVPN_OVPN_H_ -/* DATA_V2 header size with AEAD encryption */ -#define OVPN_HEAD_ROOM (OVPN_OPCODE_SIZE + OVPN_NONCE_WIRE_SIZE + \ - 16 /* AEAD TAG length */ + \ - max(sizeof(struct udphdr), sizeof(struct tcphdr)) +\ +/* headroom needed by directly installed AEAD keys */ +#define OVPN_HEAD_ROOM (OVPN_AEAD_DIRECT_AAD_SIZE + \ + OVPN_AEAD_TAG_SIZE + \ + max(sizeof(struct udphdr), sizeof(struct tcphdr)) + \ max(sizeof(struct ipv6hdr), sizeof(struct iphdr))) /* max padding required by encryption */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index b7d285b4d9c1..5fa053584b15 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -47,8 +47,38 @@ #define OVPN_DATA_V1 6 /* data channel v1 packet */ #define OVPN_DATA_V2 9 /* data channel v2 packet */ +/* direct-key AEAD packet layout */ +#define OVPN_AEAD_TAG_SIZE 16 +#define OVPN_AEAD_DIRECT_OP_OFFSET 0 +#define OVPN_AEAD_DIRECT_PKTID_OFFSET (OVPN_AEAD_DIRECT_OP_OFFSET + \ + OVPN_OPCODE_SIZE) +#define OVPN_AEAD_DIRECT_TAG_OFFSET (OVPN_AEAD_DIRECT_PKTID_OFFSET + \ + OVPN_NONCE_WIRE_SIZE) +#define OVPN_AEAD_DIRECT_AAD_SIZE OVPN_AEAD_DIRECT_TAG_OFFSET + #define OVPN_PEER_ID_UNDEF 0x00FFFFFF +static inline unsigned int +ovpn_aead_direct_payload_offset(unsigned int tag_size) +{ + return OVPN_AEAD_DIRECT_TAG_OFFSET + tag_size; +} + +static inline u32 ovpn_aead_direct_pktid(const struct sk_buff *skb) +{ + const __be32 *pktid; + + pktid = (__force const __be32 *)(skb->data + + OVPN_AEAD_DIRECT_PKTID_OFFSET); + + return be32_to_cpu(*pktid); +} + +static inline u8 *ovpn_aead_direct_wire_nonce(struct sk_buff *skb) +{ + return skb->data + OVPN_AEAD_DIRECT_PKTID_OFFSET; +} + /** * ovpn_opcode_from_skb - extract OP code from skb at specified offset * @skb: the packet to extract the OP code from From patchwork Thu Jul 2 11:52:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5070 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550846max; Thu, 2 Jul 2026 04:53:34 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/Aqwpz7YECYUZQgBYz3yrAxu2HK48MYSn4j/QFyEY023yLTZtWbeY0gbCyn0ODrB3vPnkTPkpWweE=@openvpn.net X-Received: by 2002:a05:6830:6a87:b0:7e9:3765:79c2 with SMTP id 46e09a7af769-7eb503b8dd2mr3490699a34.12.1782993214623; Thu, 02 Jul 2026 04:53:34 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993214; cv=none; d=google.com; s=arc-20260327; b=X82U2B8/zM93tfpxYz1AQaFZg7ximF0yuWwcIB754EDN3Ug96vEaDmHTi2DGQzHiCh 5/4q1C+0en7Y0Ppob8oTibtHs9liFHjziKc8Mbe0rTOMnF+h8aeghV0f0ob+RNRlqVz+ R5VF7KHTekpMy1P5U0aM9hmGIj5EBTHUvLAJbPTOMDaII5UgERvsy4o3kD2+bhBTmNiL kgMA2bgKymatmFMBtEM1FQFOG0gBfzdUT6B7Ec571Tlv5SKeTQFo0dEzx/5467jrvbrN ZbkJMNPJmpsq4n/bEp16/G9dHnO5yZBsY+tMazeBx35y7W1QGeAJ0LrsFvQ4gUp+uwfV rAXQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=Ek2GcsRXmHkxzQRZpwxaY2tdyszfq6BkCzviEGigdDs=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=U3tRx4Jjo7hp6IuJu9FdZi6gqPyym7MrpAhDyXe7CNIbJ/VPxzmL//9SLb9z+/JiFb SGOntGu4BRZIQ+zvHBQslNtTOvYqh37/s/hDqlnSajBoTpbfTt1AknxxOJ9smp4s8eqC zNgiXSlBr8e4r0jn0NQEwlUahq+jsaXGDrL+ZNVYx/ofZ8Zdlbdwhnswt+ErdeRcjOtL w3kZW+AG3x6wqA/FyhVG5M58tt3pN2WnGYirCJ5LXaIVh4shHLU2kmP5L6uWNWoLZfZy 9aJl8MfO2le03Q5ZuUZp9+el3cWEU5S86xS7umnLGw/waBwE3BuDigs37/ixhfdiqMGD 1HVQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=m0h2f6gu; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=EoGCUJ7b; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=OUuD5W4Y; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=s310XksO; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-7eb54549699si2271054a34.100.2026.07.02.04.53.34 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:34 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=m0h2f6gu; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=EoGCUJ7b; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=OUuD5W4Y; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=s310XksO; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=Ek2GcsRXmHkxzQRZpwxaY2tdyszfq6BkCzviEGigdDs=; b=m0h2f6gupwFmMwJnJPnYzmnuvz d70p5OzYLokqtx5RRFMwLy7gLRm+Z0ks2KiGGWmFRfDiOmsxUrQ9IBEXAy2ukgSi7cKb3yavGQdsl IxCxKzEORgFC//VsNEAGJnwN8CWhOK9lflHjNWDopsxs0N+Jl88B6QHoty2LEEpgUvQI=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz0-0001hX-VB; Thu, 02 Jul 2026 11:53:31 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFyx-0001h9-U5 for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:28 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=OK7NpS4CfLQYxY/MnzoqmDnwV3lrxJ1QqVFCDCWJ/nc=; b=EoGCUJ7bBjmwHs9qZ1+6fr+TXo V0GubqFWcribie372KBkjY8TB+ybTPkkRByOl8GcwMxvUc9SUngIQGMqpa1/eGVDen932P2A2716R u0OFGqFNBYbue7Xbzo9d2zxTJOJ4EU0SiLSGiEwDvF5LdBDvACPyQBKjvxUaInj8AgqA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=OK7NpS4CfLQYxY/MnzoqmDnwV3lrxJ1QqVFCDCWJ/nc=; b=OUuD5W4YMhnhiWyWxd5UgXTOn7 +PInsh9YuSCqkoFecUHU+bDl+qecQ11Ewkv3cuq4jPsZSeJhUe75UFC8j8UFySG/skGHtYpIW2zWb n343nXJjY2Uvo4ssbABjCh0PiOSaTbz4XiTR8LovwznCHKUaF/gmoybXOdP/zvolFDbU=; Received: from mout-b-107.mailbox.org ([195.10.208.47]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyw-0004K6-Un for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:28 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-107.mailbox.org (Postfix) with ESMTPS id 4grZyd6CxMzDs5H; Thu, 2 Jul 2026 13:53:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993193; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OK7NpS4CfLQYxY/MnzoqmDnwV3lrxJ1QqVFCDCWJ/nc=; b=s310XksOQLPXwn1TOG5qn8xoYH1spohxVCHW5nA8SWlDNngr/RcFAn+cO0mkkhovIecGfV LfJaXf5c6LsZ5lBNYxtXwES/F9msBOXiikncLG+MA3nYrUKTMjc4MbTalp5ebUoXXDKLpW hZHd06wIkDTYeBeGOUobgziyyCD1E6VTilaJciyy6HshpMXj4n9j/OMnOucBce2h10v913 5BYgMc+FJkmvgJLjPgXuKIuUibkKwCfaNr+QnDsiNKy51X2MvT17hUg/JIGsvUomjASG1f AdMR+Ewifq+C6hSuzhX+HX8WkihFkSVV4nE9n4a8lbUyK/3BdU4hDXqEjY/5FQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:20 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: crypto_aead.c now contains both AEAD packet operations and key slot setup, destruction and cipher introspection. Those lifecycle helpers are used by the generic crypto state code and do not need to li [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFyw-0004K6-Un Subject: [Openvpn-devel] [RFC ovpn net-next v2 05/14] ovpn: move key slot lifecycle out of AEAD X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603893200521771 X-GMAIL-MSGID: 1869603893200521771 crypto_aead.c now contains both AEAD packet operations and key slot setup, destruction and cipher introspection. Those lifecycle helpers are used by the generic crypto state code and do not need to live with the per-packet encrypt and decrypt path. Move key slot lifecycle code into crypto_key.c and expose it through ovpn_crypto_key_slot helpers. This leaves crypto_aead.c focused on AEAD packet processing without changing key allocation, validation or teardown behavior. Signed-off-by: Ralf Lici --- No changes since v1 https://lore.kernel.org/openvpn-devel/1a0ef417303069c983fc8eb9effcede54c231e26.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/crypto.c | 7 +- drivers/net/ovpn/crypto.h | 7 ++ drivers/net/ovpn/crypto_aead.c | 158 ------------------------------ drivers/net/ovpn/crypto_aead.h | 5 - drivers/net/ovpn/crypto_key.c | 172 +++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 167 deletions(-) create mode 100644 drivers/net/ovpn/crypto_key.c diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 229be66167e1..704af1deb7c1 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += bind.o ovpn-y += crypto.o ovpn-y += crypto_aead.o +ovpn-y += crypto_key.o ovpn-y += main.o ovpn-y += io.o ovpn-y += netlink.o diff --git a/drivers/net/ovpn/crypto.c b/drivers/net/ovpn/crypto.c index 90580e32052f..239d95492574 100644 --- a/drivers/net/ovpn/crypto.c +++ b/drivers/net/ovpn/crypto.c @@ -15,7 +15,6 @@ #include "ovpnpriv.h" #include "main.h" #include "pktid.h" -#include "crypto_aead.h" #include "crypto.h" static void ovpn_ks_destroy_rcu(struct rcu_head *head) @@ -23,7 +22,7 @@ static void ovpn_ks_destroy_rcu(struct rcu_head *head) struct ovpn_crypto_key_slot *ks; ks = container_of(head, struct ovpn_crypto_key_slot, rcu); - ovpn_aead_crypto_key_slot_destroy(ks); + ovpn_crypto_key_slot_destroy(ks); } void ovpn_crypto_key_slot_release(struct kref *kref) @@ -89,7 +88,7 @@ int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, pkr->slot != OVPN_KEY_SLOT_SECONDARY) return -EINVAL; - new = ovpn_aead_crypto_key_slot_new(&pkr->key); + new = ovpn_crypto_key_slot_new(&pkr->key); if (IS_ERR(new)) return PTR_ERR(new); @@ -202,7 +201,7 @@ int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, return -ENOENT; } - keyconf->cipher_alg = ovpn_aead_crypto_alg(ks); + keyconf->cipher_alg = ovpn_crypto_key_slot_alg(ks); keyconf->key_id = ks->key_id; rcu_read_unlock(); diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 2be7ff54fc40..3b21b42a25eb 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -136,6 +136,13 @@ static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks) int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, const struct ovpn_peer_key_reset *pkr); +struct ovpn_crypto_key_slot * +ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc); + +void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks); + +enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks); + void ovpn_crypto_key_slot_delete(struct ovpn_crypto_state *cs, enum ovpn_key_slot slot); diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index d2e3532934fb..5306c0d2b5c8 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -24,8 +24,6 @@ #include "proto.h" #include "skb.h" -#define ALG_NAME_AES "gcm(aes)" -#define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY BIT_ULL(35) #define OVPN_AEAD_DECRYPT_FAILURE_LIMIT BIT_ULL(36) #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT 0 @@ -351,159 +349,3 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* decrypt it */ return crypto_aead_decrypt(req); } - -/* Initialize a struct crypto_aead object */ -static struct crypto_aead *ovpn_aead_init(const char *title, - const char *alg_name, - const unsigned char *key, - unsigned int keylen) -{ - struct crypto_aead *aead; - int ret; - - aead = crypto_alloc_aead(alg_name, 0, 0); - if (IS_ERR(aead)) { - ret = PTR_ERR(aead); - pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret); - aead = NULL; - goto error; - } - - ret = crypto_aead_setkey(aead, key, keylen); - if (ret) { - pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title, - keylen, ret); - goto error; - } - - ret = crypto_aead_setauthsize(aead, OVPN_AEAD_TAG_SIZE); - if (ret) { - pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title, - ret); - goto error; - } - - /* basic AEAD assumption - * all current algorithms use OVPN_NONCE_SIZE. - * ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt - * expect this. - */ - if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) { - pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE); - ret = -EINVAL; - goto error; - } - - pr_debug("********* Cipher %s (%s)\n", alg_name, title); - pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead)); - pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead)); - pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead)); - pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead)); - pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead)); - - return aead; - -error: - crypto_free_aead(aead); - return ERR_PTR(ret); -} - -void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) -{ - if (!ks) - return; - - crypto_free_aead(ks->encrypt); - crypto_free_aead(ks->decrypt); - kfree(ks); -} - -struct ovpn_crypto_key_slot * -ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc) -{ - struct ovpn_crypto_key_slot *ks = NULL; - const char *alg_name; - int ret; - - /* validate crypto alg */ - switch (kc->cipher_alg) { - case OVPN_CIPHER_ALG_AES_GCM: - alg_name = ALG_NAME_AES; - break; - case OVPN_CIPHER_ALG_CHACHA20_POLY1305: - alg_name = ALG_NAME_CHACHAPOLY; - break; - default: - return ERR_PTR(-EOPNOTSUPP); - } - - if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE || - kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE) - return ERR_PTR(-EINVAL); - - /* build the key slot */ - ks = kmalloc_obj(*ks); - if (!ks) - return ERR_PTR(-ENOMEM); - - ks->encrypt = NULL; - ks->decrypt = NULL; - kref_init(&ks->refcount); - ks->key_id = kc->key_id; - ks->cipher_alg = kc->cipher_alg; - ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); - ovpn_key_usage_init(&ks->usage_xmit); - ovpn_key_usage_init(&ks->usage_recv); - atomic64_set(&ks->decrypt_failures, 0); - ks->decrypt_failure_flags = 0; - - ks->encrypt = ovpn_aead_init("encrypt", alg_name, - kc->encrypt.cipher_key, - kc->encrypt.cipher_key_size); - if (IS_ERR(ks->encrypt)) { - ret = PTR_ERR(ks->encrypt); - ks->encrypt = NULL; - goto destroy_ks; - } - - ks->decrypt = ovpn_aead_init("decrypt", alg_name, - kc->decrypt.cipher_key, - kc->decrypt.cipher_key_size); - if (IS_ERR(ks->decrypt)) { - ret = PTR_ERR(ks->decrypt); - ks->decrypt = NULL; - goto destroy_ks; - } - - memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail, - OVPN_NONCE_TAIL_SIZE); - memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail, - OVPN_NONCE_TAIL_SIZE); - - /* init packet ID generation/validation */ - ovpn_pktid_xmit_init(&ks->pid_xmit); - ovpn_pktid_recv_init(&ks->pid_recv); - - return ks; - -destroy_ks: - ovpn_aead_crypto_key_slot_destroy(ks); - return ERR_PTR(ret); -} - -enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks) -{ - const char *alg_name; - - if (!ks->encrypt) - return OVPN_CIPHER_ALG_NONE; - - alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt)); - - if (!strcmp(alg_name, ALG_NAME_AES)) - return OVPN_CIPHER_ALG_AES_GCM; - else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY)) - return OVPN_CIPHER_ALG_CHACHA20_POLY1305; - else - return OVPN_CIPHER_ALG_NONE; -} diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index e5ff0b9b5ee3..57a7b88cd6c5 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -20,11 +20,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb); -struct ovpn_crypto_key_slot * -ovpn_aead_crypto_key_slot_new(const struct ovpn_key_config *kc); -void ovpn_aead_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks); - -enum ovpn_cipher_alg ovpn_aead_crypto_alg(struct ovpn_crypto_key_slot *ks); bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks); #endif /* _NET_OVPN_OVPNAEAD_H_ */ diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c new file mode 100644 index 000000000000..08fce700811f --- /dev/null +++ b/drivers/net/ovpn/crypto_key.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2026 OpenVPN, Inc. + * + * Author: Ralf Lici + * Antonio Quartulli + */ + +#include + +#include "ovpnpriv.h" +#include "crypto.h" +#include "pktid.h" +#include "proto.h" + +#define ALG_NAME_AES "gcm(aes)" +#define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" + +/* initialize a struct crypto_aead object */ +static struct crypto_aead *ovpn_aead_init(const char *title, + const char *alg_name, + const unsigned char *key, + unsigned int keylen) +{ + struct crypto_aead *aead; + int ret; + + aead = crypto_alloc_aead(alg_name, 0, 0); + if (IS_ERR(aead)) { + ret = PTR_ERR(aead); + pr_err("%s crypto_alloc_aead failed, err=%d\n", title, ret); + aead = NULL; + goto error; + } + + ret = crypto_aead_setkey(aead, key, keylen); + if (ret) { + pr_err("%s crypto_aead_setkey size=%u failed, err=%d\n", title, + keylen, ret); + goto error; + } + + ret = crypto_aead_setauthsize(aead, OVPN_AEAD_TAG_SIZE); + if (ret) { + pr_err("%s crypto_aead_setauthsize failed, err=%d\n", title, + ret); + goto error; + } + + /* Basic AEAD assumption: all current algorithms use OVPN_NONCE_SIZE. + * ovpn_aead_crypto_tmp_size and ovpn_aead_encrypt/decrypt expect this. + */ + if (crypto_aead_ivsize(aead) != OVPN_NONCE_SIZE) { + pr_err("%s IV size must be %d\n", title, OVPN_NONCE_SIZE); + ret = -EINVAL; + goto error; + } + + pr_debug("********* Cipher %s (%s)\n", alg_name, title); + pr_debug("*** IV size=%u\n", crypto_aead_ivsize(aead)); + pr_debug("*** req size=%u\n", crypto_aead_reqsize(aead)); + pr_debug("*** block size=%u\n", crypto_aead_blocksize(aead)); + pr_debug("*** auth size=%u\n", crypto_aead_authsize(aead)); + pr_debug("*** alignmask=0x%x\n", crypto_aead_alignmask(aead)); + + return aead; + +error: + crypto_free_aead(aead); + return ERR_PTR(ret); +} + +void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) +{ + if (!ks) + return; + + crypto_free_aead(ks->encrypt); + crypto_free_aead(ks->decrypt); + kfree(ks); +} + +struct ovpn_crypto_key_slot * +ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) +{ + struct ovpn_crypto_key_slot *ks = NULL; + const char *alg_name; + int ret; + + /* validate crypto alg */ + switch (kc->cipher_alg) { + case OVPN_CIPHER_ALG_AES_GCM: + alg_name = ALG_NAME_AES; + break; + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + alg_name = ALG_NAME_CHACHAPOLY; + break; + default: + return ERR_PTR(-EOPNOTSUPP); + } + + if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE || + kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE) + return ERR_PTR(-EINVAL); + + /* build the key slot */ + ks = kmalloc_obj(*ks); + if (!ks) + return ERR_PTR(-ENOMEM); + + ks->encrypt = NULL; + ks->decrypt = NULL; + kref_init(&ks->refcount); + ks->key_id = kc->key_id; + ks->cipher_alg = kc->cipher_alg; + ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); + ovpn_key_usage_init(&ks->usage_xmit); + ovpn_key_usage_init(&ks->usage_recv); + atomic64_set(&ks->decrypt_failures, 0); + ks->decrypt_failure_flags = 0; + + ks->encrypt = ovpn_aead_init("encrypt", alg_name, + kc->encrypt.cipher_key, + kc->encrypt.cipher_key_size); + if (IS_ERR(ks->encrypt)) { + ret = PTR_ERR(ks->encrypt); + ks->encrypt = NULL; + goto destroy_ks; + } + + ks->decrypt = ovpn_aead_init("decrypt", alg_name, + kc->decrypt.cipher_key, + kc->decrypt.cipher_key_size); + if (IS_ERR(ks->decrypt)) { + ret = PTR_ERR(ks->decrypt); + ks->decrypt = NULL; + goto destroy_ks; + } + + memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail, + OVPN_NONCE_TAIL_SIZE); + memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail, + OVPN_NONCE_TAIL_SIZE); + + /* init packet ID generation/validation */ + ovpn_pktid_xmit_init(&ks->pid_xmit); + ovpn_pktid_recv_init(&ks->pid_recv); + + return ks; + +destroy_ks: + ovpn_crypto_key_slot_destroy(ks); + return ERR_PTR(ret); +} + +enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks) +{ + const char *alg_name; + + if (!ks->encrypt) + return OVPN_CIPHER_ALG_NONE; + + alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt)); + + if (!strcmp(alg_name, ALG_NAME_AES)) + return OVPN_CIPHER_ALG_AES_GCM; + else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY)) + return OVPN_CIPHER_ALG_CHACHA20_POLY1305; + else + return OVPN_CIPHER_ALG_NONE; +} From patchwork Thu Jul 2 11:52:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5072 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550876max; Thu, 2 Jul 2026 04:53:36 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/mnkWCEo0oBB+Or7iYdDzcLgQsV3R3iWAUT3Uz+qwazd/UWZZY6cM7deyn865qToMc4IyOoZ6Bcgc=@openvpn.net X-Received: by 2002:a05:6871:288:b0:448:558c:d8b5 with SMTP id 586e51a60fabf-44cabc3e33amr3394516fac.43.1782993216303; Thu, 02 Jul 2026 04:53:36 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993216; cv=none; d=google.com; s=arc-20260327; b=GIzVtfGJs84+EswXbrwUcQYujpr4fzl3NkC3RGr2EcJaUWzffawat9jlOJFY31PJRz pJuL2m3TQMk3lX3C6qEM5xRMUPDOxCqyyWv00qypI7DWTesxcedXIuzpYysWHuJ/Mfvq sFgPOa9BnKJyLJXV74LwUxL4lyEoF86ydRjeunLV+QDxEWZPtWzBt3oorasoh3degx0i uaAgul97yREzC2kuSBOR+6T3lPPVqsklxCibS43/62J9+pnTRnJ0L69H9D/uic944BtR 5983KfWDAExcGk+FOQytx9ATuTcwns5fdnE6z0xdd3CcfjlVXOOvRU8pPrrXVu5DLeXn zmkg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=Ja6oNG676bF+fqspC9tvcp3Dnd+22XuorLl1lot7xn4=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=XiA7jtEN1y9TbXIaDnVO6J0Z349TM/8rrbQOxLlPXioVw5g330c12mvf6MA3NO3WtA utQSITE81j/q+uo7pphdy4Lg6PYgi46TMy90BJMDdtvQ9eLd9R7ev7aO9/mYe+QlQIzu kl5TL94y9oxjbxLqVLkXMmzpmm0H1JCsG+6QSbP8DRUDwzbTK9ZR3Ldgyn3wYcGe8HxF lK+4GBvJsdMId8XJNl7TqSxB2+2UnvIYJ4E59RMlrTr+Y+Yz5aiWQW6igs96hpm8Ny9S Ju56WmAFb4Nq789UWjzoQdgb6AQAkJY4ZRwmpk/CFXAo3wvqOoOgcHFsOaq6XvbBL+d8 3nQQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ayVNc1sf; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=N3YejIej; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iTzEjc90; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="AQP0gNo/"; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-44cbf14fc94si2414995fac.339.2026.07.02.04.53.36 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:36 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ayVNc1sf; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=N3YejIej; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iTzEjc90; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="AQP0gNo/"; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=Ja6oNG676bF+fqspC9tvcp3Dnd+22XuorLl1lot7xn4=; b=ayVNc1sfKqDmG8IbKzMUeETgTv 1+OkgZwezH1EnLC9dT8DBJDmmaC64ehOqWNzKVjAv2qew1pA+cNhi9yEaZVo/wGJJqh98t1ZljG/A mhOkswh//wWFJWbw7TFwkXSNP3yUzAkQeIE/oOPqB41fr912TlS+kxlXiyaLKZkgLTvI=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz3-0005Zm-Ci; Thu, 02 Jul 2026 11:53:33 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz0-0005Zc-8t for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:30 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=QwZ5n4Xk+Gc4jJbUBMtwHc01MoMtO7m0ptHUWXKplgM=; b=N3YejIejDuWErMjQnwZV+rc7BB TLLQ50XdVKfMA5hnyq7Vd0VqMUAnrkkKZoo0Zv66NVO1DkrgJS06PCUf60GaG2Zv9FIjEmXwJdtH8 Ubd2UNvQLGat0v6ZxGCw3LwTjd65xcTf9IyzF9lNB5HLRwZJe02ICAXgeHlQYu9i6LkA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=QwZ5n4Xk+Gc4jJbUBMtwHc01MoMtO7m0ptHUWXKplgM=; b=iTzEjc909k8qYbSgjRUzXM7ZM+ ReMCPevAOedNDt275vKBDWWiWbXUsukiMs5hFvG/HtlxeqbXfqmePLFmzt3SSugYc62Kk1wdBn031 4JC/h6eTGu3Ytc5c6L7H35v6AgUfemF0KEogopui8sDxQIePyhcquWuv3qQbsw04HqSU=; Received: from mout-b-110.mailbox.org ([195.10.208.55]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyy-0004KC-Ev for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:30 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-110.mailbox.org (Postfix) with ESMTPS id 4grZyg3hWGzB0tF; Thu, 2 Jul 2026 13:53:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993195; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QwZ5n4Xk+Gc4jJbUBMtwHc01MoMtO7m0ptHUWXKplgM=; b=AQP0gNo/bNeLX+AgV7qEq0h/tvuizQvE67dGwWdPA2Op6sGmT7O1GihmD5b5bfFoV1RWxw gP+cfIFwD/xrs55YAQ4rwhpXz7Su+VgTPYCdfcNszqjyLF/KcwcntL48R302qprBUhHM03 w4PtRTPVGi2BIPncSU7+ZX7sJ5XXxLBZiHyO8jL+GBkOUjcfzoqRKqFUZY3wof6QXhxgdf lU7jTbEAsV+OI1UZ5maFD+nLS2A+MxgEbWSMQfSrhlUWlFWneqYOtG5Oh2isvtXlEHGFP7 +6fX4ZvK9fFO2NVxTzPQYeoBSFxapobBtpGkTOJa/R3+JbdxbgYfglmkWBJyrA== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:21 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Key slots currently mix slot-wide state with per-direction runtime state. That includes AEAD transforms, implicit IV material, packet ID state, usage accounting and decrypt failure tracking. Move those per-direction pieces into struct ovpn_key_ctx. Keep the slot focused on key id, cipher selection and shared usage limits. This gives directly installed keys a cleaner state boundary before [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.55 listed in wl.mailspike.net] X-Headers-End: 1wfFyy-0004KC-Ev Subject: [Openvpn-devel] [RFC ovpn net-next v2 06/14] ovpn: move direct key state into key contexts X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603894802779169 X-GMAIL-MSGID: 1869603894802779169 Key slots currently mix slot-wide state with per-direction runtime state. That includes AEAD transforms, implicit IV material, packet ID state, usage accounting and decrypt failure tracking. Move those per-direction pieces into struct ovpn_key_ctx. Keep the slot focused on key id, cipher selection and shared usage limits. This gives directly installed keys a cleaner state boundary before adding derived key formats. Signed-off-by: Ralf Lici --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/1c59dc2e7d63b93d66427eab461e4b1ea7dfcd20.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto.h | 28 ++++++----- drivers/net/ovpn/crypto_aead.c | 56 +++++++++++----------- drivers/net/ovpn/crypto_aead.h | 2 +- drivers/net/ovpn/crypto_key.c | 85 ++++++++++++++++++++++++---------- drivers/net/ovpn/io.c | 9 ++-- drivers/net/ovpn/pktid.h | 10 ++-- 6 files changed, 118 insertions(+), 72 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 3b21b42a25eb..c36cd8299afd 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -22,7 +22,7 @@ struct ovpn_key_direction { size_t nonce_tail_size; /* only needed for GCM modes */ }; -/* all info for a particular symmetric key (primary or secondary) */ +/* direct-key material for a primary or secondary slot */ struct ovpn_key_config { enum ovpn_cipher_alg cipher_alg; u8 key_id; @@ -36,22 +36,26 @@ struct ovpn_peer_key_reset { struct ovpn_key_config key; }; +/* state for one concrete AEAD key direction */ +struct ovpn_key_ctx { + struct crypto_aead *tfm; + u8 implicit_iv[OVPN_NONCE_SIZE]; + union { + struct ovpn_pktid_recv recv; + struct ovpn_pktid_xmit xmit; + } pid ____cacheline_aligned_in_smp; + struct ovpn_key_usage usage; + atomic64_t decrypt_failures; + unsigned long decrypt_failure_flags; +}; + struct ovpn_crypto_key_slot { u8 key_id; enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; - struct crypto_aead *encrypt; - struct crypto_aead *decrypt; - atomic64_t decrypt_failures; - unsigned long decrypt_failure_flags; - u8 nonce_tail_xmit[OVPN_NONCE_TAIL_SIZE]; - u8 nonce_tail_recv[OVPN_NONCE_TAIL_SIZE]; - - struct ovpn_pktid_recv pid_recv ____cacheline_aligned_in_smp; - struct ovpn_key_usage usage_recv; - struct ovpn_pktid_xmit pid_xmit ____cacheline_aligned_in_smp; - struct ovpn_key_usage usage_xmit; + struct ovpn_key_ctx *encrypt; + struct ovpn_key_ctx *decrypt; struct kref refcount; struct rcu_head rcu; }; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 5306c0d2b5c8..31f5327694d2 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -29,24 +29,24 @@ #define OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT 0 static bool -ovpn_aead_decrypt_failure_exceeded(const struct ovpn_crypto_key_slot *ks) +ovpn_aead_decrypt_failure_exceeded(const struct ovpn_key_ctx *key) { - return atomic64_read(&ks->decrypt_failures) > + return atomic64_read(&key->decrypt_failures) > OVPN_AEAD_DECRYPT_FAILURE_LIMIT; } -bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks) +bool ovpn_aead_decrypt_failure_record(struct ovpn_key_ctx *key) { - u64 failures = atomic64_inc_return(&ks->decrypt_failures); + u64 failures = atomic64_inc_return(&key->decrypt_failures); return failures > OVPN_AEAD_DECRYPT_FAILURE_NOTIFY && !test_and_set_bit(OVPN_AEAD_DECRYPT_FAILURE_NOTIFY_BIT, - &ks->decrypt_failure_flags); + &key->decrypt_failure_flags); } -static int ovpn_aead_encap_overhead(const struct ovpn_crypto_key_slot *ks) +static int ovpn_aead_encap_overhead(const struct ovpn_key_ctx *key) { - return OVPN_AEAD_DIRECT_AAD_SIZE + crypto_aead_authsize(ks->encrypt); + return OVPN_AEAD_DIRECT_AAD_SIZE + crypto_aead_authsize(key->tfm); } /** @@ -150,8 +150,8 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead, int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - const unsigned int tag_size = crypto_aead_authsize(ks->encrypt); - unsigned int plaintext_len; + struct ovpn_key_ctx *key = ks->encrypt; + unsigned int plaintext_len, tag_size; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; @@ -164,6 +164,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ovpn_skb_cb(skb)->peer = peer; ovpn_skb_cb(skb)->ks = ks; plaintext_len = skb->len; + tag_size = crypto_aead_authsize(key->tfm); /* Sample AEAD header format: * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a... @@ -187,16 +188,16 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, return -ENOSPC; /* allocate temporary memory for iv, sg and req */ - tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->encrypt, nfrags), + tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, nfrags), GFP_ATOMIC); if (unlikely(!tmp)) return -ENOMEM; ovpn_skb_cb(skb)->crypto_tmp = tmp; - iv = ovpn_aead_crypto_tmp_iv(ks->encrypt, tmp); - req = ovpn_aead_crypto_tmp_req(ks->encrypt, iv); - sg = ovpn_aead_crypto_req_sg(ks->encrypt, req); + iv = ovpn_aead_crypto_tmp_iv(key->tfm, tmp); + req = ovpn_aead_crypto_tmp_req(key->tfm, iv); + sg = ovpn_aead_crypto_req_sg(key->tfm, req); /* sg table: * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), @@ -223,7 +224,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, OVPN_AEAD_DIRECT_AAD_SIZE, plaintext_len); - ret = ovpn_pktid_xmit_next(&ks->pid_xmit, &ks->usage_xmit, + ret = ovpn_pktid_xmit_next(&key->pid.xmit, &key->usage, &ks->usage_limit, aead_blocks, &pktid); if (unlikely(ret < 0)) return ret; @@ -233,7 +234,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes * nonce */ - ovpn_pktid_aead_write(pktid, ks->nonce_tail_xmit, iv); + ovpn_pktid_aead_write(pktid, key->implicit_iv, iv); /* make space for packet id and push it to the front */ __skb_push(skb, OVPN_NONCE_WIRE_SIZE); @@ -249,10 +250,10 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); /* setup async crypto operation */ - aead_request_set_tfm(req, ks->encrypt); + aead_request_set_tfm(req, key->tfm); aead_request_set_callback(req, 0, ovpn_encrypt_post, skb); aead_request_set_crypt(req, sg, sg, - skb->len - ovpn_aead_encap_overhead(ks), iv); + skb->len - ovpn_aead_encap_overhead(key), iv); aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); /* encrypt it */ @@ -262,15 +263,16 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - const unsigned int tag_size = crypto_aead_authsize(ks->decrypt); + struct ovpn_key_ctx *key = ks->decrypt; + unsigned int payload_offset, tag_size; int ret, payload_len, nfrags; - unsigned int payload_offset; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; void *tmp; u8 *iv; + tag_size = crypto_aead_authsize(key->tfm); payload_offset = ovpn_aead_direct_payload_offset(tag_size); payload_len = skb->len - payload_offset; @@ -282,7 +284,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, if (unlikely(payload_len < 0)) return -EINVAL; - if (unlikely(ovpn_aead_decrypt_failure_exceeded(ks))) + if (unlikely(ovpn_aead_decrypt_failure_exceeded(key))) return -EKEYREJECTED; /* Prepare the skb data buffer to be accessed up until the auth tag. @@ -301,16 +303,16 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, return -ENOSPC; /* allocate temporary memory for iv, sg and req */ - tmp = kmalloc(ovpn_aead_crypto_tmp_size(ks->decrypt, nfrags), + tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, nfrags), GFP_ATOMIC); if (unlikely(!tmp)) return -ENOMEM; ovpn_skb_cb(skb)->crypto_tmp = tmp; - iv = ovpn_aead_crypto_tmp_iv(ks->decrypt, tmp); - req = ovpn_aead_crypto_tmp_req(ks->decrypt, iv); - sg = ovpn_aead_crypto_req_sg(ks->decrypt, req); + iv = ovpn_aead_crypto_tmp_iv(key->tfm, tmp); + req = ovpn_aead_crypto_tmp_req(key->tfm, iv); + sg = ovpn_aead_crypto_req_sg(key->tfm, req); /* sg table: * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), @@ -336,11 +338,11 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* copy nonce into IV buffer */ memcpy(iv, ovpn_aead_direct_wire_nonce(skb), OVPN_NONCE_WIRE_SIZE); - memcpy(iv + OVPN_NONCE_WIRE_SIZE, ks->nonce_tail_recv, - OVPN_NONCE_TAIL_SIZE); + memcpy(iv + OVPN_NONCE_WIRE_SIZE, + key->implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE); /* setup async crypto operation */ - aead_request_set_tfm(req, ks->decrypt); + aead_request_set_tfm(req, key->tfm); aead_request_set_callback(req, 0, ovpn_decrypt_post, skb); aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv); diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index 57a7b88cd6c5..4f83a3aa37fd 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -20,6 +20,6 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb); -bool ovpn_aead_decrypt_failure_record(struct ovpn_crypto_key_slot *ks); +bool ovpn_aead_decrypt_failure_record(struct ovpn_key_ctx *key); #endif /* _NET_OVPN_OVPNAEAD_H_ */ diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 08fce700811f..cdf35e2b241c 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -71,13 +71,65 @@ static struct crypto_aead *ovpn_aead_init(const char *title, return ERR_PTR(ret); } +static void ovpn_key_ctx_free(struct ovpn_key_ctx *key) +{ + if (!key) + return; + + if (key->tfm) + crypto_free_aead(key->tfm); + memzero_explicit(key->implicit_iv, sizeof(key->implicit_iv)); + kfree(key); +} + +static struct ovpn_key_ctx * +ovpn_key_ctx_new(const char *title, const char *alg_name, + const struct ovpn_key_direction *dir, bool encrypt) +{ + struct ovpn_key_ctx *key; + size_t tail_offset; + int ret; + + key = kmalloc_obj(*key); + if (!key) + return ERR_PTR(-ENOMEM); + + /* create the concrete AEAD transform first */ + key->tfm = ovpn_aead_init(title, alg_name, dir->cipher_key, + dir->cipher_key_size); + if (IS_ERR(key->tfm)) { + ret = PTR_ERR(key->tfm); + key->tfm = NULL; + ovpn_key_ctx_free(key); + return ERR_PTR(ret); + } + + /* store the implicit IV in a full nonce-sized buffer */ + tail_offset = OVPN_NONCE_SIZE - dir->nonce_tail_size; + memset(key->implicit_iv, 0, sizeof(key->implicit_iv)); + memcpy(key->implicit_iv + tail_offset, dir->nonce_tail, + dir->nonce_tail_size); + + ovpn_key_usage_init(&key->usage); + atomic64_set(&key->decrypt_failures, 0); + key->decrypt_failure_flags = 0; + + /* initialize only the packet ID direction this context owns */ + if (encrypt) + ovpn_pktid_xmit_init(&key->pid.xmit); + else + ovpn_pktid_recv_init(&key->pid.recv); + + return key; +} + void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) { if (!ks) return; - crypto_free_aead(ks->encrypt); - crypto_free_aead(ks->decrypt); + ovpn_key_ctx_free(ks->encrypt); + ovpn_key_ctx_free(ks->decrypt); kfree(ks); } @@ -115,38 +167,23 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) ks->key_id = kc->key_id; ks->cipher_alg = kc->cipher_alg; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); - ovpn_key_usage_init(&ks->usage_xmit); - ovpn_key_usage_init(&ks->usage_recv); - atomic64_set(&ks->decrypt_failures, 0); - ks->decrypt_failure_flags = 0; - - ks->encrypt = ovpn_aead_init("encrypt", alg_name, - kc->encrypt.cipher_key, - kc->encrypt.cipher_key_size); + + ks->encrypt = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, + true); if (IS_ERR(ks->encrypt)) { ret = PTR_ERR(ks->encrypt); ks->encrypt = NULL; goto destroy_ks; } - ks->decrypt = ovpn_aead_init("decrypt", alg_name, - kc->decrypt.cipher_key, - kc->decrypt.cipher_key_size); + ks->decrypt = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, + false); if (IS_ERR(ks->decrypt)) { ret = PTR_ERR(ks->decrypt); ks->decrypt = NULL; goto destroy_ks; } - memcpy(ks->nonce_tail_xmit, kc->encrypt.nonce_tail, - OVPN_NONCE_TAIL_SIZE); - memcpy(ks->nonce_tail_recv, kc->decrypt.nonce_tail, - OVPN_NONCE_TAIL_SIZE); - - /* init packet ID generation/validation */ - ovpn_pktid_xmit_init(&ks->pid_xmit); - ovpn_pktid_recv_init(&ks->pid_recv); - return ks; destroy_ks: @@ -158,10 +195,10 @@ enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks) { const char *alg_name; - if (!ks->encrypt) + if (!ks->encrypt || !ks->encrypt->tfm) return OVPN_CIPHER_ALG_NONE; - alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt)); + alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt->tfm)); if (!strcmp(alg_name, ALG_NAME_AES)) return OVPN_CIPHER_ALG_AES_GCM; diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 6f3396f6f72e..2b1df68a1b1e 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -111,6 +111,7 @@ void ovpn_decrypt_post(void *data, int ret) unsigned int payload_offset = 0; struct sk_buff *skb = data; struct ovpn_socket *sock; + struct ovpn_key_ctx *key; struct ovpn_peer *peer; u64 aead_blocks; __be16 proto; @@ -124,13 +125,14 @@ void ovpn_decrypt_post(void *data, int ret) payload_offset = ovpn_skb_cb(skb)->payload_offset; ks = ovpn_skb_cb(skb)->ks; + key = ks->decrypt; peer = ovpn_skb_cb(skb)->peer; /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); if (unlikely(ret == -EBADMSG)) { - if (unlikely(ovpn_aead_decrypt_failure_record(ks))) + if (unlikely(ovpn_aead_decrypt_failure_record(key))) ovpn_nl_key_swap_notify(peer, ks->key_id); goto drop; } @@ -139,7 +141,7 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; pktid = ovpn_aead_direct_pktid(skb); - ret = ovpn_pktid_recv(&ks->pid_recv, pktid, 0); + ret = ovpn_pktid_recv(&key->pid.recv, pktid, 0); if (unlikely(ret < 0)) { net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n", netdev_name(peer->ovpn->dev), peer->id, @@ -150,8 +152,7 @@ void ovpn_decrypt_post(void *data, int ret) aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, OVPN_AEAD_DIRECT_AAD_SIZE, skb->len - payload_offset); - if (unlikely(ovpn_pktid_recv_update_aead(&ks->pid_recv, - &ks->usage_recv, + if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, &ks->usage_limit, aead_blocks))) ovpn_nl_key_swap_notify(peer, ks->key_id); diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index a85a1d160150..235c9111d085 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -128,14 +128,16 @@ ovpn_pktid_recv_update_aead(struct ovpn_pktid_recv *pr, return ret; } -/* Write 12-byte AEAD IV to dest */ +/* write the direct-key AEAD IV to dest */ static inline void ovpn_pktid_aead_write(const u32 pktid, - const u8 nt[], + const u8 implicit_iv[], unsigned char *dest) { *(__force __be32 *)(dest) = htonl(pktid); - BUILD_BUG_ON(4 + OVPN_NONCE_TAIL_SIZE != OVPN_NONCE_SIZE); - memcpy(dest + 4, nt, OVPN_NONCE_TAIL_SIZE); + BUILD_BUG_ON(OVPN_NONCE_WIRE_SIZE + OVPN_NONCE_TAIL_SIZE != + OVPN_NONCE_SIZE); + memcpy(dest + OVPN_NONCE_WIRE_SIZE, + implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE); } void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid); From patchwork Thu Jul 2 11:52:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5071 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550855max; Thu, 2 Jul 2026 04:53:35 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ9Jq7nR1DRkTroQyWCpNVQmb/Y9PNcm+QTw5/IL4Ym4ZCot5voAIUZZMnZmwYNB+z2CJkRy6cAB64Q=@openvpn.net X-Received: by 2002:a05:6808:3999:b0:495:d5f7:c63a with SMTP id 5614622812f47-4960ed04ee5mr3885648b6e.4.1782993215336; Thu, 02 Jul 2026 04:53:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993215; cv=none; d=google.com; s=arc-20260327; b=PLyvZLUT9AtW8m4kE7b+sMzZ9hh+n2+hkIiHgVNgCS8w68t890Ymshu168+/HFUbHF RI1Nyx4GA6jto6i2/Hp4yfBWBQkWBfdEcEh0JIrxXcDxMedJ+yzhNJ+rsYskf1mYdQme 7WEiazOYn/Mx0v1rHzl0ucBO83O45IIpWu6HnpcOfheuqMTxkWWbHiaWxnHCWbAJ2cd3 znpQhzmbghqHuIWOPZFaRac+yNqyA86I6PaKkMVfl59bi17L9v3682pzMYgOymdRlVNu syvAybl9x8fyFQuy7QusO/T3r4wJwTJRYkcr+R+rI22L26b0iCFykVxcrqvyxcn2ejRz Zktg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=W4GObJKFSkZ2gznX50uTcqghh5GM09ABkPrGbxpq45I=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=aQyu5OJQnYL/yDxEpuGTmSg35t9M9JgoRLJ+6IETqQAn19syl9M68C2ctOFnxfW5T2 Srw9FdNkq7Q4yvW9WxWkUdMwYcxif4wfCXzvV1RliDd8eeY2+VBhB6jA1Ksx5RP+QdFO VcoFJFavBG63TpD17EWZqpy7QKoRgn3MvQDcjeJ2apaeOKX/rgLXzKCl2d2qt39hWq6e AepCWhC5Nf6ZaBtPwaegPtYP2hAK4SpkcoIRDT2U7gdz1f9NsntLeZz2HVvv6O0XByc5 2iKaJ9ARyjaZJt5J9nbSNF62xTTPk6aHQqAP+DM3vOaZTTsZzX1YrpXpml5j2+MP4oCf 6iNg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=II2tTDXj; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=Y5v+lNZf; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iPMw3m2u; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=Br5NBVaR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 5614622812f47-497d1839c64si1524340b6e.39.2026.07.02.04.53.35 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:35 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=II2tTDXj; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=Y5v+lNZf; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=iPMw3m2u; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=Br5NBVaR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=W4GObJKFSkZ2gznX50uTcqghh5GM09ABkPrGbxpq45I=; b=II2tTDXjvSUj/5pjFJoOOqXZ6g Ou0DSpj9dvAbVAHX9FyNK0t+Ui+YHL8QizS3xr0g7BRZoVXqOyyq6YEDEud5kfVAYWG69PyQzflD9 FPB5ldDh25A7uPQCW0wRSo1y4dCwFRzz1KfQnvgZtR5oKeDIt1pkML+UFI5FACFPzrSc=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz4-0007u7-PZ; Thu, 02 Jul 2026 11:53:32 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz3-0007ty-WE for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:31 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=gNNWQBHSgiSH6C5d4SGGsWn9cTB7lrv22EjfKfKw/FQ=; b=Y5v+lNZfYKyvnsezNS4oQIUoQB ZyVa0jjwzISUU/g9Vlz6rhQ5nE14EAwf7vAlR15C4lZaxSazpcnQl7LSYXQTzwu4vhtBHyNAtlv/g mThgiOFqjUMarOwDLrAPbfssT205AyL3TM+opRhwN5qRF2wP4hiy6zCvAAd8RJI9029Q=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=gNNWQBHSgiSH6C5d4SGGsWn9cTB7lrv22EjfKfKw/FQ=; b=iPMw3m2uW19HPS5r/rpLQP9cgt eSIc2X3QUd8eLHve+OEASKQej+/iKe+PropubRhlBgMF5EUxYAJlwNyEjtBH66x21yX5ZjplDQXhU 1cAeaBVq9/lVZcmivIo0ImLmf8e7Le3EtZXD9IDPbsDuRLBWRNFdOCWfbRuKSTAjlhHU=; Received: from mout-b-206.mailbox.org ([195.10.208.51]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFyz-0004KI-Gx for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:31 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-206.mailbox.org (Postfix) with ESMTPS id 4grZyj0KHNz9xsT; Thu, 2 Jul 2026 13:53:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993197; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gNNWQBHSgiSH6C5d4SGGsWn9cTB7lrv22EjfKfKw/FQ=; b=Br5NBVaRotQo18zNCGbsaooFr6lMfWyK1vh71dhgIA9fYArs4sGOrZ5z/95upng+/+9z6B FHi0+v6JxESiIsUIb21TDEExjvK+P3nBPH8W275yJO6Jkl6tSXxJOnbiKiS6tAHpV/BhgJ 8+IM96iF2fu3ldH2AFZBHojRmPvTC8CCR9gN+uFTlGB5E91iOj4y/6UAwQS9seqgL6wqAQ VkE6fw506YHqSI+x8nZ5eUphHSlBEQmOAaDNeqLgX7jofWm7MuuZdWgnnOBKkOgn+3I1wu lBkDGpSeJRAUF2bxufBo6b1O52VTTujbIpdpIHmEMcK8uBqxJ03i4xskBpM+4Q== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:22 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Epoch key rotation needs to replace encrypt and decrypt key contexts while packets may still hold references from the async AEAD path. Publish key contexts through RCU, add a per-context kref, and let encrypt and decrypt requests hold a context until their completion callback runs. Direct-key behavior is otherwise unchanged. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFyz-0004KI-Gx Subject: [Openvpn-devel] [RFC ovpn net-next v2 07/14] ovpn: make key contexts refcounted X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603893577419601 X-GMAIL-MSGID: 1869603893577419601 Epoch key rotation needs to replace encrypt and decrypt key contexts while packets may still hold references from the async AEAD path. Publish key contexts through RCU, add a per-context kref, and let encrypt and decrypt requests hold a context until their completion callback runs. Direct-key behavior is otherwise unchanged. Signed-off-by: Ralf Lici --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/4ae05d37fc69c2c29d83f824616b1aee6315ab6b.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto.h | 34 ++++++++++++++++++++-- drivers/net/ovpn/crypto_aead.c | 20 ++++++++++--- drivers/net/ovpn/crypto_key.c | 53 +++++++++++++++++++--------------- drivers/net/ovpn/io.c | 8 +++-- drivers/net/ovpn/skb.h | 2 ++ 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index c36cd8299afd..1d62f2be23c9 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -10,6 +10,9 @@ #ifndef _NET_OVPN_OVPNCRYPTO_H_ #define _NET_OVPN_OVPNCRYPTO_H_ +#include +#include + #include "crypto_limits.h" #include "pktid.h" #include "proto.h" @@ -47,6 +50,8 @@ struct ovpn_key_ctx { struct ovpn_key_usage usage; atomic64_t decrypt_failures; unsigned long decrypt_failure_flags; + struct kref refcount; + struct rcu_head rcu; }; struct ovpn_crypto_key_slot { @@ -54,8 +59,8 @@ struct ovpn_crypto_key_slot { enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; - struct ovpn_key_ctx *encrypt; - struct ovpn_key_ctx *decrypt; + struct ovpn_key_ctx __rcu *encrypt; + struct ovpn_key_ctx __rcu *decrypt; struct kref refcount; struct rcu_head rcu; }; @@ -137,6 +142,31 @@ static inline void ovpn_crypto_key_slot_put(struct ovpn_crypto_key_slot *ks) kref_put(&ks->refcount, ovpn_crypto_key_slot_release); } +void ovpn_key_ctx_release(struct kref *kref); + +static inline void ovpn_key_ctx_put(struct ovpn_key_ctx *key) +{ + if (key) + kref_put(&key->refcount, ovpn_key_ctx_release); +} + +static inline int ovpn_key_ctx_get(struct ovpn_key_ctx **out, + struct ovpn_key_ctx __rcu **rcu_ptr) +{ + struct ovpn_key_ctx *key; + + rcu_read_lock(); + key = rcu_dereference(*rcu_ptr); + if (unlikely(!key || !kref_get_unless_zero(&key->refcount))) { + rcu_read_unlock(); + return -ENOKEY; + } + rcu_read_unlock(); + + *out = key; + return 0; +} + int ovpn_crypto_state_reset(struct ovpn_crypto_state *cs, const struct ovpn_peer_key_reset *pkr); diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 31f5327694d2..39bd320169d3 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -150,8 +150,8 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead, int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - struct ovpn_key_ctx *key = ks->encrypt; unsigned int plaintext_len, tag_size; + struct ovpn_key_ctx *key = NULL; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; @@ -164,6 +164,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ovpn_skb_cb(skb)->peer = peer; ovpn_skb_cb(skb)->ks = ks; plaintext_len = skb->len; + + ret = ovpn_key_ctx_get(&key, &ks->encrypt); + if (unlikely(ret)) + return ret; + ovpn_skb_cb(skb)->key = key; + tag_size = crypto_aead_authsize(key->tfm); /* Sample AEAD header format: @@ -263,8 +269,8 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - struct ovpn_key_ctx *key = ks->decrypt; unsigned int payload_offset, tag_size; + struct ovpn_key_ctx *key = NULL; int ret, payload_len, nfrags; struct aead_request *req; struct sk_buff *trailer; @@ -272,13 +278,19 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, void *tmp; u8 *iv; + ovpn_skb_cb(skb)->peer = peer; + ovpn_skb_cb(skb)->ks = ks; + + ret = ovpn_key_ctx_get(&key, &ks->decrypt); + if (unlikely(ret)) + return ret; + ovpn_skb_cb(skb)->key = key; + tag_size = crypto_aead_authsize(key->tfm); payload_offset = ovpn_aead_direct_payload_offset(tag_size); payload_len = skb->len - payload_offset; ovpn_skb_cb(skb)->payload_offset = payload_offset; - ovpn_skb_cb(skb)->peer = peer; - ovpn_skb_cb(skb)->ks = ks; /* sanity check on packet size, payload size must be >= 0 */ if (unlikely(payload_len < 0)) diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index cdf35e2b241c..44110a0b38eb 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -82,6 +82,22 @@ static void ovpn_key_ctx_free(struct ovpn_key_ctx *key) kfree(key); } +static void ovpn_key_ctx_free_rcu(struct rcu_head *head) +{ + struct ovpn_key_ctx *key; + + key = container_of(head, struct ovpn_key_ctx, rcu); + ovpn_key_ctx_free(key); +} + +void ovpn_key_ctx_release(struct kref *kref) +{ + struct ovpn_key_ctx *key; + + key = container_of(kref, struct ovpn_key_ctx, refcount); + call_rcu(&key->rcu, ovpn_key_ctx_free_rcu); +} + static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, const struct ovpn_key_direction *dir, bool encrypt) @@ -113,6 +129,7 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, ovpn_key_usage_init(&key->usage); atomic64_set(&key->decrypt_failures, 0); key->decrypt_failure_flags = 0; + kref_init(&key->refcount); /* initialize only the packet ID direction this context owns */ if (encrypt) @@ -128,8 +145,8 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) if (!ks) return; - ovpn_key_ctx_free(ks->encrypt); - ovpn_key_ctx_free(ks->decrypt); + ovpn_key_ctx_put(rcu_access_pointer(ks->encrypt)); + ovpn_key_ctx_put(rcu_access_pointer(ks->decrypt)); kfree(ks); } @@ -137,6 +154,7 @@ struct ovpn_crypto_key_slot * ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) { struct ovpn_crypto_key_slot *ks = NULL; + struct ovpn_key_ctx *key; const char *alg_name; int ret; @@ -168,21 +186,19 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) ks->cipher_alg = kc->cipher_alg; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); - ks->encrypt = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, - true); - if (IS_ERR(ks->encrypt)) { - ret = PTR_ERR(ks->encrypt); - ks->encrypt = NULL; + key = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, true); + if (IS_ERR(key)) { + ret = PTR_ERR(key); goto destroy_ks; } + RCU_INIT_POINTER(ks->encrypt, key); - ks->decrypt = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, - false); - if (IS_ERR(ks->decrypt)) { - ret = PTR_ERR(ks->decrypt); - ks->decrypt = NULL; + key = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, false); + if (IS_ERR(key)) { + ret = PTR_ERR(key); goto destroy_ks; } + RCU_INIT_POINTER(ks->decrypt, key); return ks; @@ -193,17 +209,8 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) enum ovpn_cipher_alg ovpn_crypto_key_slot_alg(struct ovpn_crypto_key_slot *ks) { - const char *alg_name; - - if (!ks->encrypt || !ks->encrypt->tfm) + if (!ks) return OVPN_CIPHER_ALG_NONE; - alg_name = crypto_tfm_alg_name(crypto_aead_tfm(ks->encrypt->tfm)); - - if (!strcmp(alg_name, ALG_NAME_AES)) - return OVPN_CIPHER_ALG_AES_GCM; - else if (!strcmp(alg_name, ALG_NAME_CHACHAPOLY)) - return OVPN_CIPHER_ALG_CHACHA20_POLY1305; - else - return OVPN_CIPHER_ALG_NONE; + return ks->cipher_alg; } diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 2b1df68a1b1e..006d83fe3e2a 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -125,14 +125,14 @@ void ovpn_decrypt_post(void *data, int ret) payload_offset = ovpn_skb_cb(skb)->payload_offset; ks = ovpn_skb_cb(skb)->ks; - key = ks->decrypt; + key = ovpn_skb_cb(skb)->key; peer = ovpn_skb_cb(skb)->peer; /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); if (unlikely(ret == -EBADMSG)) { - if (unlikely(ovpn_aead_decrypt_failure_record(key))) + if (key && unlikely(ovpn_aead_decrypt_failure_record(key))) ovpn_nl_key_swap_notify(peer, ks->key_id); goto drop; } @@ -224,6 +224,7 @@ void ovpn_decrypt_post(void *data, int ret) ovpn_peer_put(peer); if (likely(ks)) ovpn_crypto_key_slot_put(ks); + ovpn_key_ctx_put(key); } /* RX path entry point: decrypt packet and forward it to the device */ @@ -256,6 +257,7 @@ void ovpn_encrypt_post(void *data, int ret) struct ovpn_crypto_key_slot *ks; struct sk_buff *skb = data; struct ovpn_socket *sock; + struct ovpn_key_ctx *key; struct ovpn_peer *peer; unsigned int orig_len; @@ -267,6 +269,7 @@ void ovpn_encrypt_post(void *data, int ret) ks = ovpn_skb_cb(skb)->ks; peer = ovpn_skb_cb(skb)->peer; + key = ovpn_skb_cb(skb)->key; /* crypto is done, cleanup skb CB and its members */ kfree(ovpn_skb_cb(skb)->crypto_tmp); @@ -322,6 +325,7 @@ void ovpn_encrypt_post(void *data, int ret) ovpn_peer_put(peer); if (likely(ks)) ovpn_crypto_key_slot_put(ks); + ovpn_key_ctx_put(key); kfree_skb(skb); } diff --git a/drivers/net/ovpn/skb.h b/drivers/net/ovpn/skb.h index 4fb7ea025426..75d64161d774 100644 --- a/drivers/net/ovpn/skb.h +++ b/drivers/net/ovpn/skb.h @@ -22,6 +22,7 @@ * struct ovpn_cb - ovpn skb control block * @peer: the peer this skb was received from/sent to * @ks: the crypto key slot used to encrypt/decrypt this skb + * @key: the key context used to encrypt/decrypt this skb * @crypto_tmp: pointer to temporary memory used for crypto operations * containing the IV, the scatter gather list and the aead request * @payload_offset: offset in the skb where the payload starts @@ -30,6 +31,7 @@ struct ovpn_cb { struct ovpn_peer *peer; struct ovpn_crypto_key_slot *ks; + struct ovpn_key_ctx *key; void *crypto_tmp; unsigned int payload_offset; bool nosignal; From patchwork Thu Jul 2 11:52:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5074 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550963max; Thu, 2 Jul 2026 04:53:41 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8Q+s8AIqhSd0Id94XkocdJppmDZIjVdq3wtnbt1h8rv/Y6dSZNS5/TgD3eLPIhppA1CM1Zei0iE04=@openvpn.net X-Received: by 2002:a05:6870:2383:b0:439:b99e:441e with SMTP id 586e51a60fabf-44cab50cf33mr3507902fac.1.1782993221460; Thu, 02 Jul 2026 04:53:41 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993221; cv=none; d=google.com; s=arc-20260327; b=ZAl51/8kNCbi5jfgz95FJ9VB1/sWILyrr/9cpymTEJgP7zH/oiZ/z7ZMI52ZmGkKXN r6MdRoTgI2JvuayKvtDOnXmy1/9zN+2dERGEDvlPZw2cvS1x4nIcWL2h4L8ky6YmEyJj hOpCw05RXRI0XnJba4yc/EJ80N2ot+F99QqMhm9BWlT84Uq+08fALXGzApLZGgG4GeOe nUfbt9BwR2O8ZJC9KAZaveeY+zdxRQp8XZpSx/uWzNw9BgRmsedaNQQiBjRByEutfCj6 rZ1b82NvIPAGNFQAKRwiAS6ZTfOMeTGcNgOG/31njtpbUN+p3mFDI8jhPFUHJ9Os2Y97 MUSg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=eAxrQ77L8Tlfzwz2xCcFuPqt0/FWzRP2hOVRcJjhv+0=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=VMyQRwNmizAdIy0Ti8yIBLhIrcn6CiA1gIxV7fPaHCZJ6wQSm1m6iw5hoM/KQmWWVT da6fSTddhF/n1dPrdRCVxV1Z/be76RCVswZBWLTl9u/P3fi3IE1TsYhwpV2k39bdiD+f G1s+BA2HxeqGj/CRvRiqO+QwzTzuGQzWIX5Oin1hNYOeDaWraCYWcklHUnL9kPaxU90c omD7E2/ud2xPgz7tGUW/bERagFF4KHGcCToe8Kn0m/fBB/YITQz204XkFaF1VzKSM+R0 k2nghsvI3LzHjRNcdUsZnIstgFQbN4PcYMynZrqITPJGlDBV0NcyObfpR8hKKPXydMZk cZOw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ggi+darJ; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=ZNrvt8rQ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=XvJCk0G4; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=IaImGko2; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-44cbe80a952si2258061fac.26.2026.07.02.04.53.41 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:41 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ggi+darJ; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=ZNrvt8rQ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=XvJCk0G4; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=IaImGko2; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=eAxrQ77L8Tlfzwz2xCcFuPqt0/FWzRP2hOVRcJjhv+0=; b=ggi+darJYsZ29OAmjGdQ92M3PW jEqJdwDhLtzwma9WcBU7f7qryIFDUC7TgMneFh8MvTL0u1XQKdH4Cg83XFonG7NnpPYxo86G4ulkl j2a5IBRd9s1pZ4DiSx8phq2ZoRP6PuFdc7WA3TDATGG7+5pxWU4tKvEgi7X3JEN6uLX0=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz8-00008U-Kk; Thu, 02 Jul 2026 11:53:39 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz2-00007k-PZ for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:33 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=mty8ij+8GKVIeotJiU9m6j7sY6TkOpI0HY0VW2TTJhM=; b=ZNrvt8rQa0F6ftrhyUV5Nykn4q iw7pGswKtVLqMep7PiGC65FJLIM9Dy/CXrJS1bF1XQDrPo2CSWQQEuvbCvGge/O5UotEp0HfxPMR8 4XK+2EZQn53BRAu724aoKQ64NPcjM28S5sEEzvoFY+SYQr9fav2T3DS4h/fwjna9NOj8=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=mty8ij+8GKVIeotJiU9m6j7sY6TkOpI0HY0VW2TTJhM=; b=XvJCk0G4R8MtHKsTYmtwhLaSap /HGfY/lezVNF98/JaeLJx1Xt7dR6cuWwzM7l0McPBjXTtJ/FihEB4x8WRfNLzpYNXmkJaX1AXK8d2 WvwkV9eiKWFRTZ52zVidvD4HJW2J1HdoWM+9/ZDUF5QpfoP9wGqHE0cako8sio3ksTQU=; Received: from mout-b-203.mailbox.org ([195.10.208.52]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz1-0004KP-E2 for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:33 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-203.mailbox.org (Postfix) with ESMTPS id 4grZyk3jt3z9xbk; Thu, 2 Jul 2026 13:53:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993198; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=mty8ij+8GKVIeotJiU9m6j7sY6TkOpI0HY0VW2TTJhM=; b=IaImGko2g5enGCv04RnvNw3aRc7zs7ydJqcpbAvUDKrVUEiYfNUpaPUJ73eKYXlqGBnv+c rcd59qh5uytkAKmF+WmdmphS8Zq7F39iX3RZ/FnRuuuQRcD4FkY7vKd2G+IRJ77l4hXesV 2Aj5zeZwNOdR4wsv9uZ3uJcCeVQnshpYtrLnB+tiByq2LFDI+W49mfqCh7VrM9qnR4CFeJ sMR5/ujU8M2VbIQjswmERrAvND6Nq/rpin3yMAcjmtVRyQNF+v3KVHQI4oQhZMZNzUzPtV J913MuF2r9Yz5YIvVCVT4KGst6AzecqmoZii7Ces4h8/TwXnDLL7D32U+LinCQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:23 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Prepare the data path for packet ID spaces larger than the direct-key 32-bit counter. Widen packet ID accounting to u64 and store packet-ID soft and hard limits in the transmit packet-ID state. This keeps wrap and rekey-notification policy with the packet-ID allocator instead of passin [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFz1-0004KP-E2 Subject: [Openvpn-devel] [RFC ovpn net-next v2 08/14] ovpn: generalize AEAD packet ID handling X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603899865598325 X-GMAIL-MSGID: 1869603899865598325 Prepare the data path for packet ID spaces larger than the direct-key 32-bit counter. Widen packet ID accounting to u64 and store packet-ID soft and hard limits in the transmit packet-ID state. This keeps wrap and rekey-notification policy with the packet-ID allocator instead of passing raw thresholds through the AEAD path. Make AES-GCM usage accounting include the packet layout's AAD length. Add shared packet ID read/write helpers and direct-key slot layout fields that later epoch support can reuse. Signed-off-by: Ralf Lici --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/ca32807ed19fe093ee611254a551bba6a5552e2d.1782919654.git.ralf@mandelbit.com/ drivers/net/ovpn/crypto.h | 3 + drivers/net/ovpn/crypto_aead.c | 31 ++++---- drivers/net/ovpn/crypto_key.c | 12 +++- drivers/net/ovpn/io.c | 10 +-- drivers/net/ovpn/pktid.c | 13 ++-- drivers/net/ovpn/pktid.h | 126 +++++++++++++++++++++++---------- drivers/net/ovpn/proto.h | 8 +++ 7 files changed, 141 insertions(+), 62 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 1d62f2be23c9..8e7235f41960 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -58,6 +58,9 @@ struct ovpn_crypto_key_slot { u8 key_id; enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; + bool epoch_format; + unsigned int aad_size; + unsigned int pktid_size; struct ovpn_key_ctx __rcu *encrypt; struct ovpn_key_ctx __rcu *decrypt; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index 39bd320169d3..cf0a3d861eb9 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -154,11 +154,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct ovpn_key_ctx *key = NULL; struct aead_request *req; struct sk_buff *trailer; + u64 aead_blocks, pktid; struct scatterlist *sg; + bool pktid_notify; int nfrags, ret; - u64 aead_blocks; - u32 pktid, op; void *tmp; + u32 op; u8 *iv; ovpn_skb_cb(skb)->peer = peer; @@ -227,11 +228,15 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* obtain packet ID, which is used both as a first * 4 bytes of nonce and last 4 bytes of associated data. */ - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_AEAD_DIRECT_AAD_SIZE, + ret = ovpn_pktid_xmit_next(&key->pid.xmit, &pktid); + if (unlikely(ret < 0)) + return ret; + pktid_notify = ret > 0; + + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, plaintext_len); - ret = ovpn_pktid_xmit_next(&key->pid.xmit, &key->usage, - &ks->usage_limit, aead_blocks, &pktid); + ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, pktid, + aead_blocks, pktid_notify); if (unlikely(ret < 0)) return ret; if (unlikely(ret > 0)) @@ -240,11 +245,11 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes * nonce */ - ovpn_pktid_aead_write(pktid, key->implicit_iv, iv); + pktid = ovpn_pktid_aead_write(0, pktid, key->implicit_iv, iv); /* make space for packet id and push it to the front */ - __skb_push(skb, OVPN_NONCE_WIRE_SIZE); - memcpy(skb->data, iv, OVPN_NONCE_WIRE_SIZE); + __skb_push(skb, ks->pktid_size); + ovpn_pktid_wire_write(skb->data, false, pktid); /* add packet op as head of additional data */ op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id); @@ -253,14 +258,14 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, *((__force __be32 *)skb->data) = htonl(op); /* AEAD Additional data */ - sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); + sg_set_buf(sg, skb->data, ks->aad_size); /* setup async crypto operation */ aead_request_set_tfm(req, key->tfm); aead_request_set_callback(req, 0, ovpn_encrypt_post, skb); aead_request_set_crypt(req, sg, sg, skb->len - ovpn_aead_encap_overhead(key), iv); - aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); + aead_request_set_ad(req, ks->aad_size); /* encrypt it */ return crypto_aead_encrypt(req); @@ -334,7 +339,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg_init_table(sg, nfrags + 2); /* packet op is head of additional data */ - sg_set_buf(sg, skb->data, OVPN_AEAD_DIRECT_AAD_SIZE); + sg_set_buf(sg, skb->data, ks->aad_size); /* build scatterlist to decrypt packet payload */ ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); @@ -358,7 +363,7 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, aead_request_set_callback(req, 0, ovpn_decrypt_post, skb); aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv); - aead_request_set_ad(req, OVPN_AEAD_DIRECT_AAD_SIZE); + aead_request_set_ad(req, ks->aad_size); /* decrypt it */ return crypto_aead_decrypt(req); diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 44110a0b38eb..8ac13a3485fe 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -102,6 +102,7 @@ static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, const struct ovpn_key_direction *dir, bool encrypt) { + struct ovpn_limit pktid_limit; struct ovpn_key_ctx *key; size_t tail_offset; int ret; @@ -132,10 +133,12 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, kref_init(&key->refcount); /* initialize only the packet ID direction this context owns */ - if (encrypt) - ovpn_pktid_xmit_init(&key->pid.xmit); - else + if (encrypt) { + ovpn_pktid_xmit_limit_init(&pktid_limit, false); + ovpn_pktid_xmit_init(&key->pid.xmit, &pktid_limit); + } else { ovpn_pktid_recv_init(&key->pid.recv); + } return key; } @@ -184,6 +187,9 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) kref_init(&ks->refcount); ks->key_id = kc->key_id; ks->cipher_alg = kc->cipher_alg; + ks->epoch_format = false; + ks->aad_size = OVPN_AEAD_DIRECT_AAD_SIZE; + ks->pktid_size = OVPN_NONCE_WIRE_SIZE; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); key = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, true); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 006d83fe3e2a..f5198d5bf104 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -112,10 +112,10 @@ void ovpn_decrypt_post(void *data, int ret) struct sk_buff *skb = data; struct ovpn_socket *sock; struct ovpn_key_ctx *key; + u64 aead_blocks, pktid; struct ovpn_peer *peer; - u64 aead_blocks; + u16 pkt_epoch; __be16 proto; - u32 pktid; /* crypto is happening asynchronously. this function will be called * again later by the crypto callback with a proper return code @@ -140,7 +140,8 @@ void ovpn_decrypt_post(void *data, int ret) if (unlikely(ret < 0)) goto drop; - pktid = ovpn_aead_direct_pktid(skb); + pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, false, + &pkt_epoch); ret = ovpn_pktid_recv(&key->pid.recv, pktid, 0); if (unlikely(ret < 0)) { net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n", @@ -149,8 +150,7 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, - OVPN_AEAD_DIRECT_AAD_SIZE, + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, skb->len - payload_offset); if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, &ks->usage_limit, diff --git a/drivers/net/ovpn/pktid.c b/drivers/net/ovpn/pktid.c index f1c243b84463..16289035c287 100644 --- a/drivers/net/ovpn/pktid.c +++ b/drivers/net/ovpn/pktid.c @@ -17,9 +17,12 @@ #include "main.h" #include "pktid.h" -void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid) +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid, + const struct ovpn_limit *limit) { - atomic_set(&pid->seq_num, 1); + pid->seq_num = 1; + pid->limit = *limit; + spin_lock_init(&pid->lock); } void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr) @@ -31,7 +34,7 @@ void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr) /* Packet replay detection. * Allows ID backtrack of up to REPLAY_WINDOW_SIZE - 1. */ -int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u64 pkt_id, u32 pkt_time) { const unsigned long now = jiffies; int ret; @@ -71,7 +74,7 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) pr->id = pkt_id; } else if (pkt_id > pr->id) { /* ID jumped forward by more than one */ - const unsigned int delta = pkt_id - pr->id; + const u64 delta = pkt_id - pr->id; if (delta < REPLAY_WINDOW_SIZE) { unsigned int i; @@ -95,7 +98,7 @@ int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time) pr->id = pkt_id; } else { /* ID backtrack */ - const unsigned int delta = pr->id - pkt_id; + const u64 delta = pr->id - pkt_id; if (delta > pr->max_backtrack) pr->max_backtrack = delta; diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 235c9111d085..62666017f051 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -13,6 +13,9 @@ #include "crypto_limits.h" #include "proto.h" +#include +#include + /* If no packets received for this length of time, set a backtrack floor * at highest received packet ID thus far. */ @@ -20,10 +23,17 @@ /* notify userspace when the packet ID space is close to wrapping */ #define PKTID_XMIT_REKEY_NOTIFY 0xff000000U +#define PKTID_DIRECT_MAX U32_MAX +#define PKTID_EPOCH_SEQ_MAX GENMASK_ULL(47, 0) +#define PKTID_EPOCH_MASK GENMASK_ULL(63, 48) +#define PKTID_EPOCH_SEQ_MASK GENMASK_ULL(47, 0) /* Packet-ID state for transmitter */ struct ovpn_pktid_xmit { - atomic_t seq_num; + u64 seq_num; + struct ovpn_limit limit; + /* protects seq_num */ + spinlock_t lock; }; /* replay window sizing in bytes = 2^REPLAY_WINDOW_ORDER */ @@ -46,56 +56,61 @@ struct ovpn_pktid_recv { /* expiration of history in jiffies */ unsigned long expire; /* highest sequence number received */ - u32 id; + u64 id; /* highest time stamp received */ u32 time; /* we will only accept backtrack IDs > id_floor */ - u32 id_floor; - unsigned int max_backtrack; + u64 id_floor; + u64 max_backtrack; /* protects entire pktd ID state */ spinlock_t lock; }; +static inline void ovpn_pktid_xmit_limit_init(struct ovpn_limit *limit, + bool epoch_format) +{ + if (epoch_format) { + limit->soft = U64_MAX; + limit->hard = PKTID_EPOCH_SEQ_MAX; + } else { + limit->soft = PKTID_XMIT_REKEY_NOTIFY; + limit->hard = PKTID_DIRECT_MAX; + } +} + /** * ovpn_pktid_xmit_next - allocate a transmit packet ID * @pid: transmit packet ID state - * @usage: key usage state - * @limit: key usage limits - * @aead_blocks: AEAD usage blocks consumed by this packet * @pktid: location where the generated packet ID is stored * - * The returned packet ID becomes part of the AEAD nonce, so the helper rejects - * the packet before the 32-bit packet-ID space wraps. It also reserves this - * packet's AEAD usage against the key and rejects the packet before the hard - * AES-GCM usage limit would be exceeded. + * The returned packet ID becomes part of the AEAD nonce. Reusing it would + * reuse an IV, so the helper rejects the packet before the configured packet-ID + * space wraps. * - * Soft thresholds do not reject the packet. They return 1 once so the caller - * can notify userspace to rekey while packet-ID space remains and before the - * hard AES-GCM usage limit is reached. + * The hard limit stops TX. The soft limit asks userspace to rekey before that + * happens and is reported as return value 1. * * Return: 1 if userspace should be notified, 0 if no notification is needed, * or a negative error code otherwise. */ -static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, - struct ovpn_key_usage *usage, - const struct ovpn_limit *limit, - u64 aead_blocks, u32 *pktid) +static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u64 *pktid) { - const u32 seq_num = atomic_fetch_add_unless(&pid->seq_num, 1, 0); - bool pktid_notify; + u64 seq_num; int ret; + spin_lock_bh(&pid->lock); + seq_num = pid->seq_num; + /* packet IDs are used to create cipher IVs and must not wrap */ - if (unlikely(!seq_num)) + if (unlikely(!seq_num || seq_num > pid->limit.hard)) { + spin_unlock_bh(&pid->lock); return -ERANGE; - - pktid_notify = seq_num >= PKTID_XMIT_REKEY_NOTIFY; - ret = ovpn_key_usage_xmit(usage, limit, seq_num, aead_blocks, - pktid_notify); - if (unlikely(ret < 0)) - return ret; + } + pid->seq_num++; + spin_unlock_bh(&pid->lock); *pktid = seq_num; + ret = unlikely(seq_num >= pid->limit.soft) ? 1 : 0; return ret; } @@ -128,21 +143,60 @@ ovpn_pktid_recv_update_aead(struct ovpn_pktid_recv *pr, return ret; } -/* write the direct-key AEAD IV to dest */ -static inline void ovpn_pktid_aead_write(const u32 pktid, - const u8 implicit_iv[], - unsigned char *dest) +/* write the AEAD IV to dest */ +static inline u64 ovpn_pktid_aead_write(u16 epoch, u64 seq, + const u8 implicit_iv[], + unsigned char *dest) { - *(__force __be32 *)(dest) = htonl(pktid); - BUILD_BUG_ON(OVPN_NONCE_WIRE_SIZE + OVPN_NONCE_TAIL_SIZE != - OVPN_NONCE_SIZE); + /* epoch packets carry a 16-bit epoch and 48-bit counter */ + u64 pktid = FIELD_PREP(PKTID_EPOCH_MASK, epoch) | + FIELD_PREP(PKTID_EPOCH_SEQ_MASK, seq); + + if (epoch) { + u64 iv_head = get_unaligned_be64(implicit_iv); + + put_unaligned_be64(pktid ^ iv_head, dest); + memcpy(dest + OVPN_EPOCH_NONCE_WIRE_SIZE, + implicit_iv + OVPN_EPOCH_NONCE_WIRE_SIZE, + OVPN_NONCE_SIZE - OVPN_EPOCH_NONCE_WIRE_SIZE); + return pktid; + } + + put_unaligned_be32(seq, dest); memcpy(dest + OVPN_NONCE_WIRE_SIZE, implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE); + + return seq; +} + +static inline u64 ovpn_pktid_read(const u8 *buf, bool epoch_format, + u16 *epoch) +{ + u64 pktid; + + if (epoch_format) { + pktid = get_unaligned_be64(buf); + *epoch = FIELD_GET(PKTID_EPOCH_MASK, pktid); + return FIELD_GET(PKTID_EPOCH_SEQ_MASK, pktid); + } + + *epoch = 0; + return get_unaligned_be32(buf); +} + +static inline void ovpn_pktid_wire_write(u8 *buf, bool epoch_format, + u64 pktid) +{ + if (epoch_format) + put_unaligned_be64(pktid, buf); + else + put_unaligned_be32(pktid, buf); } -void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid); +void ovpn_pktid_xmit_init(struct ovpn_pktid_xmit *pid, + const struct ovpn_limit *limit); void ovpn_pktid_recv_init(struct ovpn_pktid_recv *pr); -int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u32 pkt_id, u32 pkt_time); +int ovpn_pktid_recv(struct ovpn_pktid_recv *pr, u64 pkt_id, u32 pkt_time); #endif /* _NET_OVPN_OVPNPKTID_H_ */ diff --git a/drivers/net/ovpn/proto.h b/drivers/net/ovpn/proto.h index 5fa053584b15..a4dce8f0a5fa 100644 --- a/drivers/net/ovpn/proto.h +++ b/drivers/net/ovpn/proto.h @@ -37,6 +37,7 @@ * and is normally the head of the IV */ #define OVPN_NONCE_WIRE_SIZE (OVPN_NONCE_SIZE - OVPN_NONCE_TAIL_SIZE) +#define OVPN_EPOCH_NONCE_WIRE_SIZE 8 #define OVPN_OPCODE_SIZE 4 /* DATA_V2 opcode size */ #define OVPN_OPCODE_KEYID_MASK 0x07000000 @@ -56,6 +57,13 @@ OVPN_NONCE_WIRE_SIZE) #define OVPN_AEAD_DIRECT_AAD_SIZE OVPN_AEAD_DIRECT_TAG_OFFSET +/* epoch-key AEAD packet layout */ +#define OVPN_AEAD_EPOCH_OP_OFFSET 0 +#define OVPN_AEAD_EPOCH_PKTID_OFFSET (OVPN_AEAD_EPOCH_OP_OFFSET + \ + OVPN_OPCODE_SIZE) +#define OVPN_AEAD_EPOCH_AAD_SIZE (OVPN_AEAD_EPOCH_PKTID_OFFSET + \ + OVPN_EPOCH_NONCE_WIRE_SIZE) + #define OVPN_PEER_ID_UNDEF 0x00FFFFFF static inline unsigned int From patchwork Thu Jul 2 11:52:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5073 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550946max; Thu, 2 Jul 2026 04:53:40 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/+nQ6Khw/oZs57P/jpBV4W4Hob+EwztPQeK3IaMFsty2cbm0PET1afY5IEeWDdMWGOq24Yxh2Sb7k=@openvpn.net X-Received: by 2002:a05:6820:810c:b0:6a0:f3b5:129f with SMTP id 006d021491bc7-6a30d7a5774mr3101219eaf.18.1782993220573; Thu, 02 Jul 2026 04:53:40 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993220; cv=none; d=google.com; s=arc-20260327; b=Pgdyt1hNVOzDyGt+WWBkG8OdzCRwvIES4CEBch1hVBgJ+XUcjsv+fQWqVTsgIRDSRm N/Ya5Rs6PhyItIDnV6aNiDlcecFS2Tcx5rESlkJ3b1L97SpY+EkyI761aExrfMLje2yp /oZIERYpfqqFrBTpE8YOlK7ujBNeypn93lj6EVpc7RSTa/jMF/MTbRU0ZXK+aEpFC+qF Nc5PBDyahcLVw+U00wbZbeJxIvbs9dfUjGf+Bc8Fm0ehBZv+0YLy7t2nsGItRtHTVkpK oj/59rqPZsS+cYV7c0EbRY9AWVf464Dlr8zWZSfdmqIRIaPtXyjoYYpJzVBa4F6vn+O2 go2g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=T8GctoYbCVAXwYIueJoSH06p71HM7CrsuVHPmNk+M+E=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Y1AKl2Nxjf3iIBIsHXJ9jun3O1ZHYBlI/UcJnKgzv8Qn3CLiwazpgksWuxbqabC8Js 636nuSdoueI4H3U28/9UZ61t2kSesC+hnOj0wNWZ/9MRShnPJaN59NyFpmIw//uzYIs/ AycGWlyBDENQQq4OuLOO3TR8cnNM1gikx8/+3/WHugS1+YEtS5Ot43zBBCL2GSj3QeS5 QViV5T3mscd05/Wd8ZLnqMR+ZN6FEDejejKLLLIRWKrfw+Dzq8Ulh88m8jx68wX/JKlP Q3I2zow3Ugmqcr6cXlkoNDiARXSoSbRYcZ/SJUos29PMe496szEtkInvzfYoA/CASdEZ l0qw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=kWcugfJm; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=QEWM7nWo; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="Sune/pmh"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=cIPGyFQn; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-44cbeec9138si2403956fac.228.2026.07.02.04.53.40 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:40 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=kWcugfJm; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=QEWM7nWo; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="Sune/pmh"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=cIPGyFQn; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=T8GctoYbCVAXwYIueJoSH06p71HM7CrsuVHPmNk+M+E=; b=kWcugfJmg2z3s/DvmepZSVHRR7 xh8ftjzxeCzNTleDn38UYW50lhq3X6whAbsKI9q64Yqe9B1iILMeYqkQJnEOjEVy96qMWaqwFzpWn +4UjB31dAbf19T9g1GP0P0gVxhrz0kQVz/6xZfpzS+eum5zhrIun3ipKV2tYTPd2RXko=; Received: from [127.0.0.1] (helo=sfs-ml-3.v29.lw.sourceforge.com) by sfs-ml-3.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz6-0001iX-M6; Thu, 02 Jul 2026 11:53:37 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-3.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz4-0001iD-Us for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:35 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=sjq7jY9Rm4Q6/SwvXGysovTkQSkTqVPT+AyyIY5Slck=; b=QEWM7nWoc7RF5FX7qf4QB/7yJy tWzrhVW8W0oQniMlCJuXIv8ezRjJ0dfoGye+rrzLkkSu2WKwHjQ8wlNPrWNUsqEFyXwOIaQIBEMtZ enOIB1Ni7pSVHKDEFcW68UgRrby7BC8mSQ/HV0msMWu4Eqy0DDSXQd4g7Nc4iSkl6R8g=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=sjq7jY9Rm4Q6/SwvXGysovTkQSkTqVPT+AyyIY5Slck=; b=Sune/pmh4hKhWGUdndkiqwNEKP yrpZVDJ6a42RxpKjHSAZgifsEJMG5JAeiMCUZttRyKwKHu7EK+EGgvzht6s1T46nk5uoZWttd6e// R3jy2/OGnB37Pl2AVhBKLvKEjFE+bYwYtIWv5Uwndlpv8bnPmS4o0qTxqe3/WPkeE9fs=; Received: from mout-b-201.mailbox.org ([195.10.208.61]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz3-0004KX-Gn for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:35 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-201.mailbox.org (Postfix) with ESMTPS id 4grZym1NPGzDs9w; Thu, 2 Jul 2026 13:53:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993200; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=sjq7jY9Rm4Q6/SwvXGysovTkQSkTqVPT+AyyIY5Slck=; b=cIPGyFQng+srZnPdYzkbwBR/WJqyvJ4BzUs1w8RprZJ7+JKizkxkTyUxufjfu61EsMGVdb 79vc70Ux5MPKj3KtdaDE83UPgnB4h/HNdyEeCsFB5ianO7u4ckaRI9qOUGf6qxrsn45Q3N I4VewTCpPVl2x5K20oY8aqt7ZHG1nQyAtjyjaX/K5oOTlXtohiYtT74JAqvf48/lRXynYG CmXCy1gk9LKsy8JmyNG0ftq6EZ/vhMgkIhGHeyGZ+iIXHkhCnTJxA4h/nKe6mn9FkNGVtY VKL6NbBHWrUTKkSzKxBrvSMssg50tM33smqDTmUf0mY0t4OcqH3j+beS/23YdQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:24 +0200 Message-ID: <8074a8cd4c5c9ce1c0d0dabf0830514d20dd47a2.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Add the HKDF-Expand-Label helper used to derive per-epoch data keys and implicit IVs from an imported epoch PRK. OpenVPN currently uses the labels "datakey upd", "data_key" and "data_iv" for this deri [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFz3-0004KX-Gn Subject: [Openvpn-devel] [RFC ovpn net-next v2 09/14] ovpn: add epoch key derivation support X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603898907452806 X-GMAIL-MSGID: 1869603898907452806 Add the HKDF-Expand-Label helper used to derive per-epoch data keys and implicit IVs from an imported epoch PRK. OpenVPN currently uses the labels "datakey upd", "data_key" and "data_iv" for this derivation. Epoch derivation uses HMAC-SHA256, so the PRK is fixed at 32 bytes. Keep that size internal until the netlink key format is introduced. Signed-off-by: Ralf Lici --- Changes since v1 https://lore.kernel.org/openvpn-devel/2232f95098268c00dd6ce56e346e02fa0b111a17.1782919654.git.ralf@mandelbit.com/ - Split PRK derivation from PRK installation so later refill code can derive scratch future-key state without mutating the active epoch key. drivers/net/Kconfig | 2 + drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/crypto_epoch.c | 223 ++++++++++++++++++++++++++++++++ drivers/net/ovpn/crypto_epoch.h | 59 +++++++++ 4 files changed, 285 insertions(+) create mode 100644 drivers/net/ovpn/crypto_epoch.c create mode 100644 drivers/net/ovpn/crypto_epoch.h diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index ff79c466712d..cd4193ce51b4 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -109,6 +109,8 @@ config OVPN select CRYPTO_AES select CRYPTO_GCM select CRYPTO_CHACHA20POLY1305 + select CRYPTO_HMAC + select CRYPTO_SHA256 select STREAM_PARSER help This module enhances the performance of the OpenVPN userspace software diff --git a/drivers/net/ovpn/Makefile b/drivers/net/ovpn/Makefile index 704af1deb7c1..9dc2906e7c22 100644 --- a/drivers/net/ovpn/Makefile +++ b/drivers/net/ovpn/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_OVPN) := ovpn.o ovpn-y += bind.o ovpn-y += crypto.o ovpn-y += crypto_aead.o +ovpn-y += crypto_epoch.o ovpn-y += crypto_key.o ovpn-y += main.o ovpn-y += io.o diff --git a/drivers/net/ovpn/crypto_epoch.c b/drivers/net/ovpn/crypto_epoch.c new file mode 100644 index 000000000000..bfdf030bb0fb --- /dev/null +++ b/drivers/net/ovpn/crypto_epoch.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +/* OpenVPN data channel offload + * + * Copyright (C) 2026 OpenVPN, Inc. + * + * Author: Ralf Lici + * Antonio Quartulli + */ + +#include +#include + +#include "crypto_epoch.h" +#include "proto.h" + +#define OVPN_EPOCH_DATA_KEY_LABEL "data_key" +#define OVPN_EPOCH_DATA_IV_LABEL "data_iv" +#define OVPN_EPOCH_UPDATE_LABEL "datakey upd" +#define OVPN_EPOCH_LABEL_PREFIX "ovpn " +#define OVPN_EPOCH_INFO_MAX_SIZE 21 + +#define OVPN_EPOCH_HASH_ALG "hmac(sha256)" + +static int ovpn_hkdf_expand(struct crypto_shash *shash, const u8 *info, + size_t info_len, u8 *okm, size_t okm_len) +{ + unsigned int prev_len = 0, digest_len; + SHASH_DESC_ON_STACK(desc, shash); + u8 prev[OVPN_EPOCH_PRK_SIZE]; + size_t copied = 0, todo; + u8 counter = 1; + int ret = 0; + + digest_len = crypto_shash_digestsize(shash); + if (WARN_ON_ONCE(digest_len != sizeof(prev))) + return -EINVAL; + + desc->tfm = shash; + + /* T(0) is the empty string */ + while (copied < okm_len) { + /* T(n) = HMAC-Hash(PRK, T(n-1) | info | n) */ + ret = crypto_shash_init(desc); + if (ret) + goto out; + ret = crypto_shash_update(desc, prev, prev_len); + if (ret) + goto out; + ret = crypto_shash_update(desc, info, info_len); + if (ret) + goto out; + ret = crypto_shash_update(desc, &counter, sizeof(counter)); + if (ret) + goto out; + ret = crypto_shash_final(desc, prev); + if (ret) + goto out; + + prev_len = digest_len; + /* copy a full digest block or the final partial block */ + todo = min_t(size_t, digest_len, okm_len - copied); + memcpy(okm + copied, prev, todo); + copied += todo; + counter++; + } + +out: + memzero_explicit(prev, sizeof(prev)); + shash_desc_zero(desc); + return ret; +} + +struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size) +{ + struct crypto_shash *shash; + int ret; + + shash = crypto_alloc_shash(OVPN_EPOCH_HASH_ALG, 0, 0); + if (IS_ERR(shash)) + return shash; + + if (key_size != crypto_shash_digestsize(shash)) { + crypto_free_shash(shash); + return ERR_PTR(-EINVAL); + } + + /* store the PRK as the shash key so it can be advanced in place */ + ret = crypto_shash_setkey(shash, key, key_size); + if (ret) { + crypto_free_shash(shash); + return ERR_PTR(ret); + } + + return shash; +} + +static int ovpn_expand_label(struct crypto_shash *shash, const u8 *label, + size_t label_len, u8 *okm, u16 okm_len) +{ + static const u8 label_prefix[] = OVPN_EPOCH_LABEL_PREFIX; + u8 prefix_len = sizeof(label_prefix) - 1, full_len; + u8 info[OVPN_EPOCH_INFO_MAX_SIZE]; + u16 info_len; + int ret; + + if (WARN_ON_ONCE(!label_len || label_len > 250)) + return -EINVAL; + + full_len = prefix_len + label_len; + info_len = sizeof(okm_len) + full_len + 2; + if (WARN_ON_ONCE(info_len > sizeof(info))) + return -EINVAL; + + /* encode length, "ovpn " label and empty context */ + put_unaligned_be16(okm_len, info); + info[2] = full_len; + memcpy(&info[3], label_prefix, prefix_len); + memcpy(&info[3 + prefix_len], label, label_len); + info[3 + full_len] = 0; + + ret = ovpn_hkdf_expand(shash, info, info_len, okm, okm_len); + memzero_explicit(info, info_len); + + return ret; +} + +/** + * ovpn_epoch_derive_next_prk - derive the next epoch PRK + * @epoch_key: PRK derivation state + * @next_prk: destination for the next PRK + * + * Epoch data keys are derived from a per-direction PRK. Advancing from E_N to + * E_N+1 uses OVPN-Expand-Label(E_N, "datakey upd", "", 32). + * + * Return: 0 on success or a negative error code otherwise. + */ +int ovpn_epoch_derive_next_prk(const struct ovpn_epoch_key *epoch_key, + u8 next_prk[]) +{ + if (unlikely(epoch_key->epoch == OVPN_MAX_EPOCH)) + return -ERANGE; + + if (WARN_ON_ONCE(OVPN_EPOCH_PRK_SIZE != + crypto_shash_digestsize(epoch_key->shash))) + return -EINVAL; + + return ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_UPDATE_LABEL, + sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1, + next_prk, OVPN_EPOCH_PRK_SIZE); +} + +int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], + u16 epoch) +{ + int ret; + + ret = crypto_shash_setkey(epoch_key->shash, prk, OVPN_EPOCH_PRK_SIZE); + if (ret) + return ret; + + epoch_key->epoch = epoch; + + return 0; +} + +/** + * ovpn_epoch_iterate - advance an epoch PRK to the next epoch + * @epoch_key: PRK derivation state + * + * Epoch data keys are derived from a per-direction PRK. Advancing from E_N to + * E_N+1 uses OVPN-Expand-Label(E_N, "datakey upd", "", 32). + * + * Return: 0 on success or a negative error code otherwise. + */ +int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key) +{ + u8 key[OVPN_EPOCH_PRK_SIZE]; + int ret; + + ret = ovpn_epoch_derive_next_prk(epoch_key, key); + if (ret) + goto out; + + /* expose the next epoch only after its PRK is installed */ + ret = ovpn_epoch_set_prk(epoch_key, key, epoch_key->epoch + 1); + +out: + memzero_explicit(key, sizeof(key)); + return ret; +} + +/** + * ovpn_epoch_derive_key - derive the concrete AEAD key for an epoch + * @epoch_key: PRK derivation state + * @cipher_key: destination for the AEAD cipher key + * @implicit_iv: destination for the AEAD implicit IV + * + * K_i is OVPN-Expand-Label(E_i, "data_key", "", key_size), while the implicit + * IV is OVPN-Expand-Label(E_i, "data_iv", "", OVPN_NONCE_SIZE). + * + * Return: 0 on success or a negative error code otherwise. + */ +int ovpn_epoch_derive_key(const struct ovpn_epoch_key *epoch_key, + u8 cipher_key[], u8 implicit_iv[]) +{ + int ret; + + if (WARN_ON_ONCE(!epoch_key->cipher_key_len || + epoch_key->cipher_key_len > OVPN_EPOCH_PRK_SIZE)) + return -EINVAL; + + /* derive the concrete AEAD key for the current epoch */ + ret = ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_DATA_KEY_LABEL, + sizeof(OVPN_EPOCH_DATA_KEY_LABEL) - 1, + cipher_key, epoch_key->cipher_key_len); + if (ret) + return ret; + + /* derive the implicit IV paired with that AEAD key */ + return ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_DATA_IV_LABEL, + sizeof(OVPN_EPOCH_DATA_IV_LABEL) - 1, + implicit_iv, OVPN_NONCE_SIZE); +} diff --git a/drivers/net/ovpn/crypto_epoch.h b/drivers/net/ovpn/crypto_epoch.h new file mode 100644 index 000000000000..1ffa6f55e729 --- /dev/null +++ b/drivers/net/ovpn/crypto_epoch.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* OpenVPN data channel offload + * + * Copyright (C) 2026 OpenVPN, Inc. + * + * Author: Ralf Lici + * Antonio Quartulli + */ + +#ifndef _NET_OVPN_OVPNEPOCH_H_ +#define _NET_OVPN_OVPNEPOCH_H_ + +#include +#include +#include +#include +#include + +#define OVPN_EPOCH_PRK_SIZE 32 +#define OVPN_MAX_EPOCH U16_MAX +#define OVPN_EPOCH_FUTURE_KEYS_COUNT 16 + +struct ovpn_key_ctx; + +/* crypto handle used for key derivation through HKDF-Expand-Label */ +struct ovpn_epoch_key { + u16 epoch; + unsigned int cipher_key_len; + struct crypto_shash *shash; +}; + +/* ring buffer of prederived future epoch data keys */ +struct ovpn_future_keys { + struct ovpn_key_ctx __rcu *keys[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + u16 head; + u16 tail; + bool full; +}; + +static inline u16 +ovpn_epoch_future_keys_count(const struct ovpn_future_keys *fk) +{ + if (fk->full) + return OVPN_EPOCH_FUTURE_KEYS_COUNT; + + return (fk->head - fk->tail + OVPN_EPOCH_FUTURE_KEYS_COUNT) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; +} + +struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size); +int ovpn_epoch_derive_next_prk(const struct ovpn_epoch_key *epoch_key, + u8 next_prk[]); +int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], + u16 epoch); +int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key); +int ovpn_epoch_derive_key(const struct ovpn_epoch_key *epoch_key, + u8 cipher_key[], u8 implicit_iv[]); + +#endif /* _NET_OVPN_OVPNEPOCH_H_ */ From patchwork Thu Jul 2 11:52:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5076 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550994max; Thu, 2 Jul 2026 04:53:43 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/hpaw1JfBDid0ZntFi2Kbbwpq47vl1Zvmmi0XbVtmbU/BeywBNQNabCWI+pD0VyCZnYKp70lU9T/Q=@openvpn.net X-Received: by 2002:a05:6820:825:b0:6a1:518a:54bd with SMTP id 006d021491bc7-6a30d737ef7mr3056085eaf.12.1782993223612; Thu, 02 Jul 2026 04:53:43 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993223; cv=none; d=google.com; s=arc-20260327; b=dBe1ykeCgrTWPY+fYqLHuEJ47wXpfYDpyWp5J0gHCOZ2d+EqlzJz2ZD1BpF8UWpppP c/hZW1NQq4b0vlLwO5wllGQgq89wxYtK5uIyleIfc2Vhaa2LTNqmGopsuBkcI1C38cR8 GjZDJ/ksXTi2w6TmHrKCzpVamPafCaRQEozfXbxXrSTf/YmY7xJpF+OgZyNsw+W2o7lK edSt6q+KDrCjda4l5wBbmuXtdXM8teHIqT+DiuG8YLwdNhQ4x2qDZCFHNXMECyARsaWG isFoX7m5SO08PRZBXNseyZkEakH8HqCaQTjZR2icbPo/6ey/fQUTTCLs0/R3aVX4NcAP I5Lg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=6utUc6mLIbCaW82/X5/VH9qLD085ippgo+bKfuK8CMk=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Oy0GNlg4jm00/DOIuq3sBKr5OFrgWPlQVILqRS2wXJVntPU7SCO713moNtJLII5gZt sF4t5HZU14u2yIuC9e+uy6nZHXDMYXIo2C4UpXvnMiA9UOckc7QVEyP29icVCl95Uo5y 3dm/J71f6U0t5TBuai4sta5NsKL9FKixU6/aJpS8CWmmLHDUrAHjzJkyogxX6/GyvckJ gMlSFIHUpr+B54RFrZU6FWcnkjzW+5bC0Wj9EVFw8Sza8iKrL6oXBGCQAk9bwv0YqGA2 Q1Sd2V48mSx62G7+kr852pmcz9hynXOTksvUeKQn9y9V508/uXLcFY1RLEKrLk+z/d+j ZFVQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=OZU7ToAz; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="j7Si5/zl"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=RoumQDKm; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=nsov0TQV; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-6a30ffa4604si2137993eaf.14.2026.07.02.04.53.43 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:43 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=OZU7ToAz; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="j7Si5/zl"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=RoumQDKm; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=nsov0TQV; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=6utUc6mLIbCaW82/X5/VH9qLD085ippgo+bKfuK8CMk=; b=OZU7ToAzgDOC3v8PYaPv2L6Sc1 mkZl+JpS7EkOPblZBOnoQbmovx51wYbtIL1PlRDyMdWNXT42fnngv+AZZIWf0TNSAQbB/lPrLivya u2oZZ1lBonbuKJszJ6W8Ktd0qgY3qlHQ/zsrromC8LXHw+xggKX+JfMloh2AuJX/mS6k=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFzA-0005ac-Va; Thu, 02 Jul 2026 11:53:40 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz6-0005a9-Py for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:36 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=xzq4l84BNWvqqfDvi0egvAEZVwydU6Mab9PEzREbc/4=; b=j7Si5/zlbIlhKLogGsFqe+3A6J GI+1c4AWnxZQnLIqrvVKVG+CfyF5/ASe+5wQCqxEMNV9GX6e93eclXXW5zeMsW2H9vkbIFLjo5wTS yTji8SJ2stxN5yrqMG171qXmRjVsu3GpqY9Tj5Kcn3omKgmkdYY4q4P3K3EKe8+zrDpw=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=xzq4l84BNWvqqfDvi0egvAEZVwydU6Mab9PEzREbc/4=; b=RoumQDKmryvd4jZN2hnGBfjfSu J+xRYNeyTSGKgKxvH4iWz3A1qsC+7y22cteHhZwZfDDudpa2eywNY1+4RoyQxVz9f44BvcxfCcZwc Nb3ymyXMcj+d0Pqi8pyc9SjHq5xf/QE1rvU0TEZuzNI04Bnc1i1LiqfA3Qs60O+rrKCE=; Received: from mout-b-202.mailbox.org ([195.10.208.62]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz4-0004Ka-5S for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:36 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-202.mailbox.org (Postfix) with ESMTPS id 4grZyn5xVbzDs8p; Thu, 2 Jul 2026 13:53:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993201; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=xzq4l84BNWvqqfDvi0egvAEZVwydU6Mab9PEzREbc/4=; b=nsov0TQVSy91vDr9bOVm0abilowTfwxdrzs08bNYS1Z2RJo8gJ+qHzBKr+4WO/t54INb7/ CZZLo+tcMzK5hVLmyydZcnhJdwRFpWet/M8tU3Y1fCpE4/dcDo2Mf3HBJfRZaaSCHE+z0E MShHXhtfMU1TGSgxXLpDa05yaJ/JNieUJsociCTeqQyYPVUGZlSgPkXxrT8iP47g2s5n/F Z6sUt2gigTT4LuQFeVqK8+0KumUc8cJ6w23gtWwL9vFHOGkZ3qa2V+gL87RkEBdEaOeiFt A5xQIQlyrM74enAvaFm1KJxpHimBEoADV2sdXv8+dsa77FIRQnplNAYsmfBVqw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:25 +0200 Message-ID: <89168cc93435b0d954e136a9bea55460813fd6a4.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4grZyn5xVbzDs8p X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Epoch keys do not import concrete AEAD keys for direct packet processing. Userspace provides per-direction PRKs, and the kernel derives the concrete AEAD key context and implicit IV for each epoch. Add the slot state needed to keep TX and RX epoch derivation separate, track the current encrypt/decrypt key contexts, and prederive future key rings for both directions. The future rings let the data [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFz4-0004Ka-5S Subject: [Openvpn-devel] [RFC ovpn net-next v2 10/14] ovpn: add epoch key slot state X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603902481228427 X-GMAIL-MSGID: 1869603902481228427 Epoch keys do not import concrete AEAD keys for direct packet processing. Userspace provides per-direction PRKs, and the kernel derives the concrete AEAD key context and implicit IV for each epoch. Add the slot state needed to keep TX and RX epoch derivation separate, track the current encrypt/decrypt key contexts, and prederive future key rings for both directions. The future rings let the data path advance to a new epoch without deriving keys in the fast path. Refill work items hold a key-slot reference, which protects the slot object but not module text. Queue them on a module-owned workqueue that is drained and destroyed in module_exit, so no refill work can outlive the module. Direct keys continue to create one concrete AEAD context per direction. Name that constructor explicitly as the direct-key path so the later epoch constructor can share the same key-context boundary without hiding which key format is being installed. Packet layout handling and promotion between epoch keys are added by later patches. Signed-off-by: Ralf Lici --- Changes since v1 https://lore.kernel.org/openvpn-devel/d0034ab911faf74ba4bb658f9f13fa97dd7a274c.1782919654.git.ralf@mandelbit.com/ - Derive future keys from scratch PRK state and only commit the slot PRK after all future keys are created, keeping failed refills retryable. - Zero temporary epoch key material on all key-context creation paths, including failures. - Queue refill work on an ovpn-owned workqueue that is drained during module exit, so refill workers cannot outlive module text. drivers/net/ovpn/crypto.h | 56 ++++- drivers/net/ovpn/crypto_key.c | 378 ++++++++++++++++++++++++++++++++-- drivers/net/ovpn/main.c | 15 +- drivers/net/ovpn/netlink.c | 8 +- 4 files changed, 432 insertions(+), 25 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 8e7235f41960..d7b7e115c339 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -12,7 +12,9 @@ #include #include +#include +#include "crypto_epoch.h" #include "crypto_limits.h" #include "pktid.h" #include "proto.h" @@ -25,12 +27,28 @@ struct ovpn_key_direction { size_t nonce_tail_size; /* only needed for GCM modes */ }; -/* direct-key material for a primary or secondary slot */ +/* PRK material imported from userspace to derive epoch data keys */ +struct ovpn_epoch_prk { + const u8 *key; + size_t key_size; + unsigned int cipher_key_len; +}; + +/* key material for a primary or secondary slot */ struct ovpn_key_config { enum ovpn_cipher_alg cipher_alg; u8 key_id; - struct ovpn_key_direction encrypt; - struct ovpn_key_direction decrypt; + bool use_epoch_keys; + union { + struct { + struct ovpn_key_direction encrypt; + struct ovpn_key_direction decrypt; + } direct; + struct { + struct ovpn_epoch_prk encrypt; + struct ovpn_epoch_prk decrypt; + } epoch; + }; }; /* used to pass settings from netlink to the crypto engine */ @@ -41,6 +59,7 @@ struct ovpn_peer_key_reset { /* state for one concrete AEAD key direction */ struct ovpn_key_ctx { + u16 epoch; struct crypto_aead *tfm; u8 implicit_iv[OVPN_NONCE_SIZE]; union { @@ -58,12 +77,29 @@ struct ovpn_crypto_key_slot { u8 key_id; enum ovpn_cipher_alg cipher_alg; struct ovpn_limit usage_limit; + const char *alg_name; bool epoch_format; unsigned int aad_size; unsigned int pktid_size; + struct ovpn_epoch_key epoch_key_send; + struct ovpn_epoch_key epoch_key_recv; + struct ovpn_key_ctx __rcu *encrypt; struct ovpn_key_ctx __rcu *decrypt; + struct ovpn_key_ctx __rcu *retiring_key; + + struct ovpn_future_keys future_tx_keys; + struct ovpn_future_keys future_rx_keys; + + struct work_struct tx_refill; + struct work_struct rx_refill; + + /* protects encrypt and future_tx_keys */ + spinlock_t tx_lock; + /* protects decrypt, retiring_key and future_rx_keys */ + spinlock_t rx_lock; + struct kref refcount; struct rcu_head rcu; }; @@ -187,10 +223,24 @@ void ovpn_crypto_state_release(struct ovpn_crypto_state *cs); void ovpn_crypto_key_slots_swap(struct ovpn_crypto_state *cs); +int ovpn_crypto_workqueue_init(void); + +void ovpn_crypto_workqueue_destroy(void); + int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, enum ovpn_key_slot slot, struct ovpn_key_config *keyconf); bool ovpn_crypto_kill_key(struct ovpn_crypto_state *cs, u8 key_id); +void ovpn_schedule_refill(struct ovpn_crypto_key_slot *ks, bool encrypt); + +struct ovpn_key_ctx * +ovpn_key_ctx_create_direct(bool encrypt, const char *alg_name, + const struct ovpn_key_direction *dir); + +struct ovpn_key_ctx * +ovpn_key_ctx_create_epoch(bool encrypt, const char *alg_name, + struct ovpn_epoch_key *epoch_key); + #endif /* _NET_OVPN_OVPNCRYPTO_H_ */ diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 8ac13a3485fe..8626c4e2db60 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -8,15 +8,35 @@ */ #include +#include +#include #include "ovpnpriv.h" #include "crypto.h" +#include "crypto_epoch.h" #include "pktid.h" #include "proto.h" #define ALG_NAME_AES "gcm(aes)" #define ALG_NAME_CHACHAPOLY "rfc7539(chacha20,poly1305)" +static struct workqueue_struct *ovpn_wq; + +int ovpn_crypto_workqueue_init(void) +{ + ovpn_wq = alloc_workqueue("ovpn", 0, 0); + if (!ovpn_wq) + return -ENOMEM; + + return 0; +} + +void ovpn_crypto_workqueue_destroy(void) +{ + destroy_workqueue(ovpn_wq); + ovpn_wq = NULL; +} + /* initialize a struct crypto_aead object */ static struct crypto_aead *ovpn_aead_init(const char *title, const char *alg_name, @@ -100,11 +120,11 @@ void ovpn_key_ctx_release(struct kref *kref) static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, - const struct ovpn_key_direction *dir, bool encrypt) + const u8 *cipher_key, unsigned int cipher_key_len, + const u8 *implicit_iv, u16 epoch, bool encrypt) { struct ovpn_limit pktid_limit; struct ovpn_key_ctx *key; - size_t tail_offset; int ret; key = kmalloc_obj(*key); @@ -112,8 +132,7 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, return ERR_PTR(-ENOMEM); /* create the concrete AEAD transform first */ - key->tfm = ovpn_aead_init(title, alg_name, dir->cipher_key, - dir->cipher_key_size); + key->tfm = ovpn_aead_init(title, alg_name, cipher_key, cipher_key_len); if (IS_ERR(key->tfm)) { ret = PTR_ERR(key->tfm); key->tfm = NULL; @@ -122,10 +141,8 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, } /* store the implicit IV in a full nonce-sized buffer */ - tail_offset = OVPN_NONCE_SIZE - dir->nonce_tail_size; - memset(key->implicit_iv, 0, sizeof(key->implicit_iv)); - memcpy(key->implicit_iv + tail_offset, dir->nonce_tail, - dir->nonce_tail_size); + key->epoch = epoch; + memcpy(key->implicit_iv, implicit_iv, sizeof(key->implicit_iv)); ovpn_key_usage_init(&key->usage); atomic64_set(&key->decrypt_failures, 0); @@ -143,16 +160,289 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, return key; } +struct ovpn_key_ctx * +ovpn_key_ctx_create_direct(bool encrypt, const char *alg_name, + const struct ovpn_key_direction *dir) +{ + u8 implicit_iv[OVPN_NONCE_SIZE]; + struct ovpn_key_ctx *key; + size_t tail_offset; + + tail_offset = OVPN_NONCE_SIZE - dir->nonce_tail_size; + memset(implicit_iv, 0, sizeof(implicit_iv)); + memcpy(implicit_iv + tail_offset, dir->nonce_tail, + dir->nonce_tail_size); + + key = ovpn_key_ctx_new(encrypt ? "encrypt" : "decrypt", alg_name, + dir->cipher_key, dir->cipher_key_size, + implicit_iv, 0, encrypt); + memzero_explicit(implicit_iv, sizeof(implicit_iv)); + + return key; +} + +struct ovpn_key_ctx * +ovpn_key_ctx_create_epoch(bool encrypt, const char *alg_name, + struct ovpn_epoch_key *epoch_key) +{ + u8 cipher_key[OVPN_EPOCH_PRK_SIZE], implicit_iv[OVPN_NONCE_SIZE]; + struct ovpn_key_ctx *key; + int ret; + + ret = ovpn_epoch_derive_key(epoch_key, cipher_key, implicit_iv); + if (ret) { + key = ERR_PTR(ret); + goto out; + } + + key = ovpn_key_ctx_new(encrypt ? "encrypt" : "decrypt", alg_name, + cipher_key, epoch_key->cipher_key_len, + implicit_iv, epoch_key->epoch, encrypt); + +out: + memzero_explicit(cipher_key, sizeof(cipher_key)); + memzero_explicit(implicit_iv, sizeof(implicit_iv)); + + return key; +} + +static int +ovpn_epoch_init_future_keys(bool encrypt, const char *alg_name, + struct ovpn_epoch_key *epoch_key, + struct ovpn_future_keys *future_keys) +{ + struct ovpn_key_ctx *key; + int ret; + u16 i; + + future_keys->head = 0; + future_keys->tail = 0; + future_keys->full = false; + + /* each generated key advances the PRK and stores the next epoch */ + for (i = 0; i < OVPN_EPOCH_FUTURE_KEYS_COUNT; i++) { + ret = ovpn_epoch_iterate(epoch_key); + if (ret) + goto err; + + key = ovpn_key_ctx_create_epoch(encrypt, alg_name, epoch_key); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + goto err; + } + + RCU_INIT_POINTER(future_keys->keys[future_keys->head], key); + future_keys->head = (future_keys->head + 1) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + } + + future_keys->full = true; + return 0; + +err: + for (i = 0; i < OVPN_EPOCH_FUTURE_KEYS_COUNT; i++) { + ovpn_key_ctx_put(rcu_access_pointer(future_keys->keys[i])); + RCU_INIT_POINTER(future_keys->keys[i], NULL); + } + future_keys->head = 0; + future_keys->tail = 0; + future_keys->full = false; + + return ret; +} + +void ovpn_schedule_refill(struct ovpn_crypto_key_slot *ks, bool encrypt) +{ + struct work_struct *work = encrypt ? &ks->tx_refill : &ks->rx_refill; + + if (!ovpn_crypto_key_slot_hold(ks)) + return; + + if (!queue_work(ovpn_wq, work)) + ovpn_crypto_key_slot_put(ks); +} + +static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks, + bool encrypt) +{ + struct ovpn_key_ctx *new_futures[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + struct ovpn_key_ctx *old_futures[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + u16 replaced = 0, created = 0, free_slots, i; + const struct ovpn_epoch_key *source_key; + struct ovpn_epoch_key scratch_key = {}; + size_t prk_size = OVPN_EPOCH_PRK_SIZE; + struct ovpn_future_keys *future_keys; + struct ovpn_epoch_key *epoch_key; + struct ovpn_key_ctx __rcu **slot; + u8 next_prk[OVPN_EPOCH_PRK_SIZE]; + /* selected tx/rx future-key ring lock */ + spinlock_t *lock; + bool full; + int ret; + + /* select the tx or rx future-key state */ + if (encrypt) { + future_keys = &ks->future_tx_keys; + epoch_key = &ks->epoch_key_send; + lock = &ks->tx_lock; + } else { + future_keys = &ks->future_rx_keys; + epoch_key = &ks->epoch_key_recv; + lock = &ks->rx_lock; + } + + /* calculate how many keys are needed to refill the ring */ + spin_lock_bh(lock); + free_slots = OVPN_EPOCH_FUTURE_KEYS_COUNT - + ovpn_epoch_future_keys_count(future_keys); + spin_unlock_bh(lock); + + if (!free_slots) + return; + + /* derive new keys without holding the ring lock */ + scratch_key.cipher_key_len = epoch_key->cipher_key_len; + for (created = 0; created < free_slots; created++) { + source_key = scratch_key.shash ? &scratch_key : epoch_key; + ret = ovpn_epoch_derive_next_prk(source_key, next_prk); + if (ret) + goto err; + + if (!scratch_key.shash) { + scratch_key.shash = ovpn_epoch_init_key(next_prk, + prk_size); + if (IS_ERR(scratch_key.shash)) { + ret = PTR_ERR(scratch_key.shash); + scratch_key.shash = NULL; + goto err; + } + scratch_key.epoch = epoch_key->epoch + 1; + } else { + ret = ovpn_epoch_set_prk(&scratch_key, next_prk, + scratch_key.epoch + 1); + if (ret) + goto err; + } + + new_futures[created] = + ovpn_key_ctx_create_epoch(encrypt, ks->alg_name, + &scratch_key); + if (IS_ERR(new_futures[created])) { + ret = PTR_ERR(new_futures[created]); + goto err; + } + } + + ret = ovpn_epoch_set_prk(epoch_key, next_prk, scratch_key.epoch); + if (ret) + goto err; + + /* insert generated keys under the selected ring lock */ + spin_lock_bh(lock); + for (replaced = 0; replaced < created; replaced++) { + if (WARN_ON_ONCE(future_keys->full)) + break; + + /* the ring remains monotonic and consecutive */ + slot = &future_keys->keys[future_keys->head]; + old_futures[replaced] = + rcu_dereference_protected(*slot, lockdep_is_held(lock)); + rcu_assign_pointer(*slot, new_futures[replaced]); + future_keys->head = (future_keys->head + 1) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + if (future_keys->head == future_keys->tail) + future_keys->full = true; + } + full = future_keys->full; + spin_unlock_bh(lock); + + for (i = 0; i < replaced; i++) + ovpn_key_ctx_put(old_futures[i]); + for (i = replaced; i < created; i++) + ovpn_key_ctx_put(new_futures[i]); + + crypto_free_shash(scratch_key.shash); + memzero_explicit(next_prk, sizeof(next_prk)); + + /* keep refilling until the ring is full */ + if (!full) + ovpn_schedule_refill(ks, encrypt); + + return; + +err: + for (i = 0; i < created; i++) + ovpn_key_ctx_put(new_futures[i]); + if (scratch_key.shash) + crypto_free_shash(scratch_key.shash); + memzero_explicit(next_prk, sizeof(next_prk)); +} + +static void ovpn_refill_future_buffer_tx(struct work_struct *work) +{ + struct ovpn_crypto_key_slot *ks; + + ks = container_of(work, struct ovpn_crypto_key_slot, tx_refill); + ovpn_refill_future_buffer(ks, true); + ovpn_crypto_key_slot_put(ks); +} + +static void ovpn_refill_future_buffer_rx(struct work_struct *work) +{ + struct ovpn_crypto_key_slot *ks; + + ks = container_of(work, struct ovpn_crypto_key_slot, rx_refill); + ovpn_refill_future_buffer(ks, false); + ovpn_crypto_key_slot_put(ks); +} + void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks) { + struct ovpn_key_ctx *future; + u8 i; + if (!ks) return; ovpn_key_ctx_put(rcu_access_pointer(ks->encrypt)); ovpn_key_ctx_put(rcu_access_pointer(ks->decrypt)); + + if (ks->epoch_format) { + if (ks->epoch_key_send.shash) + crypto_free_shash(ks->epoch_key_send.shash); + if (ks->epoch_key_recv.shash) + crypto_free_shash(ks->epoch_key_recv.shash); + ovpn_key_ctx_put(rcu_access_pointer(ks->retiring_key)); + for (i = 0; i < OVPN_EPOCH_FUTURE_KEYS_COUNT; i++) { + future = rcu_access_pointer(ks->future_tx_keys.keys[i]); + ovpn_key_ctx_put(future); + future = rcu_access_pointer(ks->future_rx_keys.keys[i]); + ovpn_key_ctx_put(future); + } + } + kfree(ks); } +static int ovpn_epoch_key_init(struct ovpn_epoch_key *epoch_key, + const struct ovpn_epoch_prk *prk) +{ + int ret; + + epoch_key->shash = ovpn_epoch_init_key(prk->key, prk->key_size); + if (IS_ERR(epoch_key->shash)) { + ret = PTR_ERR(epoch_key->shash); + epoch_key->shash = NULL; + return ret; + } + + /* epoch 0 is reserved for direct keys, so epoch keys start at 1 */ + epoch_key->epoch = 1; + epoch_key->cipher_key_len = prk->cipher_key_len; + + return 0; +} + struct ovpn_crypto_key_slot * ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) { @@ -173,12 +463,20 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) return ERR_PTR(-EOPNOTSUPP); } - if (kc->encrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE || - kc->decrypt.nonce_tail_size != OVPN_NONCE_TAIL_SIZE) + if (!kc->use_epoch_keys) { + if (kc->direct.encrypt.nonce_tail_size != + OVPN_NONCE_TAIL_SIZE || + kc->direct.decrypt.nonce_tail_size != + OVPN_NONCE_TAIL_SIZE) + return ERR_PTR(-EINVAL); + } else if (!kc->epoch.encrypt.cipher_key_len || + kc->epoch.encrypt.cipher_key_len > OVPN_EPOCH_PRK_SIZE || + kc->epoch.decrypt.cipher_key_len != + kc->epoch.encrypt.cipher_key_len) { return ERR_PTR(-EINVAL); - + } /* build the key slot */ - ks = kmalloc_obj(*ks); + ks = kzalloc(sizeof(*ks), GFP_KERNEL); if (!ks) return ERR_PTR(-ENOMEM); @@ -187,25 +485,71 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) kref_init(&ks->refcount); ks->key_id = kc->key_id; ks->cipher_alg = kc->cipher_alg; - ks->epoch_format = false; - ks->aad_size = OVPN_AEAD_DIRECT_AAD_SIZE; - ks->pktid_size = OVPN_NONCE_WIRE_SIZE; + ks->alg_name = alg_name; + ks->epoch_format = kc->use_epoch_keys; + ks->aad_size = kc->use_epoch_keys ? OVPN_AEAD_EPOCH_AAD_SIZE : + OVPN_AEAD_DIRECT_AAD_SIZE; + ks->pktid_size = kc->use_epoch_keys ? OVPN_EPOCH_NONCE_WIRE_SIZE : + OVPN_NONCE_WIRE_SIZE; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); - key = ovpn_key_ctx_new("encrypt", alg_name, &kc->encrypt, true); + if (kc->use_epoch_keys) { + /* tx and rx PRKs advance independently */ + ret = ovpn_epoch_key_init(&ks->epoch_key_send, + &kc->epoch.encrypt); + if (ret) + goto destroy_ks; + + INIT_WORK(&ks->tx_refill, ovpn_refill_future_buffer_tx); + spin_lock_init(&ks->tx_lock); + key = ovpn_key_ctx_create_epoch(true, alg_name, + &ks->epoch_key_send); + } else { + key = ovpn_key_ctx_create_direct(true, alg_name, + &kc->direct.encrypt); + } if (IS_ERR(key)) { ret = PTR_ERR(key); goto destroy_ks; } RCU_INIT_POINTER(ks->encrypt, key); - key = ovpn_key_ctx_new("decrypt", alg_name, &kc->decrypt, false); + if (kc->use_epoch_keys) { + ret = ovpn_epoch_key_init(&ks->epoch_key_recv, + &kc->epoch.decrypt); + if (ret) + goto destroy_ks; + + INIT_WORK(&ks->rx_refill, ovpn_refill_future_buffer_rx); + spin_lock_init(&ks->rx_lock); + key = ovpn_key_ctx_create_epoch(false, alg_name, + &ks->epoch_key_recv); + } else { + key = ovpn_key_ctx_create_direct(false, alg_name, + &kc->direct.decrypt); + } if (IS_ERR(key)) { ret = PTR_ERR(key); goto destroy_ks; } RCU_INIT_POINTER(ks->decrypt, key); + RCU_INIT_POINTER(ks->retiring_key, NULL); + if (kc->use_epoch_keys) { + /* prederive future keys outside the fast path */ + ret = ovpn_epoch_init_future_keys(true, alg_name, + &ks->epoch_key_send, + &ks->future_tx_keys); + if (ret) + goto destroy_ks; + + ret = ovpn_epoch_init_future_keys(false, alg_name, + &ks->epoch_key_recv, + &ks->future_rx_keys); + if (ret) + goto destroy_ks; + } + return ks; destroy_ks: diff --git a/drivers/net/ovpn/main.c b/drivers/net/ovpn/main.c index 9993c1dfe471..10f61377bd54 100644 --- a/drivers/net/ovpn/main.c +++ b/drivers/net/ovpn/main.c @@ -18,6 +18,7 @@ #include #include "ovpnpriv.h" +#include "crypto.h" #include "main.h" #include "netlink.h" #include "io.h" @@ -233,13 +234,20 @@ static struct rtnl_link_ops ovpn_link_ops = { static int __init ovpn_init(void) { - int err = rtnl_link_register(&ovpn_link_ops); + int err; + err = ovpn_crypto_workqueue_init(); if (err) { - pr_err("ovpn: can't register rtnl link ops: %d\n", err); + pr_err("ovpn: can't allocate workqueue: %d\n", err); return err; } + err = rtnl_link_register(&ovpn_link_ops); + if (err) { + pr_err("ovpn: can't register rtnl link ops: %d\n", err); + goto destroy_wq; + } + err = ovpn_nl_register(); if (err) { pr_err("ovpn: can't register netlink family: %d\n", err); @@ -252,6 +260,8 @@ static int __init ovpn_init(void) unreg_rtnl: rtnl_link_unregister(&ovpn_link_ops); +destroy_wq: + ovpn_crypto_workqueue_destroy(); return err; } @@ -259,6 +269,7 @@ static __exit void ovpn_cleanup(void) { ovpn_nl_unregister(); rtnl_link_unregister(&ovpn_link_ops); + ovpn_crypto_workqueue_destroy(); rcu_barrier(); } diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 83d81a468b5a..910e4e67a68a 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -888,7 +888,7 @@ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; - struct ovpn_peer_key_reset pkr; + struct ovpn_peer_key_reset pkr = {}; struct ovpn_peer *peer; u32 peer_id; int ret; @@ -923,12 +923,14 @@ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) pkr.key.cipher_alg = nla_get_u32(attrs[OVPN_A_KEYCONF_CIPHER_ALG]); ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_ENCRYPT_DIR], - pkr.key.cipher_alg, &pkr.key.encrypt); + pkr.key.cipher_alg, + &pkr.key.direct.encrypt); if (ret < 0) return ret; ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR], - pkr.key.cipher_alg, &pkr.key.decrypt); + pkr.key.cipher_alg, + &pkr.key.direct.decrypt); if (ret < 0) return ret; From patchwork Thu Jul 2 11:52:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5075 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp550972max; Thu, 2 Jul 2026 04:53:42 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RrhYEM0r3n9BH04mwKd2VxFY841vpX9TCpE1THn2QKFVoBiL2GmafbWWnNJOpg5FdBOAieg4b+B108=@openvpn.net X-Received: by 2002:a05:6871:4b0a:b0:43d:3092:cf06 with SMTP id 586e51a60fabf-44cb42e97a6mr3236544fac.2.1782993222038; Thu, 02 Jul 2026 04:53:42 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993222; cv=none; d=google.com; s=arc-20260327; b=OMFDQu+PA0MzKicz4HX7XPr2MxGAFgE0YpHuxWATrbhmy8bnNiWks9+ywL2e4EyRua Csv3zRlhY8Zt0tpBvNdmLows1HodcthqTEl9XVKdMeByFkEoO4sCVlL0sjADMnENWVy1 BrQ5w7vzPDGOGctK7EHGr3+FTr4404lHVaJjtZgOaJD0agr0QLzlca1BurQk7tvhsmfn 7vlo1VchRQ69CJgOW3rOCeqiQ0e0LFOHKVxLMbDxHa5oxEY+n7rJNpbnRXixWUuEGa/y Xvy1xwcl3ydO0VR94p62AicSgbN8R4v0SPxiuRHGI+CKZO92N7yPmmP1eRTlhE143HkA 34gA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=NKnTRjUp9nmZDUjoVExQebeck3sJQXME4WWZ12a7x+Y=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=ff7UGstL2lMKSXgLV8DnARspt/xdAnwXD76ckazIqzoD9wDIsCEfaZLnV+hCCdVVph JIRIMmvE5UPHPTObxtxn5NXZ+cHBtfLM8LupDnjPJ5PeGxFpuXk6VN7FNClCnKEzUREC FS3wuQdqEx7sTNI+Xz9jvgNQHtQYkzWt3nx1SnJS+wOl3o62+X3xqRvJTrG+TIfYINiU 6ph4eQRPwAl4LA+HIdZtqW/Ys2KZPB9V1YPn3OaRpS6610A5Pg7jTzNpYJZQN3pajNMe LgmYX6s40am8CGhb2yOqPcDrhGBq1ES6P9kUzscBJJ2a9XF210zEPakEbTbsKjtir2i4 v8Yw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=jxo9+6vW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=hiYA5vTz; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=QGow85iy; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=npBh7qnR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-44cbf26f58dsi2382389fac.352.2026.07.02.04.53.41 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:42 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=jxo9+6vW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=hiYA5vTz; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=QGow85iy; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=npBh7qnR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=NKnTRjUp9nmZDUjoVExQebeck3sJQXME4WWZ12a7x+Y=; b=jxo9+6vWSAq0IKniVbdB6JbmbM GTXg274AT1wlG9XD9UxFQr2uLGweeeT7WoLd9r0yKGU4OCgGYYjci6YlQAwv4PpPU6bDJZ+4QAHSA fcEjveHbUR6X4Z/xLW9FavN1NDGEd07mII0ktTVtWbth8GEsk5vZKjg8dMGlYN+VtH0s=; Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFz8-00008j-Vp; Thu, 02 Jul 2026 11:53:39 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz7-00008J-NE for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:38 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=as/bVIBWU0CDkor9k5JTEI3Q9zOtkGjeBHz21GZqZBY=; b=hiYA5vTz34VOxMbhFJpOyl0Ibs ftU4a41XH7vOyZYIbMZLlTtnyTBE39AImS0TPgSZm207W/1hs13QOiN3RkUuNz37tEIDfuJhJE9Ej jMO/x7WOkw9HySVUHpxWYxk36OxGdVkqvTYh8BulaHn2qg6vyFJGGNBL5GpgEydmMq/c=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=as/bVIBWU0CDkor9k5JTEI3Q9zOtkGjeBHz21GZqZBY=; b=QGow85iy8QTA3OA0yBnoxtKzHV N+gslWFzke6X1tolhe+9eJ9jRDuYfozJ66aMLF8BAsK8dhGBVvYg+Q+pyXkfDjf2jVO1jctbRl4R3 sIccEuPmACBVFR8xhmK5EiMBpbwmbo9zCkw/2lIruyoNz1PNdNf/5Zug0MRHg3twP49s=; Received: from mout-b-105.mailbox.org ([195.10.208.50]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz5-0004Kk-Kp for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:37 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-105.mailbox.org (Postfix) with ESMTPS id 4grZyq2nrkz9yTm; Thu, 2 Jul 2026 13:53:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993203; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=as/bVIBWU0CDkor9k5JTEI3Q9zOtkGjeBHz21GZqZBY=; b=npBh7qnRdj3dNXsPIaAfH9zqn502KgOP6hqITElxm1PeYJ19Ksmk2E00M6fIu2pWVIzIgC zQ9oiRUimOgWJlmjq3A90nLQ3wJVuYbBnJtWQRt9vPGVm7s2B75tc0HhgzC3WFB3G4RTkO gR8S4ftqma/q6cYcMDJp63uhVeobG/DSjmVxvnoR1HJ6lDyQecv5ic01KA2J78b1Rk1pqI 6n7CcI6kGo68wt8T573pHEBpMdfVeGRYIAVO4w7vg3tc0nFZopo+fk2eHeNeSS3SuTPqsZ fjc2RGOxyAl9Crit4NMU17ClzBuzDo6GxkjsX3Aw5BNezYvG6GsR5WAIWxV7Bg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:26 +0200 Message-ID: <2cc52c1ed08c310a554f800f4bf1bf0b3d78712f.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4grZyq2nrkz9yTm X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Teach the AEAD path to build and parse the epoch packet layout. Epoch packets carry the authentication tag at the tail and use the epoch-aware packet ID encoding. Direct packets keep the existing layout. Epoch keys are still not accepted through netlink, and key promotion is added separately. Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFz5-0004Kk-Kp Subject: [Openvpn-devel] [RFC ovpn net-next v2 11/14] ovpn: handle epoch AEAD packet format X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603900844173302 X-GMAIL-MSGID: 1869603900844173302 Teach the AEAD path to build and parse the epoch packet layout. Epoch packets carry the authentication tag at the tail and use the epoch-aware packet ID encoding. Direct packets keep the existing layout. Epoch keys are still not accepted through netlink, and key promotion is added separately. Signed-off-by: Ralf Lici --- Changes since v1 https://lore.kernel.org/openvpn-devel/6536c6ad31267534d8daefb08b44f904ba7112da.1782919654.git.ralf@mandelbit.com/ - Size AEAD scatterlists from the actual direct/epoch entry count and mark the end on the last populated entry, avoiding an empty trailing SG entry for the epoch tail-tag layout. drivers/net/ovpn/crypto.h | 2 + drivers/net/ovpn/crypto_aead.c | 275 +++++++++++++++++++++++---------- drivers/net/ovpn/crypto_key.c | 8 + drivers/net/ovpn/io.c | 28 +++- 4 files changed, 224 insertions(+), 89 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index d7b7e115c339..dba393ee7cde 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -81,6 +81,8 @@ struct ovpn_crypto_key_slot { bool epoch_format; unsigned int aad_size; unsigned int pktid_size; + unsigned int payload_offset; + unsigned int tail_tag_size; struct ovpn_epoch_key epoch_key_send; struct ovpn_epoch_key epoch_key_recv; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index cf0a3d861eb9..a1cf4c1f70c7 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -44,17 +44,12 @@ bool ovpn_aead_decrypt_failure_record(struct ovpn_key_ctx *key) &key->decrypt_failure_flags); } -static int ovpn_aead_encap_overhead(const struct ovpn_key_ctx *key) -{ - return OVPN_AEAD_DIRECT_AAD_SIZE + crypto_aead_authsize(key->tfm); -} - /** * ovpn_aead_crypto_tmp_size - compute the size of a temporary object containing * an AEAD request structure with extra space for SG * and IV. * @tfm: the AEAD cipher handle - * @nfrags: the number of fragments in the skb + * @sg_nents: the number of scatterlist entries * * This function calculates the size of a contiguous memory block that includes * the initialization vector (IV), the AEAD request, and an array of scatterlist @@ -66,7 +61,7 @@ static int ovpn_aead_encap_overhead(const struct ovpn_key_ctx *key) * Return: the size of the temporary memory that needs to be allocated */ static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm, - const unsigned int nfrags) + const unsigned int sg_nents) { unsigned int len = OVPN_NONCE_SIZE; @@ -82,8 +77,8 @@ static unsigned int ovpn_aead_crypto_tmp_size(struct crypto_aead *tfm, /* round up to the next multiple of the scatterlist alignment */ len = ALIGN(len, __alignof__(struct scatterlist)); - /* add enough space for nfrags + 2 scatterlist entries */ - len += array_size(sizeof(struct scatterlist), nfrags + 2); + /* add enough space for the scatterlist entries */ + len += array_size(sizeof(struct scatterlist), sg_nents); return len; } @@ -150,11 +145,11 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead, int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - unsigned int plaintext_len, tag_size; + unsigned int plaintext_len, payload_sg_len, sg_nents, tag_size; struct ovpn_key_ctx *key = NULL; + u64 aead_blocks, pktid, seq; struct aead_request *req; struct sk_buff *trailer; - u64 aead_blocks, pktid; struct scatterlist *sg; bool pktid_notify; int nfrags, ret; @@ -166,6 +161,18 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, ovpn_skb_cb(skb)->ks = ks; plaintext_len = skb->len; + /* direct-key DATA_V2 wire format: + * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a... + * [ OP32 ] [seq # ] [ auth tag ] [ payload ... ] + * [4-byte + * IV head] + * + * epoch-key DATA_V2 wire format: + * 48000001 0001 000000000005 380275a... 7e7046bd 444a7e28 cc6387b1 64a4d6c1 + * [ OP32 ] [ epoch ][ seq # ] [ payload ... ] [ auth tag ] + * [ 8-byte packet ID ] + */ + ret = ovpn_key_ctx_get(&key, &ks->encrypt); if (unlikely(ret)) return ret; @@ -173,12 +180,19 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, tag_size = crypto_aead_authsize(key->tfm); - /* Sample AEAD header format: - * 48000001 00000005 7e7046bd 444a7e28 cc6387b1 64a4d6c1 380275a... - * [ OP32 ] [seq # ] [ auth tag ] [ payload ... ] - * [4-byte - * IV head] - */ + ret = ovpn_pktid_xmit_next(&key->pid.xmit, &seq); + if (unlikely(ret < 0)) + return ret; + pktid_notify = ret > 0; + + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, + plaintext_len); + ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, seq, + aead_blocks, pktid_notify); + if (unlikely(ret < 0)) + return ret; + if (unlikely(ret > 0 && !ks->epoch_format)) + ovpn_nl_key_swap_notify(peer, ks->key_id); /* check that there's enough headroom in the skb for packet * encapsulation @@ -186,16 +200,22 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, if (unlikely(skb_cow_head(skb, OVPN_HEAD_ROOM))) return -ENOBUFS; - /* get number of skb frags and ensure that packet data is writable */ - nfrags = skb_cow_data(skb, 0, &trailer); + /* ensure packet data is writable and epoch tag tailroom exists */ + nfrags = skb_cow_data(skb, ks->tail_tag_size, &trailer); if (unlikely(nfrags < 0)) return nfrags; - if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + sg_nents = nfrags + 1 + !ks->tail_tag_size; + if (unlikely(sg_nents > MAX_SKB_FRAGS + 2)) return -ENOSPC; + /* epoch packets place the authentication tag after payload */ + if (unlikely(ks->tail_tag_size)) + pskb_put(skb, trailer, ks->tail_tag_size); + payload_sg_len = plaintext_len + ks->tail_tag_size; + /* allocate temporary memory for iv, sg and req */ - tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, nfrags), + tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, sg_nents), GFP_ATOMIC); if (unlikely(!tmp)) return -ENOMEM; @@ -207,49 +227,34 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg = ovpn_aead_crypto_req_sg(key->tfm, req); /* sg table: - * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), - * 1, 2, 3, ..., n: payload, - * n+1: auth_tag (len=tag_size) + * 0: op, packet ID (AD, len=ks->aad_size), + * 1, 2, 3, ..., n: payload and epoch auth_tag, + * n+1: direct auth_tag (len=tag_size) */ - sg_init_table(sg, nfrags + 2); + sg_init_table(sg, sg_nents); /* build scatterlist to encrypt packet payload */ - ret = skb_to_sgvec_nomark(skb, sg + 1, 0, skb->len); + ret = skb_to_sgvec_nomark(skb, sg + 1, 0, payload_sg_len); if (unlikely(ret < 0)) { netdev_err(peer->ovpn->dev, "encrypt: cannot map skb to sg: %d\n", ret); return ret; } - /* append auth_tag onto scatterlist */ - __skb_push(skb, tag_size); - sg_set_buf(sg + ret + 1, skb->data, tag_size); - - /* obtain packet ID, which is used both as a first - * 4 bytes of nonce and last 4 bytes of associated data. - */ - ret = ovpn_pktid_xmit_next(&key->pid.xmit, &pktid); - if (unlikely(ret < 0)) - return ret; - pktid_notify = ret > 0; - - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, - plaintext_len); - ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, pktid, - aead_blocks, pktid_notify); - if (unlikely(ret < 0)) - return ret; - if (unlikely(ret > 0)) - ovpn_nl_key_swap_notify(peer, ks->key_id); + if (likely(!ks->tail_tag_size)) { + /* direct packets prepend the tag before payload */ + __skb_push(skb, tag_size); + sg_set_buf(sg + ret + 1, skb->data, tag_size); + } + sg_mark_end(sg + ret + !ks->tail_tag_size); - /* concat 4 bytes packet id and 8 bytes nonce tail into 12 bytes - * nonce - */ - pktid = ovpn_pktid_aead_write(0, pktid, key->implicit_iv, iv); + /* create the AEAD IV from packet ID and implicit IV */ + pktid = ovpn_pktid_aead_write(key->epoch, seq, key->implicit_iv, iv); /* make space for packet id and push it to the front */ __skb_push(skb, ks->pktid_size); - ovpn_pktid_wire_write(skb->data, false, pktid); + /* packet ID is 64 bits for epoch packets and 32 bits otherwise */ + ovpn_pktid_wire_write(skb->data, ks->epoch_format, pktid); /* add packet op as head of additional data */ op = ovpn_opcode_compose(OVPN_DATA_V2, ks->key_id, peer->tx_id); @@ -263,64 +268,163 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, /* setup async crypto operation */ aead_request_set_tfm(req, key->tfm); aead_request_set_callback(req, 0, ovpn_encrypt_post, skb); - aead_request_set_crypt(req, sg, sg, - skb->len - ovpn_aead_encap_overhead(key), iv); + aead_request_set_crypt(req, sg, sg, plaintext_len, iv); aead_request_set_ad(req, ks->aad_size); /* encrypt it */ return crypto_aead_encrypt(req); } +/** + * ovpn_aead_decrypt_key - get the decrypt key matching a packet epoch + * @ks: key slot containing RX epoch state + * @pkt_epoch: epoch decoded from the packet ID + * + * Direct-key packets must carry epoch 0. Epoch RX first tries the current key, + * then the retiring key for reordered packets, and finally the future ring. A + * future key is returned only for authentication; promotion happens after the + * packet has authenticated. + * + * Return: referenced key context or an error pointer. + */ +static struct ovpn_key_ctx * +ovpn_aead_decrypt_key(struct ovpn_crypto_key_slot *ks, u16 pkt_epoch) +{ + u16 current_epoch, epoch_diff, index; + struct ovpn_key_ctx *key; + + if (likely(!ks->epoch_format)) { + if (unlikely(pkt_epoch)) + return ERR_PTR(-EINVAL); + if (unlikely(ovpn_key_ctx_get(&key, &ks->decrypt))) + return ERR_PTR(-ENOKEY); + return key; + } + + spin_lock_bh(&ks->rx_lock); + + key = rcu_dereference_protected(ks->decrypt, + lockdep_is_held(&ks->rx_lock)); + if (unlikely(!key)) { + key = ERR_PTR(-ENOKEY); + goto out; + } + /* current key is the expected rx path */ + if (likely(key->epoch == pkt_epoch)) { + if (kref_get_unless_zero(&key->refcount)) + goto out; + key = ERR_PTR(-ENOKEY); + goto out; + } + current_epoch = key->epoch; + + key = rcu_dereference_protected(ks->retiring_key, + lockdep_is_held(&ks->rx_lock)); + /* retiring key accepts late packets from the previous epoch */ + if (unlikely(key && key->epoch == pkt_epoch)) { + if (kref_get_unless_zero(&key->refcount)) + goto out; + key = ERR_PTR(-ENOKEY); + goto out; + } + + if (unlikely(pkt_epoch < current_epoch)) { + key = ERR_PTR(-ERANGE); + goto out; + } + /* stay away from the epoch range where future refill would wrap */ + if (unlikely(pkt_epoch > + OVPN_MAX_EPOCH - OVPN_EPOCH_FUTURE_KEYS_COUNT - 1)) { + key = ERR_PTR(-ERANGE); + goto out; + } + + epoch_diff = pkt_epoch - current_epoch; + if (unlikely(!epoch_diff || + epoch_diff > OVPN_EPOCH_FUTURE_KEYS_COUNT)) { + key = ERR_PTR(-ERANGE); + goto out; + } + + /* future keys are consecutive starting at tail/current_epoch + 1 */ + index = (ks->future_rx_keys.tail + epoch_diff - 1) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + key = rcu_dereference_protected(ks->future_rx_keys.keys[index], + lockdep_is_held(&ks->rx_lock)); + if (unlikely(!key || key->epoch != pkt_epoch || + !kref_get_unless_zero(&key->refcount))) { + key = ERR_PTR(-ENOKEY); + goto out; + } + +out: + spin_unlock_bh(&ks->rx_lock); + + return key; +} + int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { - unsigned int payload_offset, tag_size; - struct ovpn_key_ctx *key = NULL; + unsigned int payload_offset, payload_sg_len, sg_nents, tag_size; int ret, payload_len, nfrags; + struct ovpn_key_ctx *key; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; + u16 pkt_epoch; + u64 pkt_seq; void *tmp; u8 *iv; + payload_offset = ks->payload_offset; + ovpn_skb_cb(skb)->payload_offset = payload_offset; ovpn_skb_cb(skb)->peer = peer; ovpn_skb_cb(skb)->ks = ks; - ret = ovpn_key_ctx_get(&key, &ks->decrypt); - if (unlikely(ret)) - return ret; - ovpn_skb_cb(skb)->key = key; - - tag_size = crypto_aead_authsize(key->tfm); - payload_offset = ovpn_aead_direct_payload_offset(tag_size); + if (unlikely(skb->len < payload_offset)) + return -EINVAL; payload_len = skb->len - payload_offset; - - ovpn_skb_cb(skb)->payload_offset = payload_offset; + if (unlikely(ks->tail_tag_size)) { + if (unlikely(payload_len < ks->tail_tag_size)) + return -EINVAL; + payload_len -= ks->tail_tag_size; + } /* sanity check on packet size, payload size must be >= 0 */ if (unlikely(payload_len < 0)) return -EINVAL; + /* make additional data contiguous for sg[0] */ + if (unlikely(!pskb_may_pull(skb, payload_offset))) + return -ENODATA; + + /* epoch packet ID includes both epoch and per-epoch counter */ + pkt_seq = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, + ks->epoch_format, &pkt_epoch); + key = ovpn_aead_decrypt_key(ks, pkt_epoch); + if (IS_ERR(key)) + return PTR_ERR(key); + ovpn_skb_cb(skb)->key = key; + if (unlikely(ovpn_aead_decrypt_failure_exceeded(key))) return -EKEYREJECTED; - /* Prepare the skb data buffer to be accessed up until the auth tag. - * This is required because this area is directly mapped into the sg - * list. - */ - if (unlikely(!pskb_may_pull(skb, payload_offset))) - return -ENODATA; + tag_size = crypto_aead_authsize(key->tfm); + /* epoch tag is at the tail and remains in the crypto input */ + payload_sg_len = payload_len + ks->tail_tag_size; /* get number of skb frags and ensure that packet data is writable */ nfrags = skb_cow_data(skb, 0, &trailer); if (unlikely(nfrags < 0)) return nfrags; - if (unlikely(nfrags + 2 > (MAX_SKB_FRAGS + 2))) + sg_nents = nfrags + 1 + !ks->tail_tag_size; + if (unlikely(sg_nents > MAX_SKB_FRAGS + 2)) return -ENOSPC; /* allocate temporary memory for iv, sg and req */ - tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, nfrags), + tmp = kmalloc(ovpn_aead_crypto_tmp_size(key->tfm, sg_nents), GFP_ATOMIC); if (unlikely(!tmp)) return -ENOMEM; @@ -332,31 +436,34 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, sg = ovpn_aead_crypto_req_sg(key->tfm, req); /* sg table: - * 0: op, wire nonce (AD, len=OVPN_AEAD_DIRECT_AAD_SIZE), - * 1, 2, 3, ..., n: payload, - * n+1: auth_tag (len=tag_size) + * 0: op, packet ID (AD, len=ks->aad_size), + * 1, 2, 3, ..., n: payload and epoch auth_tag, + * n+1: direct auth_tag (len=tag_size) */ - sg_init_table(sg, nfrags + 2); + sg_init_table(sg, sg_nents); /* packet op is head of additional data */ sg_set_buf(sg, skb->data, ks->aad_size); /* build scatterlist to decrypt packet payload */ - ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len); + ret = skb_to_sgvec_nomark(skb, sg + 1, payload_offset, + payload_sg_len); if (unlikely(ret < 0)) { netdev_err(peer->ovpn->dev, "decrypt: cannot map skb to sg: %d\n", ret); return ret; } - /* append auth_tag onto scatterlist */ - sg_set_buf(sg + ret + 1, skb->data + OVPN_AEAD_DIRECT_TAG_OFFSET, - tag_size); + if (likely(!ks->tail_tag_size)) { + /* direct tag is prepended; epoch tag is in payload sg */ + sg_set_buf(sg + ret + 1, + skb->data + OVPN_AEAD_DIRECT_TAG_OFFSET, + tag_size); + } + sg_mark_end(sg + ret + !ks->tail_tag_size); - /* copy nonce into IV buffer */ - memcpy(iv, ovpn_aead_direct_wire_nonce(skb), OVPN_NONCE_WIRE_SIZE); - memcpy(iv + OVPN_NONCE_WIRE_SIZE, - key->implicit_iv + OVPN_NONCE_WIRE_SIZE, OVPN_NONCE_TAIL_SIZE); + /* rebuild the AEAD IV from packet ID and implicit IV */ + ovpn_pktid_aead_write(pkt_epoch, pkt_seq, key->implicit_iv, iv); /* setup async crypto operation */ aead_request_set_tfm(req, key->tfm); diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 8626c4e2db60..a501b7e54962 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -447,6 +447,7 @@ struct ovpn_crypto_key_slot * ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) { struct ovpn_crypto_key_slot *ks = NULL; + unsigned int direct_payload_offset; struct ovpn_key_ctx *key; const char *alg_name; int ret; @@ -480,6 +481,9 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) if (!ks) return ERR_PTR(-ENOMEM); + direct_payload_offset = + ovpn_aead_direct_payload_offset(OVPN_AEAD_TAG_SIZE); + ks->encrypt = NULL; ks->decrypt = NULL; kref_init(&ks->refcount); @@ -491,6 +495,10 @@ ovpn_crypto_key_slot_new(const struct ovpn_key_config *kc) OVPN_AEAD_DIRECT_AAD_SIZE; ks->pktid_size = kc->use_epoch_keys ? OVPN_EPOCH_NONCE_WIRE_SIZE : OVPN_NONCE_WIRE_SIZE; + ks->payload_offset = kc->use_epoch_keys ? + OVPN_AEAD_EPOCH_AAD_SIZE : + direct_payload_offset; + ks->tail_tag_size = kc->use_epoch_keys ? OVPN_AEAD_TAG_SIZE : 0; ovpn_key_usage_limit_init(&ks->usage_limit, kc->cipher_alg); if (kc->use_epoch_keys) { diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index f5198d5bf104..925d3798d95d 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -114,6 +114,7 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_key_ctx *key; u64 aead_blocks, pktid; struct ovpn_peer *peer; + int payload_len; u16 pkt_epoch; __be16 proto; @@ -132,7 +133,8 @@ void ovpn_decrypt_post(void *data, int ret) kfree(ovpn_skb_cb(skb)->crypto_tmp); if (unlikely(ret == -EBADMSG)) { - if (key && unlikely(ovpn_aead_decrypt_failure_record(key))) + if (key && unlikely(ovpn_aead_decrypt_failure_record(key)) && + likely(!ks->epoch_format)) ovpn_nl_key_swap_notify(peer, ks->key_id); goto drop; } @@ -140,8 +142,8 @@ void ovpn_decrypt_post(void *data, int ret) if (unlikely(ret < 0)) goto drop; - pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, false, - &pkt_epoch); + pktid = ovpn_pktid_read(skb->data + OVPN_OPCODE_SIZE, + ks->epoch_format, &pkt_epoch); ret = ovpn_pktid_recv(&key->pid.recv, pktid, 0); if (unlikely(ret < 0)) { net_err_ratelimited("%s: PKT ID RX error for peer %u: %d\n", @@ -150,13 +152,29 @@ void ovpn_decrypt_post(void *data, int ret) goto drop; } + if (unlikely(skb->len < payload_offset)) + goto drop; + payload_len = skb->len - payload_offset; + if (unlikely(ks->tail_tag_size)) { + if (unlikely(payload_len < ks->tail_tag_size)) + goto drop; + payload_len -= ks->tail_tag_size; + } + if (unlikely(payload_len < 0)) + goto drop; + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, - skb->len - payload_offset); + payload_len); if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, &ks->usage_limit, - aead_blocks))) + aead_blocks) && + !ks->epoch_format)) ovpn_nl_key_swap_notify(peer, ks->key_id); + if (unlikely(ks->tail_tag_size && + pskb_trim(skb, skb->len - ks->tail_tag_size))) + goto drop; + /* keep track of last received authenticated packet for keepalive */ WRITE_ONCE(peer->last_recv, ktime_get_real_seconds()); From patchwork Thu Jul 2 11:52:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5078 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp551029max; Thu, 2 Jul 2026 04:53:46 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8F70hE+jESgjbjQGry3+IIlnkzPIcZ+uq/cwmA7vhy8ezwg1JiUzMfV62vPGaMrhljV/NeLTn3lsE=@openvpn.net X-Received: by 2002:a05:6830:81c6:b0:7e6:f2ef:e232 with SMTP id 46e09a7af769-7eb4cc0dc7cmr3309856a34.19.1782993225905; Thu, 02 Jul 2026 04:53:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993225; cv=none; d=google.com; s=arc-20260327; b=dgcb92AOKv/VwgrsdnMF2dMtCLvOlhbxRulDygSJi+gFnuVtJgitOFJpHpA1poYCmR fd0es3hW1RTaMwxeGoF+BKeGxOsGZX1sTpwTehvUwBed6JWDATyYV6PQumbfw2Q4Czd3 c1DNbnWJmPzH/xhUVWhfL2WLHB5KoCj+NVgzDT4WxaDfRkRDMghPZ4WxuGOvylXYSs1l dU5gH1elk6ZjBIYei1NYCklLXzc59Q/IW3g9SYhp3LuYNU/2tZiDPR5kIsybfY71l1zD LoG4E44ZxZYVypBVswcAqFlyeAu61NHRu+qbgJUUwuZg5UkqQGFa0s0qa1z2nvbP9AoS V1kg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=cUBo/9xnyQnPDladQlYrpUtttdTmvCQHzuNJrecfnzM=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=WKhKNlk20y0ppK84lc2hJMG2gQXLeiwDMsRM5iUG7sjhcsNp/BNQZFrp559ZJuyMyX BAEH2eE67rq7R2Ni0XWahissvT1Npsf24O9sDdE62cxPvlhtUyGRx6v2rbkRtK6EqJdy NGhcee17m9AVCS+jrSZJkrR3ZitL5rQgfPXnElEhFED5m8mW8UKfZwQTAipVesrK7U50 rOu6uhBEoeteDtHsW3M+AHP1r7X/mSnqTMEhEeetjwGP6QdIveBLv5nhptI13S0wxcxb uudl0YYijhvQuAgQo448Iu+ymkLS7aARgQXJLcT0L5nwNdcYPEbWi4jxkpREkrgFJnh6 x0QA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ZlqtUc5R; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=K2Stauzu; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=NZ9KDRPm; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=mk8bmyY+; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-7eb544f400asi2313193a34.90.2026.07.02.04.53.45 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:45 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=ZlqtUc5R; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=K2Stauzu; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=NZ9KDRPm; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=mk8bmyY+; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=cUBo/9xnyQnPDladQlYrpUtttdTmvCQHzuNJrecfnzM=; b=ZlqtUc5RBpV9+zrsyoSp3kDVTT KfQ/GeWlA4weOONl/hCtceaLXVZRIk4LQHewMhEBcCufAWdcrmccFRl33hLK+ZEWL8BuECGMRCUn7 9yWTKLYGxry4k/WRpSe2jVDiFvibEYTAGeNS6vO1UnbziLaETtRk5VRwE7YRBskn5cmg=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFzF-0007vl-K8; Thu, 02 Jul 2026 11:53:43 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFzD-0007vI-Gt for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:41 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=coMxIArmtwOn9Wd4j5wVHLUR+b6CHwqBCCDDM82x/7I=; b=K2StauzukwMbgPqLXYUezmLD8T T34S0dZRab49TgBaU9eKKFoshpogcdtKcktpmqHJDlqGgA0Go+Nclovb7YttTId0obvozDPa63wPs rPCg2Rvyk2ksSkhNwm1+i7/7fmqUz+rcc0HOjzb5bNCAT2/MEPrHcdttSYY9SkV2tF6g=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=coMxIArmtwOn9Wd4j5wVHLUR+b6CHwqBCCDDM82x/7I=; b=NZ9KDRPmjQ6gGlilQxGwry1UnV DZHpblJYr3ba90ZWubl2UDlZoAP0SJya9aXJVXSFkyLlJxItNBcDWNIfRSxTKBSO9Jv+zt/fojwC0 QpPIvVijljunS2nIiGTx5DgfxLFHcjzRlt4SDjoThK6/DTrzSO0RZ74lZ3sBcJneyRG4=; Received: from mout-b-202.mailbox.org ([195.10.208.62]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz8-0004L2-NH for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:40 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-202.mailbox.org (Postfix) with ESMTPS id 4grZyr6P5zzDs3k; Thu, 2 Jul 2026 13:53:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993204; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=coMxIArmtwOn9Wd4j5wVHLUR+b6CHwqBCCDDM82x/7I=; b=mk8bmyY+6umK7GUSuLlOeZ56HIL6Cm08taTg2teJB3s/+HTgBzA+/58xMIKQQZqh2RE6D2 /w6iUENWGnCvgeHfIwg5QVOFEs/GXUGMG8Zp1FWpFaqUggff9YiDZKlydJEyga+A/J5zAy XRr1iPfsYP7xa0Ht4rU/7QihAxGdwllNciE/bjX1h/KHTrGegQr80eHPeo5KAE45Mblm/w 0v6MEtIqh+/NpenarhGzSlO3C8o5dVVOXDLDKr/9p2j2To6ELwtcfeXdsSDp4LKAHFltiO cdcTA68pysIwTm4mOMZrZij6V0vJ3dnFYbiFt3N63X6O+JzxgDUZ0pfTcHo7JQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:27 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Epoch keys derive concrete AEAD keys for individual epochs. TX must move to a fresh epoch before the current sequence space or AEAD usage budget is exhausted; otherwise it would either reuse nonces or [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wfFz8-0004L2-NH Subject: [Openvpn-devel] [RFC ovpn net-next v2 12/14] ovpn: rotate epoch data keys X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603904893090155 X-GMAIL-MSGID: 1869603904893090155 Epoch keys derive concrete AEAD keys for individual epochs. TX must move to a fresh epoch before the current sequence space or AEAD usage budget is exhausted; otherwise it would either reuse nonces or exceed the cipher usage limit. Add TX promotion from the prederived future-key ring and retry encryption with the new key when rotation succeeds. Required rotations stop the packet if refill cannot provide the next key. Optional rotations are requested when TX crosses the soft AEAD limit or RX asks the peer to leave an exhausted or failing current decrypt epoch; the pending request is kept on the slot so TX can retry without sampling RX state on every packet. On RX, promote to a future key only after a packet from that epoch has authenticated. Keep the closest previous epoch as retiring key so reordered packets can still be accepted after multi-epoch jumps, and refill consumed future-key slots in workqueue context. When RX observes that the peer has moved forward, advance local TX as well so outbound packets signal the peer to leave the old epoch. Signed-off-by: Ralf Lici --- Changes since v1 https://lore.kernel.org/openvpn-devel/52f95fd94d4093c784820ecd46e28d0cb1784916.1782919654.git.ralf@mandelbit.com/ - Hoist loop-local declarations to function scope and clarify the comment explaining why the TX rotate-request clear race is harmless (no functional change). drivers/net/ovpn/crypto.h | 4 + drivers/net/ovpn/crypto_aead.c | 181 ++++++++++++++++++++++++++-- drivers/net/ovpn/crypto_aead.h | 3 + drivers/net/ovpn/crypto_key.c | 9 +- drivers/net/ovpn/io.c | 214 +++++++++++++++++++++++++++++++-- drivers/net/ovpn/pktid.h | 6 +- 6 files changed, 392 insertions(+), 25 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index dba393ee7cde..94ecb507d737 100644 --- a/drivers/net/ovpn/crypto.h +++ b/drivers/net/ovpn/crypto.h @@ -10,6 +10,7 @@ #ifndef _NET_OVPN_OVPNCRYPTO_H_ #define _NET_OVPN_OVPNCRYPTO_H_ +#include #include #include #include @@ -57,6 +58,8 @@ struct ovpn_peer_key_reset { struct ovpn_key_config key; }; +#define OVPN_CRYPTO_TX_ROTATE_PENDING 0 + /* state for one concrete AEAD key direction */ struct ovpn_key_ctx { u16 epoch; @@ -83,6 +86,7 @@ struct ovpn_crypto_key_slot { unsigned int pktid_size; unsigned int payload_offset; unsigned int tail_tag_size; + unsigned long flags; struct ovpn_epoch_key epoch_key_send; struct ovpn_epoch_key epoch_key_recv; diff --git a/drivers/net/ovpn/crypto_aead.c b/drivers/net/ovpn/crypto_aead.c index a1cf4c1f70c7..fed1f6b4807f 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -142,17 +142,170 @@ static struct scatterlist *ovpn_aead_crypto_req_sg(struct crypto_aead *aead, __alignof__(struct scatterlist)); } +/** + * ovpn_advance_encrypt_key - promote TX to a target epoch + * @ks: key slot containing TX epoch state + * @target_epoch: epoch to promote TX to + * @required: whether the caller must stop if promotion cannot complete + * + * TX promotion consumes prederived future keys up to @target_epoch. If another + * context is already updating TX state or refill has not caught up, optional + * promotions keep using the current key while required promotions fail in the + * caller. + * + * Return: 0 on success, -EINPROGRESS if the TX lock is busy, -EALREADY if TX + * is already at or beyond @target_epoch, -ENOKEY if the future ring cannot + * provide @target_epoch, or another negative error code otherwise. + */ +int ovpn_advance_encrypt_key(struct ovpn_crypto_key_slot *ks, u16 target_epoch, + bool required) +{ + u16 stale_count = 0, advance, count, index, stale_index, i; + struct ovpn_key_ctx *stale[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + struct ovpn_key_ctx *old_encrypt, *new_encrypt, *future; + struct ovpn_key_ctx __rcu **slot; + bool lock_held; + + /* concurrent promotion or refill means another context is moving tx */ + if (unlikely(!spin_trylock_bh(&ks->tx_lock))) + return -EINPROGRESS; + + lock_held = lockdep_is_held(&ks->tx_lock); + old_encrypt = rcu_dereference_protected(ks->encrypt, + lock_held); + if (unlikely(old_encrypt->epoch >= target_epoch)) { + spin_unlock_bh(&ks->tx_lock); + return -EALREADY; + } + + count = ovpn_epoch_future_keys_count(&ks->future_tx_keys); + advance = target_epoch - old_encrypt->epoch; + /* refill can lag behind packet processing under pressure */ + if (unlikely(count < advance)) { + spin_unlock_bh(&ks->tx_lock); + ovpn_schedule_refill(ks, true); + return -ENOKEY; + } + + index = (ks->future_tx_keys.tail + advance - 1) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + new_encrypt = rcu_dereference_protected(ks->future_tx_keys.keys[index], + lock_held); + if (WARN_ON_ONCE(!new_encrypt || + new_encrypt->epoch != target_epoch)) { + spin_unlock_bh(&ks->tx_lock); + return -ENOKEY; + } + + for (i = 0; i < advance; i++) { + /* drop skipped future keys when advancing past them */ + stale_index = (ks->future_tx_keys.tail + i) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + slot = &ks->future_tx_keys.keys[stale_index]; + future = rcu_dereference_protected(*slot, lock_held); + if (future != new_encrypt) + stale[stale_count++] = future; + RCU_INIT_POINTER(*slot, NULL); + } + + /* each concrete key carries fresh packet-ID state */ + rcu_assign_pointer(ks->encrypt, new_encrypt); + ks->future_tx_keys.tail = (ks->future_tx_keys.tail + advance) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + ks->future_tx_keys.full = false; + spin_unlock_bh(&ks->tx_lock); + + ovpn_key_ctx_put(old_encrypt); + for (i = 0; i < stale_count; i++) + ovpn_key_ctx_put(stale[i]); + + ovpn_schedule_refill(ks, true); + + return 0; +} + +static int ovpn_aead_encrypt_next_seq(struct ovpn_peer *peer, + struct ovpn_crypto_key_slot *ks, + struct ovpn_key_ctx *encrypt, + unsigned int pkt_len, u64 *seq) +{ + bool required = false, pktid_notify; + u64 aead_blocks; + int ret; + + /* hard packet-ID exhaustion requires fresh key material */ + ret = ovpn_pktid_xmit_next(&encrypt->pid.xmit, seq); + if (unlikely(ret < 0)) { + if (!ks->epoch_format) + return ret; + required = true; + goto new_key; + } + pktid_notify = ret > 0; + + aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, + pkt_len); + ret = ovpn_key_usage_xmit(&encrypt->usage, &ks->usage_limit, *seq, + aead_blocks, pktid_notify); + if (unlikely(ret < 0)) { + /* epoch keys rotate internally instead of asking userspace */ + if (!ks->epoch_format) + return ret; + required = true; + goto new_key; + } + if (unlikely(ret > 0)) { + if (!ks->epoch_format) { + ovpn_nl_key_swap_notify(peer, ks->key_id); + return 0; + } + /* epoch tx rotates locally when the soft limit is crossed */ + set_bit(OVPN_CRYPTO_TX_ROTATE_PENDING, &ks->flags); + } + + if (!ks->epoch_format) + return 0; + + /* rx can request a tx epoch bump without adding rx work to every + * tx packet + */ + if (unlikely(test_bit(OVPN_CRYPTO_TX_ROTATE_PENDING, &ks->flags))) + goto new_key; + + return 0; + +new_key: + /* keep enough epoch space for the future-key ring after promotion */ + if (unlikely(encrypt->epoch + 1 + OVPN_EPOCH_FUTURE_KEYS_COUNT >= + OVPN_MAX_EPOCH)) + return -ERANGE; + + ret = ovpn_advance_encrypt_key(ks, encrypt->epoch + 1, required); + if (unlikely(ret == -EINPROGRESS || ret == -ENOKEY)) + return required ? -EBUSY : 0; + if (unlikely(ret < 0 && ret != -EALREADY)) + return ret; + + /* A concurrent soft-limit request can theoretically be lost here, + * but it would require the new tx epoch to consume its soft limit + * before this CPU clears the bit. Hard limits still force rotation. + */ + clear_bit(OVPN_CRYPTO_TX_ROTATE_PENDING, &ks->flags); + + return -EAGAIN; +} + int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb) { unsigned int plaintext_len, payload_sg_len, sg_nents, tag_size; struct ovpn_key_ctx *key = NULL; - u64 aead_blocks, pktid, seq; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; - bool pktid_notify; + bool retried = false; int nfrags, ret; + u64 pktid, seq; void *tmp; u32 op; u8 *iv; @@ -173,6 +326,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, * [ 8-byte packet ID ] */ +retry: ret = ovpn_key_ctx_get(&key, &ks->encrypt); if (unlikely(ret)) return ret; @@ -180,19 +334,20 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, tag_size = crypto_aead_authsize(key->tfm); - ret = ovpn_pktid_xmit_next(&key->pid.xmit, &seq); - if (unlikely(ret < 0)) - return ret; - pktid_notify = ret > 0; - - aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, - plaintext_len); - ret = ovpn_key_usage_xmit(&key->usage, &ks->usage_limit, seq, - aead_blocks, pktid_notify); + ret = ovpn_aead_encrypt_next_seq(peer, ks, key, plaintext_len, &seq); + if (unlikely(ret == -EAGAIN)) { + if (retried) + return -EBUSY; + + /* retry once after epoch promotion */ + ovpn_skb_cb(skb)->key = NULL; + ovpn_key_ctx_put(key); + key = NULL; + retried = true; + goto retry; + } if (unlikely(ret < 0)) return ret; - if (unlikely(ret > 0 && !ks->epoch_format)) - ovpn_nl_key_swap_notify(peer, ks->key_id); /* check that there's enough headroom in the skb for packet * encapsulation diff --git a/drivers/net/ovpn/crypto_aead.h b/drivers/net/ovpn/crypto_aead.h index 4f83a3aa37fd..c2c2fbd72cfa 100644 --- a/drivers/net/ovpn/crypto_aead.h +++ b/drivers/net/ovpn/crypto_aead.h @@ -15,6 +15,9 @@ #include #include +int ovpn_advance_encrypt_key(struct ovpn_crypto_key_slot *ks, u16 target_epoch, + bool required); + int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *skb); int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index a501b7e54962..68ebf16c66c8 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -121,7 +121,8 @@ void ovpn_key_ctx_release(struct kref *kref) static struct ovpn_key_ctx * ovpn_key_ctx_new(const char *title, const char *alg_name, const u8 *cipher_key, unsigned int cipher_key_len, - const u8 *implicit_iv, u16 epoch, bool encrypt) + const u8 *implicit_iv, u16 epoch, bool epoch_format, + bool encrypt) { struct ovpn_limit pktid_limit; struct ovpn_key_ctx *key; @@ -151,7 +152,7 @@ ovpn_key_ctx_new(const char *title, const char *alg_name, /* initialize only the packet ID direction this context owns */ if (encrypt) { - ovpn_pktid_xmit_limit_init(&pktid_limit, false); + ovpn_pktid_xmit_limit_init(&pktid_limit, epoch_format); ovpn_pktid_xmit_init(&key->pid.xmit, &pktid_limit); } else { ovpn_pktid_recv_init(&key->pid.recv); @@ -175,7 +176,7 @@ ovpn_key_ctx_create_direct(bool encrypt, const char *alg_name, key = ovpn_key_ctx_new(encrypt ? "encrypt" : "decrypt", alg_name, dir->cipher_key, dir->cipher_key_size, - implicit_iv, 0, encrypt); + implicit_iv, 0, false, encrypt); memzero_explicit(implicit_iv, sizeof(implicit_iv)); return key; @@ -197,7 +198,7 @@ ovpn_key_ctx_create_epoch(bool encrypt, const char *alg_name, key = ovpn_key_ctx_new(encrypt ? "encrypt" : "decrypt", alg_name, cipher_key, epoch_key->cipher_key_len, - implicit_iv, epoch_key->epoch, encrypt); + implicit_iv, epoch_key->epoch, true, encrypt); out: memzero_explicit(cipher_key, sizeof(cipher_key)); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index 925d3798d95d..103baf3714eb 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -105,10 +105,199 @@ static void ovpn_netdev_write(struct ovpn_peer *peer, struct sk_buff *skb) local_bh_enable(); } +static void ovpn_rx_request_tx_rotation(struct ovpn_crypto_key_slot *ks, + const struct ovpn_key_ctx *decrypt) +{ + struct ovpn_key_ctx *current_decrypt, *encrypt; + + if (unlikely(!ks->epoch_format)) + return; + + rcu_read_lock(); + current_decrypt = rcu_dereference(ks->decrypt); + encrypt = rcu_dereference(ks->encrypt); + /* only current rx key usage can ask the peer to move forward */ + if (decrypt == current_decrypt && + (!encrypt || decrypt->epoch >= encrypt->epoch)) + set_bit(OVPN_CRYPTO_TX_ROTATE_PENDING, &ks->flags); + rcu_read_unlock(); +} + +/** + * ovpn_advance_decrypt_key - promote RX to a target epoch + * @ks: key slot containing RX epoch state + * @target_epoch: authenticated epoch to promote RX to + * + * RX promotion consumes future keys up to @target_epoch, moves the closest + * previous epoch to the retiring slot for reordered packets, and schedules + * refill for the consumed future-key slots. Local TX is advanced as well so + * outbound packets signal the peer to leave older epochs. + * + * Return: 0 on success, -EINPROGRESS if the RX lock is busy, -EALREADY if RX + * is already at or beyond @target_epoch, or -EINVAL if the future ring cannot + * provide @target_epoch. + */ +static int ovpn_advance_decrypt_key(struct ovpn_crypto_key_slot *ks, + u16 target_epoch) +{ + u16 stale_count = 0, advance, count, index, stale_index, i; + struct ovpn_key_ctx *stale[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + struct ovpn_key_ctx *old_decrypt, *new_decrypt, *future; + struct ovpn_key_ctx *old_retiring, *new_retiring; + struct ovpn_key_ctx __rcu **retire_slot, **slot; + u16 retire_index; + bool lock_held; + int ret; + + /* concurrent promotion or refill means another context is moving rx */ + if (unlikely(!spin_trylock_bh(&ks->rx_lock))) + return -EINPROGRESS; + + lock_held = lockdep_is_held(&ks->rx_lock); + old_retiring = rcu_dereference_protected(ks->retiring_key, + lock_held); + old_decrypt = rcu_dereference_protected(ks->decrypt, + lock_held); + /* current decrypt key is always installed while the slot is alive */ + if (unlikely(target_epoch <= old_decrypt->epoch)) { + spin_unlock_bh(&ks->rx_lock); + return -EALREADY; + } + + count = ovpn_epoch_future_keys_count(&ks->future_rx_keys); + advance = target_epoch - old_decrypt->epoch; + /* authenticated epoch must be available in the future ring */ + if (unlikely(count < advance)) { + spin_unlock_bh(&ks->rx_lock); + return -EINVAL; + } + + /* get the authenticated future key from the ring */ + index = (ks->future_rx_keys.tail + advance - 1) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + new_decrypt = rcu_dereference_protected(ks->future_rx_keys.keys[index], + lock_held); + if (WARN_ON_ONCE(!new_decrypt || + new_decrypt->epoch != target_epoch)) { + spin_unlock_bh(&ks->rx_lock); + return -EINVAL; + } + + new_retiring = old_decrypt; + if (unlikely(advance > 1)) { + /* keep target_epoch - 1 for reordered packets after jumps */ + retire_index = (ks->future_rx_keys.tail + advance - 2) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + retire_slot = &ks->future_rx_keys.keys[retire_index]; + new_retiring = rcu_dereference_protected(*retire_slot, + lock_held); + if (WARN_ON_ONCE(!new_retiring || + new_retiring->epoch != target_epoch - 1)) { + spin_unlock_bh(&ks->rx_lock); + return -EINVAL; + } + } + + for (i = 0; i < advance; i++) { + /* drop skipped future keys when advancing past them */ + stale_index = (ks->future_rx_keys.tail + i) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + slot = &ks->future_rx_keys.keys[stale_index]; + future = rcu_dereference_protected(*slot, lock_held); + if (future != new_decrypt && future != new_retiring) + stale[stale_count++] = future; + RCU_INIT_POINTER(*slot, NULL); + } + + /* keep the nearest previous rx key for reordered packets */ + rcu_assign_pointer(ks->retiring_key, new_retiring); + rcu_assign_pointer(ks->decrypt, new_decrypt); + ks->future_rx_keys.tail = (ks->future_rx_keys.tail + advance) % + OVPN_EPOCH_FUTURE_KEYS_COUNT; + ks->future_rx_keys.full = false; + spin_unlock_bh(&ks->rx_lock); + + ovpn_schedule_refill(ks, false); + ovpn_key_ctx_put(old_retiring); + if (new_retiring != old_decrypt) + ovpn_key_ctx_put(old_decrypt); + for (i = 0; i < stale_count; i++) + ovpn_key_ctx_put(stale[i]); + + /* move local tx forward to signal the peer to advance as well */ + ret = ovpn_advance_encrypt_key(ks, target_epoch, false); + /* A concurrent rx request can theoretically be lost here, but rx uses + * this bit as a proactive peer-nudge signal. Hard limits still force + * rotation. + */ + if (!ret) + clear_bit(OVPN_CRYPTO_TX_ROTATE_PENDING, &ks->flags); + + return 0; +} + +/** + * ovpn_check_rotate_keys - promote RX after authenticating a future epoch + * @peer: peer that supplied the authenticated packet + * @ks: key slot containing RX epoch state + * @decrypt: key that authenticated the packet + * + * Current and retiring keys do not move RX state. A future key is promoted only + * after authentication has completed, which prevents unauthenticated packets + * from forcing epoch advancement. + * + * Return: 0 if no fatal rotation error occurred, -ERANGE if epoch space is + * exhausted and userspace must install fresh key material. + */ +static int ovpn_check_rotate_keys(struct ovpn_peer *peer, + struct ovpn_crypto_key_slot *ks, + struct ovpn_key_ctx *decrypt) +{ + struct ovpn_key_ctx *current_decrypt; + u16 target_epoch; + int ret; + + if (likely(!ks->epoch_format)) + return 0; + + /* current key means no promotion is needed */ + current_decrypt = rcu_access_pointer(ks->decrypt); + if (likely(decrypt == current_decrypt)) + return 0; + + /* take a stable reference to distinguish retiring from future keys */ + if (unlikely(ovpn_key_ctx_get(¤t_decrypt, &ks->decrypt))) + return 0; + /* retiring or older packets do not move rx forward */ + if (likely(decrypt->epoch <= current_decrypt->epoch)) { + ovpn_key_ctx_put(current_decrypt); + return 0; + } + ovpn_key_ctx_put(current_decrypt); + + target_epoch = decrypt->epoch; + /* keep enough epoch space for the future-key ring after promotion */ + if (unlikely(target_epoch + OVPN_EPOCH_FUTURE_KEYS_COUNT >= + OVPN_MAX_EPOCH)) + return -ERANGE; + + /* a future key is promoted only after successful authentication */ + ret = ovpn_advance_decrypt_key(ks, target_epoch); + if (unlikely(ret == -EALREADY || ret == -EINPROGRESS)) + return 0; + if (unlikely(ret)) + net_warn_ratelimited("%s: decrypt: cannot advance key to epoch %u\n", + netdev_name(peer->ovpn->dev), + target_epoch); + + return 0; +} + void ovpn_decrypt_post(void *data, int ret) { struct ovpn_crypto_key_slot *ks; unsigned int payload_offset = 0; + bool aead_notify, aead_hard; struct sk_buff *skb = data; struct ovpn_socket *sock; struct ovpn_key_ctx *key; @@ -133,9 +322,12 @@ void ovpn_decrypt_post(void *data, int ret) kfree(ovpn_skb_cb(skb)->crypto_tmp); if (unlikely(ret == -EBADMSG)) { - if (key && unlikely(ovpn_aead_decrypt_failure_record(key)) && - likely(!ks->epoch_format)) - ovpn_nl_key_swap_notify(peer, ks->key_id); + if (key && unlikely(ovpn_aead_decrypt_failure_record(key))) { + if (!ks->epoch_format) + ovpn_nl_key_swap_notify(peer, ks->key_id); + else + ovpn_rx_request_tx_rotation(ks, key); + } goto drop; } @@ -165,12 +357,20 @@ void ovpn_decrypt_post(void *data, int ret) aead_blocks = ovpn_aead_limit_blocks(ks->cipher_alg, ks->aad_size, payload_len); - if (unlikely(ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, - &ks->usage_limit, - aead_blocks) && - !ks->epoch_format)) + aead_notify = ovpn_pktid_recv_update_aead(&key->pid.recv, &key->usage, + &ks->usage_limit, aead_blocks, + &aead_hard); + if (unlikely(aead_hard && ks->epoch_format)) + ovpn_rx_request_tx_rotation(ks, key); + if (unlikely(aead_notify && !ks->epoch_format)) ovpn_nl_key_swap_notify(peer, ks->key_id); + if (unlikely(ovpn_check_rotate_keys(peer, ks, key) < 0)) { + if (ovpn_crypto_kill_key(&peer->crypto, ks->key_id)) + ovpn_nl_key_swap_notify(peer, ks->key_id); + goto drop; + } + if (unlikely(ks->tail_tag_size && pskb_trim(skb, skb->len - ks->tail_tag_size))) goto drop; diff --git a/drivers/net/ovpn/pktid.h b/drivers/net/ovpn/pktid.h index 62666017f051..9e6fb80e1f24 100644 --- a/drivers/net/ovpn/pktid.h +++ b/drivers/net/ovpn/pktid.h @@ -121,6 +121,7 @@ static inline int ovpn_pktid_xmit_next(struct ovpn_pktid_xmit *pid, u64 *pktid) * @usage: key usage state * @limit: key usage limits * @aead_blocks: AEAD usage blocks consumed by this packet + * @hard_exceeded: set to true if RX has crossed the hard usage limit * * RX AEAD accounting is only informational for the local userspace process. * The peer's packets that already passed authentication and replay checks are @@ -132,12 +133,15 @@ static inline bool ovpn_pktid_recv_update_aead(struct ovpn_pktid_recv *pr, struct ovpn_key_usage *usage, const struct ovpn_limit *limit, - u64 aead_blocks) + u64 aead_blocks, bool *hard_exceeded) { bool ret; spin_lock_bh(&pr->lock); ret = ovpn_key_usage_recv(usage, limit, pr->id, aead_blocks); + *hard_exceeded = + ovpn_key_usage_over_limit(limit->hard, pr->id, + atomic64_read(&usage->blocks)); spin_unlock_bh(&pr->lock); return ret; From patchwork Thu Jul 2 11:52:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5077 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp551021max; Thu, 2 Jul 2026 04:53:45 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+WDUXWrNGTqaLi0gs7Bf3DQw6u4pFEWDNdw+vpyE5aFdJeDUMXnDFeAd7a2pqGt6wpXcEn+xoYqSU=@openvpn.net X-Received: by 2002:a05:6830:f8a:b0:7d7:ed69:81b2 with SMTP id 46e09a7af769-7eb5031ac9emr3083028a34.5.1782993225557; Thu, 02 Jul 2026 04:53:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993225; cv=none; d=google.com; s=arc-20260327; b=C5pqeCr5MonLddrmSbTZZ2HWgyXZKokeod+wF9M7Kk7cgz6O7wfcb/12Uug98TvAiz bbDAdpVwy2A3/FZxMwxluMXX6e3beed2zsDSR0eBIE9cNH5DJOgqhjhC0m1loprtlFL1 0wukh+zYfJGg4x72qh7gmoCTjwWDWl5eMSIW78Ero0t/F5E/m8kYQjTwOVgbOnl+nq+a 3NIp1ePsQQIIC9XCSdCusXzq0D67d4y+O8A/a+adusA6V8KhrnoXu5FZx1E2ASbVAB8F LUGPEA4s/qYWsYuzce6KWr9zpB4+4Odufj6P0nl7BnwUyJQfTOGfge0G3uBSBluR8E+V jNcQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=1/NXujAr0ymKHEThJTzUk6yTyobgLYSbfWHs7Qcru5w=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=geUZK42JwwZwUWfrADk5HNJi0PgydUbtEL9YG5+NpxRsWu2brkWz8kwApDNMVZB2id +jHXI5POrE+fxelQVtFvYLSPdaq1VRfKcIZR4k4XBuCN9uhI5JGpGOg5H+HZOcCSOtLQ 8ZptP42SJRdGOorl967twJxkwzLGIf0LzCefY0IK3CkdcxgcLyIIlD7xyK2C+4xRBT1R jQdtL3P7w1CKmwfKmYiot5yrmLOhDyCfCIcCzi5eKLpmomQpvbfMCYmNw4DD4JvWLddg UGLXKLmRgoXzuOZ91AmCotQfVHPZr+13iNoF7hd+eUI6hO9KfYXDQ2ejPShiT6zThnGi 7czw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=BnFO8V+N; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=F1sprLDg; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=HuJrZDKl; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=NTVe8TYM; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 46e09a7af769-7eb542a194csi2362019a34.16.2026.07.02.04.53.45 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:45 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=BnFO8V+N; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=F1sprLDg; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=HuJrZDKl; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=NTVe8TYM; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=1/NXujAr0ymKHEThJTzUk6yTyobgLYSbfWHs7Qcru5w=; b=BnFO8V+NmdJlobacfwEW4irIjQ N0zPIi1q34hHHuxk58DYUIgSQinmFb4cuwhYKme7YEOodjQpKf6FKB8apkxjQTCBafJ6VoXapjurt YBp1u0iYiL+QcJQdFtVmshxIwohyg3WyLS8nrzemsM1uEzsb1+d0wgBfyBJMkBd8gCyI=; Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFzF-0007vY-8t; Thu, 02 Jul 2026 11:53:42 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFz9-0007ut-TL for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:37 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=i9VT7au5g+YVt0PcCsUHa5q7oedv8EEOpGeFqfUdqDI=; b=F1sprLDgYneO7ozErKZG3Oy7bU 7SsZyP7ZHxFscZoN3ZQNmiyXtGMKrwRj66xYB1P17RS28fJyATWPAzXxnHCQbZ+UObZ19RTdrRYJo PrrCw3a9On9IQ/VoS+tK5gL4xTlflhAVTH6u8cIv4XgseD/Dffg5943V0BO/qyT4nVwk=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=i9VT7au5g+YVt0PcCsUHa5q7oedv8EEOpGeFqfUdqDI=; b=HuJrZDKlDzELquRsP8HYALjKFP AdF+2Sdte7aHxxhqkGxRh5Ansbw4x3lVMxjMrExyem/ymk3+gxPqpzspE0KmCdKfi2iV24HxhUFB4 4oqR3r7ppmq7KG46v29GK3ZpLUzXOskkRaxjHwafAUYbnEc2dXp7iAwk/Daj6HQ/Kq0E=; Received: from mout-b-105.mailbox.org ([195.10.208.50]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFz4-0004Kd-Sv for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:36 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [10.196.197.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-105.mailbox.org (Postfix) with ESMTPS id 4grZyt2lrYz9yqN; Thu, 2 Jul 2026 13:53:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993206; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=i9VT7au5g+YVt0PcCsUHa5q7oedv8EEOpGeFqfUdqDI=; b=NTVe8TYMqvBKy5+aNCEaK/D5lciAKRoQeATaWf1mbvHnH8/iiBBPKwBztODsq98dQmndPW BV5/NsTJ0rZeOObIT1dHy0Hxa3sv/t7UbDBpGjxi+vvyu5QYfeJaoZTgp2wvdzq5SVdUqC GNaqTG3GVlO4XCbkyddalU+B2YI1cajRoXqdush6pXb6GIdlfNUhMKuttdDsLwt2Gg3LSn Z40vddZxVcPQwcKNMwtlI2YtiNknhEglJy49tBXeL0lfmVv8DiYddMVmih7vLmssN7Fm8g qqInk8IVy0NBNxgUwvAblFI22LCw5IJIIqGMnsQqt/BkzEU+jNHhv+tQkSQLTw== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:28 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Allow KEY_NEW to receive either direct key material or epoch PRK material. Userspace must provide exactly one key format: the existing encrypt/decrypt direction attributes select direct keys, while th [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFz4-0004Kd-Sv Subject: [Openvpn-devel] [RFC ovpn net-next v2 13/14] ovpn: accept epoch keys from netlink X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603904623144253 X-GMAIL-MSGID: 1869603904623144253 Allow KEY_NEW to receive either direct key material or epoch PRK material. Userspace must provide exactly one key format: the existing encrypt/decrypt direction attributes select direct keys, while the new epoch attributes select epoch-derived keys. Parse epoch key attributes into the common key configuration used by crypto slot setup. Direct key attributes keep the existing UAPI shape, so current userspace continues to install direct keys without any new format attribute. Signed-off-by: Ralf Lici --- No functional changes since v1 https://lore.kernel.org/openvpn-devel/6a100e535685abf00f2b9d3982eda44ea01a2512.1782919654.git.ralf@mandelbit.com/ Documentation/netlink/specs/ovpn.yaml | 31 ++++++++ drivers/net/ovpn/crypto_epoch.h | 1 - drivers/net/ovpn/netlink-gen.c | 9 ++- drivers/net/ovpn/netlink-gen.h | 3 +- drivers/net/ovpn/netlink.c | 107 ++++++++++++++++++++++---- include/uapi/linux/ovpn.h | 11 +++ 6 files changed, 144 insertions(+), 18 deletions(-) diff --git a/Documentation/netlink/specs/ovpn.yaml b/Documentation/netlink/specs/ovpn.yaml index b0c782e59a32..43b9ab1eb3da 100644 --- a/Documentation/netlink/specs/ovpn.yaml +++ b/Documentation/netlink/specs/ovpn.yaml @@ -16,6 +16,10 @@ definitions: type: const name: nonce-tail-size value: 8 + - + type: const + name: epoch-prk-size + value: 32 - type: enum name: cipher-alg @@ -274,6 +278,16 @@ attribute-sets: type: nest doc: Key material for decrypt direction nested-attributes: keydir + - + name: encrypt-epoch + type: nest + doc: Epoch PRK material used to derive encrypt data keys + nested-attributes: epoch + - + name: decrypt-epoch + type: nest + doc: Epoch PRK material used to derive decrypt data keys + nested-attributes: epoch - name: keydir attributes: @@ -291,6 +305,23 @@ attribute-sets: obtain the actual cipher IV checks: exact-len: nonce-tail-size + - + name: epoch + attributes: + - + name: key + type: binary + doc: >- + The epoch PRK used to derive cipher keys and implicit IVs + checks: + exact-len: epoch-prk-size + - + name: cipher-key-len + type: u32 + doc: Length in bytes of the AEAD cipher key derived from the epoch PRK + checks: + min: 1 + max: epoch-prk-size - name: keyconf-get diff --git a/drivers/net/ovpn/crypto_epoch.h b/drivers/net/ovpn/crypto_epoch.h index 1ffa6f55e729..0dce1e905917 100644 --- a/drivers/net/ovpn/crypto_epoch.h +++ b/drivers/net/ovpn/crypto_epoch.h @@ -16,7 +16,6 @@ #include #include -#define OVPN_EPOCH_PRK_SIZE 32 #define OVPN_MAX_EPOCH U16_MAX #define OVPN_EPOCH_FUTURE_KEYS_COUNT 16 diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c index 2147cec7c2c5..667a51f52158 100644 --- a/drivers/net/ovpn/netlink-gen.c +++ b/drivers/net/ovpn/netlink-gen.c @@ -25,13 +25,20 @@ static const struct netlink_range_validation ovpn_a_keyconf_peer_id_range = { }; /* Common nested types */ -const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1] = { +const struct nla_policy ovpn_epoch_nl_policy[OVPN_A_EPOCH_CIPHER_KEY_LEN + 1] = { + [OVPN_A_EPOCH_KEY] = NLA_POLICY_EXACT_LEN(OVPN_EPOCH_PRK_SIZE), + [OVPN_A_EPOCH_CIPHER_KEY_LEN] = NLA_POLICY_RANGE(NLA_U32, 1, OVPN_EPOCH_PRK_SIZE), +}; + +const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_EPOCH + 1] = { [OVPN_A_KEYCONF_PEER_ID] = NLA_POLICY_FULL_RANGE(NLA_U32, &ovpn_a_keyconf_peer_id_range), [OVPN_A_KEYCONF_SLOT] = NLA_POLICY_MAX(NLA_U32, 1), [OVPN_A_KEYCONF_KEY_ID] = NLA_POLICY_MAX(NLA_U32, 7), [OVPN_A_KEYCONF_CIPHER_ALG] = NLA_POLICY_MAX(NLA_U32, 2), [OVPN_A_KEYCONF_ENCRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy), [OVPN_A_KEYCONF_DECRYPT_DIR] = NLA_POLICY_NESTED(ovpn_keydir_nl_policy), + [OVPN_A_KEYCONF_ENCRYPT_EPOCH] = NLA_POLICY_NESTED(ovpn_epoch_nl_policy), + [OVPN_A_KEYCONF_DECRYPT_EPOCH] = NLA_POLICY_NESTED(ovpn_epoch_nl_policy), }; const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1] = { diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h index 67cd85f86173..72f765f574a5 100644 --- a/drivers/net/ovpn/netlink-gen.h +++ b/drivers/net/ovpn/netlink-gen.h @@ -13,7 +13,8 @@ #include /* Common nested types */ -extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_DIR + 1]; +extern const struct nla_policy ovpn_epoch_nl_policy[OVPN_A_EPOCH_CIPHER_KEY_LEN + 1]; +extern const struct nla_policy ovpn_keyconf_nl_policy[OVPN_A_KEYCONF_DECRYPT_EPOCH + 1]; extern const struct nla_policy ovpn_keyconf_del_input_nl_policy[OVPN_A_KEYCONF_SLOT + 1]; extern const struct nla_policy ovpn_keyconf_get_nl_policy[OVPN_A_KEYCONF_CIPHER_ALG + 1]; extern const struct nla_policy ovpn_keyconf_swap_input_nl_policy[OVPN_A_KEYCONF_PEER_ID + 1]; diff --git a/drivers/net/ovpn/netlink.c b/drivers/net/ovpn/netlink.c index 910e4e67a68a..4f04a5976f33 100644 --- a/drivers/net/ovpn/netlink.c +++ b/drivers/net/ovpn/netlink.c @@ -859,6 +859,40 @@ static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key, return 0; } +static int ovpn_nl_get_epoch_key(struct genl_info *info, struct nlattr *key, + enum ovpn_cipher_alg cipher, + struct ovpn_epoch_prk *prk) +{ + struct nlattr *attrs[OVPN_A_EPOCH_MAX + 1]; + int ret; + + ret = nla_parse_nested(attrs, OVPN_A_EPOCH_MAX, key, + ovpn_epoch_nl_policy, info->extack); + if (ret) + return ret; + + switch (cipher) { + case OVPN_CIPHER_ALG_AES_GCM: + case OVPN_CIPHER_ALG_CHACHA20_POLY1305: + if (NL_REQ_ATTR_CHECK(info->extack, key, attrs, + OVPN_A_EPOCH_KEY) || + NL_REQ_ATTR_CHECK(info->extack, key, attrs, + OVPN_A_EPOCH_CIPHER_KEY_LEN)) + return -EINVAL; + + prk->key = nla_data(attrs[OVPN_A_EPOCH_KEY]); + prk->key_size = nla_len(attrs[OVPN_A_EPOCH_KEY]); + prk->cipher_key_len = + nla_get_u32(attrs[OVPN_A_EPOCH_CIPHER_KEY_LEN]); + break; + default: + NL_SET_ERR_MSG_MOD(info->extack, "unsupported cipher"); + return -EINVAL; + } + + return 0; +} + /** * ovpn_nl_key_new_doit - configure a new key for the specified peer * @skb: incoming netlink message @@ -889,6 +923,7 @@ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) struct nlattr *attrs[OVPN_A_KEYCONF_MAX + 1]; struct ovpn_priv *ovpn = info->user_ptr[0]; struct ovpn_peer_key_reset pkr = {}; + bool direct_key, epoch_key; struct ovpn_peer *peer; u32 peer_id; int ret; @@ -911,28 +946,70 @@ int ovpn_nl_key_new_doit(struct sk_buff *skb, struct genl_info *info) NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, OVPN_A_KEYCONF_KEY_ID) || NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, - OVPN_A_KEYCONF_CIPHER_ALG) || - NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, - OVPN_A_KEYCONF_ENCRYPT_DIR) || - NL_REQ_ATTR_CHECK(info->extack, info->attrs[OVPN_A_KEYCONF], attrs, - OVPN_A_KEYCONF_DECRYPT_DIR)) + OVPN_A_KEYCONF_CIPHER_ALG)) return -EINVAL; pkr.slot = nla_get_u32(attrs[OVPN_A_KEYCONF_SLOT]); pkr.key.key_id = nla_get_u32(attrs[OVPN_A_KEYCONF_KEY_ID]); pkr.key.cipher_alg = nla_get_u32(attrs[OVPN_A_KEYCONF_CIPHER_ALG]); - ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_ENCRYPT_DIR], - pkr.key.cipher_alg, - &pkr.key.direct.encrypt); - if (ret < 0) - return ret; + direct_key = attrs[OVPN_A_KEYCONF_ENCRYPT_DIR] || + attrs[OVPN_A_KEYCONF_DECRYPT_DIR]; + epoch_key = attrs[OVPN_A_KEYCONF_ENCRYPT_EPOCH] || + attrs[OVPN_A_KEYCONF_DECRYPT_EPOCH]; + if (direct_key == epoch_key) { + NL_SET_ERR_MSG_MOD(info->extack, + "specify exactly one key format"); + return -EINVAL; + } - ret = ovpn_nl_get_key_dir(info, attrs[OVPN_A_KEYCONF_DECRYPT_DIR], - pkr.key.cipher_alg, - &pkr.key.direct.decrypt); - if (ret < 0) - return ret; + if (direct_key) { + if (NL_REQ_ATTR_CHECK(info->extack, + info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_ENCRYPT_DIR) || + NL_REQ_ATTR_CHECK(info->extack, + info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_DECRYPT_DIR)) + return -EINVAL; + + pkr.key.use_epoch_keys = false; + ret = ovpn_nl_get_key_dir(info, + attrs[OVPN_A_KEYCONF_ENCRYPT_DIR], + pkr.key.cipher_alg, + &pkr.key.direct.encrypt); + if (ret < 0) + return ret; + + ret = ovpn_nl_get_key_dir(info, + attrs[OVPN_A_KEYCONF_DECRYPT_DIR], + pkr.key.cipher_alg, + &pkr.key.direct.decrypt); + if (ret < 0) + return ret; + } else { + if (NL_REQ_ATTR_CHECK(info->extack, + info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_ENCRYPT_EPOCH) || + NL_REQ_ATTR_CHECK(info->extack, + info->attrs[OVPN_A_KEYCONF], attrs, + OVPN_A_KEYCONF_DECRYPT_EPOCH)) + return -EINVAL; + + pkr.key.use_epoch_keys = true; + ret = ovpn_nl_get_epoch_key(info, + attrs[OVPN_A_KEYCONF_ENCRYPT_EPOCH], + pkr.key.cipher_alg, + &pkr.key.epoch.encrypt); + if (ret < 0) + return ret; + + ret = ovpn_nl_get_epoch_key(info, + attrs[OVPN_A_KEYCONF_DECRYPT_EPOCH], + pkr.key.cipher_alg, + &pkr.key.epoch.decrypt); + if (ret < 0) + return ret; + } peer_id = nla_get_u32(attrs[OVPN_A_KEYCONF_PEER_ID]); peer = ovpn_peer_get_by_id(ovpn, peer_id); diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h index 06690090a1a9..fa6436557abd 100644 --- a/include/uapi/linux/ovpn.h +++ b/include/uapi/linux/ovpn.h @@ -11,6 +11,7 @@ #define OVPN_FAMILY_VERSION 1 #define OVPN_NONCE_TAIL_SIZE 8 +#define OVPN_EPOCH_PRK_SIZE 32 enum ovpn_cipher_alg { OVPN_CIPHER_ALG_NONE, @@ -68,6 +69,8 @@ enum { OVPN_A_KEYCONF_CIPHER_ALG, OVPN_A_KEYCONF_ENCRYPT_DIR, OVPN_A_KEYCONF_DECRYPT_DIR, + OVPN_A_KEYCONF_ENCRYPT_EPOCH, + OVPN_A_KEYCONF_DECRYPT_EPOCH, __OVPN_A_KEYCONF_MAX, OVPN_A_KEYCONF_MAX = (__OVPN_A_KEYCONF_MAX - 1) @@ -81,6 +84,14 @@ enum { OVPN_A_KEYDIR_MAX = (__OVPN_A_KEYDIR_MAX - 1) }; +enum { + OVPN_A_EPOCH_KEY = 1, + OVPN_A_EPOCH_CIPHER_KEY_LEN, + + __OVPN_A_EPOCH_MAX, + OVPN_A_EPOCH_MAX = (__OVPN_A_EPOCH_MAX - 1) +}; + enum { OVPN_A_IFINDEX = 1, OVPN_A_PEER, From patchwork Thu Jul 2 11:52:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ralf Lici X-Patchwork-Id: 5079 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:1887:b0:869:83bc:8c48 with SMTP id r7csp551044max; Thu, 2 Jul 2026 04:53:46 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/M17GagXzqPcLhvyXqVJDbdiwXIXzN/pnrDeuAWTfqtXHVmnZQ7jrktVRlNOuAwhTq17HohxyITqw=@openvpn.net X-Received: by 2002:a05:6820:2d49:b0:6a1:4be6:52d1 with SMTP id 006d021491bc7-6a309ae6d86mr3164905eaf.39.1782993226794; Thu, 02 Jul 2026 04:53:46 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782993226; cv=none; d=google.com; s=arc-20260327; b=D44lEz41oPyqLSPB/AyYWA2F4v7/ofqdCeWAINxWaSGHGO9g8Pgdwya3UqDXofhH1g fCdIEv4JPDs+St8wqTKGf5OPrZfy3KbBwuqZbt0e3yCvaQMW1VcAtFIf7qigL6ptZycx qyZf3yKd/pG2jB5AhkV16r9IJLHa7UoJ7FVqxCXcexBtMPWS8VkBQI8nxANBlMtDmEwB 5mQKnHI16rzg28ZHI5ezS52iykw9rTgDAz1SRaiDorOdJ9Ylf3Ljqq47X6Yuhz4zdqcg 2vDmfWxGnEhig24758OZIXXBUZZZhpduJBa5sR9u22jX53mUHuhdB42Q3pQmkm7b850Z dPBw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature:dkim-signature; bh=3R1/nWdwYLYVmszjeYtdPN4t3xl5oW5fdsh6PKAr/6k=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Tdf5z07nQaxUpMFnh6uvThOiwDdqEphsgYY7KQtCJqnF0Bbds4XQ5f19gfuUTNXZCx q9bX32aTtrHRhQ4fBQ75F6a0InEFP7DflYswtcTVeSg+HzSXr8h/eUvPzkVc5mAaAJcR BpD7y9Z523XaH/TTvwI4HnFA8zO/nXPi8cHBD3Q0jMzTIFPZwNntaEvMVFdBZkd2kVmi ER2ogQ6MFnW3opcocdvrI/06DJDWM0OXjDr40xAv1Ss+kTmVzmMibY844Q3AX0Yqw80M Q9GRnCcqM6sBY8FYPLSz6A/H9O3t52aYpkXGPzdqKcMznsg7qW/8Fu5GQsCgijQXeBzq bVmA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Xg3dllXp; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=R+CMefOl; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=hfUD6zsu; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=jqpe2URN; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-6a310419ad8si2108764eaf.77.2026.07.02.04.53.46 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 02 Jul 2026 04:53:46 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=Xg3dllXp; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=R+CMefOl; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=hfUD6zsu; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=jqpe2URN; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=3R1/nWdwYLYVmszjeYtdPN4t3xl5oW5fdsh6PKAr/6k=; b=Xg3dllXp59jo+yvqCYNCyi7CAd SynSSB4CzoJ2nyeqnDNcKfUDXFBM0IJBA6xabGk7f2AmL3wxvLnfPCIMnSFPARH5Wn+KAMMQElGBl LqNIBhUb6WcwcTlHgn3oP6GNwyP49hzwyXss3K9taq75qJ9S3HLzq5/BQ6yC+06fJFWE=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wfFzE-0005bG-1I; Thu, 02 Jul 2026 11:53:44 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wfFzD-0005b7-Gl for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:43 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=NmROwgokUFkhRRdbb2a8lDdv8GmDpdfnGrJ8PyHwbmQ=; b=R+CMefOleiKndxj5d7TxHtTXAN ocrhxXgdJ4aiogwqoiSrZT8AW8xL/zVQ6y8rBU89oMvupm9JKVGMn9NIJqb+Ye65RvGogq84+jUWB d8F0IG53V38fdIKzi5BDl9UGB/L52w/9LnQVZhfI+xbWoJyf2pcauDEndNAdD/sjDxHk=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=NmROwgokUFkhRRdbb2a8lDdv8GmDpdfnGrJ8PyHwbmQ=; b=hfUD6zsunZsrt6ePl8R0aPcYcl icBnC450oRxjUorzv2zbRNsbGWEu0IGEnfeseIKe7dB+IYogfcCMvDy70kxYppvJdfduEE+bJU6F/ RpC5rqvbBMauKscZYBl55zbvhTTqpauffZJ+o+jQyfnWo4Uze8//+R1aM1FcyHT6ORFI=; Received: from mout-b-206.mailbox.org ([195.10.208.51]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wfFzA-0004LC-97 for openvpn-devel@lists.sourceforge.net; Thu, 02 Jul 2026 11:53:43 +0000 Received: from smtp2.mailbox.org (smtp2.mailbox.org [IPv6:2001:67c:2050:b231:465::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-206.mailbox.org (Postfix) with ESMTPS id 4grZyv6jKcz9yCD; Thu, 2 Jul 2026 13:53:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782993208; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NmROwgokUFkhRRdbb2a8lDdv8GmDpdfnGrJ8PyHwbmQ=; b=jqpe2URN0ECWlvHn/KqVZY5uAROEIPmDNyABZXSn5rJ4uCNKgSuJk5Ivqe15Z/Jbp7G+YR N3WWaixlek53EZ7LSEmfUdlFe5b1hZ3uex5l6bJ/n9424dIGW88ov+rgBWvGnHDIkeVGga lMrs0TzuBullno8FChKpI1r9cvcSkUxu2nWMAm3BJQ+l9sbrP55afP/1IFiXkQX0hcvEOa C9OrLAS1xCBNqKy1GLJsRb8CfjSbwqrFszcTw23DtNmG5iRV8yBfcB/jiKatlZqueQlBRN 5ezdqvM1XE4AqKZesbsn+x5rDKsZ8OKdP2OEs/qrqncT7M9Jz0GMDzWqGyAXtg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::2 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Thu, 2 Jul 2026 13:52:29 +0200 Message-ID: <93d28583fda6f6983e567ca9a724892478e688ad.1782986577.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4grZyv6jKcz9yCD X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-1.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Extend ovpn-cli's key installation command with an explicit key type so that the tests can install either direct keys or epoch keys. Use the same selector for the TCP connect helper when it auto-insta [...] Content analysis details: (-0.2 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1wfFzA-0004LC-97 Subject: [Openvpn-devel] [RFC ovpn net-next v2 14/14] selftests: ovpn: exercise epoch keys X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1869603906236678464 X-GMAIL-MSGID: 1869603906236678464 Extend ovpn-cli's key installation command with an explicit key type so that the tests can install either direct keys or epoch keys. Use the same selector for the TCP connect helper when it auto-installs a key. Add epoch wrappers around the existing UDP and TCP data-path tests. The packet capture filter also checks epoch 1 in DATA_V2 packet IDs, so the tests verify netlink key installation and the epoch packet layout without adding a separate topology. This intentionally does not cover epoch rotation or future-key refill: the selftests use normal production limits, so the short traffic run does not consume enough packet-ID or AEAD budget to advance epochs. Signed-off-by: Ralf Lici --- No changes since v1 https://lore.kernel.org/openvpn-devel/d6fa57646d97b24e60613f8b28665610244846e8.1782919654.git.ralf@mandelbit.com/ tools/testing/selftests/net/ovpn/Makefile | 2 + tools/testing/selftests/net/ovpn/common.sh | 30 +++-- tools/testing/selftests/net/ovpn/ovpn-cli.c | 116 +++++++++++++++--- .../selftests/net/ovpn/test-epoch-tcp.sh | 11 ++ .../testing/selftests/net/ovpn/test-epoch.sh | 10 ++ tools/testing/selftests/net/ovpn/test-mark.sh | 3 +- tools/testing/selftests/net/ovpn/test.sh | 12 +- 7 files changed, 154 insertions(+), 30 deletions(-) create mode 100755 tools/testing/selftests/net/ovpn/test-epoch-tcp.sh create mode 100755 tools/testing/selftests/net/ovpn/test-epoch.sh diff --git a/tools/testing/selftests/net/ovpn/Makefile b/tools/testing/selftests/net/ovpn/Makefile index 169f0464ac3a..515aeac945a4 100644 --- a/tools/testing/selftests/net/ovpn/Makefile +++ b/tools/testing/selftests/net/ovpn/Makefile @@ -36,6 +36,8 @@ TEST_PROGS := \ test-chachapoly.sh \ test-close-socket-tcp.sh \ test-close-socket.sh \ + test-epoch-tcp.sh \ + test-epoch.sh \ test-float.sh \ test-large-mtu.sh \ test-mark.sh \ diff --git a/tools/testing/selftests/net/ovpn/common.sh b/tools/testing/selftests/net/ovpn/common.sh index 2d844eb3aa6e..de5ff9a9e97a 100644 --- a/tools/testing/selftests/net/ovpn/common.sh +++ b/tools/testing/selftests/net/ovpn/common.sh @@ -12,6 +12,7 @@ OVPN_TCP_PEERS_FILE=${OVPN_TCP_PEERS_FILE:-tcp_peers.txt} OVPN_CLI=${OVPN_CLI:-${OVPN_COMMON_DIR}/ovpn-cli} OVPN_YNL=${OVPN_YNL:-${OVPN_COMMON_DIR}/../../../../net/ynl/pyynl/cli.py} OVPN_ALG=${OVPN_ALG:-aes} +OVPN_KEY_TYPE=${OVPN_KEY_TYPE:-direct} OVPN_PROTO=${OVPN_PROTO:-UDP} OVPN_FLOAT=${OVPN_FLOAT:-0} OVPN_SYMMETRIC_ID=${OVPN_SYMMETRIC_ID:-0} @@ -184,14 +185,26 @@ ovpn_build_capture_filter() { # address. The IPv6 branch assumes there are no extension # headers in the outer packet. if [[ "${2}" == *:* ]]; then - printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" "${1}" + printf "ip6 and ip6[6] = 17 and ip6[48:4] = %s" \ + "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and ip6[52:2] = 0x0001" + fi else printf "ip and udp[8:4] = %s" "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and udp[12:2] = 0x0001" + fi fi else # openvpn over TCP prepends a 2-byte packet length ahead of the # DATA_V2 opcode, so skip it before matching the payload header - printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" "${1}" + printf "ip and tcp[(((tcp[12] & 0xf0) >> 2) + 2):4] = %s" \ + "${1}" + if [ "${OVPN_KEY_TYPE}" == "epoch" ]; then + printf " and tcp[(((tcp[12] & 0xf0) >> 2) + 6):2] = " + printf "0x0001" + fi fi } @@ -222,8 +235,8 @@ ovpn_add_peer() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do ip netns exec "${server_ns}" ${OVPN_CLI} \ - new_key tun0 ${p} 1 0 ${OVPN_ALG} 0 \ - data64.key + new_key tun0 ${p} 1 0 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 0 data64.key done else peer_ns="ovpn_peer${1}" @@ -245,7 +258,8 @@ ovpn_add_peer() { tun${1} ${PEER_ID} ${TX_ID} ${LPORT} ${RADDR} \ ${RPORT} ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${1} \ - ${PEER_ID} 1 0 ${OVPN_ALG} 1 data64.key + ${PEER_ID} 1 0 ${OVPN_ALG} ${OVPN_KEY_TYPE} \ + 1 data64.key fi else if [ ${1} -eq 0 ]; then @@ -254,7 +268,8 @@ ovpn_add_peer() { for p in $(seq 1 ${OVPN_NUM_PEERS}); do ip netns exec "${server_ns}" \ ${OVPN_CLI} new_key tun0 ${p} \ - 1 0 ${OVPN_ALG} 0 data64.key + 1 0 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 0 data64.key done }) & sleep 5 @@ -269,7 +284,8 @@ ovpn_add_peer() { TX_ID=${1} fi ip netns exec "${peer_ns}" ${OVPN_CLI} connect tun${1} \ - ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 data64.key + ${PEER_ID} ${TX_ID} 10.10.${1}.1 1 \ + ${OVPN_KEY_TYPE} data64.key fi fi } diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index d40953375c86..90bfcb6d39e2 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -61,6 +61,11 @@ enum ovpn_key_direction { KEY_DIR_OUT, }; +enum ovpn_key_type { + KEY_TYPE_DIRECT = 0, + KEY_TYPE_EPOCH, +}; + #define KEY_LEN (256 / 8) #define NONCE_LEN 8 @@ -130,6 +135,7 @@ struct ovpn_ctx { __u32 keepalive_interval; __u32 keepalive_timeout; + enum ovpn_key_type key_type; enum ovpn_key_direction key_dir; enum ovpn_key_slot key_slot; int key_id; @@ -451,6 +457,18 @@ static int ovpn_parse_cipher(const char *cipher, struct ovpn_ctx *ctx) return 0; } +static int ovpn_parse_key_type(const char *type, struct ovpn_ctx *ctx) +{ + if (strcmp(type, "direct") == 0) + ctx->key_type = KEY_TYPE_DIRECT; + else if (strcmp(type, "epoch") == 0) + ctx->key_type = KEY_TYPE_EPOCH; + else + return -ENOTSUP; + + return 0; +} + static int ovpn_parse_key_direction(const char *dir, struct ovpn_ctx *ctx) { int in_dir; @@ -921,9 +939,38 @@ static int ovpn_get_peer(struct ovpn_ctx *ovpn) return ret; } +static int ovpn_put_direct_key(struct nl_msg *msg, int attr, const __u8 *key, + const __u8 *nonce) +{ + struct nlattr *key_dir; + + key_dir = nla_nest_start(msg, attr); + NLA_PUT(msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, key); + NLA_PUT(msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, nonce); + nla_nest_end(msg, key_dir); + + return 0; +nla_put_failure: + return -1; +} + +static int ovpn_put_epoch_key(struct nl_msg *msg, int attr, const __u8 *key) +{ + struct nlattr *epoch; + + epoch = nla_nest_start(msg, attr); + NLA_PUT(msg, OVPN_A_EPOCH_KEY, KEY_LEN, key); + NLA_PUT_U32(msg, OVPN_A_EPOCH_CIPHER_KEY_LEN, KEY_LEN); + nla_nest_end(msg, epoch); + + return 0; +nla_put_failure: + return -1; +} + static int ovpn_new_key(struct ovpn_ctx *ovpn) { - struct nlattr *keyconf, *key_dir; + struct nlattr *keyconf; struct nl_ctx *ctx; int ret = -1; @@ -937,15 +984,37 @@ static int ovpn_new_key(struct ovpn_ctx *ovpn) NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_KEY_ID, ovpn->key_id); NLA_PUT_U32(ctx->nl_msg, OVPN_A_KEYCONF_CIPHER_ALG, ovpn->cipher); - key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_ENCRYPT_DIR); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_enc); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); - nla_nest_end(ctx->nl_msg, key_dir); + switch (ovpn->key_type) { + case KEY_TYPE_DIRECT: + ret = ovpn_put_direct_key(ctx->nl_msg, + OVPN_A_KEYCONF_ENCRYPT_DIR, + ovpn->key_enc, ovpn->nonce); + if (ret) + goto nla_put_failure; - key_dir = nla_nest_start(ctx->nl_msg, OVPN_A_KEYCONF_DECRYPT_DIR); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_CIPHER_KEY, KEY_LEN, ovpn->key_dec); - NLA_PUT(ctx->nl_msg, OVPN_A_KEYDIR_NONCE_TAIL, NONCE_LEN, ovpn->nonce); - nla_nest_end(ctx->nl_msg, key_dir); + ret = ovpn_put_direct_key(ctx->nl_msg, + OVPN_A_KEYCONF_DECRYPT_DIR, + ovpn->key_dec, ovpn->nonce); + if (ret) + goto nla_put_failure; + break; + case KEY_TYPE_EPOCH: + ret = ovpn_put_epoch_key(ctx->nl_msg, + OVPN_A_KEYCONF_ENCRYPT_EPOCH, + ovpn->key_enc); + if (ret) + goto nla_put_failure; + + ret = ovpn_put_epoch_key(ctx->nl_msg, + OVPN_A_KEYCONF_DECRYPT_EPOCH, + ovpn->key_dec); + if (ret) + goto nla_put_failure; + break; + default: + ret = -EINVAL; + goto nla_put_failure; + } nla_nest_end(ctx->nl_msg, keyconf); @@ -1691,7 +1760,7 @@ static void usage(const char *cmd) "\tipv6: whether the socket should listen to the IPv6 wildcard address\n"); fprintf(stderr, - "* connect [key_file]: start connecting peer of TCP-based VPN session\n"); + "* connect [key_type key_file]: start connecting peer of TCP-based VPN session\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID found in data packets received from this peer\n"); @@ -1699,8 +1768,8 @@ static void usage(const char *cmd) "\ttx_id: peer ID to be used when sending to this peer, 'none' for symmetric peer ID\n"); fprintf(stderr, "\traddr: peer IP address to connect to\n"); fprintf(stderr, "\trport: peer TCP port to connect to\n"); - fprintf(stderr, - "\tkey_file: file containing the symmetric key for encryption\n"); + fprintf(stderr, "\tkey_type: key type, supported: direct, epoch\n"); + fprintf(stderr, "\tkey_file: file containing the pre-shared key\n"); fprintf(stderr, "* new_peer [vpnaddr]: add new peer\n"); @@ -1748,7 +1817,7 @@ static void usage(const char *cmd) "\tpeer_id: peer ID of the peer to query. All peers are returned if omitted\n"); fprintf(stderr, - "* new_key : set data channel key\n"); + "* new_key : set data channel key\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); fprintf(stderr, "\tpeer_id: peer ID of the peer to configure the key for\n"); @@ -1756,6 +1825,7 @@ static void usage(const char *cmd) fprintf(stderr, "\tkey_id: an ID from 0 to 7\n"); fprintf(stderr, "\tcipher: cipher to use, supported: aes (AES-GCM), chachapoly (CHACHA20POLY1305)\n"); + fprintf(stderr, "\tkey_type: key type, supported: direct, epoch\n"); fprintf(stderr, "\tkey_dir: key direction, must 0 on one host and 1 on the other\n"); fprintf(stderr, "\tkey_file: file containing the pre-shared key\n"); @@ -2247,12 +2317,19 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } if (argc > 7) { + if (argc < 9) + return -EINVAL; + ovpn->key_slot = OVPN_KEY_SLOT_PRIMARY; ovpn->key_id = 0; ovpn->cipher = OVPN_CIPHER_ALG_AES_GCM; ovpn->key_dir = KEY_DIR_OUT; - ret = ovpn_parse_key(argv[7], ovpn); + ret = ovpn_parse_key_type(argv[7], ovpn); + if (ret < 0) + return -1; + + ret = ovpn_parse_key(argv[8], ovpn); if (ret) return -1; } @@ -2351,7 +2428,7 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } break; case CMD_NEW_KEY: - if (argc < 9) + if (argc < 10) return -EINVAL; ovpn->peer_id = strtoul(argv[3], NULL, 10); @@ -2374,11 +2451,15 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) if (ret < 0) return -1; - ret = ovpn_parse_key_direction(argv[7], ovpn); + ret = ovpn_parse_key_type(argv[7], ovpn); + if (ret < 0) + return -1; + + ret = ovpn_parse_key_direction(argv[8], ovpn); if (ret < 0) return -1; - ret = ovpn_parse_key(argv[8], ovpn); + ret = ovpn_parse_key(argv[9], ovpn); if (ret) return -1; break; @@ -2442,6 +2523,7 @@ int main(int argc, char *argv[]) memset(&ovpn, 0, sizeof(ovpn)); ovpn.sa_family = AF_UNSPEC; ovpn.cipher = OVPN_CIPHER_ALG_NONE; + ovpn.key_type = KEY_TYPE_DIRECT; ovpn.cmd = ovpn_parse_cmd(argv[1]); if (ovpn.cmd == CMD_INVALID) { diff --git a/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh b/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh new file mode 100755 index 000000000000..ec7b243d1923 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-epoch-tcp.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +OVPN_KEY_TYPE="epoch" +OVPN_PROTO="TCP" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-epoch.sh b/tools/testing/selftests/net/ovpn/test-epoch.sh new file mode 100755 index 000000000000..17641dad87c4 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/test-epoch.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026 OpenVPN, Inc. +# +# Author: Ralf Lici +# Antonio Quartulli + +OVPN_KEY_TYPE="epoch" + +source test.sh diff --git a/tools/testing/selftests/net/ovpn/test-mark.sh b/tools/testing/selftests/net/ovpn/test-mark.sh index 7c1d56e9c525..43b4798fefde 100755 --- a/tools/testing/selftests/net/ovpn/test-mark.sh +++ b/tools/testing/selftests/net/ovpn/test-mark.sh @@ -43,7 +43,8 @@ ovpn_mark_prepare_network() { for p in $(seq 1 3); do ovpn_cmd_ok "install server key for peer ${p}" \ ip netns exec ovpn_peer0 "${OVPN_CLI}" new_key tun0 \ - "${p}" 1 0 "${OVPN_ALG}" 0 data64.key + "${p}" 1 0 "${OVPN_ALG}" "${OVPN_KEY_TYPE}" \ + 0 data64.key done for p in $(seq 1 3); do diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..a30842903fdb 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -67,14 +67,15 @@ ovpn_run_basic_traffic() { local tcpdump_timeout="1.5s" for p in $(seq 1 ${OVPN_NUM_PEERS}); do - # The first part of the data packet header consists of: + # The tcpdump filters match the cleartext data packet prefix: # - TCP only: 2 bytes for the packet length # - 5 bits for opcode ("9" for DATA_V2) # - 3 bits for key-id ("0" at this point) - # - 12 bytes for peer-id: + # - 24 bits for peer-id: # - with asymmetric ID: "${p}" one way and "${p} + 9" the # other way # - with symmetric ID: "${p}" both ways + # - epoch keys only: 2 bytes for epoch ("1" at this point) header1=$(printf "0x4800000%x" ${p}) header2=$(printf "0x4800000%x" $((p + OVPN_ID_OFFSET))) raddr="" @@ -152,11 +153,12 @@ ovpn_run_key_rollover() { peer_ns="ovpn_peer${p}" ovpn_cmd_ok "add secondary key on peer0 for peer ${p}" \ ip netns exec ovpn_peer0 ${OVPN_CLI} new_key tun0 \ - ${p} 2 1 ${OVPN_ALG} 0 data64.key + ${p} 2 1 ${OVPN_ALG} ${OVPN_KEY_TYPE} \ + 0 data64.key ovpn_cmd_ok "add secondary key on peer${p} for peer ${p}" \ ip netns exec "${peer_ns}" ${OVPN_CLI} new_key tun${p} \ - $((p + OVPN_ID_OFFSET)) 2 1 ${OVPN_ALG} 1 \ - data64.key + $((p + OVPN_ID_OFFSET)) 2 1 ${OVPN_ALG} \ + ${OVPN_KEY_TYPE} 1 data64.key ovpn_cmd_ok "swap keys on peer${p}" \ ip netns exec "${peer_ns}" ${OVPN_CLI} swap_keys \ tun${p} $((p + OVPN_ID_OFFSET))