From patchwork Wed Jul 1 15:43: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: 5050 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417425ejc; Wed, 1 Jul 2026 08:44:08 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/4Fs2p2lRAASqPQUUTSwMNAZAImMndfCqmoIJc+QYXmoiY8CEBMeIfAuTSGjFEA6++vZ4k7VqhYkw=@openvpn.net X-Received: by 2002:a05:6820:174e:b0:6a1:50ec:43fd with SMTP id 006d021491bc7-6a30d8fc2f4mr733018eaf.58.1782920648641; Wed, 01 Jul 2026 08:44:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920648; cv=none; d=google.com; s=arc-20260327; b=ckjkeCZs7jfdglM1vgmfUl6JDTTVghhZirV5Reg5T+oUrX/OoCyJhESIPNu/l3eOD/ 1Cnf1yWkYUvFC6atEz/jhIALtufONEX2J+sGfJtmHaK/C0QUV6c7+rLoTjK5OU+TENNH SSRG8+DoRzJ3z5TFGuCygq5i0zr2e1YwqfGcuD++zzHL3OwoVQ1cG3PP3jXP4FNm39qj AkJ27/KrhfAWKbCrKsiAbpVCcwiNQK8wP6iQJUkusgzDovFJPyfXRiv/HwKdxkeSI/wO EjbktDbt2xaN01HpRLrlvlCG2MjE87tEzB154oH60u1sLbjY+8jIZNGl0sWxCJmoVxuB i2Ow== 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=gC/P/HrqBNFIYklgfZySIBdHlKJLtHAQCzaMEnV0KAE=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Ft3JmeEbgZeV4nn6RCAk+itI1NH8wOChtYV3nPJeIDq12zfvKqSWtLA2PBwIZVxCFG AlpCzAND/eR68Y96Ji6yNGC8cNVMSREXhoVCnUsff64Io819rYrPL4Abefw5kq1BZMJQ p13B75Mxf1EjHayVJ5PGRvfmGk1VFexDq/JCmLv/mmDX6qH97aHbnOYBtgU7WopC8BKX ulNllyxZ+HzJdRBfcd4wLeHZx8ywAchOMkXVjvjiBW6lOGhYWpuL28BTWgBtp0lnMqHX pg+eTu8IrfVch7IkKniz2nYVxqnR8WCzZ+sRXUl+GIdhesEWdB6RFCD78qdGYNBctzK/ 9I7Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="jd5xDD/y"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=SNuFxtim; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mvQQEPVl; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="XpXbhpw/"; 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-6a31000ee1esi225515eaf.33.2026.07.01.08.44.06 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:08 -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="jd5xDD/y"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=SNuFxtim; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mvQQEPVl; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="XpXbhpw/"; 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=gC/P/HrqBNFIYklgfZySIBdHlKJLtHAQCzaMEnV0KAE=; b=jd5xDD/y24kUHZx14mOKWqQ/Le fE8u6PbPBOU/NkyVS4LJXPo40uuhks4lpjdmgpiBmL5TVN5zXuN2Fec46pJZSdMOIQhMWfE6eIZNo OKqrhuTXGP5zFcsV1pmA3l4STtL7NPk2YOHrUF93vMrGqJ74SQKMPWWf/ZQCnELu5Nuc=; 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 1wex6W-0003jC-NC; Wed, 01 Jul 2026 15:44:01 +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 1wex6U-0003iz-9V for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:00 +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=V6CP2QFoKAE3Bgtas4MvggHDvL3CdgdmA4+QPuixhkM=; b=SNuFxtimGf2Vdpy4LdQR2WHYlu OgBajA8mMugGj77ff/oD+v5x9M+6+xixpcZA5OKslR3LVY26IBulOZCjP7KEjFyjsEkE+OCS2XPT9 UcQrMg6ya2G7nZ3rc1xJJRih5A9bFzWiI3XJGLTloCWKDNfRIHtlfObYRBcNZVMiZUAw=; 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=V6CP2QFoKAE3Bgtas4MvggHDvL3CdgdmA4+QPuixhkM=; b=mvQQEPVlqI21dKpdCV8axx10Kw GDbixsMAtBD506+McpOwil0Pa1uA65ebhMFmHGYsM9bBM+uDYCNcFXK4VFbWpJtPoBP+9K6tLY4lI kyhk41/fYF5sD4ockyBoHfE40LXTPYAg5Pc3V3f9ygxok0Z91MG4OIUVsBEm4evuTw4U=; 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 1wex6R-00027z-Fl for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:43:58 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr4776vkyz9xGd; Wed, 1 Jul 2026 17:43:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920628; 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=V6CP2QFoKAE3Bgtas4MvggHDvL3CdgdmA4+QPuixhkM=; b=XpXbhpw/2o24mTXA/7fK7jktBGJ+fMCpBkMAkMPuvAJhwxH4ro8NeyO/FK0vIEnhbxcBQm jwDg/XCqvHZwvLIwD1DB9mDBBzdZfITAy7YkdyTnwBo3f0863ogVbG90ofUGaJZu0yILwT vdo504zO93OogPJwvvKUfwuj/FClHJ8V1mZJvJatksx42zR+JgiPU43nvl7gPLwnN3q9K9 cdxTi8TisVX1+gfYbV7sVjQd2Y4am5OOwPZAGI+0g22wxbE5/MhzxZURSRYF1IchR5QvXZ 3AiG1IOP9rhQig+yhrtzpBBnoRNg9OEgF++My++DmHS7d4k/X9krPzYFd8PAxw== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:16 +0200 Message-ID: <6c66a38af8ea53e4fbc587f087c594573fd8ee6a.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr4776vkyz9xGd 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 Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6R-00027z-Fl Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527801972934827 X-GMAIL-MSGID: 1869527801972934827 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 --- 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 Wed Jul 1 15:43: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: 5051 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417426ejc; Wed, 1 Jul 2026 08:44:09 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ83dT433rrmr9g6NPf7tKcIFdAZe6tloX7zPb/How4j0mA4QPXjMOS3NhcOJl3TDUnVMSw3vrvs7/M=@openvpn.net X-Received: by 2002:a05:6870:d892:b0:43e:fe1b:1a76 with SMTP id 586e51a60fabf-44cb45e1216mr759910fac.22.1782920648702; Wed, 01 Jul 2026 08:44:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920648; cv=none; d=google.com; s=arc-20260327; b=LNgClyYq3CZNNus2OqGV9z2kiN6BV4DR3ChTPxRZLj/LGVzxYWgbS47CgPJw/Q2gLw 2ZUwG7BXfuPJu6rusk9rRmXVeKNWxI2ZzTBSuxUT5yE6dm9nJujJxG0QiMmjP/BP0jl8 RFn9gpeIjJIECedYt2lQF8quT34nLqXm20ZXzkWuSh3k/DO77AQIlY37KI1cevx8Qjjn AW9CosFTzPk5fe4KDSOA//cg+cq3ArcS2Ggp+AZ/NhcSkvpoKfMOCEJGQhVJ/8OSh/Gy BR6eeya15d/yz2t0K3Uxn1QsJt6A6miy4reJnMLM+YN+FZoJ/5quumv6NtqYMsyGQyof YPYw== 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=oP/+v9xis8aG0rKfYTynkqX62bu56n9fJTZbNfnCcpQ=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=rcNxIoNrLD1bXm+mOPajLhJMe39+SDER0s81n5O5vG3wm0wSigli3TsYbwZOonzImS F5/g9bI+VURbJfMVKMrreBTY/hdMMEqqJsaboCKfIjFjVWcU3JR9CNswpKOMNv4URk2w CpHxNupWVVVwGzuCStTV/LsUdj1mfFI5KVIGaJZaMpi2w4rIkjQGuPCDEs/D7Bf/UPXb 2I7ucrtcNo+XcsJKlhPKFWnVETZnQK0AHVAAjULMZldaMKzhbQ9PCjE8D5NX31XyOBAF Nvoy0RQ4dlaKdRhi7aAu2JE7czvcPNl/mJVXfcNVzzPKuZgOCxKRSIaRmZ197rIw7h85 s35g==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=QdrYsXaf; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=GyRcBsrX; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=geokSZNV; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="k7AS/fqG"; 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-44cbe80a952si52694fac.26.2026.07.01.08.44.08 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:08 -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=QdrYsXaf; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=GyRcBsrX; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=geokSZNV; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="k7AS/fqG"; 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=oP/+v9xis8aG0rKfYTynkqX62bu56n9fJTZbNfnCcpQ=; b=QdrYsXafEp+CZ6V5qthLV2VTBY 118L+augtlSFxqzwLLA5csbK+gU/9QRBXjzkVA278OXMVrrIy8nasnlMVHA7iXV+bx8+JcebrbyGP GjR+nzBqTVS7JLNCRGKsuX6xjXAJ34+zqJgcrKaSxQN/q4YUK41Im0PAhNEeiEuI5hag=; 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 1wex6d-0003iC-U6; Wed, 01 Jul 2026 15:44:05 +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 1wex6b-0003i4-Ub for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:03 +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=l1CjKUauDyWO5B/K4rjzCGrDGkKqcClCk8NSZW0h7ho=; b=GyRcBsrXKlNvcQdEEVH4986Deh QrLdCnzU6Dr+yjBq080Qur/z0WMt28Z/90LRbg5T2vlmNDRB7//64rItJ6VYLeONZ3RYqhXFfpKN1 +nv0gNRQ6J6xXfYW+p89wGPZziz2UAuI7BXSziUIKbGqwemFk3pDaObEdvYBF88gIEis=; 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=l1CjKUauDyWO5B/K4rjzCGrDGkKqcClCk8NSZW0h7ho=; b=geokSZNVvtRXYFgD/ojI8YAjuS HIs3JXkLM3jmwYzk+4h0Tgk4aV13GWBgAikWqyVyC2IB+RfFnWGvVnaK4lgN4ea1cXi418Sqye0q4 sczPG1qsYPUM4i379szEP08JxU1isX0sMGreDqbEoZuvZ8DYE1naqyuZ70bCVf71vG58=; 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 1wex6X-00028K-DG for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:03 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr4794nYjzDsHY; Wed, 1 Jul 2026 17:43:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920629; 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=l1CjKUauDyWO5B/K4rjzCGrDGkKqcClCk8NSZW0h7ho=; b=k7AS/fqG+RWYopJPeRFGYC5wVFmTRrAjOU0ULEk4KuyxzOHcxnu9bT4dt3/CR6uzJBn0Lq Ct636w3iFBJmvAhs3TnGS8nexVQX1TMDy1dw3ZxOAgsDhg6GCreU0X4zFAG2JHIQVt9gf/ Xl2h+AyueA/g5/I28W3KYxsyq6YQciLulWcYlGCYWptdcGI9JY4CFnhs7g7PTw5rV6+Cgy rA54fC7ylyX1Hl7EdaN9D7DgH6u04IkYiAJooiklB9JJvDx33zAXuchn+Tei7ZO76rm/iK jDETOfp0IoacfmZeJtKpw2LzNsMoZQCTgrYGJdaQej3fD73KGpoIX8HC4/nDYg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:17 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr4794nYjzDsHY 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 Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6X-00028K-DG Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527801792464948 X-GMAIL-MSGID: 1869527801792464948 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 --- 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..5b09b39f9a94 --- /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) +{ + u64 total; + int ret = 0; + + 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 Wed Jul 1 15:43: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: 5048 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417417ejc; Wed, 1 Jul 2026 08:44:08 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RqoVgab64B2xRR6OcqC/tGmQy4QjyCsw0dsp/biZ7m6eyVD81p21mSTVHA3681Kz/5w9/YuSnIun9Y=@openvpn.net X-Received: by 2002:a05:6871:79d:b0:447:1ceb:7cc6 with SMTP id 586e51a60fabf-44cb445f396mr803457fac.15.1782920647652; Wed, 01 Jul 2026 08:44:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920647; cv=none; d=google.com; s=arc-20260327; b=lrsi22E2fhLJldntIRfEWcfXsYY97yj6+yiABVlPy0Zb28XnvEOXc1F0KLAE6v4Lgu UktT6XNtEz61YsQ/lmQ/QHLSOGI55/R6G1scw5pyzypDcPdUOYrRWbjpv2sLaI/hYDUT B9/foscvtOid5fwr5RqePCRrMG7xX/DNBBHj8yYoGdd24K8FTSWYsD73ztn5HjQBUG2q jzB76HSmo2xkZNRw5Xj0A6DHrzF+ae2gDtaiMjIy1/HamfA+AqnSkL0OuvwV214Q2Io1 WXLYHpnleLSVPpojWtTnHmUizIhItXl6UmAZSX613R9tkpGR3AbttsBKexVatwiKI2Kt LxVA== 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=Inxbfda5hQlOcSA5iYWFt+H0yladaOqkSFLtJsBqUB0=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=hjYYTvF7pznFzwOCuJGWvboggHdIKYlgIDBY7prl8W5nEt7+X6kMgKyOUIkhe50uX4 GVevp4vn6Mqkb30argZvWRThCnl2v6/oCYaThYw/dyhMijDEY1KvwsKswb59EIe4nUpo vhb1DvacxhBhVSIu02fndQuSlNDflqEgCO0X7tsPJtKFNBpDdXxJGGFCZDUThGDLukiL eXQTTEsebEna8NRd0PSaFqZwJ7aGNLzNotEENJ6r63A8OXib/nNFr7zGv0Y60l+PhIil oy006w14TTt4t9gLkDlPq6pqORZzeiQPc31iXaw4bO1Dwl3tBi0DcDQE91rWtT8MA2B6 YdIw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=V22JXYov; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=SYnQaAI4; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="ji/vR2Xt"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=PxVP6gCY; 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-44cbef209e5si12870fac.255.2026.07.01.08.44.07 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:07 -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=V22JXYov; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=SYnQaAI4; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="ji/vR2Xt"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=PxVP6gCY; 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=Inxbfda5hQlOcSA5iYWFt+H0yladaOqkSFLtJsBqUB0=; b=V22JXYov8D4ywefOk6oePmIiql mAX6T+m7c4m3wU0IuAZF2JQI8bvFoic0wAjScWQbAMxHfwlgWtSDIB45tj5Bkqt11ZywPq1Ck1MkJ KCTvdEVl6MsEWGtsJnqF7XOithA7xzOa0ptd+YBFatHcMhyCsXyCDeK52eBcKf9VhNTM=; 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 1wex6X-0003j8-Ax; Wed, 01 Jul 2026 15:44:02 +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 1wex6V-0003ir-24 for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:00 +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=odsmdw56VN+KOLiAueYEKfzk4tYXPmjpVqVNF6YRwmY=; b=SYnQaAI4D2enEsPweu3x1U7/ee I2A5hTkK96D8dFKajly3YHw3xZ8CBU3Yi/PQnO372N3CW/mk0RtQmLaAhiQzTSoKB48HwmBf5teKy qjGzhg8ztNs4Ce7gSkHq2NIDuTuYvvcLeSeID9lwIDh5HtpwXO69YBb6jkm7+7bGivmY=; 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=odsmdw56VN+KOLiAueYEKfzk4tYXPmjpVqVNF6YRwmY=; b=ji/vR2XtnC5+m3fol0ipsDcWo9 irm/DlE11wN48ZLfy1depc0c+OeXpN0x+KojxzSXqt0t4ZSqmvm247M2QrSq53js9svojeq7v3E2X vQ6Jn6/HalmffPaBVVAMTX8amXn4Rc70lwg/H3c6pY5ivOQNltF4zmKzZmjAaRq2mtjw=; Received: from mout-b-210.mailbox.org ([195.10.208.40]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6U-000288-It for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:43:59 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-210.mailbox.org (Postfix) with ESMTPS id 4gr47C1TGjzMkt2; Wed, 01 Jul 2026 17:43:51 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920631; 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=odsmdw56VN+KOLiAueYEKfzk4tYXPmjpVqVNF6YRwmY=; b=PxVP6gCYardGBsb7bx7oezApt3KNlZvY+NCkRnjzWkX+2KJ02lHAsVHiSandg9QeSvRJrc VHUksJ//q6pEuZaO5gA51YmpuIzS1N9G8uTrZHghsBE1xIF59dyxJAPMwAP6hRNhS7ve3u cFiSGHm6wTmrBoJ8G4bNkTgflux2S3OO3hJNM+EWlayb/BqagLY2sMUupLZlP7ifYmoWWS r0VLOuq/Oj6ut5Kax51cz8BhH0uq4O1mVrtzWkmsiV0WJsj67AnAdtPA1CCD8j1vaVOVma IDcGXpir0f/qfEw3s40uJQB4zlqROGt70NyWwL2vp/cRvc0v4ebf0IFV9Gb2CA== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:18 +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: 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_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6U-000288-It Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527800742370968 X-GMAIL-MSGID: 1869527800742370968 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 --- 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 Wed Jul 1 15:43: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: 5049 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417424ejc; Wed, 1 Jul 2026 08:44:08 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+Rr9RQpTIdF8o7bRDfoiKN5gziwEPmtmNgwJzLuGyldhNXXiPgP1soRY+ApLEDnPK2q7P8OZ6Y1vIpw=@openvpn.net X-Received: by 2002:a05:6870:e0c9:b0:443:1de6:beb3 with SMTP id 586e51a60fabf-44ca7ed1602mr1104990fac.5.1782920648641; Wed, 01 Jul 2026 08:44:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920648; cv=none; d=google.com; s=arc-20260327; b=ollnpvC45QxLIH3q+MnKi6+h2ZhmlteGtG53qUp9qvQ3chZSNtB6cQfPz8BEWFEhyC aqpziyZkloCc+5bnYdsd075AiZEFUKvDmW7t6v97eds7NKqpVLltCTERoUimMe5t0mwZ uSuI6P1W3lE9yLzK19cck5V8LmL9xEGvVBkrmhcSb1/iQS6VaEx2F0dzrqyYZY3ml8nu ONFfJBKQjGsjHiep9Q07wxKhLYNoEcFD3OkX/qOnxB+fjlexadypHm4N+V3Ny8FMeuEB i+t5FnWLVUXOYxvP+9r8D46CgNlyWSYYW3OafGbTBq+VpXEgdcv34dzI2/ENvCiK5Inh 9dyg== 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=DN5J2MqY725BjFQ/9vr2EUjBukjfi72ptWi1CgsDL4Y=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=EkFqJJ9QAkVg5W7SpyHfPQbN+1C0LIjVNiykVARd9t7O66t8soi9bhlTnYGGYbAEdW rBPL780RigANiCD5K+lgfjSZ2oUkycBe1SqGCqXndRXqQ990M/pwCyhT/wwLtjr7PYCJ TMgy9SRJ35w/j+wKt+lIagl6OQbdS7rzda+cUaRlWUXQtGGHfl75h204MOLemXEkZ8FV qhHeb1Eg/9VNgOqHz6q1+sW+PsZsx+kfASpX5oZpQkFM5id5UzHRA3S49YnAj2iN8/j+ GNAx3Th3D4PKvB1s8oy8C1SHWu/jzPyhxvV3WeGmsVbeEdJWeNY4TpuncwNie8SIswCA zmSQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="bqDnlVP/"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=KnRzzHxQ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Di7ngrfq; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="X/nda3gB"; 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-44cbe822bcdsi55961fac.58.2026.07.01.08.44.07 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:08 -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="bqDnlVP/"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=KnRzzHxQ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Di7ngrfq; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="X/nda3gB"; 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=DN5J2MqY725BjFQ/9vr2EUjBukjfi72ptWi1CgsDL4Y=; b=bqDnlVP/FDOU/tyydaN+CzmRcC 9HyyBrtapd4rl8VYlEWHu57xb35DX9/zXVz/8C3yh9sbSeboZhEiW+Ux1reMI+EeV9KZs+xAkQe3X mbbkFUMnyypNR+hPCNa/xdPQ4nB/zoHgzq3B5fBiC71qO6RWBkG21FH094dDFdidxjKk=; 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 1wex6a-0003jp-8F; Wed, 01 Jul 2026 15:44:05 +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 1wex6Y-0003jW-MR for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:03 +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=E4jVkK+QVIBEYRK1JFRTr/vl+E2ZEH6ppPt8A2EE4Hw=; b=KnRzzHxQdF3oPvQipBj6JNAuxH gVeaA7ZT/Wcr8lUooCGLPMc85n11yuz/umI2E9ZIK7A/od9y7y4Z1f5du+q2MtGQXVedjBHWQAdjO SNh7HxQu3/uTOMOCTQnmZ+Nur5CHhYZrmSWVnftHTY0objCMQx0r1mmSizH1mW9YjoF0=; 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=E4jVkK+QVIBEYRK1JFRTr/vl+E2ZEH6ppPt8A2EE4Hw=; b=Di7ngrfqFLjVg5DSZyQyMx6l8I pblNIpjB1ObYd56E74apvJRSrHCWtYdVShrj2nnKPZKIMdnt2szRWHUP1+DNFMc26o0fyrNEA9BLZ OIWjMT1xJy8PYj8dVAWaXbW8SV6Jos6QwZeA0XvrHvMmqEn5LOv2zCLnmF7/FEYNE+dY=; 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 1wex6W-00028H-9a for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:03 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr47D5WnYz9ySy; Wed, 1 Jul 2026 17:43:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920632; 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=E4jVkK+QVIBEYRK1JFRTr/vl+E2ZEH6ppPt8A2EE4Hw=; b=X/nda3gBl64tMjL/Hu9pnN9PAFjoaEUaEarC53BybT9ouQch93mT1TTIEuTmkvRuAtcBMx TxmW3+Lqatbretdx1bqRwfAFC2evtKD5Rm75oTfkyb8/G3nS92ist4hwDRLnYkiv/xftbt XajtVYWrI+CCVdG2JcUdoOq95tRWKwQh5nbwzABBzEtg7XXya2dSDrjpJ73nV/XzuB9kZv 81tsdpDsMYjwrrfXwWuLbWz0AwVhj7j3tNy9mKHqH89BQjO9Ao+lPkF65G1v2RfaXVbIZQ Cr74GuQYMzoZ6h6qaN9W2O29EcYb04lFd6HdJKJf78Had0O1EN6QfY+NTPNdqQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:19 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47D5WnYz9ySy 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: 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_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6W-00028H-9a Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527802109036310 X-GMAIL-MSGID: 1869527802109036310 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 --- 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 Wed Jul 1 15:43: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: 5052 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417493ejc; Wed, 1 Jul 2026 08:44:12 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ9lrklnpxmujpFsmUcNpPTSn5vBJWybrGhbwXnv3wP1+plfoR+IUFH0QkFehb4Nc3nkiL91tMLdQx8=@openvpn.net X-Received: by 2002:a4a:ec45:0:b0:6a1:93a5:cd6e with SMTP id 006d021491bc7-6a3090c0d2amr988959eaf.35.1782920652699; Wed, 01 Jul 2026 08:44:12 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920652; cv=none; d=google.com; s=arc-20260327; b=CpR2buCQVj4oqNU6puYfI04iu//bKkgYQkBz3NGsbt1vKdFjChc9ndIzKkXzEcO/pf +I8QuTTDitdabudU+S7xmjTuKAwJ+1k5linZ7FlYRQSr/PFXWF+oMqMi4quVosW5vKqr uBC2eZW8m1Bn4ojoTXv6eUwVZlY/R9lQMERoLGtvz+ZHfjhew+zuNQtvXhWIDwwkMrvh W5PJjwme7oXiNDoDHsGhpLa8764DCfR7zufwnSXWmU/aoESTFkM8vO5zwKyonhKp5XAv a4L5KGs+6mEu3bKH6YnY/5ywkf9pu+zP7NuGIpC72vOe4//dcUWfZu4gAenOpBOga7+i 7ToA== 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=XjvFFWXGstq+8cEmE43XRjcVU5LvvdJYAJGAm1sIQSg=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=UDZn57e+exJRCbZYWjY6c8+KgkdoZBKVG75D2SRhzmjTmP0fS+UpP1Te30tElAy3Bg U/bK7DGw6Lk9GKja2zs/Pab0gFiojKD7L83zpXP3IeEQAh7R7/VSFqq1dztA/wqAB9lP 7r1CL+8xg/oeiOQtJXyilvk1ut49oomt5OBFmOgWEC+hx1NV4PINlu9F3VNYoBL6zi86 MgGZjgpmzSUibKTS4XshbhhwZNth2qKhD01CWzNq2QVpN+0ybsR3Xd8lC6Wk2EvfTkAM yGO3Xr/Lc2QO0w4k1kdn2VM80Lts6fRW2cbp6DCoPq0BQ/7RkkCrrTqPvRECYYHBN0Bd 6bLA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=VamPrL9V; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=f3G2FMT9; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Dz7sj71w; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=plGKNYkO; 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-7eb5458db7bsi203614a34.122.2026.07.01.08.44.12 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:12 -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=VamPrL9V; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=f3G2FMT9; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Dz7sj71w; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=plGKNYkO; 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=XjvFFWXGstq+8cEmE43XRjcVU5LvvdJYAJGAm1sIQSg=; b=VamPrL9VO7nRCh1oZxEGUVCJLe DtH4WaQGldD7rM0eNlA7AanKksy+7U1iQBmptXCbrEtgrtmPsXZRyDIuGLnwBKT2tyXZOw3l5PkvW VG4fUpTV3YQHVjBWQgjcqUFwi+XpK4NxymEjgLsq44WyRsvb3/HdDGK7LgUe/fncZTAY=; 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 1wex6f-0007gp-PD; Wed, 01 Jul 2026 15:44:09 +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 1wex6f-0007gi-60 for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:09 +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=6DGZoqbajsKIIRV3xHYWleR9i+rmNixeST55EfsPgEs=; b=f3G2FMT9YQ+nKBCwHCzbdsWsxu sCotcV5/xQMJ/yW8oL0+EVegMdnrMXLLvemrSufH2GDlH5f1ZZV+s9C36aeZjG8nCI/SdVTboZolj gvIYyY1kMbGkZcYRWL1dD2Gz7TW4b11j2yxfFaQsHP9MbzoZ8T9I41YEOFB7lPmdopvE=; 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=6DGZoqbajsKIIRV3xHYWleR9i+rmNixeST55EfsPgEs=; b=Dz7sj71wr3G/oXikfysdT0TlEf 0dUJuIfHQfaAGbDa5cJleo48dBeLj6iIxN1+L1zoFg3JKppWSKgzZ8q5qBwxwc50TtBJi9PYh6fVi m8d2kiGV1n6bSHJfyg7ZDHYJnNWYKVjMudN4wGXZz2ufiQJUrm5mNIDkMjmQRrGviMzQ=; 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 1wex6c-00028w-MD for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:09 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr47G20QrzDsC1; Wed, 1 Jul 2026 17:43:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920634; 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=6DGZoqbajsKIIRV3xHYWleR9i+rmNixeST55EfsPgEs=; b=plGKNYkONZGcglu3gIo7XpUKCD1i4l3mPOaDlVPLy/BCaM+3kdoJO7AMIY8gaP+U/SwQ1p 6GJSwG3RTPsvo26bKoh7k5DVFpSiMrbNPNsuv++huvUgvDaZvp0Z2SRaVVbot49X61Y/t6 f1Awm4vDrhUzYzDIUYA58mtJyErzuFK5/ron28jaVyflea9i73mfps3MYMQzqEkelVNGU7 mZmDFhUWAWZnIRtZU5eKv0Wmx9PcTdIYVUQDe924/3ZUK4HU9ohPjTdBLOsgICwYaS3hv6 kBHxhkTRI95tv+9DAAzQKn/eG5PVksSNkehM4sjsExSa+dWcnbBxaRHg9S7eVA== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:20 +0200 Message-ID: <1a0ef417303069c983fc8eb9effcede54c231e26.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47G20QrzDsC1 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: 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_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6c-00028w-MD Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527805906638826 X-GMAIL-MSGID: 1869527805906638826 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 --- 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 Wed Jul 1 15:43: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: 5054 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417554ejc; Wed, 1 Jul 2026 08:44:17 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8r49x/KQs5eOC3KUmjn23B2P9euz9L/cGZURLO6ky1Hki6cMmrGRUBAhwX/YevHcNxive6hdLkBfg=@openvpn.net X-Received: by 2002:a05:6820:8107:b0:6a1:50eb:2106 with SMTP id 006d021491bc7-6a309b04cc3mr1351185eaf.59.1782920656893; Wed, 01 Jul 2026 08:44:16 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920656; cv=none; d=google.com; s=arc-20260327; b=DBQSZQZHypCLwA+A+SMYzLRyn5aHgITtn69jqo1JCDnsLRMcLueuDuPyIVb710HkcC H4F4hCVBS01gd80NWVWoFfCl+jNZphZ2l4va6xG2yxGPZ57M1MQREBcQ7H62oUNR0xHY VSWUu//MvxcI+fxQ2Vh7FVNDBcOHixYTES6SDf2boYyvwhFUeNV5mrA9V4hbnaJsB9DM GG4FSnAeIhzNcF8z+d2bfMJ/67Yfw6A1Qw/4yFNWyixr00LGPvPn9FKC6dvLE0X+ebo8 A+tm03RDglv+XxDL4jPkuvcePQBCnrveBPEDLPfZl6hpCoEsK92tNl+L+/myU//UKDTD xglQ== 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=ffXPDMeVPe7VJaornObUwBx2hPw5wVBPUV4zEl5REwQ=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=gk37909F/QlmyoUmOAMiQfYPn05+L6BrEPuazZLkSE1wcMkNPn4lsuWqb8rlNP4bnW rshXvoKzN8R1s8aCA8lgRtuSIev9iYyEVCUuNywqiWM9a1kOf5lfZKGglReVkIyFRJWs 15E9pWZvNeZsNrY5N7qa3x3n/Ty+eoMvHk/2ZuTFIZt0C3tGw5npXxt23thOkiAnfVXB mJ54dn1W+ibW/xJknReNjO6EAa1iLQYDDmGOd/73gaBaVjBv8/JPK3I6V9bLM+DFUXqj HdYvFGty1NrOcPilhO6vqCb1bOw2F1zVCGNYNhh7esyoh2wd5OvMxusAz7jwdO8vXlSd WAQA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=iAOYuIea; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HYAOfvMw; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="AOiCdG/d"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=OsAFXD1J; 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-44cbef8f1absi13959fac.276.2026.07.01.08.44.16 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:16 -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=iAOYuIea; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=HYAOfvMw; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="AOiCdG/d"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=OsAFXD1J; 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=ffXPDMeVPe7VJaornObUwBx2hPw5wVBPUV4zEl5REwQ=; b=iAOYuIeaAEdLHAy1Q2OPpWdPFM LOfFTiUt7ymXP1+g92aVERRpkEKcXIMYSizoi1dUzUFYYzK5xqnjQ13k7nW14s3H+pxPRuEDRWaAj o++wpTQSHYdCvqbv6qT5lrmfRGzh//LisleRsNdh2Dvwfha35KJpdTOtdkfj7wPyuZlw=; 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 1wex6i-0003lQ-Il; Wed, 01 Jul 2026 15:44:13 +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 1wex6h-0003l2-1p for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:11 +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=zpjCjIHg5FgiiJgF7V+LiVbte7S7mLXmklFHZu2J6ts=; b=HYAOfvMwJinFzBH2XnpDAT8i+e n68J7SKpTSnOApubl6KuEMZ7PecbHnXWYS3gyBgPIT4X5KLNpOwCL3jb0ZWsX+0cQLDOZQqfJWDZe NXwbEk6GBhW7Hb/CWIgypc0qKNgJwTKb7P7wpzMDe9nQGiZC0yiooPwRcx0G+tGETxdw=; 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=zpjCjIHg5FgiiJgF7V+LiVbte7S7mLXmklFHZu2J6ts=; b=AOiCdG/dK6xH0H782Q3wURZxhx KQdIaZGC5JI0MeWw4x+5zLqXAfRhRcJe+Se0AxKFUA9A68OEuryn7rFVp2X7/N6IKyW8cWVRboLrH 2Gn/FJhT9rbCgMoXMqJmUSkGiJOxxpIutxNijWenv2bsKYajN8Y0NCKHzmGVy4RTg0OM=; 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 1wex6e-000299-9A for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:11 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr47H6VJzzB0B8; Wed, 1 Jul 2026 17:43:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920635; 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=zpjCjIHg5FgiiJgF7V+LiVbte7S7mLXmklFHZu2J6ts=; b=OsAFXD1JC1z9gAFmuJGR47hVkWhWvXAQz3zwlz0gCXGwDjqJiA/qJdIZenaMfzASJ8HKQL 4nidUR/oUtFs5mEksb4y2stvXr/m3WHVz9znx35dckpCQbt+NmOG6OXOHksvAbSB0s103g ikw39foWmD+R1oyAj321jftjcU15EEC3P45BCzoKoEE0zV6yUkyTboNbLmF/eUY2zsH0E6 VoNMGogyM+5I/W1eiqKjs+qarz8WDXTScn8Tr/zEsThiAQjgRghGyHOIQG55wx4PaZjr2I a0nDPAldPd5cxS/FDIYvGtJQs3pq0IkXJsO7Or+JBeaIOipM/4bybbrGJTRoTQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:21 +0200 Message-ID: <1c59dc2e7d63b93d66427eab461e4b1ea7dfcd20.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47H6VJzzB0B8 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: 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_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6e-000299-9A Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527810545693362 X-GMAIL-MSGID: 1869527810545693362 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 --- 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, 119 insertions(+), 71 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..ae4b87a2faec 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,11 +150,12 @@ 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); + struct ovpn_key_ctx *key = ks->encrypt; unsigned int plaintext_len; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; + unsigned int tag_size; int nfrags, ret; u64 aead_blocks; u32 pktid, op; @@ -164,6 +165,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 +189,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 +225,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 +235,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 +251,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 +264,17 @@ 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); - int ret, payload_len, nfrags; + struct ovpn_key_ctx *key = ks->decrypt; unsigned int payload_offset; + int ret, payload_len, nfrags; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; + unsigned int tag_size; 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 +286,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 +305,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 +340,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..d3633cb4e2a9 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -110,6 +110,7 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_crypto_key_slot *ks; unsigned int payload_offset = 0; struct sk_buff *skb = data; + struct ovpn_key_ctx *key; struct ovpn_socket *sock; struct ovpn_peer *peer; u64 aead_blocks; @@ -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 Wed Jul 1 15:43: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: 5053 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417542ejc; Wed, 1 Jul 2026 08:44:16 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8Rb0kslhAr6ImgaTn+9uYvcgiy10Ywa0otJ3SW6aCltTk/QlLDdAAQZG24eU+omy1yIIm3C+S2/+U=@openvpn.net X-Received: by 2002:a05:6820:1512:b0:6a1:50eb:2110 with SMTP id 006d021491bc7-6a309b831e1mr1157222eaf.69.1782920656036; Wed, 01 Jul 2026 08:44:16 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920656; cv=none; d=google.com; s=arc-20260327; b=og5BBiHjf0ZO2ztAfASs7SUB2hkx+mJH6MgvK41G7rSdcOszAWdxD8Gi89aJ3mSBWz JVviJ4WuqxHo2skg6lc+JZCoMxjsx7B1KsJ89EnKRayNxhiaLpFtS+nbTn3m7McnGopW eizbXQlBwT4YRHOdQaGijmuJ9OD6Zp4doSyh3fvWuhxICSM5cURR7IPjp4V4nzblHFt2 6IsF+dq6B0FRtgiULyg90j7Nev5ubDo9+/GdEXYjIrfhui+6/WV60wj6tBF5FbmPVOzX P3Fuh1QzbK46/+SEb7qqF6cX6ZIuJOIyMsmb2hSvMUE8N8xja9VN11tAaXSref/SyI6f LkXg== 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=jgPAGbOng96O5oMSIwOu34se4HEnKrnjNxQewvTkY1k=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=Ls3dB26pL68vwVqpAgx8gInlQdjCBtgC/ut4jOA9zn7TiIUNxr8fkNH95GqtbI+MP+ IS1VKP+Vz1xR12adhWCTS9kdjXvRsS6VA8+CSUq0VaFmiZdB55jW4Dpmx5VpnjEJg6YC 198PH6GDoWDf8h1gaaKchjXKTMKnfTjQPl+ruhhQlEy0nkc/22N+mqynXapQNVdgKKev /CMWX3PXGFtzZQxoEYRnhQk50GSb7YFwfhHUM44b+gkDui7g/AWCPHDhw2kOWZ3/Ihl0 fOEOSxHQI2/0DHtfbE0gbZNYiMYMuTze8WzMpV2G4mu3DQZqVrXCtmVrpKEdhjIEbCZX OG8w==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=HAxrkTHC; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=miGa9GiZ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=EQjDFcHE; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="K/67fN9g"; 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-44cbf00534dsi10152fac.318.2026.07.01.08.44.15 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:16 -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=HAxrkTHC; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=miGa9GiZ; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=EQjDFcHE; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="K/67fN9g"; 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=jgPAGbOng96O5oMSIwOu34se4HEnKrnjNxQewvTkY1k=; b=HAxrkTHCxxO1RAFGFRG3tMhlOb fmIX0RiSPmoweg2XPvlRp99Rwt+Jfc2riJls5QGNWXnBFrSV7/amY6zKR+QnYvO8RZbOcWsgxUei7 /amw0GGJBKKRdZ/HMVVxtrlQIBOh52lbyLytgfVqBlAVxoiVol713o+EHfke1H3UbSwo=; 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 1wex6l-0003is-HI; Wed, 01 Jul 2026 15:44:13 +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 1wex6j-0003if-Hz for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:11 +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=8eHaoThz7oG4s8Vip6GEI7g3K50ddl7vSdnAYqr+Fgw=; b=miGa9GiZXfd2HBBHDC4g9Ho9zn NVrjSF8qMCoCNh1bzRe1Bs509V58z1IqKKMo2PjzUPINPl0OJvPra9sxKruDPsuXbFo8qYN/MBFng rJu5eIDb/WTD1YwCHxyEUr+S2XnZt5tKJXnhttxgvieQnfS16eTxCyAgFJLHKoemuc+k=; 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=8eHaoThz7oG4s8Vip6GEI7g3K50ddl7vSdnAYqr+Fgw=; b=EQjDFcHEBt4GPPBvF4eMDq6B15 SfBBoG9DQEGGLFOE/G0C9g0JxMaUKHExvDljVRjdmJ+azeL9JpiFvXMqphDluGmVGZOLL2/DUReRQ OKtROgsN4aw70fSK5L++lg81xlSL6ruyW+8pYCotJnbBPZBa+uwcLFPXl7twXXXjYzXA=; Received: from mout-b-210.mailbox.org ([195.10.208.40]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6f-00029T-LE for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:11 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-210.mailbox.org (Postfix) with ESMTPS id 4gr47K2wsxzMkt4; Wed, 01 Jul 2026 17:43:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920637; 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=8eHaoThz7oG4s8Vip6GEI7g3K50ddl7vSdnAYqr+Fgw=; b=K/67fN9gtoAstyxzjl35CpKh9krAGaF9Us3J5RY3vU06NKzTNKbSXpjQHdKPbov9knl7Xh bX67VUYK98AJV2Gv38CbE7GHmrh3zHJ7f/Z8w5d8z68eSDCXIumYWRAPS63ucaxKNgQ1xo Awv7qg1b/uBTISEXwWntbY0dMQIHNxUA7gRurUrcy4JcimHYssAY9tmBJB0mrx/8JnGnkw ftotMJzhkwiHPydGPZKaWZph4rwiUCQqSlP6PbcANi1fHacmXG7iiNY3+1IfHHf7ZLeh/N JDg0gxt7lDvHTu7Jjm3vPJSGKK3P0lgdtzIANSRVCTjyIKEj12ITiEbL+yJchg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:22 +0200 Message-ID: <4ae05d37fc69c2c29d83f824616b1aee6315ab6b.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47K2wsxzMkt4 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 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_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6f-00029T-LE Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527809887715264 X-GMAIL-MSGID: 1869527809887715264 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 --- 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 ae4b87a2faec..a869545cd145 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -150,7 +150,7 @@ 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; + struct ovpn_key_ctx *key = NULL; unsigned int plaintext_len; struct aead_request *req; struct sk_buff *trailer; @@ -165,6 +165,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: @@ -264,7 +270,7 @@ 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; + struct ovpn_key_ctx *key = NULL; unsigned int payload_offset; int ret, payload_len, nfrags; struct aead_request *req; @@ -274,13 +280,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 d3633cb4e2a9..2b57964679c7 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 Wed Jul 1 15:43: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: 5055 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417594ejc; Wed, 1 Jul 2026 08:44:19 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+U8qfmoWaC08Ds2/GEOS2LSUQ0hisj3XRakljppj4xz8eF+q0/hmwVxFBbcRf+MMqH8AyjfcVhHUU=@openvpn.net X-Received: by 2002:a05:6830:438e:b0:7e6:d7b3:76f4 with SMTP id 46e09a7af769-7eb3caeb505mr1134369a34.0.1782920659184; Wed, 01 Jul 2026 08:44:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920659; cv=none; d=google.com; s=arc-20260327; b=X2FBKUuJkamlUVZtbvMlfd2TyPqbc8MgaooCz0c4BIdJBCyn3ue9z1LzzQYYLQg3S3 vPWIdyKqGaIpuab6+g0Dgxbsni1FJRC5Cz2JclzEmXg+dzLA8m6bdbRKWpSDeERfsysX Os2S9Q1hGNz43lu4fFoFK3YX3W+I8ovWUm50WGPDQGtvai7ViDlN/zYRW5n+5LKm4ESu KszmaFAz+I0Z/dMv9P2ybHtZDuvvClLBpFQWewgpFWdIq6ki0KlFc2tabYQ+xoiMtGly DurFqQ30DhubEntZNkO7gbC3mMfsIXWxIdccPYJ7p8w4CXyw7E5ml+Bb1PJgYMspztba MUUg== 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=7vSEMbo62MbdQpST6PmAHAgX26CfzSjtGRvRgl9HvME=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=D2d/iqG0ykiJRMj2jwMtSzDqIFCzYBJJWvD4KWSUCWsJcpLeMnQM3aBLuAX1sAahuN qW9u2rXr3+RV0rSNWAMzDTG2ANgqX1JSD7MPMTqaA5CbUgfx5PXvc6aqdajwrE6Sxchg +hLEpRAP41p4A/DZZsBXf2fYiWKopQdDF5YYUeYjLbiaB4qFgPGLH7Ijiyi10N9P+gO+ HlFsF9zeH2nO2Xi+1aL6Ay+qzkrpgI22/6iNkDFXnkxbQKlLysLlTq4jM848HzSxwEH7 SZsL0DNOWcmkhT9dr0gfOs2+2KPgn6yU5tI8y/tDY2Mvo5/fYzUzl30VYqHERtgpJeXh cEWA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="MSig/xdI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=BINsP56h; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="lg/RsaU+"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=RY+GzkLx; 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-7eb5454501bsi212646a34.97.2026.07.01.08.44.18 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:19 -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="MSig/xdI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=BINsP56h; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="lg/RsaU+"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=RY+GzkLx; 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=7vSEMbo62MbdQpST6PmAHAgX26CfzSjtGRvRgl9HvME=; b=MSig/xdI5sJDBdKtufFReI3n5z YR52J+7mP58+csYpk6Rw97ERyu/wP4UOlKUN9r1c+B1lZffOdS2wMjC6cBI4HwcPJhocPv/oCS00P Iurt+JmhvcihhyfnH2+tAxdQ/wBLqg5yXZ58XlEhATmeeYi96GNWKvPh0NlITDtsV/a4=; 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 1wex6m-0007hm-6N; Wed, 01 Jul 2026 15:44:16 +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 1wex6l-0007ha-1H for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:15 +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=kgkMZHpHBtlY+qd0MNtNplQ3d5zEvo3yx0fjS0KIOcQ=; b=BINsP56hmM/Rsnug7PvV6eEZNS ybblG1iIGkvkgK2mEfVllOcv2yWdG0iY+WhPB/B6Zl7NjtHkNUhsln8DBoum1T2ZhvUCjmmJNizxV bCi4MS90p2SHazr5KI3vHQRchKgEzsAH2aVuM5065uFDOBG7AVng0h1bG1BGDDO2PDQI=; 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=kgkMZHpHBtlY+qd0MNtNplQ3d5zEvo3yx0fjS0KIOcQ=; b=lg/RsaU+RtlFO6LvqYM6IcbIo1 cgw6ASY89cDjdtiqulIzQipfu1CgsDfXNjDijOFk+4ZSIGmN5lyKXqOBwe7qdItTzCHKqJjYCo2Ka erZLveVc5RlPTsUGpRU8PG9hR0k6ubtiw///5+nrPPMsJw8c1WzZsJbnV4F1CYhrN1hQ=; 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 1wex6g-00029g-Ud for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:14 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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 4gr47M0813zDsCK; Wed, 1 Jul 2026 17:43:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920639; 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=kgkMZHpHBtlY+qd0MNtNplQ3d5zEvo3yx0fjS0KIOcQ=; b=RY+GzkLxhXLHyE8ysSvUDmOBPWYf4LIvelRzvWi3Cy7JLOH4PWTjqItuNaBfjzzh8J3Yb2 vN42VsQU82y4ZgZYdzNW0RXvJn1fQgdpJPE/ssE+CM0msT1TI2eS7eMZnUZ7Jk3x+R16OH cMMkNrUIRfUqg5ULz76o3yjHOZmFdfgJ1uzneHrss8w0zFqAGziTQucSW29lVNkzeMtODX 4Y0T44UfbV1nAlmLypNDArV2yKFC+jic/uQ/669OWHQeIhUVruTG0Lw2KFyuFKplz3C+Oh Ckz6AP2sSvezKgn0XTAYMbR0YMW49r+BTLba3XdlRKdmRfL9buXLRQrGzlUvSA== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43: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 Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6g-00029g-Ud Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527813387371398 X-GMAIL-MSGID: 1869527813387371398 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 --- drivers/net/ovpn/crypto.h | 3 + drivers/net/ovpn/crypto_aead.c | 32 +++++---- drivers/net/ovpn/crypto_key.c | 12 +++- drivers/net/ovpn/io.c | 9 +-- drivers/net/ovpn/pktid.c | 13 ++-- drivers/net/ovpn/pktid.h | 126 +++++++++++++++++++++++---------- drivers/net/ovpn/proto.h | 8 +++ 7 files changed, 142 insertions(+), 61 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 a869545cd145..11b1e490c3fb 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -156,10 +156,12 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *trailer; struct scatterlist *sg; unsigned int tag_size; - int nfrags, ret; + bool pktid_notify; u64 aead_blocks; - u32 pktid, op; + int nfrags, ret; + u64 pktid; void *tmp; + u32 op; u8 *iv; ovpn_skb_cb(skb)->peer = peer; @@ -228,11 +230,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)) @@ -241,11 +247,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); @@ -254,14 +260,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); @@ -336,7 +342,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); @@ -360,7 +366,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 2b57964679c7..27c61947ebaa 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -114,8 +114,9 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_socket *sock; struct ovpn_peer *peer; u64 aead_blocks; + u16 pkt_epoch; __be16 proto; - u32 pktid; + u64 pktid; /* crypto is happening asynchronously. this function will be called * again later by the crypto callback with a proper return code @@ -140,7 +141,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 +151,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 Wed Jul 1 15:43: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: 5057 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417615ejc; Wed, 1 Jul 2026 08:44:20 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+zg+QkLltOF1tsymevlp93NhDgNNytl9uYZ7eZXiVoa+/4a2KP5Yv+5V9vygJzzKFPxnkneP+fYJ0=@openvpn.net X-Received: by 2002:a05:6830:640f:b0:7df:5fc:3fd8 with SMTP id 46e09a7af769-7eb50314acemr934691a34.1.1782920659919; Wed, 01 Jul 2026 08:44:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920659; cv=none; d=google.com; s=arc-20260327; b=GsMQCiT4Am+Xj5K8tDTQdbRKc2iBu34gpjlTXUQMQ46Sx+N/U+7srVXB2RW+8/xGlv Z/Sq0bAvgXM0oL1GmGmxhRpmg3SgCIKQSxHfNhDPmH6vgIUqDWrDUHYp85ya4q77kAEE g46UgM0pbya9U9FU5sWilTiv9dD08Wo/uer1Nx/sgU48uNBf1b54KSFW2HLjcjpu/5yH vWQTchUk2SR/tLy7VGJb7BZoYDkdq67rOGAeom7UDpoV55Eazz/yEKYHaUfWXmpMBS/7 G1LuVdiX8lKStwcyHOWWTPg3ZYxW+097ACLX63K59AyerG1SDgDZFAlSW/7XRVo07TrN MY+g== 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=TVe8EzupWGmjWxju68fT9S3SrzLENmCpmwCPRSwfCmk=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=pZiEpwvGtvuh1xjOiJURVr6fxLy/dcAQHDxWeMULWuI92G1k4AeeMdbmYNreTbnzF/ 57yatPe0R43+JyyfmeH6RWcp0vWiMuzsk+0JF4YFA9oxesDSPgiuFuXRhIJyp35RAD6r vlqE1qBuQMKqr1HShNJRVvj0ddcjiKnbPeLjf/Z/i6Z/W3Y5DXVbHjQFN1i42SN1SKNJ A8EoCNgFhyZEy3pdNQD4C6I7nAAzxLUwEHioumDbeL09TRqAlerymLTJ+ZjE/Eks+AET 8Lj8v7FWptKerp1Sm3VhT09J1CJ92kid2ja8ozeRJAebm7HsR1TBoEb1Yiq4TEfq+A0r UMHQ==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=P9RWBfXv; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=D+qwNK1b; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Tz91Ld1P; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=cKogFCrM; 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-7eb542a164dsi260789a34.12.2026.07.01.08.44.19 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:19 -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=P9RWBfXv; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=D+qwNK1b; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=Tz91Ld1P; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=cKogFCrM; 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=TVe8EzupWGmjWxju68fT9S3SrzLENmCpmwCPRSwfCmk=; b=P9RWBfXvdiETGorNCCL57jjN7W i/vQgyoSmLj4NP324BPAj/k6OWspNSyD31LaXuN1FOHMTIpcqwBa/TkrnJNV77A3+w9SS9kfXq+b6 abH5bPFIEzc0u6APKGfkVPp0k0Rhdl5jAsTsAR789O+lPgzgRSQNyoH5tZOHmXHo37lc=; 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 1wex6p-0003jc-7m; Wed, 01 Jul 2026 15:44:16 +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 1wex6n-0003jM-Hb for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:15 +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=lz0pAODus2YAv96OreeOjKbZ0ltUXgkq41rr6t6pGgg=; b=D+qwNK1byq4EJ7fILkL73X7Tjc Yy1Sw36YC/FpeEz8mhmnHl1fL7s9E5qow46rfuQ9BlAHoQeOTrHAaG69NmuIxI08nDO/QFl2IYSuD 4oJqd8d7CdsLA3kYOb9pafB5JgK8op1ZvEDqvTMCOguxYbCn0tizfj5rfJzMQcvATYzU=; 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=lz0pAODus2YAv96OreeOjKbZ0ltUXgkq41rr6t6pGgg=; b=Tz91Ld1P353Z55RzOuUQdJfLdW ci+oc5gnQpDSmAS9xS1XIo8z549B/3V2XYLK3gOd5FyFpSyvAyLmtmnoJLi7IrpNAClfZ0DKQKVkd Nfee8QjLyl14GX49jibEeg2T4XRGvXPXRPDAclpHWpCJZHjgDU9BSUKNggAzOaGjzvko=; 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 1wex6i-00029r-S3 for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:14 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr47N4d4Dz9yTs; Wed, 1 Jul 2026 17:44:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920640; 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=lz0pAODus2YAv96OreeOjKbZ0ltUXgkq41rr6t6pGgg=; b=cKogFCrMKLk5u37shzODf0zB3rV2uW9+gaZW9mqeHr6dxJ9jMmeMy2Bcps+oo1E9+2FvJ9 5yCzOQWV24U62V9KTo8o41VSCrsBqO9V4jDIeddWkJfr5zT7kA208BHCk9gn9xzXUJKAbE kOUnJqfZHV9nvO1UPHVPwzUQEPQk2X/TuTgHPC52QBgUHhEkf0fjaBTLsnA3RmQ7o5qFbB E5/8N3UqS7jLYc5cORopdtowsbY2fmOUzHyK/RxPaT43VfEU3j5UYhjxI2PG+eJCO2GE2s mxYiGCjV1qTieiCqJvQCz6hmHLUKJ3hDuzPAZpXcx/OiEkKj4yoLa56p+84iEQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:24 +0200 Message-ID: <2232f95098268c00dd6ce56e346e02fa0b111a17.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47N4d4Dz9yTs 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 Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6i-00029r-S3 Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527813808162802 X-GMAIL-MSGID: 1869527813808162802 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 --- drivers/net/Kconfig | 2 + drivers/net/ovpn/Makefile | 1 + drivers/net/ovpn/crypto_epoch.c | 200 ++++++++++++++++++++++++++++++++ drivers/net/ovpn/crypto_epoch.h | 55 +++++++++ 4 files changed, 258 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..46e1a0954b88 --- /dev/null +++ b/drivers/net/ovpn/crypto_epoch.c @@ -0,0 +1,200 @@ +// 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; + 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) { + size_t todo; + + /* 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_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; + + if (unlikely(epoch_key->epoch == OVPN_MAX_EPOCH)) + return -ERANGE; + + if (WARN_ON_ONCE(sizeof(key) != + crypto_shash_digestsize(epoch_key->shash))) + return -EINVAL; + + ret = ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_UPDATE_LABEL, + sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1, key, + sizeof(key)); + if (ret) + goto out; + + /* install E_N+1 as the PRK for the next iteration */ + ret = crypto_shash_setkey(epoch_key->shash, key, sizeof(key)); + if (ret) + goto out; + + /* expose the next epoch only after its PRK is installed */ + epoch_key->epoch++; + +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..7da31c2acd44 --- /dev/null +++ b/drivers/net/ovpn/crypto_epoch.h @@ -0,0 +1,55 @@ +/* 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_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 Wed Jul 1 15:43: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: 5058 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417648ejc; Wed, 1 Jul 2026 08:44:23 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/ULApDc4VczSs9RssLUIl31SwLRr+SZJ/uqXLQ1fH7SLPIhliH3fju0pUcguqcp+iNXZuAMyQHaF8=@openvpn.net X-Received: by 2002:a05:6830:dc3:b0:7e6:e349:538e with SMTP id 46e09a7af769-7eb4cc18722mr1087441a34.19.1782920662806; Wed, 01 Jul 2026 08:44:22 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920662; cv=none; d=google.com; s=arc-20260327; b=b6qCoiMPAdFMyzn2apDoGakMyXXIN51tujbZLqqo2A9vDBviyptrmWbgQeTVpaVVGq Et2Ovvct/VDPclrevmj4y+FpBKxAOgB30VXO+1qC51UK564Gs3NeMe4C0oOFQ9B6rK5P 70lRGmKzVvoqL2DuhSKDrcIjXK4MXYk6v9ZiqnAms4BtBSYu8V8au/1ECDpDG77T6NXA WbVCMuTKu9/3bpI0N8dlc7QpN1JWHdhQjg/mvEm3s2D7zP6Pt6brfOvoNPZugpXydvsL GLPZESiWKMOlX9vwMJjxGMDHjI5vEJFybp9mMTPrOK9vAEDvkROUd0kSKvtkPe4lkZZ+ RauQ== 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=xTmx8zzXfLTn+0C90+m26IqXfIsuqitx0XfWpmBotrI=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=MOOuZeiWa4Dt6gVK2hW6Xfmkji+LtgrbbR20EdUGSvqgfHliZzPNzWpDdidDIgYvRO 0QI+VioKY/dFIp3y+irmN4ajYj+h3zwBemjw2aZl++x7uW1Tvr42yPAVYD+99vC9+7Cp qsDejVxfgxjcY77mVrAipbMeTFcTx+5DKT0gF1JR1JpDUU4Z3+pm0Ip29bTxKKaf6a7v j7MRxlWJhYMHkBsdx5ELc2pgGrk6s3BgnE4vSnPDGF08WAqZq9+5kVqC2Fs7B0kkcN7u ac4GmAe9wVBtQnFmzcc10IMcQjScLVXqG2PbVP68FbNp6b0oB+7ZMW2xmJj4+hI6JXvu FBdw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=GQlL+JSW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=Cj3tWZtG; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=BJirelnI; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=iluh6XFW; 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-7eb5454c0c6si208128a34.107.2026.07.01.08.44.22 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:22 -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=GQlL+JSW; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=Cj3tWZtG; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=BJirelnI; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=iluh6XFW; 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=xTmx8zzXfLTn+0C90+m26IqXfIsuqitx0XfWpmBotrI=; b=GQlL+JSWCHiNQ9AcO6sgOeKaWW zs/XacpF5T45wCcvkhMRmHzGDUTh55ypdRDcgMByFvsgMjHXtZ23Rh5JGoIEQG7rgM5zDrwVcYJvG +AZRYc+k1CQDm6q/oSbENciPCvtB7n7/yKRcdUtKhZ7WKO/lgU/TudHJ+Nr4P4MnIcQI=; 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 1wex6p-0003mu-6t; Wed, 01 Jul 2026 15:44:20 +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 1wex6m-0003mC-8i for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:17 +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=bUJN8BybUkmsecHdC6/M1i2LQj0Lx1eVyDGYfUtnZN8=; b=Cj3tWZtGXcR3EXucBmExNSSPsi iZvQmH86p6yvRpjHYeExeTuOAAhdJ2uaEtRrC7qWj9Z5RgET9kYywEzbBamzeT3Gjfnehx3jCQSra /yl0KTX1xhRHBLGCR/LMdgxMKGiBkZGIRR+OajZXnKbJ/3mZBFwXx2cMer2R68EwQTP8=; 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=bUJN8BybUkmsecHdC6/M1i2LQj0Lx1eVyDGYfUtnZN8=; b=BJirelnIQWBeagV3KU6sd8sOGu 33Q5Op55SEQ739OeO73Mvrfrx3TcudW6BKtCao9YDxsNk5/MzufSugsFAGEeqVYu+L44/Z6uQJrm9 j+OyMk0RMqyNhvxSPs/c8qJLKNSmo59Tk0hgptTIggPmwCcTq3xAABwDEesjNscQ2cSU=; Received: from mout-b-112.mailbox.org ([195.10.208.42]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6l-0002A7-5z for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:16 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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-112.mailbox.org (Postfix) with ESMTPS id 4gr47Q3XdpzDwRT; Wed, 1 Jul 2026 17:44:02 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920642; 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=bUJN8BybUkmsecHdC6/M1i2LQj0Lx1eVyDGYfUtnZN8=; b=iluh6XFWO8KC2FrIWg0UDIUUABXLhxkl2hEaVsryqDNr85XdJ2JvBPAw5VOV6Fn9QUnjae 3IHQRUp9Ao1W1/8zWAKFdX5cRlcZ+M9niVfm2afOUOfrvX+VY/PX3jfb32wIQS2mX09qzC RqzqFvqGgp7jDZZEW3GojFs6tvPCQ+kTPll5476WZrP7kkml1J/6QlrFaNontRPLWShYoS ywVjtojbCzPteVFVn5mFTGJCIYrS8pHkhrMFWyeJV4SQhxuLQpO79N07sRFgERx+97/C7f gd4UzK2LshQK0f73rhW5rPD/oBH024w+XjYz/AGZbtfNPVlU6qLJCylxSIvqiw== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:25 +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 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_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6l-0002A7-5z Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527816720320901 X-GMAIL-MSGID: 1869527816720320901 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. 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 --- drivers/net/ovpn/crypto.h | 52 +++++- drivers/net/ovpn/crypto_key.c | 325 ++++++++++++++++++++++++++++++++-- drivers/net/ovpn/netlink.c | 8 +- 3 files changed, 362 insertions(+), 23 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 8e7235f41960..2127fe4c454c 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; }; @@ -193,4 +229,14 @@ int ovpn_crypto_config_get(struct ovpn_crypto_state *cs, 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..6b193c5fd7e4 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -8,9 +8,12 @@ */ #include +#include +#include #include "ovpnpriv.h" #include "crypto.h" +#include "crypto_epoch.h" #include "pktid.h" #include "proto.h" @@ -100,11 +103,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 +115,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 +124,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 +143,253 @@ 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]; + u8 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) + return ERR_PTR(ret); + + key = ovpn_key_ctx_new(encrypt ? "encrypt" : "decrypt", alg_name, + cipher_key, epoch_key->cipher_key_len, + implicit_iv, epoch_key->epoch, encrypt); + 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 (!schedule_work(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]; + spinlock_t *lock; /* selected tx/rx future-key ring lock */ + u16 replaced = 0, created = 0, free_slots; + struct ovpn_future_keys *future_keys; + struct ovpn_epoch_key *epoch_key; + bool full; + int ret; + u16 i; + + 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 */ + for (created = 0; created < free_slots; created++) { + ret = ovpn_epoch_iterate(epoch_key); + if (ret) + goto err; + + new_futures[created] = + ovpn_key_ctx_create_epoch(encrypt, ks->alg_name, + epoch_key); + if (IS_ERR(new_futures[created])) { + ret = PTR_ERR(new_futures[created]); + goto err; + } + } + + /* insert generated keys under the selected ring lock */ + spin_lock_bh(lock); + for (replaced = 0; replaced < created; replaced++) { + struct ovpn_key_ctx __rcu **slot; + + 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]); + + /* 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]); +} + +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) +{ + epoch_key->shash = ovpn_epoch_init_key(prk->key, prk->key_size); + if (IS_ERR(epoch_key->shash)) { + int 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 +410,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 +432,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/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 Wed Jul 1 15:43: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: 5056 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417611ejc; Wed, 1 Jul 2026 08:44:20 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8e25ZsjjzCLUWx4vVXUvgH7zcKgWDf8bCp8MlJ7rYt/LtGsw4GVyHVZRcCv3TbpxEVbnQICMfOQo0=@openvpn.net X-Received: by 2002:a05:6830:3744:b0:7e6:ee1f:96f1 with SMTP id 46e09a7af769-7eb48aaebcfmr1173807a34.5.1782920659827; Wed, 01 Jul 2026 08:44:19 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920659; cv=none; d=google.com; s=arc-20260327; b=Yg8079q9nvolFU5BeUCQSv53IjEPK2ATjIA4MPqkercCTGREmI/w08l7Q6rsDwq6NA /OF8WRpQtRYBFcYH6rIFecdg2Sc6fD2+Z7DExC0YOwH10QhewpBAyM9skD1yAMaeou3n TFGQ3QPV1LPazU1SiMaezkFktm5rOMuM64cnB3xDiIO9uvkcyk9wg8EuOrGG30/ZslmC SOw4qqScGuoZ090HG5gpxuPamSKadoYHaoNeF44/Coi4VaUmCeLKrDFyXAoybR/gdAqr jaRLRHDxPOEJXrlJqM1K8Sq+R25xRmY4abtBIndp/05aebFv59XsJI9BaZngLFmiKzms GR7w== 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=YWBKrLcMUy9eYbgMoE2PmS+fHcYrahQUs+lUHljXtUg=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=c1f5ni612AjelaDZCBa1j9BPrq/ucRcI2Q8COaatDS3H4sTYrn/b5Qjs5hVHCF7+tQ WfhCapz8PA+BZPZ7FFy9V+lp4rjGz9u6EhzqAXxxwCvDosk+tiQax4cHvJWwTzYE4dG/ oq8dGRWEfxmEjxzdp4zpAffseV+fSZHcaNKxOLllTQ6bFVLF+lsTvZx8l2uK+7zEknkZ YCYppogbyVLEaFYR/o94LzB2Kho+lY1CjWUwEGuevcc7EOQqr4I91b6cwD0SFu1L5L3z n16rr8PrKXvYQ7+OrZVBAZBF3hmpeQcjppwVrWRFCwWrx2UV0L2gSXbmYFknYR+q4Z86 Ioeg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=EIY2LS21; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=cloxExkd; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=nHwPEYfv; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="hqnQ/mj9"; 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-7eb5431e7cdsi241954a34.57.2026.07.01.08.44.19 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:19 -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=EIY2LS21; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=cloxExkd; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=nHwPEYfv; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="hqnQ/mj9"; 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=YWBKrLcMUy9eYbgMoE2PmS+fHcYrahQUs+lUHljXtUg=; b=EIY2LS21EC/NDbhoiOLGd9YDDi jSIK4G5g11EGKQI2eI9qf7l7lOUKiUOOjHSNPgeHNdVAuDfW3r9lZaTxE5hUq4Pkxo5xcNfa7rECL Kd58XPZtUHJkvyQ2YM7N3KLBpsDM5+x2z6DqKPWdBCoA+CsqXVqNEriKlsOCsliGibZc=; 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 1wex6m-0003k1-1r; Wed, 01 Jul 2026 15:44:16 +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 1wex6i-0003jq-KH for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:13 +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=oL2OPMnrEulCRhUIUI+6b6YHDZAgI12ZTCM7wnLe9xQ=; b=cloxExkd+8IP+G58kkPFbO4KjV 5b43NkneZbxMrvzHjBPXcl6GnYKHeTw+WpVX721To1f8y3cSlyDKXxd3ck+xNHdEcW1T4GsdL3eaa nY17u8g2MkN4gBK/B7NB9OLh1/ffulOlq+jFK/RW7kfUZYjgEjFQIF+5lp4YrZyt+Ycs=; 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=oL2OPMnrEulCRhUIUI+6b6YHDZAgI12ZTCM7wnLe9xQ=; b=nHwPEYfvz7s8HEFFPxs9gomJ03 ew59fEk1rCzUh8t0Eo6EYWupi5paQk+p00GEzymHeVC9jwCaa9Q+ZSDZPEaZ96T88RzFTeVNfZqoE NuBeZd70M83+UcKtNuFq0iN9sXOR+SjLhV3ZRAg/RXE9+He5+vMd6ims1U/VJjD9l/3o=; 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 1wex6h-00029h-C5 for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:13 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [IPv6:2001:67c:2050:b231:465::202]) (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 4gr47R74kXzDsdy; Wed, 1 Jul 2026 17:44:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920644; 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=oL2OPMnrEulCRhUIUI+6b6YHDZAgI12ZTCM7wnLe9xQ=; b=hqnQ/mj9Pl5SAWv/NoPzjXQF+b+ocWxFX5He9IyowMt7tnFuPIQvgpHzmEfMaBJ/tGvSKw UqUxMng9yKmtIHGmMplB4gA7e0FNgoTlT9Ed7iuxiGRBNOvGQ5XmfUorVz0POasvJlw55T pNk4JZvIlWWTbJZHnPWZZt6g7dLypYbFQadPwK4BsvkzIEbnvamQ8pDBXEY6wyKr3gB7Dd 8c4l56kGx8ubYY4LdDrrrHDWIA7cxuSVjxLdayD4CMdOBMDhKKikUjXbIVV3G4KWLhTECm wbeZH3Rl5yU+AP/cLbNMpVh78Rz71AYIbNweWz0gb1s3P8Y6Tq3QxauoixV6NQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of ralf@mandelbit.com designates 2001:67c:2050:b231:465::202 as permitted sender) smtp.mailfrom=ralf@mandelbit.com From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:26 +0200 Message-ID: <6536c6ad31267534d8daefb08b44f904ba7112da.1782919654.git.ralf@mandelbit.com> In-Reply-To: References: MIME-Version: 1.0 X-Rspamd-Queue-Id: 4gr47R74kXzDsdy 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 Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6h-00029h-C5 Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527813501398431 X-GMAIL-MSGID: 1869527813501398431 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 --- drivers/net/ovpn/crypto.h | 2 + drivers/net/ovpn/crypto_aead.c | 243 +++++++++++++++++++++++---------- drivers/net/ovpn/crypto_key.c | 8 ++ drivers/net/ovpn/io.c | 28 +++- 4 files changed, 206 insertions(+), 75 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 2127fe4c454c..4ed44bab031f 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 11b1e490c3fb..4daf0314b046 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -44,11 +44,6 @@ 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 @@ -150,8 +145,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) { + unsigned int plaintext_len, payload_sg_len; struct ovpn_key_ctx *key = NULL; - unsigned int plaintext_len; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; @@ -159,7 +154,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, bool pktid_notify; u64 aead_blocks; int nfrags, ret; - u64 pktid; + u64 pktid, seq; void *tmp; u32 op; u8 *iv; @@ -168,6 +163,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; @@ -175,12 +182,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 @@ -188,14 +202,19 @@ 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))) 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), GFP_ATOMIC); @@ -209,49 +228,33 @@ 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), + * 0: op, packet ID (AD, len=ks->aad_size), * 1, 2, 3, ..., n: payload, * n+1: auth_tag (len=tag_size) */ sg_init_table(sg, nfrags + 2); /* 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); + } - /* 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); @@ -265,54 +268,152 @@ 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) { - struct ovpn_key_ctx *key = NULL; - unsigned int payload_offset; + unsigned int payload_offset, payload_sg_len; int ret, payload_len, nfrags; + struct ovpn_key_ctx *key; struct aead_request *req; struct sk_buff *trailer; struct scatterlist *sg; unsigned int tag_size; + 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); @@ -335,7 +436,7 @@ 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), + * 0: op, packet ID (AD, len=ks->aad_size), * 1, 2, 3, ..., n: payload, * n+1: auth_tag (len=tag_size) */ @@ -345,21 +446,23 @@ int ovpn_aead_decrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, 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); + } - /* 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 6b193c5fd7e4..b4f40db4b30b 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -394,6 +394,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; @@ -427,6 +428,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); @@ -438,6 +442,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 27c61947ebaa..de145fb0b758 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -113,6 +113,7 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_key_ctx *key; struct ovpn_socket *sock; struct ovpn_peer *peer; + int payload_len; u64 aead_blocks; u16 pkt_epoch; __be16 proto; @@ -133,7 +134,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; } @@ -141,8 +143,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", @@ -151,13 +153,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 Wed Jul 1 15:43: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: 5061 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417692ejc; Wed, 1 Jul 2026 08:44:26 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ/2HY9UDRcdh8Ch1Id7ZMhkbWfZnPn1Y3ybeDIrSF+erxBvGKAiLTRyt97moJdYADfvjQM8QbX4QxI=@openvpn.net X-Received: by 2002:a05:6820:20f:b0:6a1:363d:bc4e with SMTP id 006d021491bc7-6a309ae78e3mr1128070eaf.46.1782920666512; Wed, 01 Jul 2026 08:44:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920666; cv=none; d=google.com; s=arc-20260327; b=NMCY2YIaPFCZz/XHEo3SamkcyxE0BAUyC2RrRaWK2y+M2WaAEl3vpAy5p5SQXj57jC LXRj3eKZ2+ThRKLOZ3tzdjz0zuqJksel8e6BI8VnFsqVLyXPwc+1i+CfCjUeioS7iWk/ MqzOc7FZenYfhscXy/lgfC0aejPfYCQPkxriCKWKjWNmC1nKqvtir1cf/2yecLxj3Ub4 c4/VDVFqIdg4gMUsRYsVni8B6ZW/9Aj6dILeaT+alKeveueuZfRMKvJYX949pqKNtJo9 quoJaVkMAYxoc91dLX8/GCgExaRD62QsyNFWKwt0k6WA5/m222+WO5UhpOPgPQPJ06vE OuSw== 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=9T/wSo1F/dI7KgOglozruSjdrJmEZP3NOg364GtReZo=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=IEBfA0AI+RvbSASf+/C1TubDRQ5KjeILCmrbUDJbCUi6/0KhorPSF+0YT8AAs1wrER 9bu8r2+9mrghIvRRh5ljufOJf/fX/p5Csv4yJNn2VxWLcPOmSdyHqxo0pUIGlswmOpfH IZIJsiYZKs2WLWQB+1viI8vA32tb0qZlvnx1typYbBkpJ9YMXL7ytOU/aLBgdPqj9MAj hCFctY4hXHtTmQUmtO0vpFh3ShgmGlyPj62LMALM+oYD5acpMTLZKidtBMVwXHoVqvce W6YIadj3G2kv5N93XmW8JONCtwj+x5UShnvwx/6Isp8CUHNGIbaQGOYI6sUPQRmq39S4 jPBg==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=NjgpYEcw; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=UwqeROJO; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=j0bE3l1A; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=yoFqDtXG; 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-44cbe80867fsi48492fac.12.2026.07.01.08.44.25 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:26 -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=NjgpYEcw; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=UwqeROJO; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=j0bE3l1A; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=yoFqDtXG; 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=9T/wSo1F/dI7KgOglozruSjdrJmEZP3NOg364GtReZo=; b=NjgpYEcwCLbsgoLWrLqkWL02Io Uh3lzSOfWLjZKxZMnRPJcMcJXWxQs0xf33II3jbsyegBdWimWiSyxnueJ4lrFWq3QYKKuYIMQX9Gt 42XMMSOGDm/S5Lx2HkAUmTERnNZCv+HJypKKFL5h/r2yogC1nYbYhBPYNRcP9QaS78eo=; 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 1wex6s-0003nx-IF; Wed, 01 Jul 2026 15:44:23 +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 1wex6q-0003nN-Jp for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:21 +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=/XxFH5NZLNfcmw4uobw+mSeXV/3rLGiaKlblLIXefdw=; b=UwqeROJOKbtYBJ5Xp1i2Gm71/m mRf7XOz9AclnkKDe9DJ9ycQ1lPIZiSSspjA7oc7jYd/Ufgc/nyyNOfkCwG/gQ0jB2MyHN/OfjQ65v DE8sKvXfh7V0i7hpU7ncY1M4eN9wTrYazkFkamWQa+8Za3cRzn40xrfnVYH0XAWo081A=; 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=/XxFH5NZLNfcmw4uobw+mSeXV/3rLGiaKlblLIXefdw=; b=j0bE3l1ANFDcVip4pPhreIqc6X 0nPmyYgxMjMPTHz0hvExFJDutlOY1qM5wtQ7bl6Zx7dDi5p8YmjmXGsRoJZpQXrjHgpidLNXmoSj9 L431oO189VxT1WdlrKxlzXHndUSrq4MPXiwwSQxfPGZhDhN9nsA3sg7gOR2py6br4BFk=; Received: from mout-b-112.mailbox.org ([195.10.208.42]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6n-0002Ad-RN for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:21 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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-112.mailbox.org (Postfix) with ESMTPS id 4gr47T4LYyzDvbk; Wed, 1 Jul 2026 17:44:05 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920645; 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=/XxFH5NZLNfcmw4uobw+mSeXV/3rLGiaKlblLIXefdw=; b=yoFqDtXGC3GK5yf7zL5N8soDr2hfRvx6BUXI81QoBEYaCrLIAHhq0nnFtVHFAJaDAOJz2h 0VNoavR6g4OW9B2u2vfiPW25YPN9AuODHNrZ76iyJVqIM4tjf2vqjRArIFK5JhU8mJvbu3 6ER3MwiAivy7pmfKPzep4yv3bLf4zlFmoVT6rRiP6cG5hjk7js0aH5Zj69pKe9L4whOtOe 6r9oySLso19egncgSqEkyrB1F9eWRvhZNaoj95cHMEdP/lJHk5X4MfwRACUSjv/6McJpXR KKl/nlWzbI4fMR2E0LKCPbUXsKjBujxssejSxoUceabPbBVWCiZnthheIzb8Mg== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:27 +0200 Message-ID: <52f95fd94d4093c784820ecd46e28d0cb1784916.1782919654.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: 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_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1wex6n-0002Ad-RN Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527820433738527 X-GMAIL-MSGID: 1869527820433738527 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 --- 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 | 221 +++++++++++++++++++++++++++++++-- drivers/net/ovpn/pktid.h | 6 +- 6 files changed, 399 insertions(+), 25 deletions(-) diff --git a/drivers/net/ovpn/crypto.h b/drivers/net/ovpn/crypto.h index 4ed44bab031f..454ccbaf157f 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 4daf0314b046..033bfd4769a2 100644 --- a/drivers/net/ovpn/crypto_aead.c +++ b/drivers/net/ovpn/crypto_aead.c @@ -142,6 +142,160 @@ 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) +{ + struct ovpn_key_ctx *stale[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + u16 stale_count = 0, advance, count, index, i; + struct ovpn_key_ctx *old_encrypt; + struct ovpn_key_ctx *new_encrypt; + 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++) { + struct ovpn_key_ctx __rcu **slot; + struct ovpn_key_ctx *future; + u16 stale_index; + + /* 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; + bool 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; + + 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) { @@ -151,8 +305,7 @@ int ovpn_aead_encrypt(struct ovpn_peer *peer, struct ovpn_crypto_key_slot *ks, struct sk_buff *trailer; struct scatterlist *sg; unsigned int tag_size; - bool pktid_notify; - u64 aead_blocks; + bool retried = false; int nfrags, ret; u64 pktid, seq; void *tmp; @@ -175,6 +328,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; @@ -182,19 +336,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 b4f40db4b30b..e7172a919bad 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -104,7 +104,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; @@ -134,7 +135,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); @@ -158,7 +159,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; @@ -179,7 +180,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); memzero_explicit(cipher_key, sizeof(cipher_key)); memzero_explicit(implicit_iv, sizeof(implicit_iv)); diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c index de145fb0b758..a8485a73e585 100644 --- a/drivers/net/ovpn/io.c +++ b/drivers/net/ovpn/io.c @@ -105,6 +105,200 @@ 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; + struct ovpn_key_ctx *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) +{ + struct ovpn_key_ctx *stale[OVPN_EPOCH_FUTURE_KEYS_COUNT]; + u16 stale_count = 0, advance, count, index, i; + struct ovpn_key_ctx __rcu **retire_slot; + struct ovpn_key_ctx *old_retiring; + struct ovpn_key_ctx *new_retiring; + struct ovpn_key_ctx *old_decrypt; + struct ovpn_key_ctx *new_decrypt; + 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++) { + struct ovpn_key_ctx __rcu **slot; + struct ovpn_key_ctx *future; + u16 stale_index; + + /* 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); + /* keep stale requests because the bit also carries tx soft-limit + * work + */ + 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; @@ -113,8 +307,10 @@ void ovpn_decrypt_post(void *data, int ret) struct ovpn_key_ctx *key; struct ovpn_socket *sock; struct ovpn_peer *peer; + bool aead_notify; int payload_len; u64 aead_blocks; + bool aead_hard; u16 pkt_epoch; __be16 proto; u64 pktid; @@ -134,9 +330,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; } @@ -166,12 +365,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 Wed Jul 1 15:43: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: 5060 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417688ejc; Wed, 1 Jul 2026 08:44:26 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ+QAGwSFAMZqbn+4mSgtyq2X6DJsw2/4jmhePOw8hQA6UDL4PlXn/EPoSfp5+Oj0wM2TwM4drxVqW0=@openvpn.net X-Received: by 2002:a05:6830:b14:b0:7e9:e598:8fea with SMTP id 46e09a7af769-7eb504bfd16mr748859a34.19.1782920666454; Wed, 01 Jul 2026 08:44:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920666; cv=none; d=google.com; s=arc-20260327; b=T7mN4kh2XouxXJDUGajA/72qP36rmxyot6N+jWkIXpyx2yqViM0PD/5XW+PcTVFgaA +caXYo9eFQC500VI51hHWLoZMFi76PFGJcM9ksjOcfcTEaktHWiIf57pGL3Ur3tZ0TZH CzJn+Jb+OlRtazpHB9ClQw0vlbsFvCBF296agFbXqygUOoxcELzJGq/ofYUZ3bkoJKA8 sKtJ6nRNxrA/fY2qyIbrC10mguHf6S0ujrVdZAFI3WpfKOmF8H3hFc+laIqbO6PehnyC RaGTg7nXJTkVYf2v2Rl1q8m8YpI18HLH/dQcshwXIbUb37JcS6V0Zrmy/YX2z/1wzW4h Cx0A== 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=fHobK7INKhJqZtUnmJ4qf2oKQZsr8JYdr1CljkbqUxY=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=a8k7OS6qSYNjg4VBGw2AiKA4mdfEsabf2LMdoxrEVDzU5yqy1YljFmDthk/7cH5Xyo tlceE00Jvb0hLxhgx3xo6Xn88zV5RcXT7DX2O4o3eYAAho7An6627gD/jVplMgo4xoiv l3eiDP7Heu0TLDuxZCcR6dWzACuSBMyibCwbcXF+mH5YOUWly3WJZAK/1SuNY4yKD9oi njIt6i2Y8QUi8P2sg8mc1ViYQcLq1rrcQ6YK2WK0Er3vk3nIBo3TmrBMjdBYLQ5M6m7b O7CvQUitwn55a9bs+/41Gt68fehuXL3UbPCHDzb+7yMg/deTFB/Po9bk+VC0LyzinZ/g jjig==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=N+nJCjkl; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="SKNms/bw"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="PAA06oC/"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=QXZwfKw5; 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-7eb5432c928si238429a34.62.2026.07.01.08.44.25 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44:26 -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=N+nJCjkl; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="SKNms/bw"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="PAA06oC/"; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b=QXZwfKw5; 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=fHobK7INKhJqZtUnmJ4qf2oKQZsr8JYdr1CljkbqUxY=; b=N+nJCjklAdP3EeHVtRQp0WeIxc 5fwVATlyvTWAO8yiL/h3bPJzaejuMSoMbAlTqw7iRXCURv5adMpU8pKOaZeqlK2YULHZnwG037O3c Ks1UntZCKLPOaWJ84OO9dCx5+RbRfsi9De1Z4Y5JjQa0THle+dkWupWz8JrDFy/JBV+8=; 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 1wex6v-0003kk-Qh; Wed, 01 Jul 2026 15:44:23 +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 1wex6t-0003kP-EZ for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:20 +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=GZI1sEhhBAtO+/IPmld9SIdEaKuHyABuJ83EmsbWIng=; b=SKNms/bwfi/mQqS1xQbAXaahQs zPmIU+4gftRru2gWuCkQeimP4YbP6fek+P8kgqgplK1NNFTkPfOl3FZd5YIu/0MCQkYVNvL2Higyw iQsParOJRTKSBE5CZjRQfhsmHVnIuX/AN9lju+hlneorkz4uflUofuXMy+JgJMhad8QA=; 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=GZI1sEhhBAtO+/IPmld9SIdEaKuHyABuJ83EmsbWIng=; b=PAA06oC/bUVOPeStGLXERhHDqL 7BCllbGS+oFlWHI5hYFqrgjse8htbjS0I8ALa61cyCsi2Zg/4DbuFdLqCGA87HpVnXl5KzqH0kKuW lPtW3cm/3nTRDPAExA3duheQP2rOIlg9VJN2yPnUhaCqeuS2s39wcnBR08I2RhlfOIGI=; 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 1wex6p-0002Ao-CV for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:20 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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 4gr47W15VPz9xGd; Wed, 1 Jul 2026 17:44:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920647; 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=GZI1sEhhBAtO+/IPmld9SIdEaKuHyABuJ83EmsbWIng=; b=QXZwfKw5ERu76EtnkgfW+XvBUwheMD8JZtZ89Qod1k26aG0SGeIYlSOXfqbVuB6ksz5aTr jeGSdkrvZlnfz6duhfEcSa3gnEo8e5uTvGmt3h75EUNtOn5pQrfGYbDuFnpP4E8Tar50uv L6oAs042ZnYSuMZyRqNiHAfcgl5tF0LAKbyPDZeOz7PiXQCLXAhvhF7CVNySpgh8Lp0rET KwUXEzIbC3pIMbDtpGBSRsWQDXnsdFWQCiDevfuaD4z9c9nwo9Q+H7gGkVjPFNYSzlcCgX i5nuD4j7auNljWmYZVgqZ2HOhiW29z2f6NseyTMWGtNMul3PwnDI475osC/apA== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:28 +0200 Message-ID: <6a100e535685abf00f2b9d3982eda44ea01a2512.1782919654.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: 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_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain X-Headers-End: 1wex6p-0002Ao-CV Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527820924571511 X-GMAIL-MSGID: 1869527820924571511 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 --- 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 | 110 ++++++++++++++++++++++---- include/uapi/linux/ovpn.h | 11 +++ 6 files changed, 146 insertions(+), 19 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 7da31c2acd44..76278c73eba4 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..14c91729b392 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 @@ -887,9 +921,11 @@ static int ovpn_nl_get_key_dir(struct genl_info *info, struct nlattr *key, 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_priv *ovpn = info->user_ptr[0]; struct ovpn_peer *peer; + bool direct_key; + bool epoch_key; u32 peer_id; int ret; @@ -911,28 +947,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 Wed Jul 1 15:43: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: 5059 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a17:907:9721:b0:c12:c60:681d with SMTP id jg33csp1417676ejc; Wed, 1 Jul 2026 08:44:25 -0700 (PDT) X-Forwarded-Encrypted: i=2; AFNElJ8Ej03tv7fy/Vku58mDSJIMZHQ5a3RuXYVbTObAM5mOG4qPfA1qeHlKsYC1jxKTMHp2ojO0NekSsCY=@openvpn.net X-Received: by 2002:a05:6808:30a2:b0:495:dbe8:647f with SMTP id 5614622812f47-4960f017da9mr1547223b6e.28.1782920665290; Wed, 01 Jul 2026 08:44:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782920665; cv=none; d=google.com; s=arc-20260327; b=DNi8hQ4CE4MLgAIVKpwpVdZqLXdI/lQCug6DRb/ooZON4KlxVE+x1cjK1+ji5dgXy7 Hrzw0jI62pXsvcmWMtzK2gyEVqzZ57m1MFUmYmkEGDFOM2c625bSdlCu6P1qm8JUNsMf PIK2YyPtGZqa1HyzDHOajNGf42j+AB9TxYyRAydvbs1yw1a2+ghHLU4VvY5L4DpFOPOU tzGpjh62/5WZ+8oIod+sa6hvSzGQCIDL0abCt2VqgPAcc2lyiF/xROglRqrM5tNBhT5+ cG0tV2ig+METnqOUKe7ZtVaeUsOwZMqxdg+C8Y+g+crVBqSx7LMX7ud+7y1TIBfDN9r4 GA5Q== 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=n+Y1NaYpwBLOvpNDE91d3srZaGz4MTnk3NRSYdBjKF4=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=RwhO7uUazGQMee+YoQIPeN/FPpLX/+xlE3XlHU8d8zLf8b69L8GCefBwtuwIsWN3/0 GrCYaqXwRXncnt6fz2TcxBlT1/GTejZrfOhpZxkDqddJoMw6NKVoCmZeCYqOo7f171ug 8c1xI0kXWvWxxMIkGdcia3r9u37m7bSze6RdI1nJWEC4cJT8mvUIqzHHyq9i55TksnDP qL8L1aWnw8lOeU8AepWT06vZrGqCW/lNJvOEpHNPlQyymDJanKkrWqGnVcrMPqA7l4YL MXZqtVtDYP4sKkyILm3KrOkrklzj9ksIL1XSzImbvfJZzdYLrPiEDLBeVtVMgmQrk80v 3xaA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b="hw/xhHJI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="V073L/5V"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TItlvd3e; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="f/WM1x5H"; 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-496069e99d3si1772994b6e.29.2026.07.01.08.44.24 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jul 2026 08:44: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="hw/xhHJI"; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b="V073L/5V"; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=TItlvd3e; dkim=neutral (body hash did not verify) header.i=@mandelbit.com header.s=MBO0001 header.b="f/WM1x5H"; 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=n+Y1NaYpwBLOvpNDE91d3srZaGz4MTnk3NRSYdBjKF4=; b=hw/xhHJI62D0vBxUCM0pH9HMEK BONlONwmM4Cri7Lt9ZPgUIUqAczh71NJa8i4jXT19XcRrTs4NUn4HT7dWzfXlhbSDl6VZ6v/tHCjQ wZJJGvJCLACH0qOqHRATq8P2pO5kdm3NREggM0LnQ6ox7k/BcbAuB9IFvY1v2+nbmQAU=; 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 1wex6s-0007ik-J4; Wed, 01 Jul 2026 15:44:22 +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 1wex6p-0007iO-Rn for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44: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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=V073L/5VZJ923Z8HkVTM9N028g a9PeieHtTPFXx+pyCRI0S6Y8SGBcrUDmbOlG2LBiAPKM/YX9UOwV2QbOlkl6AtevHXwBkT0KyjVJb MweVrHyOiWLXhb8c/JSgoR8wa8eHpbf2rbRrWKcX6ZvFjHDirjPwpUR4bc94vvyFimZw=; 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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=TItlvd3ekmubNQYNOcQOwMhvQQ OhW1mj9U2p2+6VF9z4QR2wkCma7q7X8K6+04Do9vymkGF3TovvREUzQlvzVBVuycw0bUU74L3xnRX 6d23y/qcj+D0A2O0gto3QBxqjkzaf12vMRlyiDuG4PGP/RXxPfErd+ljaJIiKNqHFiUs=; Received: from mout-b-106.mailbox.org ([195.10.208.46]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wex6l-0002AE-GE for openvpn-devel@lists.sourceforge.net; Wed, 01 Jul 2026 15:44:19 +0000 Received: from smtp202.mailbox.org (smtp202.mailbox.org [10.196.197.202]) (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-106.mailbox.org (Postfix) with ESMTPS id 4gr47X48YgzDsWl; Wed, 1 Jul 2026 17:44:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1782920648; 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=1xgMd0lCWAnlzoh4FGvPJA3/hB9p/9I2EyfjuCFl4hs=; b=f/WM1x5HiOFAVAs6hewp/9xn2VhYZVIaGH9MHh3an7liTJP6CYRwDcE3wf1GRnkMtQWSS5 7W/dTvF4t7fiyT7hM4EJrObVFIkUnO0rR3Hmf8s0+RJ9Hf8VPDh2gRMKqUkPvu7A/LV8BS +a8D4j7qUDv+dcynsE07KWoMBHnHyTNbCg7DE0gj9Ylzx71+iyE7imJDoBiV+ncPKwGGfg eWm2eW4UlCqvbX822CaVS9vM8fWDOxdq3W1DxHX83r0JE3nVS7hwxqO1G3NonIyWKwukSX 8n3+Tpzn03pnS0ut6LhPbmFu9ARVVNc/1cPn8ZGRvXI/GKYUCivcjI0di+ajfQ== From: Ralf Lici To: openvpn-devel@lists.sourceforge.net Date: Wed, 1 Jul 2026 17:43:29 +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: 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_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [195.10.208.46 listed in wl.mailspike.net] X-Headers-End: 1wex6l-0002AE-GE Subject: [Openvpn-devel] [RFC ovpn net-next 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: 1869527819642416378 X-GMAIL-MSGID: 1869527819642416378 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 --- 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))