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


Reply via email to