From: Marco Baffo <marco@mandelbit.com>
To: linux-crypto@vger.kernel.org
Cc: herbert@gondor.apana.org.au, davem@davemloft.net,
ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org,
tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org,
kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me,
hare@suse.de, linux-nvme@lists.infradead.org,
andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net,
linux-kernel@vger.kernel.org, Marco Baffo <marco@mandelbit.com>
Subject: [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation
Date: Tue, 21 Jul 2026 16:06:44 +0200 [thread overview]
Message-ID: <20260721140644.780006-6-marco@mandelbit.com> (raw)
In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com>
ovpn carries a local shash-based HKDF-Expand implementation for
deriving epoch PRKs, data keys, and implicit IVs. Replace it with
hkdf_sha256_expand() and feed the HkdfLabel bytes as an array of
segments processed as if concatenated. The derived keys are unchanged.
Store PRKs as prepared HMAC-SHA256 keys embedded in struct
ovpn_epoch_key instead of allocated shash transforms.
ovpn_epoch_set_prk() now handles initial setup as well as epoch
updates via hmac_sha256_preparekey(), replacing ovpn_epoch_init_key().
This removes the shash allocation and setkey failure paths. The
embedded keys are wiped in place where the transforms were previously
freed. The labels are compile-time constants, so check their encoded
sizes at build time and remove the corresponding runtime error paths.
Select CRYPTO_LIB_SHA256 instead of CRYPTO_HMAC and CRYPTO_SHA256, as
the HKDF code was ovpn's only shash user.
Signed-off-by: Marco Baffo <marco@mandelbit.com>
---
drivers/net/Kconfig | 3 +-
drivers/net/ovpn/crypto_epoch.c | 168 ++++++++------------------------
drivers/net/ovpn/crypto_epoch.h | 11 +--
drivers/net/ovpn/crypto_key.c | 50 +++-------
4 files changed, 60 insertions(+), 172 deletions(-)
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index cd4193ce51b4..6f958535e60e 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -109,8 +109,7 @@ config OVPN
select CRYPTO_AES
select CRYPTO_GCM
select CRYPTO_CHACHA20POLY1305
- select CRYPTO_HMAC
- select CRYPTO_SHA256
+ select CRYPTO_LIB_SHA256
select STREAM_PARSER
help
This module enhances the performance of the OpenVPN userspace software
diff --git a/drivers/net/ovpn/crypto_epoch.c b/drivers/net/ovpn/crypto_epoch.c
index bfdf030bb0fb..f63537b3b9f5 100644
--- a/drivers/net/ovpn/crypto_epoch.c
+++ b/drivers/net/ovpn/crypto_epoch.c
@@ -7,7 +7,7 @@
* Antonio Quartulli <antonio@openvpn.net>
*/
-#include <crypto/hash.h>
+#include <crypto/hkdf.h>
#include <linux/unaligned.h>
#include "crypto_epoch.h"
@@ -17,111 +17,36 @@
#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_assert(OVPN_EPOCH_PRK_SIZE == SHA256_DIGEST_SIZE);
-static int ovpn_hkdf_expand(struct crypto_shash *shash, const u8 *info,
- size_t info_len, u8 *okm, size_t okm_len)
-{
- unsigned int prev_len = 0, digest_len;
- SHASH_DESC_ON_STACK(desc, shash);
- u8 prev[OVPN_EPOCH_PRK_SIZE];
- size_t copied = 0, todo;
- u8 counter = 1;
- int ret = 0;
-
- digest_len = crypto_shash_digestsize(shash);
- if (WARN_ON_ONCE(digest_len != sizeof(prev)))
- return -EINVAL;
-
- desc->tfm = shash;
-
- /* T(0) is the empty string */
- while (copied < okm_len) {
- /* T(n) = HMAC-Hash(PRK, T(n-1) | info | n) */
- ret = crypto_shash_init(desc);
- if (ret)
- goto out;
- ret = crypto_shash_update(desc, prev, prev_len);
- if (ret)
- goto out;
- ret = crypto_shash_update(desc, info, info_len);
- if (ret)
- goto out;
- ret = crypto_shash_update(desc, &counter, sizeof(counter));
- if (ret)
- goto out;
- ret = crypto_shash_final(desc, prev);
- if (ret)
- goto out;
-
- prev_len = digest_len;
- /* copy a full digest block or the final partial block */
- todo = min_t(size_t, digest_len, okm_len - copied);
- memcpy(okm + copied, prev, todo);
- copied += todo;
- counter++;
- }
-
-out:
- memzero_explicit(prev, sizeof(prev));
- shash_desc_zero(desc);
- return ret;
-}
-
-struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size)
-{
- struct crypto_shash *shash;
- int ret;
-
- shash = crypto_alloc_shash(OVPN_EPOCH_HASH_ALG, 0, 0);
- if (IS_ERR(shash))
- return shash;
-
- if (key_size != crypto_shash_digestsize(shash)) {
- crypto_free_shash(shash);
- return ERR_PTR(-EINVAL);
- }
+/* The prefixed label length must fit its u8 field. */
+#define OVPN_EPOCH_FULL_LABEL_LEN(label) \
+ (sizeof(OVPN_EPOCH_LABEL_PREFIX) - 1 + sizeof(label) - 1)
- /* 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_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_DATA_KEY_LABEL) <= U8_MAX);
+static_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_DATA_IV_LABEL) <= U8_MAX);
+static_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_UPDATE_LABEL) <= U8_MAX);
-static int ovpn_expand_label(struct crypto_shash *shash, const u8 *label,
- size_t label_len, u8 *okm, u16 okm_len)
+static void ovpn_expand_label(const struct hmac_sha256_key *prk,
+ 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;
+ static const u8 empty_context_len;
+ u8 hdr[3];
+ const struct hkdf_seg info[] = {
+ { .data = hdr, .len = sizeof(hdr) },
+ { .data = label_prefix, .len = sizeof(label_prefix) - 1 },
+ { .data = label, .len = label_len },
+ { .data = &empty_context_len, .len = 1 },
+ };
+
+ /* encode okm length and prefixed label length */
+ put_unaligned_be16(okm_len, hdr);
+ hdr[2] = sizeof(label_prefix) - 1 + label_len;
+
+ hkdf_sha256_expand(prk, info, ARRAY_SIZE(info), okm, okm_len);
}
/**
@@ -140,27 +65,18 @@ int ovpn_epoch_derive_next_prk(const struct ovpn_epoch_key *epoch_key,
if (unlikely(epoch_key->epoch == OVPN_MAX_EPOCH))
return -ERANGE;
- if (WARN_ON_ONCE(OVPN_EPOCH_PRK_SIZE !=
- crypto_shash_digestsize(epoch_key->shash)))
- return -EINVAL;
+ ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_UPDATE_LABEL,
+ sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1,
+ next_prk, OVPN_EPOCH_PRK_SIZE);
- return ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_UPDATE_LABEL,
- sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1,
- next_prk, OVPN_EPOCH_PRK_SIZE);
+ return 0;
}
-int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[],
- u16 epoch)
+void ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[],
+ u16 epoch)
{
- int ret;
-
- ret = crypto_shash_setkey(epoch_key->shash, prk, OVPN_EPOCH_PRK_SIZE);
- if (ret)
- return ret;
-
+ hmac_sha256_preparekey(&epoch_key->prk, prk, OVPN_EPOCH_PRK_SIZE);
epoch_key->epoch = epoch;
-
- return 0;
}
/**
@@ -182,7 +98,7 @@ int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key)
goto out;
/* expose the next epoch only after its PRK is installed */
- ret = ovpn_epoch_set_prk(epoch_key, key, epoch_key->epoch + 1);
+ ovpn_epoch_set_prk(epoch_key, key, epoch_key->epoch + 1);
out:
memzero_explicit(key, sizeof(key));
@@ -203,21 +119,19 @@ 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[])
{
- 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;
+ ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_DATA_KEY_LABEL,
+ sizeof(OVPN_EPOCH_DATA_KEY_LABEL) - 1,
+ cipher_key, epoch_key->cipher_key_len);
/* 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);
+ ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_DATA_IV_LABEL,
+ sizeof(OVPN_EPOCH_DATA_IV_LABEL) - 1,
+ implicit_iv, OVPN_NONCE_SIZE);
+
+ return 0;
}
diff --git a/drivers/net/ovpn/crypto_epoch.h b/drivers/net/ovpn/crypto_epoch.h
index 0dce1e905917..c14b46a6728d 100644
--- a/drivers/net/ovpn/crypto_epoch.h
+++ b/drivers/net/ovpn/crypto_epoch.h
@@ -10,7 +10,7 @@
#ifndef _NET_OVPN_OVPNEPOCH_H_
#define _NET_OVPN_OVPNEPOCH_H_
-#include <crypto/hash.h>
+#include <crypto/sha2.h>
#include <linux/limits.h>
#include <linux/rcupdate.h>
#include <linux/types.h>
@@ -21,11 +21,11 @@
struct ovpn_key_ctx;
-/* crypto handle used for key derivation through HKDF-Expand-Label */
+/* key derivation state for HKDF-Expand-Label */
struct ovpn_epoch_key {
u16 epoch;
unsigned int cipher_key_len;
- struct crypto_shash *shash;
+ struct hmac_sha256_key prk;
};
/* ring buffer of prederived future epoch data keys */
@@ -46,11 +46,10 @@ ovpn_epoch_future_keys_count(const struct ovpn_future_keys *fk)
OVPN_EPOCH_FUTURE_KEYS_COUNT;
}
-struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size);
int ovpn_epoch_derive_next_prk(const struct ovpn_epoch_key *epoch_key,
u8 next_prk[]);
-int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[],
- u16 epoch);
+void ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[],
+ u16 epoch);
int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key);
int ovpn_epoch_derive_key(const struct ovpn_epoch_key *epoch_key,
u8 cipher_key[], u8 implicit_iv[]);
diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c
index 68ebf16c66c8..f07795fc62b9 100644
--- a/drivers/net/ovpn/crypto_key.c
+++ b/drivers/net/ovpn/crypto_key.c
@@ -8,7 +8,6 @@
*/
#include <crypto/aead.h>
-#include <crypto/hash.h>
#include <linux/workqueue.h>
#include "ovpnpriv.h"
@@ -271,7 +270,6 @@ static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks,
u16 replaced = 0, created = 0, free_slots, i;
const struct ovpn_epoch_key *source_key;
struct ovpn_epoch_key scratch_key = {};
- size_t prk_size = OVPN_EPOCH_PRK_SIZE;
struct ovpn_future_keys *future_keys;
struct ovpn_epoch_key *epoch_key;
struct ovpn_key_ctx __rcu **slot;
@@ -304,26 +302,12 @@ static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks,
/* derive new keys without holding the ring lock */
scratch_key.cipher_key_len = epoch_key->cipher_key_len;
for (created = 0; created < free_slots; created++) {
- source_key = scratch_key.shash ? &scratch_key : epoch_key;
+ source_key = created ? &scratch_key : epoch_key;
ret = ovpn_epoch_derive_next_prk(source_key, next_prk);
if (ret)
goto err;
- if (!scratch_key.shash) {
- scratch_key.shash = ovpn_epoch_init_key(next_prk,
- prk_size);
- if (IS_ERR(scratch_key.shash)) {
- ret = PTR_ERR(scratch_key.shash);
- scratch_key.shash = NULL;
- goto err;
- }
- scratch_key.epoch = epoch_key->epoch + 1;
- } else {
- ret = ovpn_epoch_set_prk(&scratch_key, next_prk,
- scratch_key.epoch + 1);
- if (ret)
- goto err;
- }
+ ovpn_epoch_set_prk(&scratch_key, next_prk, source_key->epoch + 1);
new_futures[created] =
ovpn_key_ctx_create_epoch(encrypt, ks->alg_name,
@@ -334,9 +318,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks,
}
}
- ret = ovpn_epoch_set_prk(epoch_key, next_prk, scratch_key.epoch);
- if (ret)
- goto err;
+ ovpn_epoch_set_prk(epoch_key, next_prk, scratch_key.epoch);
/* insert generated keys under the selected ring lock */
spin_lock_bh(lock);
@@ -362,7 +344,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks,
for (i = replaced; i < created; i++)
ovpn_key_ctx_put(new_futures[i]);
- crypto_free_shash(scratch_key.shash);
+ memzero_explicit(&scratch_key.prk, sizeof(scratch_key.prk));
memzero_explicit(next_prk, sizeof(next_prk));
/* keep refilling until the ring is full */
@@ -374,8 +356,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypto_key_slot *ks,
err:
for (i = 0; i < created; i++)
ovpn_key_ctx_put(new_futures[i]);
- if (scratch_key.shash)
- crypto_free_shash(scratch_key.shash);
+ memzero_explicit(&scratch_key.prk, sizeof(scratch_key.prk));
memzero_explicit(next_prk, sizeof(next_prk));
}
@@ -409,10 +390,10 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
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);
+ memzero_explicit(&ks->epoch_key_send.prk,
+ sizeof(ks->epoch_key_send.prk));
+ memzero_explicit(&ks->epoch_key_recv.prk,
+ sizeof(ks->epoch_key_recv.prk));
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]);
@@ -428,17 +409,12 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_key_slot *ks)
static int ovpn_epoch_key_init(struct ovpn_epoch_key *epoch_key,
const struct ovpn_epoch_prk *prk)
{
- int ret;
-
- epoch_key->shash = ovpn_epoch_init_key(prk->key, prk->key_size);
- if (IS_ERR(epoch_key->shash)) {
- ret = PTR_ERR(epoch_key->shash);
- epoch_key->shash = NULL;
- return ret;
- }
+ if (prk->key_size != OVPN_EPOCH_PRK_SIZE)
+ return -EINVAL;
/* epoch 0 is reserved for direct keys, so epoch keys start at 1 */
- epoch_key->epoch = 1;
+ ovpn_epoch_set_prk(epoch_key, prk->key, 1);
+
epoch_key->cipher_key_len = prk->cipher_key_len;
return 0;
--
2.43.0
next prev parent reply other threads:[~2026-07-21 14:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-21 14:06 [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Marco Baffo
2026-07-21 14:06 ` [PATCH 1/5] lib/crypto: add HKDF-SHA{256,384,512} Marco Baffo
2026-07-21 14:06 ` [PATCH 2/5] lib/crypto: tests: add HKDF KUnit tests Marco Baffo
2026-07-21 14:06 ` [PATCH 3/5] fscrypt: use HKDF library functions Marco Baffo
2026-07-21 14:06 ` [PATCH 4/5] nvme-auth: use HKDF library functions for TLS PSK derivation Marco Baffo
2026-07-21 14:06 ` Marco Baffo [this message]
2026-07-21 15:06 ` [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Eric Biggers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260721140644.780006-6-marco@mandelbit.com \
--to=marco@mandelbit.com \
--cc=Jason@zx2c4.com \
--cc=andrew+netdev@lunn.ch \
--cc=antonio@openvpn.net \
--cc=ardb@kernel.org \
--cc=axboe@kernel.dk \
--cc=davem@davemloft.net \
--cc=ebiggers@kernel.org \
--cc=edumazet@google.com \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=herbert@gondor.apana.org.au \
--cc=jaegeuk@kernel.org \
--cc=kbusch@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-fscrypt@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=pabeni@redhat.com \
--cc=sagi@grimberg.me \
--cc=sd@queasysnail.net \
--cc=tytso@mit.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox