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_ */