* [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe
@ 2026-07-21 14:06 Marco Baffo
2026-07-21 14:06 ` [PATCH 1/5] lib/crypto: add HKDF-SHA{256,384,512} Marco Baffo
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
fscrypt and NVMe authentication each carry private HKDF
implementations. Add HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512
functions to lib/crypto and convert both users.
Patches 1-4 are intended for review and inclusion.
Patch 5 is RFC only and is not intended to be applied. It shows how
the OVPN epoch-key code currently under development can use the new
HKDF API. That patch has an additional dependency on the OVPN epoch
series. It is included here to allow the API to be reviewed against
a third user. The OVPN conversion will be submitted separately once
its prerequisite code is ready.
HKDF-Expand accepts info as an array of segments and processes them as
if concatenated. This preserves the byte ordering used by fscrypt and
allows NVMe to encode HkdfLabel without allocating a temporary buffer.
HKDF-Extract produces a prepared HMAC key, avoiding repeated key setup
when the same PRK is expanded more than once. The implementation uses
the direct HMAC library functions and neither allocates memory nor
returns errors.
The series is intended for the libcrypto tree. Patches 3 and 4 need
review from the fscrypt and NVMe maintainers, respectively.
Tested with:
- HKDF KUnit tests
- NVMe authentication KUnit tests
- kvm-xfstests smoketest on ext4
- kvm-xfstests generic/582, generic/583, and generic/584 on ext4 and f2fs
- kvm-xfstests encrypt group on ext4 and f2fs
The following shortlog and diffstat refers only to the first 4 patches.
Marco Baffo (4):
lib/crypto: add HKDF-SHA{256,384,512}
lib/crypto: tests: add HKDF KUnit tests
fscrypt: use HKDF library functions
nvme-auth: use HKDF library functions for TLS PSK derivation
drivers/nvme/common/auth.c | 120 ++++++++-------
fs/crypto/hkdf.c | 56 ++-----
include/crypto/hkdf.h | 131 ++++++++++++++++
lib/crypto/.kunitconfig | 1 +
lib/crypto/Makefile | 4 +-
lib/crypto/hkdf-sha256.c | 22 +++
lib/crypto/hkdf-sha512.c | 34 +++++
lib/crypto/hkdf-template.h | 97 ++++++++++++
lib/crypto/tests/Kconfig | 8 +
lib/crypto/tests/Makefile | 1 +
lib/crypto/tests/hkdf_kunit.c | 276 ++++++++++++++++++++++++++++++++++
11 files changed, 653 insertions(+), 97 deletions(-)
create mode 100644 include/crypto/hkdf.h
create mode 100644 lib/crypto/hkdf-sha256.c
create mode 100644 lib/crypto/hkdf-sha512.c
create mode 100644 lib/crypto/hkdf-template.h
create mode 100644 lib/crypto/tests/hkdf_kunit.c
base-commit: e4f23159c8d09b6b15d47f2c95e0a3e7c62f52a2
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/5] lib/crypto: add HKDF-SHA{256,384,512}
2026-07-21 14:06 [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Marco Baffo
@ 2026-07-21 14:06 ` Marco Baffo
2026-07-21 14:06 ` [PATCH 2/5] lib/crypto: tests: add HKDF KUnit tests Marco Baffo
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
fscrypt and NVMe authentication each carry private HKDF (RFC 5869)
code. Add common HKDF-SHA{256,384,512} helpers based on fscrypt's
implementation to lib/crypto, so both users can share the code.
Represent info as an array of segments processed as if concatenated.
This avoids temporary concatenation buffers.
Implement the helpers using the direct HMAC library API, so they cannot
fail, perform no allocations, and make no indirect calls. HKDF-Extract
stores the PRK as a prepared HMAC key, so key preparation is not
repeated for each HKDF-Expand call.
Use a template to instantiate the code for SHA-256, SHA-384, and
SHA-512. Add the resulting objects to libsha256 and libsha512.
Signed-off-by: Marco Baffo <marco@mandelbit.com>
---
include/crypto/hkdf.h | 131 +++++++++++++++++++++++++++++++++++++
lib/crypto/Makefile | 4 +-
lib/crypto/hkdf-sha256.c | 22 +++++++
lib/crypto/hkdf-sha512.c | 34 ++++++++++
lib/crypto/hkdf-template.h | 97 +++++++++++++++++++++++++++
5 files changed, 286 insertions(+), 2 deletions(-)
create mode 100644 include/crypto/hkdf.h
create mode 100644 lib/crypto/hkdf-sha256.c
create mode 100644 lib/crypto/hkdf-sha512.c
create mode 100644 lib/crypto/hkdf-template.h
diff --git a/include/crypto/hkdf.h b/include/crypto/hkdf.h
new file mode 100644
index 000000000000..dda4dce9f037
--- /dev/null
+++ b/include/crypto/hkdf.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * HKDF: HMAC-based Key Derivation Function (RFC 5869)
+ *
+ * Derived from fs/crypto/hkdf.c
+ * Copyright 2019 Google LLC
+ */
+
+#ifndef _CRYPTO_HKDF_H
+#define _CRYPTO_HKDF_H
+
+#include <crypto/sha2.h>
+
+/*
+ * HKDF has two steps:
+ *
+ * 1. Extract: derive a pseudorandom key from input keying material and salt.
+ * 2. Expand: derive output keying material from the pseudorandom key and info.
+ */
+
+/**
+ * struct hkdf_seg - one segment of the HKDF-Expand 'info' parameter
+ * @data: the segment data
+ * @len: length of @data in bytes
+ *
+ * The segments are processed as if concatenated, so a composite info can be
+ * passed without assembling it in a temporary buffer first.
+ */
+struct hkdf_seg {
+ const void *data;
+ size_t len;
+};
+
+/**
+ * hkdf_sha256_extract() - HKDF-Extract using HMAC-SHA256
+ * @prk: (output) the pseudorandom key, prepared for hkdf_sha256_expand()
+ * @salt: optional non-secret salt, or NULL for the RFC 5869 default
+ * (all-zero) salt
+ * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL)
+ * @ikm: input keying material
+ * @ikm_len: length of @ikm in bytes
+ *
+ * Extract a SHA256_DIGEST_SIZE-byte pseudorandom key from @ikm. If @ikm is
+ * already a pseudorandom key of that size, prepare @prk with
+ * hmac_sha256_preparekey() instead.
+ */
+void hkdf_sha256_extract(struct hmac_sha256_key *prk,
+ const u8 *salt, size_t salt_len,
+ const u8 *ikm, size_t ikm_len);
+
+/**
+ * hkdf_sha256_expand() - HKDF-Expand using HMAC-SHA256
+ * @prk: pseudorandom key from hkdf_sha256_extract() or
+ * hmac_sha256_preparekey()
+ * @info: info segments, processed as if concatenated. Use different info for
+ * different purposes.
+ * @info_nsegs: number of segments in @info
+ * @okm: (output) the output keying material
+ * @okm_len: length of @okm in bytes, at most 255 * SHA256_DIGEST_SIZE
+ *
+ * Expand @prk into @okm_len bytes of output keying material (RFC 5869
+ * section 2.3). The same (@prk, @info) always yields the same output.
+ * Different @info values yield independent outputs, but different @okm_len do
+ * not: a shorter output is a truncation of a longer one. This may be called many
+ * times per @prk and is thread-safe.
+ */
+void hkdf_sha256_expand(const struct hmac_sha256_key *prk,
+ const struct hkdf_seg *info, size_t info_nsegs,
+ u8 *okm, size_t okm_len);
+
+/**
+ * hkdf_sha384_extract() - HKDF-Extract using HMAC-SHA384
+ * @prk: (output) the pseudorandom key, prepared for hkdf_sha384_expand()
+ * @salt: optional non-secret salt, or NULL for the RFC 5869 default
+ * (all-zero) salt
+ * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL)
+ * @ikm: input keying material
+ * @ikm_len: length of @ikm in bytes
+ *
+ * SHA-384 variant of hkdf_sha256_extract().
+ */
+void hkdf_sha384_extract(struct hmac_sha384_key *prk,
+ const u8 *salt, size_t salt_len,
+ const u8 *ikm, size_t ikm_len);
+
+/**
+ * hkdf_sha384_expand() - HKDF-Expand using HMAC-SHA384
+ * @prk: pseudorandom key from hkdf_sha384_extract() or
+ * hmac_sha384_preparekey()
+ * @info: info segments, processed as if concatenated
+ * @info_nsegs: number of segments in @info
+ * @okm: (output) the output keying material
+ * @okm_len: length of @okm in bytes, at most 255 * SHA384_DIGEST_SIZE
+ *
+ * SHA-384 variant of hkdf_sha256_expand().
+ */
+void hkdf_sha384_expand(const struct hmac_sha384_key *prk,
+ const struct hkdf_seg *info, size_t info_nsegs,
+ u8 *okm, size_t okm_len);
+
+/**
+ * hkdf_sha512_extract() - HKDF-Extract using HMAC-SHA512
+ * @prk: (output) the pseudorandom key, prepared for hkdf_sha512_expand()
+ * @salt: optional non-secret salt, or NULL for the RFC 5869 default
+ * (all-zero) salt
+ * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL)
+ * @ikm: input keying material
+ * @ikm_len: length of @ikm in bytes
+ *
+ * SHA-512 variant of hkdf_sha256_extract().
+ */
+void hkdf_sha512_extract(struct hmac_sha512_key *prk,
+ const u8 *salt, size_t salt_len,
+ const u8 *ikm, size_t ikm_len);
+
+/**
+ * hkdf_sha512_expand() - HKDF-Expand using HMAC-SHA512
+ * @prk: pseudorandom key from hkdf_sha512_extract() or
+ * hmac_sha512_preparekey()
+ * @info: info segments, processed as if concatenated
+ * @info_nsegs: number of segments in @info
+ * @okm: (output) the output keying material
+ * @okm_len: length of @okm in bytes, at most 255 * SHA512_DIGEST_SIZE
+ *
+ * SHA-512 variant of hkdf_sha256_expand().
+ */
+void hkdf_sha512_expand(const struct hmac_sha512_key *prk,
+ const struct hkdf_seg *info, size_t info_nsegs,
+ u8 *okm, size_t okm_len);
+
+#endif /* _CRYPTO_HKDF_H */
diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile
index f1e9bf89785f..96173c1a0a31 100644
--- a/lib/crypto/Makefile
+++ b/lib/crypto/Makefile
@@ -300,7 +300,7 @@ endif # CONFIG_CRYPTO_LIB_SHA1_ARCH
################################################################################
obj-$(CONFIG_CRYPTO_LIB_SHA256) += libsha256.o
-libsha256-y := sha256.o
+libsha256-y := sha256.o hkdf-sha256.o
ifeq ($(CONFIG_CRYPTO_LIB_SHA256_ARCH),y)
CFLAGS_sha256.o += -I$(src)/$(SRCARCH)
@@ -329,7 +329,7 @@ endif # CONFIG_CRYPTO_LIB_SHA256_ARCH
################################################################################
obj-$(CONFIG_CRYPTO_LIB_SHA512) += libsha512.o
-libsha512-y := sha512.o
+libsha512-y := sha512.o hkdf-sha512.o
ifeq ($(CONFIG_CRYPTO_LIB_SHA512_ARCH),y)
CFLAGS_sha512.o += -I$(src)/$(SRCARCH)
diff --git a/lib/crypto/hkdf-sha256.c b/lib/crypto/hkdf-sha256.c
new file mode 100644
index 000000000000..27004293b805
--- /dev/null
+++ b/lib/crypto/hkdf-sha256.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HKDF-SHA256 library functions (RFC 5869)
+ *
+ * Derived from fs/crypto/hkdf.c
+ * Copyright 2019 Google LLC
+ */
+
+#include <crypto/hkdf.h>
+#include <linux/string.h>
+
+#define HKDF_EXTRACT hkdf_sha256_extract
+#define HKDF_EXPAND hkdf_sha256_expand
+#define HMAC_KEY hmac_sha256_key
+#define HMAC_CTX hmac_sha256_ctx
+#define HMAC_PREPAREKEY hmac_sha256_preparekey
+#define HMAC_INIT hmac_sha256_init
+#define HMAC_UPDATE hmac_sha256_update
+#define HMAC_FINAL hmac_sha256_final
+#define HMAC_USINGRAWKEY hmac_sha256_usingrawkey
+#define HKDF_HASHLEN SHA256_DIGEST_SIZE
+#include "hkdf-template.h"
diff --git a/lib/crypto/hkdf-sha512.c b/lib/crypto/hkdf-sha512.c
new file mode 100644
index 000000000000..f03f9dad1beb
--- /dev/null
+++ b/lib/crypto/hkdf-sha512.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HKDF-SHA384 and HKDF-SHA512 library functions (RFC 5869)
+ *
+ * Derived from fs/crypto/hkdf.c
+ * Copyright 2019 Google LLC
+ */
+
+#include <crypto/hkdf.h>
+#include <linux/string.h>
+
+#define HKDF_EXTRACT hkdf_sha384_extract
+#define HKDF_EXPAND hkdf_sha384_expand
+#define HMAC_KEY hmac_sha384_key
+#define HMAC_CTX hmac_sha384_ctx
+#define HMAC_PREPAREKEY hmac_sha384_preparekey
+#define HMAC_INIT hmac_sha384_init
+#define HMAC_UPDATE hmac_sha384_update
+#define HMAC_FINAL hmac_sha384_final
+#define HMAC_USINGRAWKEY hmac_sha384_usingrawkey
+#define HKDF_HASHLEN SHA384_DIGEST_SIZE
+#include "hkdf-template.h"
+
+#define HKDF_EXTRACT hkdf_sha512_extract
+#define HKDF_EXPAND hkdf_sha512_expand
+#define HMAC_KEY hmac_sha512_key
+#define HMAC_CTX hmac_sha512_ctx
+#define HMAC_PREPAREKEY hmac_sha512_preparekey
+#define HMAC_INIT hmac_sha512_init
+#define HMAC_UPDATE hmac_sha512_update
+#define HMAC_FINAL hmac_sha512_final
+#define HMAC_USINGRAWKEY hmac_sha512_usingrawkey
+#define HKDF_HASHLEN SHA512_DIGEST_SIZE
+#include "hkdf-template.h"
diff --git a/lib/crypto/hkdf-template.h b/lib/crypto/hkdf-template.h
new file mode 100644
index 000000000000..a1d6bbfdc689
--- /dev/null
+++ b/lib/crypto/hkdf-template.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of HKDF ("HMAC-based Extract-and-Expand Key Derivation
+ * Function"), aka RFC 5869. See also the original paper (Krawczyk 2010):
+ * "Cryptographic Extraction and Key Derivation: The HKDF Scheme".
+ *
+ * Derived from fs/crypto/hkdf.c
+ * Copyright 2019 Google LLC
+ *
+ * This file is a template that generates HKDF on top of a specific HMAC
+ * library. The including file must define the following macros, and may
+ * include this file multiple times to instantiate multiple variants:
+ *
+ * HKDF_EXTRACT name of the HKDF-Extract function
+ * HKDF_EXPAND name of the HKDF-Expand function
+ * HMAC_KEY HMAC prepared-key type, without 'struct'
+ * HMAC_CTX HMAC context type, without 'struct'
+ * HMAC_PREPAREKEY the hmac_*_preparekey() function
+ * HMAC_INIT the hmac_*_init() function
+ * HMAC_UPDATE the hmac_*_update() function
+ * HMAC_FINAL the hmac_*_final() function
+ * HMAC_USINGRAWKEY the hmac_*_usingrawkey() function
+ * HKDF_HASHLEN hash digest size in bytes
+ */
+
+/*
+ * HKDF-Extract (RFC 5869 section 2.2). Compute a pseudorandom key of
+ * HKDF_HASHLEN bytes from the input keying material and optional salt, and
+ * prepare it as an HMAC key.
+ *
+ * A NULL or empty 'salt', or any all-zero salt up to the HMAC block size,
+ * gives the same PRK as the RFC 5869 default salt of HKDF_HASHLEN zero bytes.
+ * If 'salt' is NULL, 'salt_len' must be 0.
+ */
+void HKDF_EXTRACT(struct HMAC_KEY *prk, const u8 *salt, size_t salt_len,
+ const u8 *ikm, size_t ikm_len)
+{
+ u8 prk_bytes[HKDF_HASHLEN];
+
+ HMAC_USINGRAWKEY(salt ? salt : (const u8 *)"", salt_len,
+ ikm, ikm_len, prk_bytes);
+ HMAC_PREPAREKEY(prk, prk_bytes, sizeof(prk_bytes));
+ memzero_explicit(prk_bytes, sizeof(prk_bytes));
+}
+EXPORT_SYMBOL_GPL(HKDF_EXTRACT);
+
+/*
+ * HKDF-Expand (RFC 5869 section 2.3). Expand the pseudorandom key 'prk' into
+ * 'okm_len' bytes of output keying material, parameterized by the
+ * application-specific info, given as 'info_nsegs' segments that are
+ * processed as if concatenated:
+ *
+ * T(0) = empty
+ * T(n) = HMAC(PRK, T(n-1) | info | n) for n = 1, 2, ...
+ * OKM = first okm_len bytes of T(1) | T(2) | ...
+ *
+ * This is thread-safe and may be called by multiple threads in parallel.
+ */
+void HKDF_EXPAND(const struct HMAC_KEY *prk, const struct hkdf_seg *info,
+ size_t info_nsegs, u8 *okm, size_t okm_len)
+{
+ struct HMAC_CTX ctx;
+ u8 counter = 1;
+ u8 tmp[HKDF_HASHLEN];
+
+ WARN_ON_ONCE(okm_len > 255 * HKDF_HASHLEN);
+
+ for (size_t i = 0; i < okm_len; i += HKDF_HASHLEN) {
+ HMAC_INIT(&ctx, prk);
+ if (i != 0)
+ HMAC_UPDATE(&ctx, &okm[i - HKDF_HASHLEN],
+ HKDF_HASHLEN);
+ for (size_t j = 0; j < info_nsegs; j++)
+ HMAC_UPDATE(&ctx, info[j].data, info[j].len);
+ HMAC_UPDATE(&ctx, &counter, 1);
+ if (okm_len - i < HKDF_HASHLEN) {
+ HMAC_FINAL(&ctx, tmp);
+ memcpy(&okm[i], tmp, okm_len - i);
+ memzero_explicit(tmp, sizeof(tmp));
+ } else {
+ HMAC_FINAL(&ctx, &okm[i]);
+ }
+ counter++;
+ }
+}
+EXPORT_SYMBOL_GPL(HKDF_EXPAND);
+
+#undef HKDF_EXTRACT
+#undef HKDF_EXPAND
+#undef HMAC_KEY
+#undef HMAC_CTX
+#undef HMAC_PREPAREKEY
+#undef HMAC_INIT
+#undef HMAC_UPDATE
+#undef HMAC_FINAL
+#undef HMAC_USINGRAWKEY
+#undef HKDF_HASHLEN
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/5] lib/crypto: tests: add HKDF KUnit tests
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 ` Marco Baffo
2026-07-21 14:06 ` [PATCH 3/5] fscrypt: use HKDF library functions Marco Baffo
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
Add KUnit tests for the HKDF-SHA{256,384,512} helpers using the
RFC 5869 SHA-256 vectors and SHA-384 and SHA-512 vectors generated
with "openssl kdf" from the same inputs. Also test empty salt and
info, multi-block output, and segmented info.
Signed-off-by: Marco Baffo <marco@mandelbit.com>
---
lib/crypto/.kunitconfig | 1 +
lib/crypto/tests/Kconfig | 8 +
lib/crypto/tests/Makefile | 1 +
lib/crypto/tests/hkdf_kunit.c | 276 ++++++++++++++++++++++++++++++++++
4 files changed, 286 insertions(+)
create mode 100644 lib/crypto/tests/hkdf_kunit.c
diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig
index 3efc854a2c08..82739358aa44 100644
--- a/lib/crypto/.kunitconfig
+++ b/lib/crypto/.kunitconfig
@@ -8,6 +8,7 @@ CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_CURVE25519_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_GHASH_KUNIT_TEST=y
+CONFIG_CRYPTO_LIB_HKDF_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_MD5_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_MLDSA_KUNIT_TEST=y
CONFIG_CRYPTO_LIB_NH_KUNIT_TEST=y
diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig
index bc084dde424f..900e5d3afd00 100644
--- a/lib/crypto/tests/Kconfig
+++ b/lib/crypto/tests/Kconfig
@@ -52,6 +52,14 @@ config CRYPTO_LIB_GHASH_KUNIT_TEST
help
KUnit tests for the GHASH library functions.
+config CRYPTO_LIB_HKDF_KUNIT_TEST
+ tristate "KUnit tests for HKDF" if !KUNIT_ALL_TESTS
+ depends on KUNIT && CRYPTO_LIB_SHA256 && CRYPTO_LIB_SHA512
+ default KUNIT_ALL_TESTS
+ help
+ KUnit tests for the HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512 key
+ derivation functions.
+
config CRYPTO_LIB_MD5_KUNIT_TEST
tristate "KUnit tests for MD5" if !KUNIT_ALL_TESTS
depends on KUNIT && CRYPTO_LIB_MD5
diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile
index a739413500b6..15a2f9b54458 100644
--- a/lib/crypto/tests/Makefile
+++ b/lib/crypto/tests/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST) += blake2s_kunit.o
obj-$(CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST) += chacha20poly1305_kunit.o
obj-$(CONFIG_CRYPTO_LIB_CURVE25519_KUNIT_TEST) += curve25519_kunit.o
obj-$(CONFIG_CRYPTO_LIB_GHASH_KUNIT_TEST) += ghash_kunit.o
+obj-$(CONFIG_CRYPTO_LIB_HKDF_KUNIT_TEST) += hkdf_kunit.o
obj-$(CONFIG_CRYPTO_LIB_MD5_KUNIT_TEST) += md5_kunit.o
obj-$(CONFIG_CRYPTO_LIB_MLDSA_KUNIT_TEST) += mldsa_kunit.o
obj-$(CONFIG_CRYPTO_LIB_NH_KUNIT_TEST) += nh_kunit.o
diff --git a/lib/crypto/tests/hkdf_kunit.c b/lib/crypto/tests/hkdf_kunit.c
new file mode 100644
index 000000000000..e8f65d761cc0
--- /dev/null
+++ b/lib/crypto/tests/hkdf_kunit.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512
+ *
+ * The HKDF-SHA256 test vectors are from RFC 5869 Appendix A. RFC 5869 does not
+ * provide SHA-384 or SHA-512 vectors, so those were generated with OpenSSL's
+ * "openssl kdf" command (available since OpenSSL 3.0) using the same inputs as
+ * the SHA-256 vectors. See: https://docs.openssl.org/3.0/man1/openssl-kdf/
+ *
+ * For example, the SHA-384 vector was generated with:
+ *
+ * $ openssl kdf -keylen 42 -kdfopt mode:EXTRACT_AND_EXPAND \
+ * -kdfopt digest:SHA384 \
+ * -kdfopt hexkey:0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b \
+ * -kdfopt hexsalt:000102030405060708090a0b0c \
+ * -kdfopt hexinfo:f0f1f2f3f4f5f6f7f8f9 HKDF
+ */
+
+#include <crypto/hkdf.h>
+#include <kunit/test.h>
+
+#define RFC5869_A1_IKM_BYTE 0x0b
+#define RFC5869_A1_IKM_LEN 22
+#define RFC5869_A1_SALT_START 0x00
+#define RFC5869_A1_SALT_LEN 13
+#define RFC5869_A1_INFO_START 0xf0
+#define RFC5869_A1_INFO_LEN 10
+
+#define RFC5869_A2_IKM_START 0x00
+#define RFC5869_A2_IKM_LEN 80
+#define RFC5869_A2_SALT_START 0x60
+#define RFC5869_A2_SALT_LEN 80
+#define RFC5869_A2_INFO_START 0xb0
+#define RFC5869_A2_INFO_LEN 80
+
+#define SEGMENT_TEST_INFO_LEN 40
+#define SEGMENT_TEST_OKM_LEN 100
+
+/* RFC 5869 A.1: ikm = 22 x 0x0b, salt = 0x00..0x0c, info = 0xf0..0xf9 */
+static const u8 rfc5869_a1_okm[] = {
+ 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a,
+ 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a,
+ 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c,
+ 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf,
+ 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18,
+ 0x58, 0x65
+};
+
+/* RFC 5869 A.2: ikm = 0x00..0x4f, salt = 0x60..0xaf, info = 0xb0..0xff */
+static const u8 rfc5869_a2_okm[] = {
+ 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1,
+ 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34,
+ 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8,
+ 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c,
+ 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72,
+ 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09,
+ 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8,
+ 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71,
+ 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87,
+ 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f,
+ 0x1d, 0x87
+};
+
+/* RFC 5869 A.3: ikm = 22 x 0x0b, zero-length salt and info */
+static const u8 rfc5869_a3_okm[] = {
+ 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f,
+ 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31,
+ 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e,
+ 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d,
+ 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a,
+ 0x96, 0xc8
+};
+
+/* HKDF-SHA384 with the RFC 5869 A.1 inputs */
+static const u8 hkdf_sha384_a1_okm[] = {
+ 0x9b, 0x50, 0x97, 0xa8, 0x60, 0x38, 0xb8, 0x05,
+ 0x30, 0x90, 0x76, 0xa4, 0x4b, 0x3a, 0x9f, 0x38,
+ 0x06, 0x3e, 0x25, 0xb5, 0x16, 0xdc, 0xbf, 0x36,
+ 0x9f, 0x39, 0x4c, 0xfa, 0xb4, 0x36, 0x85, 0xf7,
+ 0x48, 0xb6, 0x45, 0x77, 0x63, 0xe4, 0xf0, 0x20,
+ 0x4f, 0xc5
+};
+
+/* HKDF-SHA512 with the RFC 5869 A.1 inputs */
+static const u8 hkdf_sha512_a1_okm[] = {
+ 0x83, 0x23, 0x90, 0x08, 0x6c, 0xda, 0x71, 0xfb,
+ 0x47, 0x62, 0x5b, 0xb5, 0xce, 0xb1, 0x68, 0xe4,
+ 0xc8, 0xe2, 0x6a, 0x1a, 0x16, 0xed, 0x34, 0xd9,
+ 0xfc, 0x7f, 0xe9, 0x2c, 0x14, 0x81, 0x57, 0x93,
+ 0x38, 0xda, 0x36, 0x2c, 0xb8, 0xd9, 0xf9, 0x25,
+ 0xd7, 0xcb
+};
+
+/*
+ * HKDF-SHA512 with the RFC 5869 A.3 inputs and okm_len = 150, so that the
+ * output spans multiple blocks and ends with a partial block
+ */
+static const u8 hkdf_sha512_a3_okm[] = {
+ 0xf5, 0xfa, 0x02, 0xb1, 0x82, 0x98, 0xa7, 0x2a,
+ 0x8c, 0x23, 0x89, 0x8a, 0x87, 0x03, 0x47, 0x2c,
+ 0x6e, 0xb1, 0x79, 0xdc, 0x20, 0x4c, 0x03, 0x42,
+ 0x5c, 0x97, 0x0e, 0x3b, 0x16, 0x4b, 0xf9, 0x0f,
+ 0xff, 0x22, 0xd0, 0x48, 0x36, 0xd0, 0xe2, 0x34,
+ 0x3b, 0xac, 0xc4, 0xe7, 0xcb, 0x60, 0x45, 0xfa,
+ 0xaa, 0x69, 0x8e, 0x0e, 0x3b, 0x3e, 0xb9, 0x13,
+ 0x31, 0x30, 0x6d, 0xef, 0x1d, 0xb8, 0x31, 0x9e,
+ 0x8a, 0x69, 0x9b, 0x5e, 0xe4, 0x5a, 0xb9, 0x93,
+ 0x84, 0x7d, 0xc4, 0xdf, 0x75, 0xbd, 0xe0, 0x23,
+ 0x69, 0x2c, 0x8c, 0x07, 0x10, 0xa6, 0x7a, 0x55,
+ 0x12, 0x3f, 0x10, 0xa8, 0xb2, 0xd8, 0x32, 0x7f,
+ 0x9e, 0xb1, 0x38, 0xda, 0x69, 0xd5, 0xbe, 0xa1,
+ 0xe0, 0x9a, 0x39, 0xea, 0x99, 0xa3, 0x41, 0xc0,
+ 0x0b, 0x2c, 0x9e, 0xe0, 0xd4, 0xba, 0x63, 0x21,
+ 0x15, 0xae, 0xc5, 0x16, 0xbb, 0x71, 0xe9, 0x22,
+ 0x7c, 0xb0, 0x0c, 0xa9, 0x8b, 0x7d, 0xfa, 0x8f,
+ 0x1c, 0x7e, 0xca, 0x60, 0xc9, 0x31, 0x28, 0x1f,
+ 0xa8, 0x5b, 0x87, 0x2f, 0x70, 0xb4
+};
+
+/*
+ * Utility function to initialize ikm/salt/info buffers.
+ * Fill 'buf' with 'len' consecutive byte values beginning with 'start'.
+ */
+static void fill_range(u8 *buf, size_t len, u8 start)
+{
+ for (size_t i = 0; i < len; i++)
+ buf[i] = start + i;
+}
+
+static void test_hkdf_sha256_rfc5869(struct kunit *test)
+{
+ struct hmac_sha256_key prk;
+ u8 ikm[RFC5869_A2_IKM_LEN];
+ u8 salt[RFC5869_A2_SALT_LEN];
+ u8 info[RFC5869_A2_INFO_LEN];
+ u8 okm[sizeof(rfc5869_a2_okm)];
+ struct hkdf_seg seg;
+
+ /* A.1: basic test case */
+ memset(ikm, RFC5869_A1_IKM_BYTE, RFC5869_A1_IKM_LEN);
+ fill_range(salt, RFC5869_A1_SALT_LEN, RFC5869_A1_SALT_START);
+ fill_range(info, RFC5869_A1_INFO_LEN, RFC5869_A1_INFO_START);
+ hkdf_sha256_extract(&prk, salt, RFC5869_A1_SALT_LEN,
+ ikm, RFC5869_A1_IKM_LEN);
+ seg = (struct hkdf_seg){ .data = info, .len = RFC5869_A1_INFO_LEN };
+ hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a1_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a1_okm,
+ sizeof(rfc5869_a1_okm), "RFC 5869 A.1");
+
+ /* A.2: longer inputs, multi-block output ending in a partial block */
+ fill_range(ikm, RFC5869_A2_IKM_LEN, RFC5869_A2_IKM_START);
+ fill_range(salt, RFC5869_A2_SALT_LEN, RFC5869_A2_SALT_START);
+ fill_range(info, RFC5869_A2_INFO_LEN, RFC5869_A2_INFO_START);
+ hkdf_sha256_extract(&prk, salt, RFC5869_A2_SALT_LEN,
+ ikm, RFC5869_A2_IKM_LEN);
+ seg = (struct hkdf_seg){ .data = info, .len = RFC5869_A2_INFO_LEN };
+ hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a2_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a2_okm,
+ sizeof(rfc5869_a2_okm), "RFC 5869 A.2");
+
+ /* A.3: zero-length salt and info */
+ memset(ikm, RFC5869_A1_IKM_BYTE, RFC5869_A1_IKM_LEN);
+ hkdf_sha256_extract(&prk, NULL, 0, ikm, RFC5869_A1_IKM_LEN);
+ hkdf_sha256_expand(&prk, NULL, 0, okm, sizeof(rfc5869_a3_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a3_okm,
+ sizeof(rfc5869_a3_okm),
+ "RFC 5869 A.3 with NULL salt and info");
+
+ /* A.3 again, with a non-NULL empty salt and an empty info segment */
+ hkdf_sha256_extract(&prk, salt, 0, ikm, RFC5869_A1_IKM_LEN);
+ seg = (struct hkdf_seg){ .data = info, .len = 0 };
+ hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a3_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a3_okm,
+ sizeof(rfc5869_a3_okm),
+ "RFC 5869 A.3 with non-NULL empty salt and info");
+}
+
+static void test_hkdf_sha384_rfc5869_a1(struct kunit *test)
+{
+ struct hmac_sha384_key prk;
+ u8 ikm[RFC5869_A1_IKM_LEN];
+ u8 salt[RFC5869_A1_SALT_LEN];
+ u8 info[RFC5869_A1_INFO_LEN];
+ u8 okm[sizeof(hkdf_sha384_a1_okm)];
+ struct hkdf_seg seg;
+
+ memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm));
+ fill_range(salt, sizeof(salt), RFC5869_A1_SALT_START);
+ fill_range(info, sizeof(info), RFC5869_A1_INFO_START);
+ seg = (struct hkdf_seg){ .data = info, .len = sizeof(info) };
+ hkdf_sha384_extract(&prk, salt, sizeof(salt), ikm, sizeof(ikm));
+ hkdf_sha384_expand(&prk, &seg, 1, okm, sizeof(okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha384_a1_okm, sizeof(okm),
+ "SHA-384 with RFC 5869 A.1 inputs");
+}
+
+static void test_hkdf_sha512_rfc5869_a1_a3(struct kunit *test)
+{
+ struct hmac_sha512_key prk;
+ u8 ikm[RFC5869_A1_IKM_LEN];
+ u8 salt[RFC5869_A1_SALT_LEN];
+ u8 info[RFC5869_A1_INFO_LEN];
+ u8 okm[sizeof(hkdf_sha512_a3_okm)];
+ struct hkdf_seg seg;
+
+ memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm));
+ fill_range(salt, sizeof(salt), RFC5869_A1_SALT_START);
+ fill_range(info, sizeof(info), RFC5869_A1_INFO_START);
+ seg = (struct hkdf_seg){ .data = info, .len = sizeof(info) };
+ hkdf_sha512_extract(&prk, salt, sizeof(salt), ikm, sizeof(ikm));
+ hkdf_sha512_expand(&prk, &seg, 1, okm, sizeof(hkdf_sha512_a1_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha512_a1_okm,
+ sizeof(hkdf_sha512_a1_okm),
+ "SHA-512 with RFC 5869 A.1 inputs");
+
+ hkdf_sha512_extract(&prk, NULL, 0, ikm, sizeof(ikm));
+ hkdf_sha512_expand(&prk, NULL, 0, okm, sizeof(hkdf_sha512_a3_okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha512_a3_okm,
+ sizeof(hkdf_sha512_a3_okm),
+ "SHA-512 with RFC 5869 A.3 inputs and 150-byte OKM");
+}
+
+/* Test that splitting info into segments does not influence the output. */
+static void test_hkdf_expand_info_segments(struct kunit *test)
+{
+ struct hmac_sha512_key prk;
+ u8 ikm[RFC5869_A1_IKM_LEN];
+ u8 info[SEGMENT_TEST_INFO_LEN];
+ u8 okm_ref[SEGMENT_TEST_OKM_LEN];
+ u8 okm[SEGMENT_TEST_OKM_LEN];
+ struct hkdf_seg segs[sizeof(info)];
+
+ memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm));
+ fill_range(info, sizeof(info), 0x00);
+ hkdf_sha512_extract(&prk, NULL, 0, ikm, sizeof(ikm));
+
+ segs[0] = (struct hkdf_seg){ .data = info, .len = sizeof(info) };
+ hkdf_sha512_expand(&prk, segs, 1, okm_ref, sizeof(okm_ref));
+
+ /* every possible two-way split */
+ for (size_t split = 0; split <= sizeof(info); split++) {
+ segs[0] = (struct hkdf_seg){ .data = info, .len = split };
+ segs[1] = (struct hkdf_seg){ .data = &info[split],
+ .len = sizeof(info) - split };
+ memset(okm, 0, sizeof(okm));
+ hkdf_sha512_expand(&prk, segs, 2, okm, sizeof(okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, okm_ref, sizeof(okm),
+ "failed for split=%zu", split);
+ }
+
+ /* one segment per byte */
+ for (size_t i = 0; i < sizeof(info); i++)
+ segs[i] = (struct hkdf_seg){ .data = &info[i], .len = 1 };
+ memset(okm, 0, sizeof(okm));
+ hkdf_sha512_expand(&prk, segs, sizeof(info), okm, sizeof(okm));
+ KUNIT_EXPECT_MEMEQ_MSG(test, okm, okm_ref, sizeof(okm),
+ "one info segment per byte");
+}
+
+static struct kunit_case hkdf_test_cases[] = {
+ KUNIT_CASE(test_hkdf_sha256_rfc5869),
+ KUNIT_CASE(test_hkdf_sha384_rfc5869_a1),
+ KUNIT_CASE(test_hkdf_sha512_rfc5869_a1_a3),
+ KUNIT_CASE(test_hkdf_expand_info_segments),
+ {},
+};
+
+static struct kunit_suite hkdf_test_suite = {
+ .name = "hkdf",
+ .test_cases = hkdf_test_cases,
+};
+
+kunit_test_suite(hkdf_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/5] fscrypt: use HKDF library functions
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 ` Marco Baffo
2026-07-21 14:06 ` [PATCH 4/5] nvme-auth: use HKDF library functions for TLS PSK derivation Marco Baffo
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
Convert fscrypt to the common HKDF-SHA512 helpers without changing its
derived keys. The info bytes are processed in the same order as before.
An empty salt is equivalent to the previous 64-byte all-zero salt after
HMAC key padding.
Signed-off-by: Marco Baffo <marco@mandelbit.com>
---
fs/crypto/hkdf.c | 56 +++++++++++-------------------------------------
1 file changed, 13 insertions(+), 43 deletions(-)
diff --git a/fs/crypto/hkdf.c b/fs/crypto/hkdf.c
index 706f56d0076e..7df7d466a3e4 100644
--- a/fs/crypto/hkdf.c
+++ b/fs/crypto/hkdf.c
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Implementation of HKDF ("HMAC-based Extract-and-Expand Key Derivation
- * Function"), aka RFC 5869. See also the original paper (Krawczyk 2010):
- * "Cryptographic Extraction and Key Derivation: The HKDF Scheme".
+ * Key derivation using HKDF-SHA512.
*
* This is used to derive keys from the fscrypt master keys (or from the
* "software secrets" which hardware derives from the fscrypt master keys, in
@@ -11,6 +9,8 @@
* Copyright 2019 Google LLC
*/
+#include <crypto/hkdf.h>
+
#include "fscrypt_private.h"
/*
@@ -24,19 +24,11 @@
* HKDF-SHA512 being much faster than HKDF-SHA256, as the longer digest size of
* SHA-512 causes HKDF-Expand to only need to do one iteration rather than two.
*/
-#define HKDF_HASHLEN SHA512_DIGEST_SIZE
/*
- * HKDF consists of two steps:
- *
- * 1. HKDF-Extract: extract a pseudorandom key of length HKDF_HASHLEN bytes from
- * the input keying material and optional salt.
- * 2. HKDF-Expand: expand the pseudorandom key into output keying material of
- * any length, parameterized by an application-specific info string.
- *
* HKDF-Extract can be skipped if the input is already a pseudorandom key of
- * length HKDF_HASHLEN bytes. However, cipher modes other than AES-256-XTS take
- * shorter keys, and we don't want to force users of those modes to provide
+ * length SHA512_DIGEST_SIZE bytes. However, cipher modes other than AES-256-XTS
+ * take shorter keys, and we don't want to force users of those modes to provide
* unnecessarily long master keys. Thus fscrypt still does HKDF-Extract. No
* salt is used, since fscrypt master keys should already be pseudorandom and
* there's no way to persist a random salt per master key from kernel mode.
@@ -50,13 +42,7 @@
void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
unsigned int master_key_size)
{
- static const u8 default_salt[HKDF_HASHLEN];
- u8 prk[HKDF_HASHLEN];
-
- hmac_sha512_usingrawkey(default_salt, sizeof(default_salt),
- master_key, master_key_size, prk);
- hmac_sha512_preparekey(hkdf, prk, sizeof(prk));
- memzero_explicit(prk, sizeof(prk));
+ hkdf_sha512_extract(hkdf, NULL, 0, master_key, master_key_size);
}
/*
@@ -73,28 +59,12 @@ void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
const u8 *info, unsigned int infolen,
u8 *okm, unsigned int okmlen)
{
- struct hmac_sha512_ctx ctx;
- u8 counter = 1;
- u8 tmp[HKDF_HASHLEN];
-
- WARN_ON_ONCE(okmlen > 255 * HKDF_HASHLEN);
+ const struct hkdf_seg info_segs[] = {
+ { .data = "fscrypt\0", .len = 8 },
+ { .data = &context, .len = 1 },
+ { .data = info, .len = infolen },
+ };
- for (unsigned int i = 0; i < okmlen; i += HKDF_HASHLEN) {
- hmac_sha512_init(&ctx, hkdf);
- if (i != 0)
- hmac_sha512_update(&ctx, &okm[i - HKDF_HASHLEN],
- HKDF_HASHLEN);
- hmac_sha512_update(&ctx, "fscrypt\0", 8);
- hmac_sha512_update(&ctx, &context, 1);
- hmac_sha512_update(&ctx, info, infolen);
- hmac_sha512_update(&ctx, &counter, 1);
- if (okmlen - i < HKDF_HASHLEN) {
- hmac_sha512_final(&ctx, tmp);
- memcpy(&okm[i], tmp, okmlen - i);
- memzero_explicit(tmp, sizeof(tmp));
- } else {
- hmac_sha512_final(&ctx, &okm[i]);
- }
- counter++;
- }
+ hkdf_sha512_expand(hkdf, info_segs, ARRAY_SIZE(info_segs),
+ okm, okmlen);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/5] nvme-auth: use HKDF library functions for TLS PSK derivation
2026-07-21 14:06 [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Marco Baffo
` (2 preceding siblings ...)
2026-07-21 14:06 ` [PATCH 3/5] fscrypt: use HKDF library functions Marco Baffo
@ 2026-07-21 14:06 ` Marco Baffo
2026-07-21 14:06 ` [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation Marco Baffo
2026-07-21 15:06 ` [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Eric Biggers
5 siblings, 0 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
NVMe currently implements HKDF locally in nvme_auth_derive_tls_psk()
and allocates a temporary buffer for the HKDF-Expand input. Use the
common SHA-256 and SHA-384 HKDF helpers instead.
Pass the HkdfLabel fields as separate info segments, eliminating the
temporary allocation and its failure path.
The derived keys are unchanged. The segments are processed as if
concatenated, the expand counter byte is appended by the helper, and
the empty salt is equivalent to the previous digest-sized all-zero
salt after HMAC key padding.
Signed-off-by: Marco Baffo <marco@mandelbit.com>
---
drivers/nvme/common/auth.c | 120 +++++++++++++++++++++----------------
1 file changed, 68 insertions(+), 52 deletions(-)
diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
index 77f1d22512f8..b44922dd1cf6 100644
--- a/drivers/nvme/common/auth.c
+++ b/drivers/nvme/common/auth.c
@@ -10,6 +10,7 @@
#include <linux/scatterlist.h>
#include <linux/unaligned.h>
#include <crypto/dh.h>
+#include <crypto/hkdf.h>
#include <crypto/sha2.h>
#include <linux/nvme.h>
#include <linux/nvme-auth.h>
@@ -691,14 +692,19 @@ EXPORT_SYMBOL_GPL(nvme_auth_generate_digest);
int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len,
const char *psk_digest, u8 **ret_psk)
{
- static const u8 default_salt[NVME_AUTH_MAX_DIGEST_SIZE];
static const char label[] = "tls13 nvme-tls-psk";
- const size_t label_len = sizeof(label) - 1;
- u8 prk[NVME_AUTH_MAX_DIGEST_SIZE];
- size_t hash_len, ctx_len;
- u8 *hmac_data = NULL, *tls_key;
- size_t i;
- int ret;
+ const u8 label_len = sizeof(label) - 1;
+ union {
+ struct hmac_sha256_key sha256;
+ struct hmac_sha384_key sha384;
+ } prk;
+ struct hkdf_seg info[6];
+ size_t hash_len;
+ u8 output_len[2];
+ u8 ctx_len;
+ char ctx_prefix[4];
+ size_t digest_len;
+ u8 *tls_key;
hash_len = nvme_auth_hmac_hash_len(hmac_id);
if (hash_len == 0) {
@@ -717,64 +723,74 @@ int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len,
return -EINVAL;
}
- /* HKDF-Extract */
- ret = nvme_auth_hmac(hmac_id, default_salt, hash_len, psk, psk_len,
- prk);
- if (ret)
- goto out;
-
/*
- * HKDF-Expand-Label (RFC 8446 section 7.1), with output length equal to
- * the hash length (so only a single HMAC operation is needed)
+ * HKDF-Expand-Label (RFC 8446 section 7.1). The HKDF-Expand info
+ * parameter is an encoded HkdfLabel containing the output length,
+ * label, and context.
*/
- hmac_data = kmalloc(/* output length */ 2 +
- /* label */ 1 + label_len +
- /* context (max) */ 1 + 3 + 1 + strlen(psk_digest) +
- /* counter */ 1,
- GFP_KERNEL);
- if (!hmac_data) {
- ret = -ENOMEM;
- goto out;
- }
/* output length */
- i = 0;
- hmac_data[i++] = hash_len >> 8;
- hmac_data[i++] = hash_len;
+ output_len[0] = hash_len >> 8;
+ output_len[1] = hash_len;
+ info[0] = (struct hkdf_seg){
+ .data = output_len,
+ .len = sizeof(output_len),
+ };
/* label */
- static_assert(label_len <= 255);
- hmac_data[i] = label_len;
- memcpy(&hmac_data[i + 1], label, label_len);
- i += 1 + label_len;
+ static_assert(sizeof(label) - 1 <= 255);
+ info[1] = (struct hkdf_seg){
+ .data = &label_len,
+ .len = sizeof(label_len),
+ };
+ info[2] = (struct hkdf_seg){
+ .data = label,
+ .len = label_len,
+ };
/* context */
- ctx_len = sprintf(&hmac_data[i + 1], "%02d %s", hmac_id, psk_digest);
- if (ctx_len > 255) {
- ret = -EINVAL;
- goto out;
- }
- hmac_data[i] = ctx_len;
- i += 1 + ctx_len;
+ digest_len = strlen(psk_digest);
+ if (digest_len > 255 - (sizeof(ctx_prefix) - 1))
+ return -EINVAL;
- /* counter (this overwrites the NUL terminator written by sprintf) */
- hmac_data[i++] = 1;
+ snprintf(ctx_prefix, sizeof(ctx_prefix), "%02d ", hmac_id);
+ ctx_len = sizeof(ctx_prefix) - 1 + digest_len;
+ info[3] = (struct hkdf_seg){
+ .data = &ctx_len,
+ .len = sizeof(ctx_len),
+ };
+ info[4] = (struct hkdf_seg){
+ .data = ctx_prefix,
+ .len = sizeof(ctx_prefix) - 1,
+ };
+ info[5] = (struct hkdf_seg){
+ .data = psk_digest,
+ .len = digest_len,
+ };
+
+ tls_key = kmalloc(psk_len, GFP_KERNEL);
+ if (!tls_key)
+ return -ENOMEM;
- tls_key = kzalloc(psk_len, GFP_KERNEL);
- if (!tls_key) {
- ret = -ENOMEM;
- goto out;
- }
- ret = nvme_auth_hmac(hmac_id, prk, hash_len, hmac_data, i, tls_key);
- if (ret) {
+ switch (hmac_id) {
+ case NVME_AUTH_HASH_SHA256:
+ hkdf_sha256_extract(&prk.sha256, NULL, 0, psk, psk_len);
+ hkdf_sha256_expand(&prk.sha256, info, ARRAY_SIZE(info),
+ tls_key, psk_len);
+ break;
+ case NVME_AUTH_HASH_SHA384:
+ hkdf_sha384_extract(&prk.sha384, NULL, 0, psk, psk_len);
+ hkdf_sha384_expand(&prk.sha384, info, ARRAY_SIZE(info),
+ tls_key, psk_len);
+ break;
+ default:
+ WARN_ON_ONCE(1);
kfree_sensitive(tls_key);
- goto out;
+ return -EINVAL;
}
+ memzero_explicit(&prk, sizeof(prk));
*ret_psk = tls_key;
-out:
- kfree_sensitive(hmac_data);
- memzero_explicit(prk, sizeof(prk));
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(nvme_auth_derive_tls_psk);
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation
2026-07-21 14:06 [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Marco Baffo
` (3 preceding siblings ...)
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
2026-07-21 15:06 ` [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Eric Biggers
5 siblings, 0 replies; 7+ messages in thread
From: Marco Baffo @ 2026-07-21 14:06 UTC (permalink / raw)
To: linux-crypto
Cc: herbert, davem, ebiggers, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel,
Marco Baffo
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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe
2026-07-21 14:06 [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Marco Baffo
` (4 preceding siblings ...)
2026-07-21 14:06 ` [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation Marco Baffo
@ 2026-07-21 15:06 ` Eric Biggers
5 siblings, 0 replies; 7+ messages in thread
From: Eric Biggers @ 2026-07-21 15:06 UTC (permalink / raw)
To: Marco Baffo
Cc: linux-crypto, herbert, davem, Jason, ardb, tytso, jaegeuk,
linux-fscrypt, kbusch, axboe, hch, sagi, hare, linux-nvme,
andrew+netdev, edumazet, kuba, pabeni, antonio, sd, linux-kernel
Hi Marco,
On Tue, Jul 21, 2026 at 04:06:39PM +0200, Marco Baffo wrote:
> fscrypt and NVMe authentication each carry private HKDF
> implementations. Add HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512
> functions to lib/crypto and convert both users.
>
> Patches 1-4 are intended for review and inclusion.
>
> Patch 5 is RFC only and is not intended to be applied. It shows how
> the OVPN epoch-key code currently under development can use the new
> HKDF API. That patch has an additional dependency on the OVPN epoch
> series. It is included here to allow the API to be reviewed against
> a third user. The OVPN conversion will be submitted separately once
> its prerequisite code is ready.
>
> HKDF-Expand accepts info as an array of segments and processes them as
> if concatenated. This preserves the byte ordering used by fscrypt and
> allows NVMe to encode HkdfLabel without allocating a temporary buffer.
>
> HKDF-Extract produces a prepared HMAC key, avoiding repeated key setup
> when the same PRK is expanded more than once. The implementation uses
> the direct HMAC library functions and neither allocates memory nor
> returns errors.
>
> The series is intended for the libcrypto tree. Patches 3 and 4 need
> review from the fscrypt and NVMe maintainers, respectively.
>
> Tested with:
>
> - HKDF KUnit tests
> - NVMe authentication KUnit tests
> - kvm-xfstests smoketest on ext4
> - kvm-xfstests generic/582, generic/583, and generic/584 on ext4 and f2fs
> - kvm-xfstests encrypt group on ext4 and f2fs
>
> The following shortlog and diffstat refers only to the first 4 patches.
>
> Marco Baffo (4):
> lib/crypto: add HKDF-SHA{256,384,512}
> lib/crypto: tests: add HKDF KUnit tests
> fscrypt: use HKDF library functions
> nvme-auth: use HKDF library functions for TLS PSK derivation
>
> drivers/nvme/common/auth.c | 120 ++++++++-------
> fs/crypto/hkdf.c | 56 ++-----
> include/crypto/hkdf.h | 131 ++++++++++++++++
> lib/crypto/.kunitconfig | 1 +
> lib/crypto/Makefile | 4 +-
> lib/crypto/hkdf-sha256.c | 22 +++
> lib/crypto/hkdf-sha512.c | 34 +++++
> lib/crypto/hkdf-template.h | 97 ++++++++++++
> lib/crypto/tests/Kconfig | 8 +
> lib/crypto/tests/Makefile | 1 +
> lib/crypto/tests/hkdf_kunit.c | 276 ++++++++++++++++++++++++++++++++++
> 11 files changed, 653 insertions(+), 97 deletions(-)
> create mode 100644 include/crypto/hkdf.h
> create mode 100644 lib/crypto/hkdf-sha256.c
> create mode 100644 lib/crypto/hkdf-sha512.c
> create mode 100644 lib/crypto/hkdf-template.h
> create mode 100644 lib/crypto/tests/hkdf_kunit.c
If we're going to reintroduce an HKDF library, this is probably the way
to do it (no crypto_shash dependency).
But I think the positive diffstat makes having an HKDF library at all in
the kernel still kind of questionable. HKDF is just a wrapper around
HMAC and it can "unroll" to as little as 1-2 HMAC calls. It's not
really like the other crypto algorithms where the implementations can be
very complex and we often have multiple arch-specific implementations.
In drivers/nvme/common/auth.c, using the library actually ended up as
more code than not using it.
And in fs/crypto/hkdf.c this patchset only saves a bit (with some of the
savings coming from deleting helpful comments).
In the ovpn case, the extraction step isn't actually used. Nor are
multiple expansion iterations needed, since all the keys needing to be
derived are at most the hash size. So vs. using hmac_sha256_* directly
it saves maybe 10-15 lines? (Note that the diffstat in patch 5 isn't
directly relevant, as the starting point was crypto_shash.)
So then we end up with quite a bit of additional code in lib/crypto/, to
support users where the diffstat is basically neutral.
And this is only HKDF, so this hardly solves key derivation in general.
Plenty of KDFs don't use HKDF at all, either ones based on block ciphers
or stream ciphers, or newer hash functions that aren't vulnerable to
length extension attacks so don't require the HMAC layer. There are
lots of other KDFs implemented in the kernel in various places. Just to
pick a couple random examples, extract_entropy() in
drivers/char/random.c is doing a KDF, tcp_ao_calc_traffic_key() in
net/ipv4/tcp_ao.c is doing a KDF. Do those need to be libraries too?
So overall I would suggest it might be best to hold off on this for now
and just call the hmac_sha256_* functions directly from
ovpn_expand_label(). (And again, only one iteration is actually needed
there, so it doesn't even need to be the full HKDF-Expand.)
- Eric
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-21 15:08 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation Marco Baffo
2026-07-21 15:06 ` [PATCH 0/5] lib/crypto: add HKDF and convert fscrypt and NVMe Eric Biggers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox