diff --git a/benchmark/bench_modules/wh_bench_mod_all.h b/benchmark/bench_modules/wh_bench_mod_all.h
index c31f6b769..0dff6c419 100644
--- a/benchmark/bench_modules/wh_bench_mod_all.h
+++ b/benchmark/bench_modules/wh_bench_mod_all.h
@@ -165,6 +165,12 @@ int wh_Bench_Mod_HmacSha3256Dma(whClientContext* client, whBenchOpContext* ctx,
int wh_Bench_Mod_HkdfSha256(whClientContext* client, whBenchOpContext* ctx,
int id, void* params);
+/*
+ * CMAC KDF benchmark module prototypes (wh_bench_mod_cmac_kdf.c)
+ */
+int wh_Bench_Mod_CmacKdf(whClientContext* client, whBenchOpContext* ctx, int id,
+ void* params);
+
/*
* ECC benchmark module prototypes (wh_bench_mod_ecc.c)
*/
diff --git a/benchmark/bench_modules/wh_bench_mod_cmac_kdf.c b/benchmark/bench_modules/wh_bench_mod_cmac_kdf.c
new file mode 100644
index 000000000..6b30eb880
--- /dev/null
+++ b/benchmark/bench_modules/wh_bench_mod_cmac_kdf.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2025 wolfSSL Inc.
+ *
+ * This file is part of wolfHSM.
+ *
+ * wolfHSM is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * wolfHSM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wolfHSM. If not, see .
+ */
+
+#include "wh_bench_mod.h"
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_crypto.h"
+
+#include "wolfssl/wolfcrypt/cmac.h"
+#include "wolfssl/wolfcrypt/kdf.h"
+
+#if defined(WOLFHSM_CFG_BENCH_ENABLE)
+
+#if defined(HAVE_CMAC_KDF) && defined(WOLFSSL_CMAC)
+
+#define WH_BENCH_CMAC_KDF_OUT_SIZE 40
+
+static int _benchCmacKdf(whClientContext* client, whBenchOpContext* ctx, int id,
+ int devId)
+{
+ /* Derivation inputs mirror the unit test vectors to provide realistic
+ * message sizes while keeping the benchmark deterministic. */
+ static const uint8_t cmacKdfSalt[] = {
+ 0x20, 0x51, 0xaf, 0x34, 0x76, 0x2e, 0xbe, 0x55, 0x6f, 0x72, 0xa5, 0xc6,
+ 0xed, 0xc7, 0x77, 0x1e, 0xb9, 0x24, 0x5f, 0xad, 0x76, 0xf0, 0x34, 0xbe};
+ static const uint8_t cmacKdfZ[] = {
+ 0xae, 0x8e, 0x93, 0xc9, 0xc9, 0x91, 0xcf, 0x89, 0x6a, 0x49, 0x1a,
+ 0x89, 0x07, 0xdf, 0x4e, 0x4b, 0xe5, 0x18, 0x6a, 0xe4, 0x96, 0xcd,
+ 0x34, 0x0d, 0xc1, 0x9b, 0x23, 0x78, 0x21, 0xdb, 0x7b, 0x60};
+ static const uint8_t cmacKdfFixedInfo[] = {
+ 0xa2, 0x59, 0xca, 0xe2, 0xc4, 0xa3, 0x6b, 0x89, 0x56, 0x3c, 0xb1, 0x48,
+ 0xc7, 0x82, 0x51, 0x34, 0x3b, 0xbf, 0xab, 0xdc, 0x13, 0xca, 0x7a, 0xc2,
+ 0x17, 0x1c, 0x2e, 0xb6, 0x02, 0x1f, 0x44, 0x77, 0xfe, 0xa3, 0x3b, 0x28,
+ 0x72, 0x4d, 0xa7, 0x21, 0xee, 0x08, 0x7b, 0xff, 0xd7, 0x94, 0xa1, 0x56,
+ 0x37, 0x54, 0xb4, 0x25, 0xa8, 0xd0, 0x9b, 0x3e, 0x0d, 0xa5, 0xff, 0xed};
+ static const uint8_t label[] = "cmac-kdf-bench";
+
+ int ret = 0;
+ whKeyId keyId;
+ int i;
+
+ (void)devId;
+
+ for (i = 0; i < WOLFHSM_CFG_BENCH_KG_ITERS && ret == 0; i++) {
+ int benchStartRet;
+ int benchStopRet;
+
+ keyId = WH_KEYID_ERASED;
+
+ benchStartRet = wh_Bench_StartOp(ctx, id);
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ client, WH_KEYID_ERASED, cmacKdfSalt, (uint32_t)sizeof(cmacKdfSalt),
+ WH_KEYID_ERASED, cmacKdfZ, (uint32_t)sizeof(cmacKdfZ),
+ cmacKdfFixedInfo, (uint32_t)sizeof(cmacKdfFixedInfo), &keyId,
+ WH_NVM_FLAGS_NONE, label, (uint32_t)sizeof(label),
+ WH_BENCH_CMAC_KDF_OUT_SIZE);
+ benchStopRet = wh_Bench_StopOp(ctx, id);
+
+ if (benchStartRet != 0) {
+ WH_BENCH_PRINTF("Failed to wh_Bench_StartOp %d\n", benchStartRet);
+ ret = benchStartRet;
+ break;
+ }
+ if (ret != 0) {
+ WH_BENCH_PRINTF("Failed to wh_Client_CmacKdfMakeCacheKey %d\n",
+ ret);
+ break;
+ }
+ if (benchStopRet != 0) {
+ WH_BENCH_PRINTF("Failed to wh_Bench_StopOp %d\n", benchStopRet);
+ ret = benchStopRet;
+ break;
+ }
+
+ /* Evict the cached key to free resources for next iteration */
+ ret = wh_Client_KeyEvict(client, keyId);
+ if (ret != 0) {
+ WH_BENCH_PRINTF("Failed to wh_Client_KeyEvict %d\n", ret);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+int wh_Bench_Mod_CmacKdf(whClientContext* client, whBenchOpContext* ctx, int id,
+ void* params)
+{
+ (void)params;
+ return _benchCmacKdf(client, ctx, id, WH_DEV_ID);
+}
+
+#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
+
+#endif /* WOLFHSM_CFG_BENCH_ENABLE */
diff --git a/benchmark/bench_modules/wh_bench_mod_hkdf.c b/benchmark/bench_modules/wh_bench_mod_hkdf.c
index 774d7c939..1bd5d0b19 100644
--- a/benchmark/bench_modules/wh_bench_mod_hkdf.c
+++ b/benchmark/bench_modules/wh_bench_mod_hkdf.c
@@ -19,6 +19,8 @@
#include "wh_bench_mod.h"
#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_client_crypto.h"
#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_BENCH_ENABLE)
#include "wolfssl/wolfcrypt/hmac.h"
@@ -44,24 +46,26 @@ static int _benchHkdf(whClientContext* client, whBenchOpContext* ctx, int id,
0x0a, 0x0b, 0x0c};
static const uint8_t hkdf_info[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4,
0xf5, 0xf6, 0xf7, 0xf8, 0xf9};
-
+ static const uint8_t label[] = "hkdf-bench";
int ret = 0;
- uint8_t okm[WH_BENCH_HKDF_OKM_SIZE];
+ whKeyId keyId;
int i;
- (void)client;
+ (void)devId;
for (i = 0; i < WOLFHSM_CFG_BENCH_KG_ITERS && ret == 0; i++) {
int benchStartRet;
int benchStopRet;
+ keyId = WH_KEYID_ERASED;
+
benchStartRet = wh_Bench_StartOp(ctx, id);
- ret = wc_HKDF_ex(WC_SHA256, hkdf_ikm, (word32)sizeof(hkdf_ikm),
- hkdf_salt, (word32)sizeof(hkdf_salt), hkdf_info,
- (word32)sizeof(hkdf_info), okm, (word32)sizeof(okm),
- NULL, /* heap */
- devId);
+ ret = wh_Client_HkdfMakeCacheKey(
+ client, WC_SHA256, WH_KEYID_ERASED, hkdf_ikm,
+ (uint32_t)sizeof(hkdf_ikm), hkdf_salt, (uint32_t)sizeof(hkdf_salt),
+ hkdf_info, (uint32_t)sizeof(hkdf_info), &keyId, WH_NVM_FLAGS_NONE,
+ label, (uint32_t)sizeof(label), WH_BENCH_HKDF_OKM_SIZE);
benchStopRet = wh_Bench_StopOp(ctx, id);
if (benchStartRet != 0) {
@@ -70,7 +74,7 @@ static int _benchHkdf(whClientContext* client, whBenchOpContext* ctx, int id,
break;
}
if (ret != 0) {
- WH_BENCH_PRINTF("Failed to wc_HKDF_ex %d\n", ret);
+ WH_BENCH_PRINTF("Failed to wh_Client_HkdfMakeCacheKey %d\n", ret);
break;
}
if (benchStopRet != 0) {
@@ -78,6 +82,13 @@ static int _benchHkdf(whClientContext* client, whBenchOpContext* ctx, int id,
ret = benchStopRet;
break;
}
+
+ /* Evict the cached key to free resources for next iteration */
+ ret = wh_Client_KeyEvict(client, keyId);
+ if (ret != 0) {
+ WH_BENCH_PRINTF("Failed to wh_Client_KeyEvict %d\n", ret);
+ break;
+ }
}
return ret;
diff --git a/benchmark/config/user_settings.h b/benchmark/config/user_settings.h
index 3426ab01b..8a463fe7d 100644
--- a/benchmark/config/user_settings.h
+++ b/benchmark/config/user_settings.h
@@ -157,6 +157,7 @@ extern "C" {
/** Composite features */
#define HAVE_HKDF
+#define HAVE_CMAC_KDF
/* Remove unneeded crypto */
#define NO_DSA
diff --git a/benchmark/wh_bench.c b/benchmark/wh_bench.c
index 555f75935..60e79e69d 100644
--- a/benchmark/wh_bench.c
+++ b/benchmark/wh_bench.c
@@ -167,6 +167,11 @@ typedef enum BenchModuleIdx {
BENCH_MODULE_IDX_HKDF_SHA2_256,
#endif /* HAVE_HKDF */
+/* CMAC KDF */
+#if defined(HAVE_CMAC_KDF) && defined(WOLFSSL_CMAC)
+ BENCH_MODULE_IDX_CMAC_KDF,
+#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
+
/* ECC */
#if defined(HAVE_ECC)
BENCH_MODULE_IDX_ECC_P256_SIGN,
@@ -335,6 +340,11 @@ static BenchModule g_benchModules[] = {
[BENCH_MODULE_IDX_HKDF_SHA2_256] = {"HKDF-SHA2-256", wh_Bench_Mod_HkdfSha256, BENCH_THROUGHPUT_OPS, 0, NULL},
#endif /* HAVE_HKDF */
+ /* CMAC KDF */
+#if defined(HAVE_CMAC_KDF) && defined(WOLFSSL_CMAC)
+ [BENCH_MODULE_IDX_CMAC_KDF] = {"CMAC-KDF-AES", wh_Bench_Mod_CmacKdf, BENCH_THROUGHPUT_OPS, 0, NULL},
+#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
+
/* ECC */
#if defined(HAVE_ECC)
[BENCH_MODULE_IDX_ECC_P256_SIGN] = {"ECC-P256-SIGN", wh_Bench_Mod_EccP256Sign, BENCH_THROUGHPUT_OPS, 0, NULL},
diff --git a/benchmark/wh_bench_ops.h b/benchmark/wh_bench_ops.h
index 1b26853d6..d0da0d89d 100644
--- a/benchmark/wh_bench_ops.h
+++ b/benchmark/wh_bench_ops.h
@@ -26,7 +26,7 @@
#include
/* Maximum number of operations that can be registered */
-#define MAX_BENCH_OPS 88
+#define MAX_BENCH_OPS 89
/* Maximum length of operation name */
#define MAX_OP_NAME 64
diff --git a/examples/demo/client/wh_demo_client_all.c b/examples/demo/client/wh_demo_client_all.c
index 31eaa086a..15ee86d78 100644
--- a/examples/demo/client/wh_demo_client_all.c
+++ b/examples/demo/client/wh_demo_client_all.c
@@ -132,6 +132,23 @@ int wh_DemoClient_All(whClientContext* clientContext)
}
#endif /* HAVE_HKDF */
+#if defined(HAVE_CMAC_KDF) && defined(WOLFSSL_CMAC)
+ rc = wh_DemoClient_CryptoCmacKdfExport(clientContext);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = wh_DemoClient_CryptoCmacKdfCache(clientContext);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = wh_DemoClient_CryptoCmacKdfCacheInputs(clientContext);
+ if (rc != 0) {
+ return rc;
+ }
+#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
+
#if defined(WOLFSSL_CMAC)
rc = wh_DemoClient_CryptoCmac(clientContext);
if (rc != 0) {
diff --git a/examples/demo/client/wh_demo_client_crypto.c b/examples/demo/client/wh_demo_client_crypto.c
index d970ef742..c21d0d10a 100644
--- a/examples/demo/client/wh_demo_client_crypto.c
+++ b/examples/demo/client/wh_demo_client_crypto.c
@@ -40,6 +40,10 @@
#include "wolfssl/wolfcrypt/cmac.h"
#endif
+#if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF)
+#include "wolfssl/wolfcrypt/kdf.h"
+#endif
+
#include "wh_demo_client_crypto.h"
#if !defined(NO_RSA)
@@ -1503,4 +1507,157 @@ int wh_DemoClient_CryptoHkdfCacheInputKey(whClientContext* clientContext)
return ret;
}
#endif /* HAVE_HKDF */
+
+#if defined(HAVE_CMAC_KDF) && defined(WOLFSSL_CMAC)
+
+#define WH_DEMO_CMAC_KDF_OUT_SZ 40
+
+/* Test vectors based on wolfSSL CMAC KDF implementation test vectors for
+ * NIST SP 800-108 KDF in Counter Mode using CMAC */
+static const uint8_t demoCmacKdfSalt[] = {
+ 0x20, 0x51, 0xAF, 0x34, 0x76, 0x2E, 0xBE, 0x55, 0x6F, 0x72, 0xA5, 0xC6,
+ 0xED, 0xC7, 0x77, 0x1E, 0xB9, 0x24, 0x5F, 0xAD, 0x76, 0xF0, 0x34, 0xBE};
+static const uint8_t demoCmacKdfZ[] = {
+ 0xAE, 0x8E, 0x93, 0xC9, 0xC9, 0x91, 0xCF, 0x89, 0x6A, 0x49, 0x1A,
+ 0x89, 0x07, 0xDF, 0x4E, 0x4B, 0xE5, 0x18, 0x6A, 0xE4, 0x96, 0xCD,
+ 0x34, 0x0D, 0xC1, 0x9B, 0x23, 0x78, 0x21, 0xDB, 0x7B, 0x60};
+static const uint8_t demoCmacKdfFixedInfo[] = {
+ 0xA2, 0x59, 0xCA, 0xE2, 0xC4, 0xA3, 0x6B, 0x89, 0x56, 0x3C, 0xB1, 0x48,
+ 0xC7, 0x82, 0x51, 0x34, 0x3B, 0xBF, 0xAB, 0xDC, 0x13, 0xCA, 0x7A, 0xC2,
+ 0x17, 0x1C, 0x2E, 0xB6, 0x02, 0x1F, 0x44, 0x77, 0xFE, 0xA3, 0x3B, 0x28,
+ 0x72, 0x4D, 0xA7, 0x21, 0xEE, 0x08, 0x7B, 0xFF, 0xD7, 0x94, 0xA1, 0x56,
+ 0x37, 0x54, 0xB4, 0x25, 0xA8, 0xD0, 0x9B, 0x3E, 0x0D, 0xA5, 0xFF, 0xED};
+
+/*
+ * Demonstrates deriving key material using the two-step CMAC KDF through the
+ * wolfCrypt API. The HSM performs the derivation and returns the output
+ * directly to the client buffer.
+ */
+int wh_DemoClient_CryptoCmacKdfExport(whClientContext* clientContext)
+{
+ int ret = 0;
+ uint8_t derived[WH_DEMO_CMAC_KDF_OUT_SZ];
+
+ (void)clientContext;
+
+ memset(derived, 0, sizeof(derived));
+
+ ret = wc_KDA_KDF_twostep_cmac(
+ demoCmacKdfSalt, (word32)sizeof(demoCmacKdfSalt), demoCmacKdfZ,
+ (word32)sizeof(demoCmacKdfZ), demoCmacKdfFixedInfo,
+ (word32)sizeof(demoCmacKdfFixedInfo), derived, (word32)sizeof(derived),
+ NULL, WH_DEV_ID);
+ if (ret != 0) {
+ printf("Failed to wc_KDA_KDF_twostep_cmac %d\n", ret);
+ }
+
+ /* The key has now been derived and is stored in the 'derived' array */
+
+ return ret;
+}
+
+/*
+ * Demonstrates deriving key material using the CMAC KDF and storing it in the
+ * HSM key cache. The derived key is kept non-exportable and referenced by its
+ * assigned keyId.
+ */
+int wh_DemoClient_CryptoCmacKdfCache(whClientContext* clientContext)
+{
+ int ret = 0;
+ whKeyId keyId = WH_KEYID_ERASED;
+ whNvmFlags flags = WH_NVM_FLAGS_NONEXPORTABLE;
+ const char label[] = "cmac-kdf cache";
+
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ clientContext, WH_KEYID_ERASED, demoCmacKdfSalt,
+ (uint32_t)sizeof(demoCmacKdfSalt), WH_KEYID_ERASED, demoCmacKdfZ,
+ (uint32_t)sizeof(demoCmacKdfZ), demoCmacKdfFixedInfo,
+ (uint32_t)sizeof(demoCmacKdfFixedInfo), &keyId, flags,
+ (const uint8_t*)label, (uint32_t)strlen(label),
+ WH_DEMO_CMAC_KDF_OUT_SZ);
+ if (ret != WH_ERROR_OK) {
+ printf("Failed to wh_Client_CmacKdfMakeCacheKey %d\n", ret);
+ return ret;
+ }
+
+ /* The key has now been derived and can be referenced by the client via its
+ * keyId in subsequent wolfCrypt or wolfHSM API calls */
+
+ /* Example: evict the key from cache once we are done with it */
+ ret = wh_Client_KeyEvict(clientContext, keyId);
+ if (ret != 0) {
+ printf("Failed to wh_Client_KeyEvict %d\n", evictRet);
+ }
+
+ return ret;
+}
+
+/*
+ * Demonstrates deriving CMAC KDF output using cached salt and shared secret
+ * inputs and caching the derived output for subsequent use by keyId
+ */
+int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext)
+{
+ int ret = 0;
+ whKeyId saltKeyId = WH_KEYID_ERASED;
+ whKeyId zKeyId = WH_KEYID_ERASED;
+ whKeyId outKeyId = WH_KEYID_ERASED;
+ uint8_t derived[WH_DEMO_CMAC_KDF_OUT_SZ];
+ const char label[] = "cmac-kdf inputs";
+
+ /* Cache the input to be used as salt. This is typically not necessary,
+ * as the salt is usually not sensitive information. If it is desired to use
+ * HSM-only information as salt, then this would likely be provisioned
+ * as an offline step */
+ ret = wh_Client_KeyCache(clientContext, WH_NVM_FLAGS_NONE, NULL, 0,
+ demoCmacKdfSalt, (uint32_t)sizeof(demoCmacKdfSalt),
+ &saltKeyId);
+ if (ret != WH_ERROR_OK) {
+ printf("Failed to cache CMAC KDF salt %d\n", ret);
+ return ret;
+ }
+
+ /* Cache the Z input. This would typically be done offline during the HSM
+ * provisioning step, but is shown here for completeness */
+ ret = wh_Client_KeyCache(clientContext, WH_NVM_FLAGS_NONE, NULL, 0,
+ demoCmacKdfZ, (uint32_t)sizeof(demoCmacKdfZ),
+ &zKeyId);
+ if (ret != WH_ERROR_OK) {
+ printf("Failed to cache CMAC KDF Z input %d\n", ret);
+ /* Optionally evict the salt key if not needed anymore */
+ (void)wh_Client_KeyEvict(clientContext, saltKeyId);
+ return ret;
+ }
+
+ /* Cached inputs are now present on the HSM. At a later time, they can be
+ * used to derive a key */
+
+
+ /* Derive and cache the new key based on the cached input */
+ memset(derived, 0, sizeof(derived));
+
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ clientContext, saltKeyId, NULL, 0, zKeyId, NULL, 0,
+ demoCmacKdfFixedInfo, (uint32_t)sizeof(demoCmacKdfFixedInfo), &outKeyId,
+ WH_NVM_FLAGS_NONE, (const uint8_t*)label, (uint32_t)strlen(label),
+ WH_DEMO_CMAC_KDF_OUT_SZ);
+
+ if (ret != WH_ERROR_OK) {
+ printf("Failed to CMAC KDF with cached inputs %d\n", ret);
+ }
+
+
+ /* We can now refer to the generated key by its keyID from the client */
+
+ /* Optionally evict the keys from the cache if not needed anymore.
+ * Ignore failure checking for readability */
+ (void)wh_Client_KeyEvict(clientContext, outKeyId);
+ (void)wh_Client_KeyEvict(clientContext, zKeyId);
+ (void)wh_Client_KeyEvict(clientContext, saltKeyId);
+
+ return ret;
+}
+
+#endif /* HAVE_CMAC_KDF && WOLFSSL_CMAC */
+
#endif /* WOLFHSM_CFG_NO_CRYPTO */
diff --git a/examples/demo/client/wh_demo_client_crypto.h b/examples/demo/client/wh_demo_client_crypto.h
index db143a47c..72fbb187d 100644
--- a/examples/demo/client/wh_demo_client_crypto.h
+++ b/examples/demo/client/wh_demo_client_crypto.h
@@ -26,4 +26,8 @@ int wh_DemoClient_CryptoHkdfExport(whClientContext* clientContext);
int wh_DemoClient_CryptoHkdfCache(whClientContext* clientContext);
int wh_DemoClient_CryptoHkdfCacheInputKey(whClientContext* clientContext);
+int wh_DemoClient_CryptoCmacKdfExport(whClientContext* clientContext);
+int wh_DemoClient_CryptoCmacKdfCache(whClientContext* clientContext);
+int wh_DemoClient_CryptoCmacKdfCacheInputs(whClientContext* clientContext);
+
#endif /* !DEMO_CLIENT_CRYPTO_H_ */
diff --git a/examples/posix/wh_posix_server/user_settings.h b/examples/posix/wh_posix_server/user_settings.h
index 45bf0167d..3241ee996 100644
--- a/examples/posix/wh_posix_server/user_settings.h
+++ b/examples/posix/wh_posix_server/user_settings.h
@@ -146,6 +146,7 @@ extern "C" {
/** Composite features */
#define HAVE_HKDF
+#define HAVE_CMAC_KDF
/* Remove unneeded crypto */
#define NO_DSA
diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c
index 815b8ad15..ff3a8a1a2 100644
--- a/src/wh_client_crypto.c
+++ b/src/wh_client_crypto.c
@@ -100,6 +100,16 @@ static int _HkdfMakeKey(whClientContext* ctx, int hashType, whKeyId keyIdIn,
whKeyId* inout_key_id, uint8_t* out, uint32_t outSz);
#endif
+#if defined(HAVE_CMAC_KDF)
+static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz, whKeyId zKeyId,
+ const uint8_t* z, uint32_t zSz,
+ const uint8_t* fixedInfo, uint32_t fixedInfoSz,
+ whNvmFlags flags, uint32_t label_len,
+ const uint8_t* label, whKeyId* inout_key_id,
+ uint8_t* out, uint32_t outSz);
+#endif
+
#ifdef HAVE_DILITHIUM
/* Make a ML-DSA key on the server based on the flags */
static int _MlDsaMakeKey(whClientContext* ctx, int size, int level,
@@ -2597,8 +2607,8 @@ static int _HkdfMakeKey(whClientContext* ctx, int hashType, whKeyId keyIdIn,
}
/* Setup generic header and get pointer to request data */
- req = (whMessageCrypto_HkdfRequest*)_createCryptoRequest(dataPtr,
- WC_ALGO_TYPE_KDF);
+ req = (whMessageCrypto_HkdfRequest*)_createCryptoRequestWithSubtype(
+ dataPtr, WC_ALGO_TYPE_KDF, WC_KDF_TYPE_HKDF);
/* Calculate request length including variable-length data */
uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) +
@@ -2741,6 +2751,160 @@ int wh_Client_HkdfMakeExportKey(whClientContext* ctx, int hashType,
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+static int _CmacKdfMakeKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz, whKeyId zKeyId,
+ const uint8_t* z, uint32_t zSz,
+ const uint8_t* fixedInfo, uint32_t fixedInfoSz,
+ whNvmFlags flags, uint32_t label_len,
+ const uint8_t* label, whKeyId* inout_key_id,
+ uint8_t* out, uint32_t outSz)
+{
+ int ret = WH_ERROR_OK;
+ uint8_t* dataPtr = NULL;
+ whMessageCrypto_CmacKdfRequest* req = NULL;
+ whMessageCrypto_CmacKdfResponse* res = NULL;
+ uint16_t group = WH_MESSAGE_GROUP_CRYPTO;
+ uint16_t action = WC_ALGO_TYPE_KDF;
+ whKeyId key_id = WH_KEYID_ERASED;
+
+ if ((ctx == NULL) || (outSz == 0)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ if ((saltSz > 0 && salt == NULL) || (zSz > 0 && z == NULL) ||
+ (fixedInfoSz > 0 && fixedInfo == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Retrieve the shared communication buffer */
+ dataPtr = wh_CommClient_GetDataPtr(ctx->comm);
+ if (dataPtr == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Prepare request structure with subtype information */
+ req = (whMessageCrypto_CmacKdfRequest*)_createCryptoRequestWithSubtype(
+ dataPtr, WC_ALGO_TYPE_KDF, WC_KDF_TYPE_TWOSTEP_CMAC);
+
+ uint32_t total_len = sizeof(whMessageCrypto_GenericRequestHeader) +
+ sizeof(*req) + saltSz + zSz + fixedInfoSz;
+
+ if (total_len > WOLFHSM_CFG_COMM_DATA_LEN) {
+ return WH_ERROR_BADARGS;
+ }
+ uint16_t req_len = (uint16_t)total_len;
+
+ if (inout_key_id != NULL) {
+ key_id = *inout_key_id;
+ }
+
+ req->flags = flags;
+ req->keyIdSalt = saltKeyId;
+ req->keyIdZ = zKeyId;
+ req->keyIdOut = key_id;
+ req->saltSz = saltSz;
+ req->zSz = zSz;
+ req->fixedInfoSz = fixedInfoSz;
+ req->outSz = outSz;
+
+ memset(req->label, 0, WH_NVM_LABEL_LEN);
+ if ((label != NULL) && (label_len > 0)) {
+ if (label_len > WH_NVM_LABEL_LEN) {
+ label_len = WH_NVM_LABEL_LEN;
+ }
+ memcpy(req->label, label, label_len);
+ }
+
+ uint8_t* payload = (uint8_t*)(req + 1);
+
+ if (saltSz > 0 && salt != NULL) {
+ memcpy(payload, salt, saltSz);
+ payload += saltSz;
+ }
+
+ if (zSz > 0 && z != NULL) {
+ memcpy(payload, z, zSz);
+ payload += zSz;
+ }
+
+ if (fixedInfoSz > 0 && fixedInfo != NULL) {
+ memcpy(payload, fixedInfo, fixedInfoSz);
+ payload += fixedInfoSz;
+ }
+
+ /* squash unused warning */
+ (void)payload;
+
+ ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr);
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+
+ uint16_t res_len = 0;
+ do {
+ ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr);
+ } while (ret == WH_ERROR_NOTREADY);
+
+ if (ret == WH_ERROR_OK) {
+ ret = _getCryptoResponse(dataPtr, WC_ALGO_TYPE_KDF, (uint8_t**)&res);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ key_id = (whKeyId)(res->keyIdOut);
+
+ if (inout_key_id != NULL) {
+ *inout_key_id = key_id;
+ }
+
+ if (out != NULL) {
+ if (res->outSz <= outSz) {
+ uint8_t* out_data = (uint8_t*)(res + 1);
+ memcpy(out, out_data, res->outSz);
+ }
+ else {
+ ret = WH_ERROR_ABORTED;
+ }
+ }
+ }
+
+ return ret;
+}
+
+int wh_Client_CmacKdfMakeCacheKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz,
+ whKeyId zKeyId, const uint8_t* z,
+ uint32_t zSz, const uint8_t* fixedInfo,
+ uint32_t fixedInfoSz, whKeyId* inout_key_id,
+ whNvmFlags flags, const uint8_t* label,
+ uint32_t label_len, uint32_t outSz)
+{
+ if ((ctx == NULL) || (inout_key_id == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ return _CmacKdfMakeKey(ctx, saltKeyId, salt, saltSz, zKeyId, z, zSz,
+ fixedInfo, fixedInfoSz, flags, label_len, label,
+ inout_key_id, NULL, outSz);
+}
+
+int wh_Client_CmacKdfMakeExportKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz,
+ whKeyId zKeyId, const uint8_t* z,
+ uint32_t zSz, const uint8_t* fixedInfo,
+ uint32_t fixedInfoSz, uint8_t* out,
+ uint32_t outSz)
+{
+ if ((ctx == NULL) || (out == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ return _CmacKdfMakeKey(ctx, saltKeyId, salt, saltSz, zKeyId, z, zSz,
+ fixedInfo, fixedInfoSz, WH_NVM_FLAGS_EPHEMERAL, 0,
+ NULL, NULL, out, outSz);
+}
+#endif /* HAVE_CMAC_KDF */
+
#ifndef NO_AES
int wh_Client_AesSetKeyId(Aes* key, whNvmId keyId)
{
diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c
index 4054077ce..c4169ba1a 100644
--- a/src/wh_client_cryptocb.c
+++ b/src/wh_client_cryptocb.c
@@ -471,10 +471,11 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx)
}
} break; /* case WC_ALGO_TYPE_HASH */
-#ifdef HAVE_HKDF
+#if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF)
case WC_ALGO_TYPE_KDF: {
/* Handle different KDF types */
switch (info->kdf.type) {
+#ifdef HAVE_HKDF
case WC_KDF_TYPE_HKDF: {
/* Extract HKDF-specific parameters */
int hashType = info->kdf.hkdf.hashType;
@@ -491,12 +492,29 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx)
ctx, hashType, WH_KEYID_ERASED, inKey, inKeySz, salt,
saltSz, kdf_info, infoSz, out, outSz);
} break;
+#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+ case WC_KDF_TYPE_TWOSTEP_CMAC: {
+ const byte* salt = info->kdf.twostep_cmac.salt;
+ word32 saltSz = info->kdf.twostep_cmac.saltSz;
+ const byte* z = info->kdf.twostep_cmac.z;
+ word32 zSz = info->kdf.twostep_cmac.zSz;
+ const byte* fixedInfo = info->kdf.twostep_cmac.fixedInfo;
+ word32 fixedInfoSz = info->kdf.twostep_cmac.fixedInfoSz;
+ byte* out = info->kdf.twostep_cmac.out;
+ word32 outSz = info->kdf.twostep_cmac.outSz;
+
+ ret = wh_Client_CmacKdfMakeExportKey(
+ ctx, WH_KEYID_ERASED, salt, saltSz, WH_KEYID_ERASED, z, zSz,
+ fixedInfo, fixedInfoSz, out, outSz);
+ } break;
+#endif /* HAVE_CMAC_KDF */
default:
ret = CRYPTOCB_UNAVAILABLE;
break;
}
} break; /* case WC_ALGO_TYPE_KDF */
-#endif /* HAVE_HKDF */
+#endif /* HAVE_HKDF || HAVE_CMAC_KDF */
case WC_ALGO_TYPE_NONE:
default:
diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c
index fea340c6f..009ea2193 100644
--- a/src/wh_message_crypto.c
+++ b/src/wh_message_crypto.c
@@ -309,6 +309,39 @@ int wh_MessageCrypto_TranslateHkdfResponse(
return 0;
}
+int wh_MessageCrypto_TranslateCmacKdfRequest(
+ uint16_t magic, const whMessageCrypto_CmacKdfRequest* src,
+ whMessageCrypto_CmacKdfRequest* dest)
+{
+ if ((src == NULL) || (dest == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ WH_T32(magic, dest, src, flags);
+ WH_T32(magic, dest, src, keyIdSalt);
+ WH_T32(magic, dest, src, keyIdZ);
+ WH_T32(magic, dest, src, keyIdOut);
+ WH_T32(magic, dest, src, saltSz);
+ WH_T32(magic, dest, src, zSz);
+ WH_T32(magic, dest, src, fixedInfoSz);
+ WH_T32(magic, dest, src, outSz);
+ if (src != dest) {
+ memcpy(dest->label, src->label, WH_NVM_LABEL_LEN);
+ }
+ return 0;
+}
+
+int wh_MessageCrypto_TranslateCmacKdfResponse(
+ uint16_t magic, const whMessageCrypto_CmacKdfResponse* src,
+ whMessageCrypto_CmacKdfResponse* dest)
+{
+ if ((src == NULL) || (dest == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ WH_T32(magic, dest, src, keyIdOut);
+ WH_T32(magic, dest, src, outSz);
+ return 0;
+}
+
/* ECC Key Generation Request translation */
int wh_MessageCrypto_TranslateEccKeyGenRequest(
uint16_t magic, const whMessageCrypto_EccKeyGenRequest* src,
diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c
index 6713b5f4d..c9b56f2fb 100644
--- a/src/wh_server_crypto.c
+++ b/src/wh_server_crypto.c
@@ -44,6 +44,7 @@
#include "wolfssl/wolfcrypt/cmac.h"
#include "wolfssl/wolfcrypt/dilithium.h"
#include "wolfssl/wolfcrypt/hmac.h"
+#include "wolfssl/wolfcrypt/kdf.h"
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_crypto.h"
@@ -1190,6 +1191,45 @@ int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData,
return ret;
}
+#endif /* HAVE_HKDF */
+
+#ifdef HAVE_CMAC_KDF
+int wh_Server_CmacKdfKeyCacheImport(whServerContext* ctx,
+ const uint8_t* keyData, uint32_t keySize,
+ whKeyId keyId, whNvmFlags flags,
+ uint16_t label_len, uint8_t* label)
+{
+ int ret = WH_ERROR_OK;
+ uint8_t* cacheBuf;
+ whNvmMetadata* cacheMeta;
+
+ if ((ctx == NULL) || (keyData == NULL) || WH_KEYID_ISERASED(keyId) ||
+ ((label != NULL) && (label_len > sizeof(cacheMeta->label)))) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ret = wh_Server_KeystoreGetCacheSlot(ctx, keyId, keySize, &cacheBuf,
+ &cacheMeta);
+ if (ret == WH_ERROR_OK) {
+ memcpy(cacheBuf, keyData, keySize);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ cacheMeta->id = keyId;
+ cacheMeta->len = keySize;
+ cacheMeta->flags = flags;
+ cacheMeta->access = WH_NVM_ACCESS_ANY;
+
+ if ((label != NULL) && (label_len > 0)) {
+ memcpy(cacheMeta->label, label, label_len);
+ }
+ }
+
+ return ret;
+}
+#endif /* HAVE_CMAC_KDF */
+
+#ifdef HAVE_HKDF
static int _HandleHkdf(whServerContext* ctx, uint16_t magic,
const void* cryptoDataIn, uint16_t inSize,
void* cryptoDataOut, uint16_t* outSize)
@@ -1313,6 +1353,127 @@ static int _HandleHkdf(whServerContext* ctx, uint16_t magic,
}
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+static int _HandleCmacKdf(whServerContext* ctx, uint16_t magic,
+ const void* cryptoDataIn, uint16_t inSize,
+ void* cryptoDataOut, uint16_t* outSize)
+{
+ (void)inSize;
+
+ int ret = WH_ERROR_OK;
+ whMessageCrypto_CmacKdfRequest req;
+ whMessageCrypto_CmacKdfResponse res;
+
+ memset(&res, 0, sizeof(res));
+
+ ret = wh_MessageCrypto_TranslateCmacKdfRequest(
+ magic, (const whMessageCrypto_CmacKdfRequest*)cryptoDataIn, &req);
+ if (ret != 0) {
+ return ret;
+ }
+
+ uint32_t saltSz = req.saltSz;
+ uint32_t zSz = req.zSz;
+ uint32_t fixedInfoSz = req.fixedInfoSz;
+ uint32_t outSz = req.outSz;
+ whKeyId keyIdOut = wh_KeyId_TranslateFromClient(
+ WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyIdOut);
+ whKeyId saltKeyId = wh_KeyId_TranslateFromClient(
+ WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyIdSalt);
+ whKeyId zKeyId = wh_KeyId_TranslateFromClient(
+ WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyIdZ);
+ whNvmFlags flags = (whNvmFlags)req.flags;
+ uint8_t* label = req.label;
+ uint16_t label_size = WH_NVM_LABEL_LEN;
+
+ const uint8_t* salt =
+ (const uint8_t*)cryptoDataIn + sizeof(whMessageCrypto_CmacKdfRequest);
+ const uint8_t* z = salt + saltSz;
+ const uint8_t* fixedInfo = z + zSz;
+
+ uint8_t* cachedSaltBuf = NULL;
+ whNvmMetadata* cachedSaltMeta = NULL;
+ uint8_t* cachedZBuf = NULL;
+ whNvmMetadata* cachedZMeta = NULL;
+
+ if (saltSz == 0) {
+ if (WH_KEYID_ISERASED(saltKeyId)) {
+ return WH_ERROR_BADARGS;
+ }
+ ret = wh_Server_KeystoreFreshenKey(ctx, saltKeyId, &cachedSaltBuf,
+ &cachedSaltMeta);
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+ salt = cachedSaltBuf;
+ saltSz = cachedSaltMeta->len;
+ }
+
+ if (zSz == 0) {
+ if (WH_KEYID_ISERASED(zKeyId)) {
+ return WH_ERROR_BADARGS;
+ }
+ ret = wh_Server_KeystoreFreshenKey(ctx, zKeyId, &cachedZBuf,
+ &cachedZMeta);
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+ z = cachedZBuf;
+ zSz = cachedZMeta->len;
+ }
+
+ if ((salt == NULL) || (z == NULL) || (outSz == 0)) {
+ return WH_ERROR_BADARGS;
+ }
+
+ uint8_t* out =
+ (uint8_t*)cryptoDataOut + sizeof(whMessageCrypto_CmacKdfResponse);
+ uint16_t max_size = (uint16_t)(WOLFHSM_CFG_COMM_DATA_LEN -
+ ((uint8_t*)out - (uint8_t*)cryptoDataOut));
+
+ if (outSz > max_size) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ret = wc_KDA_KDF_twostep_cmac(
+ salt, saltSz, z, zSz, (fixedInfoSz > 0) ? fixedInfo : NULL, fixedInfoSz,
+ out, outSz, NULL, ctx->crypto->devId);
+ if (ret == 0) {
+ if (flags & WH_NVM_FLAGS_EPHEMERAL) {
+ keyIdOut = WH_KEYID_ERASED;
+ res.keyIdOut = WH_KEYID_ERASED;
+ res.outSz = outSz;
+ }
+ else {
+ if (WH_KEYID_ISERASED(keyIdOut)) {
+ ret = wh_Server_KeystoreGetUniqueId(ctx, &keyIdOut);
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+ }
+
+ ret = wh_Server_CmacKdfKeyCacheImport(ctx, out, outSz, keyIdOut,
+ flags, label_size, label);
+ if (ret == WH_ERROR_OK) {
+ res.keyIdOut = wh_KeyId_TranslateToClient(keyIdOut);
+ res.outSz = 0;
+ memset(out, 0, outSz);
+ }
+ }
+ }
+
+ if (ret == WH_ERROR_OK) {
+ ret = wh_MessageCrypto_TranslateCmacKdfResponse(
+ magic, &res, (whMessageCrypto_CmacKdfResponse*)cryptoDataOut);
+ if (ret == 0) {
+ *outSize = sizeof(whMessageCrypto_CmacKdfResponse) + res.outSz;
+ }
+ }
+
+ return ret;
+}
+#endif /* HAVE_CMAC_KDF */
+
#ifdef HAVE_CURVE25519
static int _HandleCurve25519KeyGen(whServerContext* ctx, uint16_t magic,
const void* cryptoDataIn, uint16_t inSize,
@@ -3212,12 +3373,27 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic,
break;
#endif /* !WC_NO_RNG */
-#ifdef HAVE_HKDF
+#if defined(HAVE_HKDF) || defined(HAVE_CMAC_KDF)
case WC_ALGO_TYPE_KDF:
- ret = _HandleHkdf(ctx, magic, cryptoDataIn, cryptoInSize,
- cryptoDataOut, &cryptoOutSize);
- break;
+ switch (rqstHeader.algoSubType) {
+#ifdef HAVE_HKDF
+ case WC_KDF_TYPE_HKDF:
+ ret = _HandleHkdf(ctx, magic, cryptoDataIn, cryptoInSize,
+ cryptoDataOut, &cryptoOutSize);
+ break;
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+ case WC_KDF_TYPE_TWOSTEP_CMAC:
+ ret = _HandleCmacKdf(ctx, magic, cryptoDataIn, cryptoInSize,
+ cryptoDataOut, &cryptoOutSize);
+ break;
+#endif /* HAVE_CMAC_KDF */
+ default:
+ ret = NOT_COMPILED_IN;
+ break;
+ }
+ break;
+#endif /* HAVE_HKDF || HAVE_CMAC_KDF */
#ifdef WOLFSSL_CMAC
case WC_ALGO_TYPE_CMAC:
diff --git a/test/config/user_settings.h b/test/config/user_settings.h
index 838d050ff..1d341c218 100644
--- a/test/config/user_settings.h
+++ b/test/config/user_settings.h
@@ -154,6 +154,7 @@
/** Composite features */
#define HAVE_HKDF
+#define HAVE_CMAC_KDF
/* Remove unneeded crypto */
#define NO_DSA
diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c
index 1175e9e76..35310c6ea 100644
--- a/test/wh_test_crypto.c
+++ b/test/wh_test_crypto.c
@@ -31,6 +31,7 @@
#include "wolfssl/wolfcrypt/settings.h"
#include "wolfssl/wolfcrypt/types.h"
+#include "wolfssl/wolfcrypt/kdf.h"
#include "wolfhsm/wh_error.h"
@@ -1246,6 +1247,193 @@ static int whTest_CryptoHkdf(whClientContext* ctx, int devId, WC_RNG* rng)
}
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+#define WH_TEST_CMAC_KDF_SALT_SIZE 24
+#define WH_TEST_CMAC_KDF_Z_SIZE 32
+#define WH_TEST_CMAC_KDF_FIXED_INFO_SIZE 60
+#define WH_TEST_CMAC_KDF_OUT_SIZE 40
+
+static int whTest_CryptoCmacKdf(whClientContext* ctx, int devId, WC_RNG* rng)
+{
+ (void)rng;
+
+ int ret = WH_ERROR_OK;
+
+ /* Test vectors based on wolfSSL CMAC KDF implementation test vectors for
+ * NIST SP 800-108 KDF in Counter Mode using CMAC */
+ static const uint8_t salt[WH_TEST_CMAC_KDF_SALT_SIZE] = {
+ 0x20, 0x51, 0xaf, 0x34, 0x76, 0x2e, 0xbe, 0x55, 0x6f, 0x72, 0xa5, 0xc6,
+ 0xed, 0xc7, 0x77, 0x1e, 0xb9, 0x24, 0x5f, 0xad, 0x76, 0xf0, 0x34, 0xbe};
+ static const uint8_t z[WH_TEST_CMAC_KDF_Z_SIZE] = {
+ 0xae, 0x8e, 0x93, 0xc9, 0xc9, 0x91, 0xcf, 0x89, 0x6a, 0x49, 0x1a,
+ 0x89, 0x07, 0xdf, 0x4e, 0x4b, 0xe5, 0x18, 0x6a, 0xe4, 0x96, 0xcd,
+ 0x34, 0x0d, 0xc1, 0x9b, 0x23, 0x78, 0x21, 0xdb, 0x7b, 0x60};
+ static const uint8_t fixedInfo[WH_TEST_CMAC_KDF_FIXED_INFO_SIZE] = {
+ 0xa2, 0x59, 0xca, 0xe2, 0xc4, 0xa3, 0x6b, 0x89, 0x56, 0x3c, 0xb1, 0x48,
+ 0xc7, 0x82, 0x51, 0x34, 0x3b, 0xbf, 0xab, 0xdc, 0x13, 0xca, 0x7a, 0xc2,
+ 0x17, 0x1c, 0x2e, 0xb6, 0x02, 0x1f, 0x44, 0x77, 0xfe, 0xa3, 0x3b, 0x28,
+ 0x72, 0x4d, 0xa7, 0x21, 0xee, 0x08, 0x7b, 0xff, 0xd7, 0x94, 0xa1, 0x56,
+ 0x37, 0x54, 0xb4, 0x25, 0xa8, 0xd0, 0x9b, 0x3e, 0x0d, 0xa5, 0xff, 0xed};
+ static const uint8_t expected[WH_TEST_CMAC_KDF_OUT_SIZE] = {
+ 0xb4, 0x0c, 0x32, 0xbe, 0x01, 0x27, 0x93, 0xba, 0xfd, 0xf7,
+ 0x78, 0xc5, 0xf4, 0x54, 0x43, 0xf4, 0xc9, 0x71, 0x23, 0x93,
+ 0x17, 0x63, 0xd8, 0x3a, 0x59, 0x27, 0x07, 0xbf, 0xf2, 0xd3,
+ 0x60, 0x59, 0x50, 0x27, 0x29, 0xca, 0xb8, 0x8b, 0x29, 0x38};
+
+ uint8_t out[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t out_direct[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t out_cached_input[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t exported[WH_TEST_CMAC_KDF_OUT_SIZE];
+ uint8_t exportLabel[12] = {0};
+ uint16_t export_len = WH_TEST_CMAC_KDF_OUT_SIZE;
+ whKeyId key_id = WH_KEYID_ERASED;
+ uint8_t keyLabel[] = "CMAC KDF Key";
+
+ /* 1. Direct wolfCrypt API call that routes through the callback */
+ memset(out, 0, sizeof(out));
+ ret = wc_KDA_KDF_twostep_cmac(salt, WH_TEST_CMAC_KDF_SALT_SIZE, z,
+ WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, out,
+ WH_TEST_CMAC_KDF_OUT_SIZE, NULL, devId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wc_KDA_KDF_twostep_cmac: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(out, expected, sizeof(out)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF output mismatch (direct wolfCrypt)\n");
+ return -1;
+ }
+
+ /* 2. Client export path using direct input buffers */
+ memset(out_direct, 0, sizeof(out_direct));
+ ret = wh_Client_CmacKdfMakeExportKey(
+ ctx, WH_KEYID_ERASED, salt, WH_TEST_CMAC_KDF_SALT_SIZE, WH_KEYID_ERASED,
+ z, WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo, WH_TEST_CMAC_KDF_FIXED_INFO_SIZE,
+ out_direct, sizeof(out_direct));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_CmacKdfMakeExportKey: %d\n", ret);
+ return ret;
+ }
+ if (memcmp(out_direct, expected, sizeof(out_direct)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF output mismatch (export key)\n");
+ return -1;
+ }
+
+ /* 3. Client cache path with direct input buffers */
+ key_id = WH_KEYID_ERASED;
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ ctx, WH_KEYID_ERASED, salt, WH_TEST_CMAC_KDF_SALT_SIZE, WH_KEYID_ERASED,
+ z, WH_TEST_CMAC_KDF_Z_SIZE, fixedInfo, WH_TEST_CMAC_KDF_FIXED_INFO_SIZE,
+ &key_id, WH_NVM_FLAGS_NONE, keyLabel, sizeof(keyLabel),
+ WH_TEST_CMAC_KDF_OUT_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed wh_Client_CmacKdfMakeCacheKey: %d\n", ret);
+ return ret;
+ }
+ if (key_id == WH_KEYID_ERASED) {
+ WH_ERROR_PRINT("CMAC KDF cache did not return a key id\n");
+ return -1;
+ }
+
+ memset(exported, 0, sizeof(exported));
+ export_len = (uint16_t)sizeof(exported);
+ memset(exportLabel, 0, sizeof(exportLabel));
+ ret = wh_Client_KeyExport(ctx, key_id, exportLabel, sizeof(exportLabel),
+ exported, &export_len);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to export cached CMAC KDF key: %d\n", ret);
+ (void)wh_Client_KeyEvict(ctx, key_id);
+ return ret;
+ }
+ if ((export_len != WH_TEST_CMAC_KDF_OUT_SIZE) ||
+ (memcmp(exported, expected, sizeof(exported)) != 0)) {
+ WH_ERROR_PRINT("Exported CMAC KDF key mismatch\n");
+ (void)wh_Client_KeyEvict(ctx, key_id);
+ return -1;
+ }
+ ret = wh_Client_KeyEvict(ctx, key_id);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to evict cached CMAC KDF key: %d\n", ret);
+ return ret;
+ }
+
+ /* 4. Use cached salt and Z inputs */
+ whKeyId saltKeyId = WH_KEYID_ERASED;
+ whKeyId zKeyId = WH_KEYID_ERASED;
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONE, NULL, 0, salt,
+ WH_TEST_CMAC_KDF_SALT_SIZE, &saltKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache CMAC KDF salt: %d\n", ret);
+ return ret;
+ }
+ ret = wh_Client_KeyCache(ctx, WH_NVM_FLAGS_NONE, NULL, 0, z,
+ WH_TEST_CMAC_KDF_Z_SIZE, &zKeyId);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed to cache CMAC KDF Z input: %d\n", ret);
+ (void)wh_Client_KeyEvict(ctx, saltKeyId);
+ return ret;
+ }
+
+ memset(out_cached_input, 0, sizeof(out_cached_input));
+ ret = wh_Client_CmacKdfMakeExportKey(
+ ctx, saltKeyId, NULL, 0, zKeyId, NULL, 0, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, out_cached_input,
+ sizeof(out_cached_input));
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed CMAC KDF export with cached inputs: %d\n", ret);
+ goto cleanup_inputs;
+ }
+ if (memcmp(out_cached_input, expected, sizeof(out_cached_input)) != 0) {
+ WH_ERROR_PRINT("CMAC KDF mismatch (cached inputs export)\n");
+ ret = -1;
+ goto cleanup_inputs;
+ }
+
+ key_id = WH_KEYID_ERASED;
+ ret = wh_Client_CmacKdfMakeCacheKey(
+ ctx, saltKeyId, NULL, 0, zKeyId, NULL, 0, fixedInfo,
+ WH_TEST_CMAC_KDF_FIXED_INFO_SIZE, &key_id, WH_NVM_FLAGS_NONE, keyLabel,
+ sizeof(keyLabel), WH_TEST_CMAC_KDF_OUT_SIZE);
+ if (ret != 0) {
+ WH_ERROR_PRINT("Failed CMAC KDF cache with cached inputs: %d\n", ret);
+ goto cleanup_inputs;
+ }
+ if (key_id == WH_KEYID_ERASED) {
+ WH_ERROR_PRINT(
+ "CMAC KDF cache (cached inputs) did not return key id\n");
+ ret = -1;
+ goto cleanup_inputs;
+ }
+
+ memset(exported, 0, sizeof(exported));
+ export_len = (uint16_t)sizeof(exported);
+ memset(exportLabel, 0, sizeof(exportLabel));
+ ret = wh_Client_KeyExport(ctx, key_id, exportLabel, sizeof(exportLabel),
+ exported, &export_len);
+ if ((ret != 0) || (export_len != WH_TEST_CMAC_KDF_OUT_SIZE) ||
+ (memcmp(exported, expected, sizeof(exported)) != 0)) {
+ WH_ERROR_PRINT("Export mismatch for CMAC KDF cached inputs (ret=%d)\n",
+ ret);
+ if (ret == 0) {
+ ret = -1;
+ }
+ }
+
+ (void)wh_Client_KeyEvict(ctx, key_id);
+
+cleanup_inputs:
+ (void)wh_Client_KeyEvict(ctx, saltKeyId);
+ (void)wh_Client_KeyEvict(ctx, zKeyId);
+
+ if (ret != WH_ERROR_OK) {
+ return ret;
+ }
+
+ printf("CMAC KDF SUCCESS\n");
+ return 0;
+}
+#endif /* HAVE_CMAC_KDF */
+
static int whTest_CacheExportKey(whClientContext* ctx, whKeyId* inout_key_id,
uint8_t* label_in, uint8_t* label_out, uint16_t label_len,
uint8_t* key_in, uint8_t* key_out, uint16_t key_len)
@@ -3691,6 +3879,12 @@ int whTest_CryptoClientConfig(whClientConfig* config)
}
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_CryptoCmacKdf(client, WH_DEV_ID, rng);
+ }
+#endif /* HAVE_CMAC_KDF */
+
#ifdef HAVE_DILITHIUM
#if !defined(WOLFSSL_DILITHIUM_NO_VERIFY) && \
diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h
index dccda70c1..096d4d1b7 100644
--- a/wolfhsm/wh_client_crypto.h
+++ b/wolfhsm/wh_client_crypto.h
@@ -426,6 +426,74 @@ int wh_Client_HkdfMakeExportKey(whClientContext* ctx, int hashType,
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+/**
+ * @brief Generate CMAC two-step KDF output and store it in the server cache
+ *
+ * This function requests the server to run the NIST SP 800-56C two-step CMAC
+ * KDF. The derived key material is cached on the server and not returned to the
+ * client.
+ *
+ * @param[in] ctx Pointer to the client context.
+ * @param[in] saltKeyId Key ID of the salt material. Set to WH_KEYID_ERASED to
+ * use the salt buffer instead.
+ * @param[in] salt Pointer to the salt buffer. May be NULL when saltKeyId is
+ * provided.
+ * @param[in] saltSz Size of the salt buffer in bytes.
+ * @param[in] zKeyId Key ID of the Z shared secret. Set to WH_KEYID_ERASED to
+ * use the z buffer instead.
+ * @param[in] z Pointer to the shared secret buffer. May be NULL when zKeyId is
+ * provided.
+ * @param[in] zSz Size of the shared secret buffer in bytes.
+ * @param[in] fixedInfo Optional fixed info buffer (may be NULL).
+ * @param[in] fixedInfoSz Size of the fixed info buffer in bytes.
+ * @param[in,out] inout_key_id Pointer to the key ID to use or update. Set to
+ * WH_KEYID_ERASED to have the server allocate one.
+ * @param[in] flags NVM flags to associate with the generated key.
+ * @param[in] label Optional label metadata to store alongside the key.
+ * @param[in] label_len Length of the optional label in bytes.
+ * @param[in] outSz Desired size of the derived key material.
+ * @return int Returns 0 on success or a negative error code on failure.
+ */
+int wh_Client_CmacKdfMakeCacheKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz,
+ whKeyId zKeyId, const uint8_t* z,
+ uint32_t zSz, const uint8_t* fixedInfo,
+ uint32_t fixedInfoSz, whKeyId* inout_key_id,
+ whNvmFlags flags, const uint8_t* label,
+ uint32_t label_len, uint32_t outSz);
+
+/**
+ * @brief Generate CMAC two-step KDF output and export to the client
+ *
+ * This function requests the server to run the NIST SP 800-56C two-step CMAC
+ * KDF and return the derived key material directly to the client.
+ *
+ * @param[in] ctx Pointer to the client context.
+ * @param[in] saltKeyId Key ID of the salt material. Set to WH_KEYID_ERASED to
+ * use the salt buffer instead.
+ * @param[in] salt Pointer to the salt buffer. May be NULL when saltKeyId is
+ * provided.
+ * @param[in] saltSz Size of the salt buffer in bytes.
+ * @param[in] zKeyId Key ID of the Z shared secret. Set to WH_KEYID_ERASED to
+ * use the z buffer instead.
+ * @param[in] z Pointer to the shared secret buffer. May be NULL when zKeyId is
+ * provided.
+ * @param[in] zSz Size of the shared secret buffer in bytes.
+ * @param[in] fixedInfo Optional fixed info buffer (may be NULL).
+ * @param[in] fixedInfoSz Size of the fixed info buffer in bytes.
+ * @param[out] out Output buffer for the derived key material.
+ * @param[in] outSz Size of the output buffer in bytes.
+ * @return int Returns 0 on success or a negative error code on failure.
+ */
+int wh_Client_CmacKdfMakeExportKey(whClientContext* ctx, whKeyId saltKeyId,
+ const uint8_t* salt, uint32_t saltSz,
+ whKeyId zKeyId, const uint8_t* z,
+ uint32_t zSz, const uint8_t* fixedInfo,
+ uint32_t fixedInfoSz, uint8_t* out,
+ uint32_t outSz);
+#endif /* HAVE_CMAC_KDF */
+
#ifndef NO_AES
/**
* @brief Associates an AES key with a specific key ID.
diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h
index 23e1f5fa4..6b4272c12 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -415,6 +415,43 @@ int wh_MessageCrypto_TranslateHkdfResponse(
uint16_t magic, const whMessageCrypto_HkdfResponse* src,
whMessageCrypto_HkdfResponse* dest);
+/*
+ * CMAC KDF
+ */
+
+typedef struct {
+ uint32_t flags; /* NVM flags */
+ uint32_t keyIdSalt; /* Key ID for salt material (from cache) */
+ uint32_t keyIdZ; /* Key ID for Z material (from cache) */
+ uint32_t keyIdOut; /* Key ID if caching output */
+ uint32_t saltSz; /* Salt size (0 if using keyIdSalt) */
+ uint32_t zSz; /* Z input size (0 if using keyIdZ) */
+ uint32_t fixedInfoSz; /* Fixed info size (0 if none) */
+ uint32_t outSz; /* Output size */
+ uint8_t label[WH_NVM_LABEL_LEN];
+ /* Data follows:
+ * uint8_t salt[saltSz]
+ * uint8_t z[zSz]
+ * uint8_t fixedInfo[fixedInfoSz]
+ */
+} whMessageCrypto_CmacKdfRequest;
+
+typedef struct {
+ uint32_t keyIdOut; /* Assigned key ID */
+ uint32_t outSz; /* Output size */
+ /* Data follows:
+ * uint8_t out[outSz]
+ */
+} whMessageCrypto_CmacKdfResponse;
+
+int wh_MessageCrypto_TranslateCmacKdfRequest(
+ uint16_t magic, const whMessageCrypto_CmacKdfRequest* src,
+ whMessageCrypto_CmacKdfRequest* dest);
+
+int wh_MessageCrypto_TranslateCmacKdfResponse(
+ uint16_t magic, const whMessageCrypto_CmacKdfResponse* src,
+ whMessageCrypto_CmacKdfResponse* dest);
+
/*
* ECC
*/
diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h
index 9f2dd829e..439dd112a 100644
--- a/wolfhsm/wh_server_crypto.h
+++ b/wolfhsm/wh_server_crypto.h
@@ -101,6 +101,14 @@ int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData,
uint8_t* label);
#endif /* HAVE_HKDF */
+#ifdef HAVE_CMAC_KDF
+/* Store CMAC KDF output into a server key cache with optional metadata */
+int wh_Server_CmacKdfKeyCacheImport(whServerContext* ctx,
+ const uint8_t* keyData, uint32_t keySize,
+ whKeyId keyId, whNvmFlags flags,
+ uint16_t label_len, uint8_t* label);
+#endif /* HAVE_CMAC_KDF */
+
#endif /* !WOLFHSM_CFG_NO_CRYPTO */
#endif /* !WOLFHSM_WH_SERVER_CRYPTO_H_ */