* [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* [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