[PATCH 2/5] lib/crypto: tests: add HKDF KUnit tests

Marco Baffo <[email protected]>
Newsgroups org.infradead.lists.linux-nvme,org.kernel.vger.linux-crypto,org.kernel.vger.linux-fscrypt,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.