From 8f68933616f96cb97850bb76776d1a7eb274232b Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Mon, 4 May 2026 13:23:23 -0600
Subject: [PATCH 1/7] trusted cert cache
---
src/wh_client_cert.c | 59 +++++++++++++
src/wh_server_cert.c | 113 ++++++++++++++++++++++++-
test/Makefile | 5 ++
test/wh_test_cert.c | 149 +++++++++++++++++++++++++++++++++
wolfhsm/wh_client.h | 33 ++++++++
wolfhsm/wh_message_cert.h | 21 ++---
wolfhsm/wh_server.h | 4 +
wolfhsm/wh_server_cert_cache.h | 90 ++++++++++++++++++++
8 files changed, 461 insertions(+), 13 deletions(-)
create mode 100644 wolfhsm/wh_server_cert_cache.h
diff --git a/src/wh_client_cert.c b/src/wh_client_cert.c
index d233da5e8..63fbc3572 100644
--- a/src/wh_client_cert.c
+++ b/src/wh_client_cert.c
@@ -507,6 +507,65 @@ int wh_Client_CertVerifyAndCacheLeafPubKey(
inout_keyId, out_rc);
}
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+int wh_Client_CertVerifyCacheClearRequest(whClientContext* c)
+{
+ if (c == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_CERT,
+ WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_CLEAR, 0,
+ NULL);
+}
+
+int wh_Client_CertVerifyCacheClearResponse(whClientContext* c, int32_t* out_rc)
+{
+ int rc;
+ uint16_t group;
+ uint16_t action;
+ uint16_t size;
+ whMessageCert_SimpleResponse resp;
+
+ if (c == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ rc = wh_Client_RecvResponse(c, &group, &action, &size, &resp);
+ if (rc == WH_ERROR_OK) {
+ if ((group != WH_MESSAGE_GROUP_CERT) ||
+ (action != WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_CLEAR) ||
+ (size != sizeof(resp))) {
+ rc = WH_ERROR_ABORTED;
+ }
+ else if (out_rc != NULL) {
+ *out_rc = resp.rc;
+ }
+ }
+ return rc;
+}
+
+int wh_Client_CertVerifyCacheClear(whClientContext* c, int32_t* out_rc)
+{
+ int rc = WH_ERROR_OK;
+
+ if (c == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ do {
+ rc = wh_Client_CertVerifyCacheClearRequest(c);
+ } while (rc == WH_ERROR_NOTREADY);
+
+ if (rc == WH_ERROR_OK) {
+ do {
+ rc = wh_Client_CertVerifyCacheClearResponse(c, out_rc);
+ } while (rc == WH_ERROR_NOTREADY);
+ }
+
+ return rc;
+}
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
#ifdef WOLFHSM_CFG_DMA
int wh_Client_CertAddTrustedDmaRequest(whClientContext* c, whNvmId id,
diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c
index 9d343a0a4..ece7d2fc8 100644
--- a/src/wh_server_cert.c
+++ b/src/wh_server_cert.c
@@ -33,6 +33,7 @@
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_server.h"
#include "wolfhsm/wh_server_cert.h"
+#include "wolfhsm/wh_server_cert_cache.h"
#include "wolfhsm/wh_server_nvm.h"
#include "wolfhsm/wh_server_keystore.h"
#include "wolfhsm/wh_message.h"
@@ -41,6 +42,57 @@
#include "wolfssl/wolfcrypt/types.h"
#include "wolfssl/ssl.h"
#include "wolfssl/wolfcrypt/asn.h"
+#include "wolfssl/wolfcrypt/sha256.h"
+
+
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
+ const uint8_t* hash)
+{
+ int i;
+ if ((server == NULL) || (hash == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
+ const whCertVerifyCacheSlot* slot = &server->certVerifyCache.slots[i];
+ if (slot->committed &&
+ (memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0)) {
+ return WH_ERROR_OK;
+ }
+ }
+ return WH_ERROR_NOTFOUND;
+}
+
+void wh_Server_CertVerifyCache_Insert(whServerContext* server,
+ const uint8_t* hash)
+{
+ whCertVerifyCacheSlot* slot;
+ uint16_t idx;
+
+ if ((server == NULL) || (hash == NULL)) {
+ return;
+ }
+ /* Skip if already present */
+ if (wh_Server_CertVerifyCache_Lookup(server, hash) == WH_ERROR_OK) {
+ return;
+ }
+ /* FIFO ring overwrite */
+ idx = server->certVerifyCache.writeIdx;
+ slot = &server->certVerifyCache.slots[idx];
+ memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
+ slot->committed = 1;
+ server->certVerifyCache.writeIdx =
+ (uint16_t)((idx + 1) % WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT);
+}
+
+void wh_Server_CertVerifyCache_Clear(whServerContext* server)
+{
+ if (server == NULL) {
+ return;
+ }
+ memset(&server->certVerifyCache, 0, sizeof(server->certVerifyCache));
+}
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
/* Replicates GetSequence, which is WOLFSSL_LOCAL. */
@@ -73,6 +125,10 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
uint32_t remaining_len = chain_len;
int cert_len = 0;
word32 idx = 0;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ uint8_t certHash[WH_CERT_VERIFY_CACHE_HASH_LEN];
+ int hashed = 0;
+#endif
if (cm == NULL || chain == NULL || chain_len == 0) {
return WH_ERROR_BADARGS;
@@ -82,6 +138,9 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
while (remaining_len > 0) {
/* Reset index for each certificate */
idx = 0;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ hashed = 0;
+#endif
/* Get the length of the current certificate */
rc = DerNextSequence(cert_ptr, remaining_len, &idx, &cert_len);
@@ -94,9 +153,27 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
return WH_ERROR_ABORTED;
}
- /* Verify the current certificate */
- rc = wolfSSL_CertManagerVerifyBuffer(cm, cert_ptr, cert_len + idx,
- WOLFSSL_FILETYPE_ASN1);
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Hash the DER cert and check the verify cache. A hit short-circuits
+ * the public-key signature check; the cert is otherwise treated as if
+ * it had verified normally so the rest of the loop (CA decode, store
+ * load, leaf pubkey extract) continues unchanged. */
+ rc = wc_Sha256Hash_ex(cert_ptr, (word32)(cert_len + idx), certHash,
+ NULL, server->devId);
+ if (rc != 0) {
+ return rc;
+ }
+ hashed = 1;
+ if (wh_Server_CertVerifyCache_Lookup(server, certHash) == WH_ERROR_OK) {
+ rc = WOLFSSL_SUCCESS;
+ }
+ else
+#endif
+ {
+ /* Verify the current certificate */
+ rc = wolfSSL_CertManagerVerifyBuffer(cm, cert_ptr, cert_len + idx,
+ WOLFSSL_FILETYPE_ASN1);
+ }
/* If this is not the leaf certificate and it's trusted, add it to the
@@ -170,6 +247,13 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
}
}
wc_FreeDecodedCert(&dc);
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Cert verified (or hit) and post-processed successfully.
+ * Record the hash so subsequent verifications short-circuit. */
+ if (hashed) {
+ wh_Server_CertVerifyCache_Insert(server, certHash);
+ }
+#endif
}
else {
return rc;
@@ -190,7 +274,13 @@ int wh_Server_CertInit(whServerContext* server)
#ifdef DEBUG_WOLFSSL
wolfSSL_Debugging_ON();
#endif
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ if (server != NULL) {
+ wh_Server_CertVerifyCache_Clear(server);
+ }
+#else
(void)server;
+#endif
return WH_ERROR_OK;
}
@@ -572,6 +662,23 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
*out_resp_size = sizeof(resp);
}; break;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ case WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_CLEAR: {
+ whMessageCert_SimpleResponse resp = {0};
+
+ rc = WH_SERVER_NVM_LOCK(server);
+ if (rc == WH_ERROR_OK) {
+ wh_Server_CertVerifyCache_Clear(server);
+ (void)WH_SERVER_NVM_UNLOCK(server);
+ }
+ resp.rc = rc;
+
+ wh_MessageCert_TranslateSimpleResponse(
+ magic, &resp, (whMessageCert_SimpleResponse*)resp_packet);
+ *out_resp_size = sizeof(resp);
+ }; break;
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
#ifdef WOLFHSM_CFG_DMA
case WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA: {
whMessageCert_AddTrustedDmaRequest req = {0};
diff --git a/test/Makefile b/test/Makefile
index 27f25921b..8b8d15ba4 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -165,6 +165,11 @@ ifeq ($(AUTH),1)
DEF += -DWOLFHSM_CFG_ENABLE_AUTHENTICATION
endif
+# Support trusted-cert verify-result cache
+ifeq ($(CERT_VERIFY_CACHE),1)
+ DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+endif
+
## Project defines
# Option to build wolfcrypt tests
ifeq ($(TESTWOLFCRYPT),1)
diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c
index ede3a0b4a..60e819053 100644
--- a/test/wh_test_cert.c
+++ b/test/wh_test_cert.c
@@ -122,12 +122,21 @@ int whTest_CertServerCfg(whServerConfig* serverCfg)
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
/* attempt to verify invalid chains, should fail */
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Clear the verify cache so the following cross-root negative cases
+ * exercise the cold path. With the cache enabled, prior positive verifies
+ * would intentionally make these chains succeed under any root. */
+ wh_Server_CertVerifyCache_Clear(server);
+#endif
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
RAW_CERT_CHAIN_A_len, rootCertB,
WH_CERT_FLAGS_NONE,
WH_NVM_FLAGS_USAGE_ANY, NULL));
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ wh_Server_CertVerifyCache_Clear(server);
+#endif
WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
wh_Server_CertVerify(server, RAW_CERT_CHAIN_B,
RAW_CERT_CHAIN_B_len, rootCertA,
@@ -142,6 +151,60 @@ int whTest_CertServerCfg(whServerConfig* serverCfg)
WH_TEST_PRINT("Test completed successfully\n");
return rc;
}
+
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+/* Exercises the trusted-cert verify cache directly through the server API:
+ * - repeat-verify of the same chain stays successful
+ * - a chain that initially fails under a foreign root succeeds after the
+ * intermediate has been cached by a prior verify under its true root
+ * (cross-root recognition)
+ * - clearing the cache reverts cross-root recognition */
+static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
+{
+ whServerContext server[1] = {0};
+ const whNvmId rootCertA = 1;
+ const whNvmId rootCertB = 2;
+
+ WH_TEST_PRINT("=== Server cert verify-cache test ===\n");
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, serverCfg));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server));
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ server, rootCertA, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_A_CERT, ROOT_A_CERT_len));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ server, rootCertB, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_B_CERT, ROOT_B_CERT_len));
+
+ /* 1. Repeat-verify hit: verify chain A twice; both succeed. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+
+ /* 2. Cross-root recognition: chain A under root B succeeds because every
+ * cert in chain A was cached by step 1. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+
+ /* 3. Clear: the same cross-root verify must fail cold. */
+ wh_Server_CertVerifyCache_Clear(server);
+ WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
+ wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len, rootCertB,
+ WH_CERT_FLAGS_NONE,
+ WH_NVM_FLAGS_USAGE_ANY, NULL));
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertA));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertB));
+ WH_TEST_PRINT("Server cert verify-cache test PASSED\n");
+ return WH_ERROR_OK;
+}
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
#endif /* WOLFHSM_CFG_ENABLE_SERVER */
#ifdef WOLFHSM_CFG_ENABLE_CLIENT
@@ -213,11 +276,22 @@ int whTest_CertClient(whClientContext* client)
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
/* attempt to verify invalid chains, should fail */
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Clear the verify cache so the following cross-root negative cases
+ * exercise the cold path. With the cache enabled, prior positive verifies
+ * would intentionally make these chains succeed under any root. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+#endif
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(
client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+#endif
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertA_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
@@ -259,6 +333,61 @@ int whTest_CertClient(whClientContext* client)
/* Test non-exportable flag enforcement */
WH_TEST_RETURN_ON_FAIL(whTest_CertNonExportable(client));
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Verify-cache scenarios over the full client/server RPC: cross-root
+ * recognition, the clear RPC, and re-verify behavior after clear. */
+ {
+ whNvmId rootCertA_id_c = 1;
+ whNvmId rootCertB_id_c = 2;
+
+ WH_TEST_PRINT("=== Client cert verify-cache test ===\n");
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_CertAddTrusted(client, rootCertA_id_c, WH_NVM_ACCESS_ANY,
+ WH_NVM_FLAGS_NONMODIFIABLE, NULL, 0,
+ ROOT_A_CERT, ROOT_A_CERT_len, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_CertAddTrusted(client, rootCertB_id_c, WH_NVM_ACCESS_ANY,
+ WH_NVM_FLAGS_NONMODIFIABLE, NULL, 0,
+ ROOT_B_CERT, ROOT_B_CERT_len, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+
+ /* Start from a known-empty cache. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+
+ /* Warm the cache by verifying chain A under its true root. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len,
+ rootCertA_id_c, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+
+ /* Cross-root recognition: chain A under root B now succeeds. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len,
+ rootCertB_id_c, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+
+ /* After clear, the same cross-root verify must fail cold. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len,
+ rootCertB_id_c, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_CertEraseTrusted(client, rootCertA_id_c, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Client_CertEraseTrusted(client, rootCertB_id_c, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+ WH_TEST_PRINT("Client cert verify-cache test PASSED\n");
+ }
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
WH_TEST_PRINT("Certificate client test completed successfully\n");
return rc;
@@ -398,11 +527,22 @@ int whTest_CertClientDma_ClientServerTestInternal(whClientContext* client)
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
/* attempt to verify invalid chains, should fail */
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ /* Clear the verify cache so the following cross-root negative cases
+ * exercise the cold path. With the cache enabled, prior positive verifies
+ * would intentionally make these chains succeed under any root. */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+#endif
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma(
client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+#endif
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma(
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertA_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
@@ -651,6 +791,15 @@ int whTest_CertRamSim(whTestNvmBackendType nvmType)
WH_ERROR_PRINT("Certificate server config tests failed: %d\n", rc);
}
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ if (rc == WH_ERROR_OK) {
+ rc = whTest_CertServerVerifyCache(s_conf);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Cert verify-cache tests failed: %d\n", rc);
+ }
+ }
+#endif
+
/* Cleanup NVM */
wh_Nvm_Cleanup(nvm);
#ifndef WOLFHSM_CFG_NO_CRYPTO
diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h
index a71a788c1..1cecfab32 100644
--- a/wolfhsm/wh_client.h
+++ b/wolfhsm/wh_client.h
@@ -2571,6 +2571,39 @@ int wh_Client_CertVerifyAndCacheLeafPubKey(
int32_t* out_rc);
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+/**
+ * @brief Send a request to clear the server's trusted certificate verify cache.
+ *
+ * Subsequent verification of any certificate will re-run the public-key
+ * signature check until that cert is verified again and re-cached.
+ *
+ * @param[in] c Pointer to the client context.
+ * @return int Returns 0 on success, or a negative error code on failure.
+ */
+int wh_Client_CertVerifyCacheClearRequest(whClientContext* c);
+
+/**
+ * @brief Receive the response to a verify-cache clear request.
+ *
+ * @param[in] c Pointer to the client context.
+ * @param[out] out_rc Pointer to store the response code from the server.
+ * @return int Returns 0 on success, or a negative error code on failure.
+ */
+int wh_Client_CertVerifyCacheClearResponse(whClientContext* c, int32_t* out_rc);
+
+/**
+ * @brief Synchronous helper to clear the server's trusted certificate verify
+ * cache.
+ *
+ * @param[in] c Pointer to the client context.
+ * @param[out] out_rc Pointer to store the response code from the server.
+ * @return int Returns 0 on success, or a negative error code on failure.
+ */
+int wh_Client_CertVerifyCacheClear(whClientContext* c, int32_t* out_rc);
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
+
#ifdef WOLFHSM_CFG_DMA
/**
diff --git a/wolfhsm/wh_message_cert.h b/wolfhsm/wh_message_cert.h
index c2370bd31..5c376a07f 100644
--- a/wolfhsm/wh_message_cert.h
+++ b/wolfhsm/wh_message_cert.h
@@ -34,16 +34,17 @@
#include "wolfhsm/wh_nvm.h"
enum WH_MESSAGE_CERT_ACTION_ENUM {
- WH_MESSAGE_CERT_ACTION_INIT = 0x1,
- WH_MESSAGE_CERT_ACTION_ADDTRUSTED = 0x2,
- WH_MESSAGE_CERT_ACTION_ERASETRUSTED = 0x3,
- WH_MESSAGE_CERT_ACTION_READTRUSTED = 0x4,
- WH_MESSAGE_CERT_ACTION_VERIFY = 0x5,
- WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA = 0x22,
- WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA = 0x24,
- WH_MESSAGE_CERT_ACTION_VERIFY_DMA = 0x25,
- WH_MESSAGE_CERT_ACTION_VERIFY_ACERT = 0x26,
- WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA = 0x27,
+ WH_MESSAGE_CERT_ACTION_INIT = 0x1,
+ WH_MESSAGE_CERT_ACTION_ADDTRUSTED = 0x2,
+ WH_MESSAGE_CERT_ACTION_ERASETRUSTED = 0x3,
+ WH_MESSAGE_CERT_ACTION_READTRUSTED = 0x4,
+ WH_MESSAGE_CERT_ACTION_VERIFY = 0x5,
+ WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_CLEAR = 0x6,
+ WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA = 0x22,
+ WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA = 0x24,
+ WH_MESSAGE_CERT_ACTION_VERIFY_DMA = 0x25,
+ WH_MESSAGE_CERT_ACTION_VERIFY_ACERT = 0x26,
+ WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA = 0x27,
};
/* Simple reusable response message */
diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h
index f57cf2d7f..20381b847 100644
--- a/wolfhsm/wh_server.h
+++ b/wolfhsm/wh_server.h
@@ -39,6 +39,7 @@ typedef struct whServerContext_t whServerContext;
#include "wolfhsm/wh_common.h"
#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_keycache.h"
+#include "wolfhsm/wh_server_cert_cache.h"
#include "wolfhsm/wh_nvm.h"
#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
#include "wolfhsm/wh_auth.h"
@@ -186,6 +187,9 @@ struct whServerContext_t {
#ifdef WOLFHSM_CFG_LOGGING
whLogContext log;
#endif /* WOLFHSM_CFG_LOGGING */
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ whCertVerifyCacheContext certVerifyCache;
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
};
diff --git a/wolfhsm/wh_server_cert_cache.h b/wolfhsm/wh_server_cert_cache.h
new file mode 100644
index 000000000..be3b5b0a7
--- /dev/null
+++ b/wolfhsm/wh_server_cert_cache.h
@@ -0,0 +1,90 @@
+/*
+ * 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 .
+ */
+
+/*
+ * wolfhsm/wh_server_cert_cache.h
+ *
+ * Trusted-cert verify-result cache. Records SHA-256 hashes of DER-encoded
+ * certificates that have already been successfully verified, so subsequent
+ * verifications encountering the same cert can short-circuit the public-key
+ * signature check. Hits apply across clients and across trusted roots.
+ *
+ * Lives in its own header to avoid circular dependencies between wh_server.h
+ * and wh_server_cert.h.
+ */
+
+#ifndef WOLFHSM_WH_SERVER_CERT_CACHE_H_
+#define WOLFHSM_WH_SERVER_CERT_CACHE_H_
+
+/* Pick up compile-time configuration */
+#include "wolfhsm/wh_settings.h"
+
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+
+#include
+
+#ifndef WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT
+#define WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT 16
+#endif
+
+#define WH_CERT_VERIFY_CACHE_HASH_LEN 32 /* SHA-256 digest size */
+
+typedef struct whCertVerifyCacheSlot {
+ uint8_t committed; /* 0 = empty, 1 = valid */
+ uint8_t WH_PAD[3];
+ uint8_t hash[WH_CERT_VERIFY_CACHE_HASH_LEN];
+} whCertVerifyCacheSlot;
+
+typedef struct whCertVerifyCacheContext {
+ whCertVerifyCacheSlot slots[WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT];
+ uint16_t writeIdx; /* FIFO ring write position */
+ uint8_t WH_PAD[6];
+} whCertVerifyCacheContext;
+
+/* Forward declaration to avoid pulling in wh_server.h */
+struct whServerContext_t;
+
+/**
+ * @brief Look up a cert hash in the verify cache.
+ * @param server The server context.
+ * @param hash Pointer to a SHA-256 (32-byte) digest of the DER cert.
+ * @return WH_ERROR_OK if the hash is present, WH_ERROR_NOTFOUND if absent,
+ * WH_ERROR_BADARGS on invalid arguments.
+ */
+int wh_Server_CertVerifyCache_Lookup(struct whServerContext_t* server,
+ const uint8_t* hash);
+
+/**
+ * @brief Insert a cert hash into the verify cache. No-op if already present.
+ * Uses FIFO ring overwrite when full.
+ * @param server The server context.
+ * @param hash Pointer to a SHA-256 (32-byte) digest of the DER cert.
+ */
+void wh_Server_CertVerifyCache_Insert(struct whServerContext_t* server,
+ const uint8_t* hash);
+
+/**
+ * @brief Clear all entries from the verify cache.
+ * @param server The server context.
+ */
+void wh_Server_CertVerifyCache_Clear(struct whServerContext_t* server);
+
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
+#endif /* !WOLFHSM_WH_SERVER_CERT_CACHE_H_ */
From 13e6108ccfa0ab1a3eb4dfe8fe3dd1b3cee77e98 Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Tue, 5 May 2026 10:23:50 -0600
Subject: [PATCH 2/7] add server certificate verify callback injection
---
src/wh_server.c | 8 ++
src/wh_server_cert.c | 26 +++++-
test/wh_test_cert.c | 165 +++++++++++++++++++++++++++++++++
wolfhsm/wh_server.h | 9 +-
wolfhsm/wh_server_cert.h | 20 ++++
wolfhsm/wh_server_cert_cache.h | 40 ++++++--
6 files changed, 254 insertions(+), 14 deletions(-)
diff --git a/src/wh_server.c b/src/wh_server.c
index 943e583c1..de57d1224 100644
--- a/src/wh_server.c
+++ b/src/wh_server.c
@@ -122,6 +122,14 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config)
}
#endif /* WOLFHSM_CFG_DMA */
+#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+ /* Register the user-supplied verify callback, if any. The cache (if
+ * compiled in) is already zero-initialized by the memset above. */
+ if (config->certConfig != NULL) {
+ server->cert.verifyCb = config->certConfig->verifyCb;
+ }
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+
/* Log the server startup */
WH_LOG(&server->log, WH_LOG_LEVEL_INFO, "Server Initialized");
diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c
index ece7d2fc8..144925c40 100644
--- a/src/wh_server_cert.c
+++ b/src/wh_server_cert.c
@@ -54,7 +54,7 @@ int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
return WH_ERROR_BADARGS;
}
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
- const whCertVerifyCacheSlot* slot = &server->certVerifyCache.slots[i];
+ const whCertVerifyCacheSlot* slot = &server->cert.cache.slots[i];
if (slot->committed &&
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0)) {
return WH_ERROR_OK;
@@ -77,11 +77,11 @@ void wh_Server_CertVerifyCache_Insert(whServerContext* server,
return;
}
/* FIFO ring overwrite */
- idx = server->certVerifyCache.writeIdx;
- slot = &server->certVerifyCache.slots[idx];
+ idx = server->cert.cache.writeIdx;
+ slot = &server->cert.cache.slots[idx];
memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
slot->committed = 1;
- server->certVerifyCache.writeIdx =
+ server->cert.cache.writeIdx =
(uint16_t)((idx + 1) % WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT);
}
@@ -90,10 +90,19 @@ void wh_Server_CertVerifyCache_Clear(whServerContext* server)
if (server == NULL) {
return;
}
- memset(&server->certVerifyCache, 0, sizeof(server->certVerifyCache));
+ memset(&server->cert.cache, 0, sizeof(server->cert.cache));
}
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+int wh_Server_CertSetVerifyCb(whServerContext* server, VerifyCallback cb)
+{
+ if (server == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+ server->cert.verifyCb = cb;
+ return WH_ERROR_OK;
+}
+
/* Replicates GetSequence, which is WOLFSSL_LOCAL. */
static int DerNextSequence(const uint8_t* input, uint32_t maxIdx,
@@ -394,6 +403,13 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert,
return WH_ERROR_ABORTED;
}
+ /* Apply the user-supplied verify callback, if registered. wolfSSL invokes
+ * it during wolfSSL_CertManagerVerifyBuffer; cache hits short-circuit that
+ * path and so deliberately do not invoke the callback. */
+ if (server->cert.verifyCb != NULL) {
+ wolfSSL_CertManagerSetVerify(cm, server->cert.verifyCb);
+ }
+
/* Get the trusted root certificate */
rc = wh_Server_CertReadTrusted(server, trustedRootNvmId, root_cert,
&root_cert_len);
diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c
index 60e819053..e624953ce 100644
--- a/test/wh_test_cert.c
+++ b/test/wh_test_cert.c
@@ -205,6 +205,156 @@ static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
return WH_ERROR_OK;
}
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
+/* State for the user-injectable verify callback test */
+static int s_verifyCb_count = 0;
+static int s_verifyCb_lastPreverify = -1;
+static int s_verifyCb_returnVal = 1;
+
+static int whTest_recordingVerifyCb(int preverify,
+ WOLFSSL_X509_STORE_CTX* store)
+{
+ (void)store;
+ s_verifyCb_count++;
+ s_verifyCb_lastPreverify = preverify;
+ return s_verifyCb_returnVal;
+}
+
+/* Exercises the user-injectable verify callback configured through
+ * whServerCertConfig. Confirms:
+ * - the callback is invoked during chain verification with preverify=1
+ * - returning zero from the callback fails the verify
+ * - cache hits bypass the callback (when the verify cache is enabled) */
+static int whTest_CertServerVerifyCallback(whServerConfig* serverCfg)
+{
+ int rc;
+ whServerContext server[1] = {0};
+ whServerCertConfig certCfg = {.verifyCb = whTest_recordingVerifyCb};
+ whServerCertConfig* savedCertConfig;
+ const whNvmId rootCertA = 1;
+
+ WH_TEST_PRINT("=== Server cert verify-callback test ===\n");
+
+ /* Inject our cert config; restore on exit. */
+ savedCertConfig = serverCfg->certConfig;
+ serverCfg->certConfig = &certCfg;
+
+ s_verifyCb_count = 0;
+ s_verifyCb_lastPreverify = -1;
+ s_verifyCb_returnVal = 1;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, serverCfg));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server));
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ server, rootCertA, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_A_CERT, ROOT_A_CERT_len));
+
+ /* 1. Callback is invoked on a successful verify with preverify=1. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count > 0);
+ WH_TEST_ASSERT_RETURN(s_verifyCb_lastPreverify == 1);
+
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ {
+ /* 2. Cache hit bypasses the callback: re-verifying the same chain
+ * must not increment the call count. */
+ int beforeCount = s_verifyCb_count;
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count == beforeCount);
+
+ /* Clear cache so the next verify re-enters wolfSSL and the cb. */
+ wh_Server_CertVerifyCache_Clear(server);
+ }
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
+ /* 3. Returning zero from the callback forces verify failure. */
+ s_verifyCb_returnVal = 0;
+ s_verifyCb_count = 0;
+ WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
+ wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE,
+ WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count > 0);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertA));
+ serverCfg->certConfig = savedCertConfig;
+ rc = WH_ERROR_OK;
+ WH_TEST_PRINT("Server cert verify-callback test PASSED\n");
+ return rc;
+}
+
+/* Exercises wh_Server_CertSetVerifyCb: register, replace, and unregister the
+ * verify callback after the server is already initialized (i.e. without
+ * supplying it via whServerCertConfig). */
+static int whTest_CertServerVerifyCallbackRuntime(whServerConfig* serverCfg)
+{
+ int rc;
+ whServerContext server[1] = {0};
+ whServerCertConfig* savedCertConfig;
+ const whNvmId rootCertA = 1;
+
+ WH_TEST_PRINT("=== Server cert verify-callback runtime test ===\n");
+
+ /* Force NULL certConfig so registration must come from the runtime API. */
+ savedCertConfig = serverCfg->certConfig;
+ serverCfg->certConfig = NULL;
+
+ s_verifyCb_count = 0;
+ s_verifyCb_lastPreverify = -1;
+ s_verifyCb_returnVal = 1;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, serverCfg));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server));
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ server, rootCertA, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_A_CERT, ROOT_A_CERT_len));
+
+ /* 1. No callback registered: verify succeeds, counter stays 0. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count == 0);
+
+ /* 2. Register at runtime; cb must fire on the next cold verify. */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Server_CertSetVerifyCb(server, whTest_recordingVerifyCb));
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ wh_Server_CertVerifyCache_Clear(server);
+#endif
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count > 0);
+ WH_TEST_ASSERT_RETURN(s_verifyCb_lastPreverify == 1);
+
+ /* 3. Unregister at runtime; verify still succeeds, counter stays 0. */
+ s_verifyCb_count = 0;
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertSetVerifyCb(server, NULL));
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ wh_Server_CertVerifyCache_Clear(server);
+#endif
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_verifyCb_count == 0);
+
+ /* 4. NULL server is rejected. */
+ WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS ==
+ wh_Server_CertSetVerifyCb(NULL, NULL));
+
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertA));
+ serverCfg->certConfig = savedCertConfig;
+ rc = WH_ERROR_OK;
+ WH_TEST_PRINT("Server cert verify-callback runtime test PASSED\n");
+ return rc;
+}
#endif /* WOLFHSM_CFG_ENABLE_SERVER */
#ifdef WOLFHSM_CFG_ENABLE_CLIENT
@@ -800,6 +950,21 @@ int whTest_CertRamSim(whTestNvmBackendType nvmType)
}
#endif
+ if (rc == WH_ERROR_OK) {
+ rc = whTest_CertServerVerifyCallback(s_conf);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Cert verify-callback tests failed: %d\n", rc);
+ }
+ }
+
+ if (rc == WH_ERROR_OK) {
+ rc = whTest_CertServerVerifyCallbackRuntime(s_conf);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Cert verify-callback runtime tests failed: %d\n",
+ rc);
+ }
+ }
+
/* Cleanup NVM */
wh_Nvm_Cleanup(nvm);
#ifndef WOLFHSM_CFG_NO_CRYPTO
diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h
index 20381b847..80cf00305 100644
--- a/wolfhsm/wh_server.h
+++ b/wolfhsm/wh_server.h
@@ -161,6 +161,9 @@ typedef struct whServerConfig_t {
#ifdef WOLFHSM_CFG_LOGGING
whLogConfig* logConfig;
#endif /* WOLFHSM_CFG_LOGGING */
+#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+ whServerCertConfig* certConfig; /* optional; NULL = no verify callback */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
} whServerConfig;
@@ -187,9 +190,9 @@ struct whServerContext_t {
#ifdef WOLFHSM_CFG_LOGGING
whLogContext log;
#endif /* WOLFHSM_CFG_LOGGING */
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- whCertVerifyCacheContext certVerifyCache;
-#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+ whServerCertContext cert; /* verify callback + verify cache */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
};
diff --git a/wolfhsm/wh_server_cert.h b/wolfhsm/wh_server_cert.h
index 0d0be42b7..9392a111d 100644
--- a/wolfhsm/wh_server_cert.h
+++ b/wolfhsm/wh_server_cert.h
@@ -95,6 +95,26 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert,
whCertFlags flags, whNvmFlags cachedKeyFlags,
whKeyId* inout_keyId);
+#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+/**
+ * @brief Register a verify callback at runtime.
+ *
+ * Replaces the callback previously set via whServerCertConfig.verifyCb (or by
+ * a prior call to this function). Pass NULL to unregister.
+ *
+ * The callback is applied to the per-request WOLFSSL_CERT_MANAGER created by
+ * wh_Server_CertVerify, so it participates in chain verification the same way
+ * a callback registered with wolfSSL_CertManagerSetVerify would. Verify-cache
+ * hits (when WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE is enabled) bypass the
+ * callback because they bypass wolfSSL's verify path entirely.
+ *
+ * @param server The server context.
+ * @param cb The callback to register, or NULL to unregister.
+ * @return WH_ERROR_OK on success, WH_ERROR_BADARGS if server is NULL.
+ */
+int wh_Server_CertSetVerifyCb(whServerContext* server, VerifyCallback cb);
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+
#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT)
/**
* @brief Verifies an attribute certificate against a trusted root certificate
diff --git a/wolfhsm/wh_server_cert_cache.h b/wolfhsm/wh_server_cert_cache.h
index be3b5b0a7..5ae67fce6 100644
--- a/wolfhsm/wh_server_cert_cache.h
+++ b/wolfhsm/wh_server_cert_cache.h
@@ -20,10 +20,14 @@
/*
* wolfhsm/wh_server_cert_cache.h
*
- * Trusted-cert verify-result cache. Records SHA-256 hashes of DER-encoded
- * certificates that have already been successfully verified, so subsequent
- * verifications encountering the same cert can short-circuit the public-key
- * signature check. Hits apply across clients and across trusted roots.
+ * Server-side cert subsystem types embedded in whServerContext:
+ * - whServerCertContext / whServerCertConfig: hold the user-injectable
+ * verify callback and (optionally) the trusted-cert verify cache.
+ * - whCertVerifyCacheContext: trusted-cert verify-result cache. Records
+ * SHA-256 hashes of DER-encoded certificates that have already been
+ * successfully verified, so subsequent verifications encountering the
+ * same cert can short-circuit the public-key signature check. Hits
+ * apply across clients and across trusted roots.
*
* Lives in its own header to avoid circular dependencies between wh_server.h
* and wh_server_cert.h.
@@ -35,10 +39,14 @@
/* Pick up compile-time configuration */
#include "wolfhsm/wh_settings.h"
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
#include
+#include "wolfssl/ssl.h" /* for VerifyCallback */
+
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+
#ifndef WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT
#define WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT 16
#endif
@@ -57,9 +65,28 @@ typedef struct whCertVerifyCacheContext {
uint8_t WH_PAD[6];
} whCertVerifyCacheContext;
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+
+/* Per-server cert subsystem config, supplied via whServerConfig.certConfig.
+ * The verify callback signature matches wolfSSL's VerifyCallback so the same
+ * callback registered with wolfSSL_CertManagerSetVerify can be used here. */
+typedef struct {
+ VerifyCallback verifyCb; /* user-supplied; NULL = no callback */
+} whServerCertConfig;
+
+/* Per-server cert subsystem context, embedded by value in whServerContext.
+ * Holds the registered verify callback and (optionally) the verify cache. */
+typedef struct {
+ VerifyCallback verifyCb;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ whCertVerifyCacheContext cache;
+#endif
+} whServerCertContext;
+
/* Forward declaration to avoid pulling in wh_server.h */
struct whServerContext_t;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
/**
* @brief Look up a cert hash in the verify cache.
* @param server The server context.
@@ -84,7 +111,8 @@ void wh_Server_CertVerifyCache_Insert(struct whServerContext_t* server,
* @param server The server context.
*/
void wh_Server_CertVerifyCache_Clear(struct whServerContext_t* server);
-
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+
#endif /* !WOLFHSM_WH_SERVER_CERT_CACHE_H_ */
From 651b4c72b5882fb4756312a43328ecb56ae52e6d Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Tue, 5 May 2026 11:34:24 -0600
Subject: [PATCH 3/7] fix nocrypto gating
---
src/wh_server.c | 4 ++--
wolfhsm/wh_server.h | 8 ++++----
wolfhsm/wh_server_cert.h | 4 ++--
wolfhsm/wh_server_cert_cache.h | 4 ++--
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/wh_server.c b/src/wh_server.c
index de57d1224..3e188cad4 100644
--- a/src/wh_server.c
+++ b/src/wh_server.c
@@ -122,13 +122,13 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config)
}
#endif /* WOLFHSM_CFG_DMA */
-#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
/* Register the user-supplied verify callback, if any. The cache (if
* compiled in) is already zero-initialized by the memset above. */
if (config->certConfig != NULL) {
server->cert.verifyCb = config->certConfig->verifyCb;
}
-#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
/* Log the server startup */
WH_LOG(&server->log, WH_LOG_LEVEL_INFO, "Server Initialized");
diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h
index 80cf00305..e332434a2 100644
--- a/wolfhsm/wh_server.h
+++ b/wolfhsm/wh_server.h
@@ -161,9 +161,9 @@ typedef struct whServerConfig_t {
#ifdef WOLFHSM_CFG_LOGGING
whLogConfig* logConfig;
#endif /* WOLFHSM_CFG_LOGGING */
-#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
whServerCertConfig* certConfig; /* optional; NULL = no verify callback */
-#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
} whServerConfig;
@@ -190,9 +190,9 @@ struct whServerContext_t {
#ifdef WOLFHSM_CFG_LOGGING
whLogContext log;
#endif /* WOLFHSM_CFG_LOGGING */
-#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
whServerCertContext cert; /* verify callback + verify cache */
-#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
};
diff --git a/wolfhsm/wh_server_cert.h b/wolfhsm/wh_server_cert.h
index 9392a111d..6914d2e04 100644
--- a/wolfhsm/wh_server_cert.h
+++ b/wolfhsm/wh_server_cert.h
@@ -95,7 +95,7 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert,
whCertFlags flags, whNvmFlags cachedKeyFlags,
whKeyId* inout_keyId);
-#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
/**
* @brief Register a verify callback at runtime.
*
@@ -113,7 +113,7 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert,
* @return WH_ERROR_OK on success, WH_ERROR_BADARGS if server is NULL.
*/
int wh_Server_CertSetVerifyCb(whServerContext* server, VerifyCallback cb);
-#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT)
/**
diff --git a/wolfhsm/wh_server_cert_cache.h b/wolfhsm/wh_server_cert_cache.h
index 5ae67fce6..9a1f578be 100644
--- a/wolfhsm/wh_server_cert_cache.h
+++ b/wolfhsm/wh_server_cert_cache.h
@@ -39,7 +39,7 @@
/* Pick up compile-time configuration */
#include "wolfhsm/wh_settings.h"
-#ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && !defined(WOLFHSM_CFG_NO_CRYPTO)
#include
@@ -113,6 +113,6 @@ void wh_Server_CertVerifyCache_Insert(struct whServerContext_t* server,
void wh_Server_CertVerifyCache_Clear(struct whServerContext_t* server);
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
-#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER */
+#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
#endif /* !WOLFHSM_WH_SERVER_CERT_CACHE_H_ */
From ab5d9e51a057d32ebd5eaed960271a2037f9258e Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Tue, 5 May 2026 13:47:58 -0600
Subject: [PATCH 4/7] bind trusted cert cache entries to root ID
---
src/wh_server_cert.c | 43 ++++++++++--------
test/wh_test_cert.c | 79 +++++++++++++---------------------
wolfhsm/wh_server_cert_cache.h | 30 ++++++++-----
3 files changed, 75 insertions(+), 77 deletions(-)
diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c
index 144925c40..f1518be15 100644
--- a/src/wh_server_cert.c
+++ b/src/wh_server_cert.c
@@ -46,8 +46,8 @@
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
-int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
- const uint8_t* hash)
+int wh_Server_CertVerifyCache_Lookup(whServerContext* server, whNvmId rootNvmId,
+ const uint8_t* hash)
{
int i;
if ((server == NULL) || (hash == NULL)) {
@@ -55,7 +55,7 @@ int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
}
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
const whCertVerifyCacheSlot* slot = &server->cert.cache.slots[i];
- if (slot->committed &&
+ if (slot->committed && (slot->rootNvmId == rootNvmId) &&
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0)) {
return WH_ERROR_OK;
}
@@ -64,7 +64,7 @@ int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
}
void wh_Server_CertVerifyCache_Insert(whServerContext* server,
- const uint8_t* hash)
+ whNvmId rootNvmId, const uint8_t* hash)
{
whCertVerifyCacheSlot* slot;
uint16_t idx;
@@ -72,13 +72,15 @@ void wh_Server_CertVerifyCache_Insert(whServerContext* server,
if ((server == NULL) || (hash == NULL)) {
return;
}
- /* Skip if already present */
- if (wh_Server_CertVerifyCache_Lookup(server, hash) == WH_ERROR_OK) {
+ /* Skip if already present under this root */
+ if (wh_Server_CertVerifyCache_Lookup(server, rootNvmId, hash) ==
+ WH_ERROR_OK) {
return;
}
/* FIFO ring overwrite */
- idx = server->cert.cache.writeIdx;
- slot = &server->cert.cache.slots[idx];
+ idx = server->cert.cache.writeIdx;
+ slot = &server->cert.cache.slots[idx];
+ slot->rootNvmId = rootNvmId;
memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
slot->committed = 1;
server->cert.cache.writeIdx =
@@ -122,12 +124,11 @@ static int DerNextSequence(const uint8_t* input, uint32_t maxIdx,
}
-static int _verifyChainAgainstCmStore(whServerContext* server,
- WOLFSSL_CERT_MANAGER* cm,
- const uint8_t* chain, uint32_t chain_len,
- whCertFlags flags,
- whNvmFlags cachedKeyFlags,
- whKeyId* inout_keyId)
+static int
+_verifyChainAgainstCmStore(whServerContext* server, WOLFSSL_CERT_MANAGER* cm,
+ const uint8_t* chain, uint32_t chain_len,
+ whNvmId trustedRootNvmId, whCertFlags flags,
+ whNvmFlags cachedKeyFlags, whKeyId* inout_keyId)
{
int rc = 0;
const uint8_t* cert_ptr = chain;
@@ -137,6 +138,8 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
uint8_t certHash[WH_CERT_VERIFY_CACHE_HASH_LEN];
int hashed = 0;
+#else
+ (void)trustedRootNvmId;
#endif
if (cm == NULL || chain == NULL || chain_len == 0) {
@@ -173,7 +176,8 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
return rc;
}
hashed = 1;
- if (wh_Server_CertVerifyCache_Lookup(server, certHash) == WH_ERROR_OK) {
+ if (wh_Server_CertVerifyCache_Lookup(server, trustedRootNvmId,
+ certHash) == WH_ERROR_OK) {
rc = WOLFSSL_SUCCESS;
}
else
@@ -258,9 +262,11 @@ static int _verifyChainAgainstCmStore(whServerContext* server,
wc_FreeDecodedCert(&dc);
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
/* Cert verified (or hit) and post-processed successfully.
- * Record the hash so subsequent verifications short-circuit. */
+ * Record the hash bound to this trusted root so subsequent
+ * verifications under the same root short-circuit. */
if (hashed) {
- wh_Server_CertVerifyCache_Insert(server, certHash);
+ wh_Server_CertVerifyCache_Insert(server, trustedRootNvmId,
+ certHash);
}
#endif
}
@@ -419,7 +425,8 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert,
WOLFSSL_FILETYPE_ASN1);
if (rc == WOLFSSL_SUCCESS) {
/* Verify the certificate */
- rc = _verifyChainAgainstCmStore(server, cm, cert, cert_len, flags,
+ rc = _verifyChainAgainstCmStore(server, cm, cert, cert_len,
+ trustedRootNvmId, flags,
cachedKeyFlags, inout_keyId);
if (rc != WH_ERROR_OK) {
rc = WH_ERROR_CERT_VERIFY;
diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c
index e624953ce..4d817fea3 100644
--- a/test/wh_test_cert.c
+++ b/test/wh_test_cert.c
@@ -121,22 +121,15 @@ int whTest_CertServerCfg(whServerConfig* serverCfg)
server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertB,
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
- /* attempt to verify invalid chains, should fail */
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- /* Clear the verify cache so the following cross-root negative cases
- * exercise the cold path. With the cache enabled, prior positive verifies
- * would intentionally make these chains succeed under any root. */
- wh_Server_CertVerifyCache_Clear(server);
-#endif
+ /* attempt to verify invalid chains, should fail. Cache entries are scoped
+ * to the trusted root NVM ID, so prior positive verifies under the true
+ * root cannot bypass these cross-root checks. */
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
RAW_CERT_CHAIN_A_len, rootCertB,
WH_CERT_FLAGS_NONE,
WH_NVM_FLAGS_USAGE_ANY, NULL));
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- wh_Server_CertVerifyCache_Clear(server);
-#endif
WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
wh_Server_CertVerify(server, RAW_CERT_CHAIN_B,
RAW_CERT_CHAIN_B_len, rootCertA,
@@ -154,11 +147,11 @@ int whTest_CertServerCfg(whServerConfig* serverCfg)
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
/* Exercises the trusted-cert verify cache directly through the server API:
- * - repeat-verify of the same chain stays successful
- * - a chain that initially fails under a foreign root succeeds after the
- * intermediate has been cached by a prior verify under its true root
- * (cross-root recognition)
- * - clearing the cache reverts cross-root recognition */
+ * - repeat-verify of the same chain under the same root stays successful
+ * - cache entries are bound to the trusted root NVM ID: chain A under root B
+ * must fail even after chain A has been cached by a verify under root A
+ * (regression test against cross-root cache bypass)
+ * - clearing the cache leaves the cross-root case still failing */
static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
{
whServerContext server[1] = {0};
@@ -177,7 +170,7 @@ static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
server, rootCertB, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
0, ROOT_B_CERT, ROOT_B_CERT_len));
- /* 1. Repeat-verify hit: verify chain A twice; both succeed. */
+ /* 1. Repeat-verify hit: verify chain A twice under root A; both succeed. */
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
@@ -185,13 +178,16 @@ static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
- /* 2. Cross-root recognition: chain A under root B succeeds because every
- * cert in chain A was cached by step 1. */
- WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
- server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB,
- WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ /* 2. Cache is bound to root NVM ID: chain A under root B must fail even
+ * though every cert in chain A was cached under root A by step 1. The
+ * cache hit must not let an unsigned-by-rootB chain through. */
+ WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
+ wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len, rootCertB,
+ WH_CERT_FLAGS_NONE,
+ WH_NVM_FLAGS_USAGE_ANY, NULL));
- /* 3. Clear: the same cross-root verify must fail cold. */
+ /* 3. Clear: the same cross-root verify still fails cold. */
wh_Server_CertVerifyCache_Clear(server);
WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
wh_Server_CertVerify(server, RAW_CERT_CHAIN_A,
@@ -425,23 +421,14 @@ int whTest_CertClient(whClientContext* client)
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
- /* attempt to verify invalid chains, should fail */
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- /* Clear the verify cache so the following cross-root negative cases
- * exercise the cold path. With the cache enabled, prior positive verifies
- * would intentionally make these chains succeed under any root. */
- WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
- WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
-#endif
+ /* attempt to verify invalid chains, should fail. Cache entries are scoped
+ * to the trusted root NVM ID, so prior positive verifies under the true
+ * root cannot bypass these cross-root checks. */
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(
client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
- WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
-#endif
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertA_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
@@ -513,13 +500,16 @@ int whTest_CertClient(whClientContext* client)
rootCertA_id_c, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
- /* Cross-root recognition: chain A under root B now succeeds. */
+ /* Cache entries are bound to the trusted root NVM ID: chain A under
+ * root B fails even though every cert in chain A is cached under
+ * root A. The cache hit must not bypass the cross-root check. */
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
RAW_CERT_CHAIN_A_len,
rootCertB_id_c, &out_rc));
- WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
+ WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
- /* After clear, the same cross-root verify must fail cold. */
+ /* After clear, the cross-root verify still fails cold. Exercises the
+ * clear RPC path. */
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
@@ -676,23 +666,14 @@ int whTest_CertClientDma_ClientServerTestInternal(whClientContext* client)
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
- /* attempt to verify invalid chains, should fail */
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- /* Clear the verify cache so the following cross-root negative cases
- * exercise the cold path. With the cache enabled, prior positive verifies
- * would intentionally make these chains succeed under any root. */
- WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
- WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
-#endif
+ /* attempt to verify invalid chains, should fail. Cache entries are scoped
+ * to the trusted root NVM ID, so prior positive verifies under the true
+ * root cannot bypass these cross-root checks. */
WH_TEST_PRINT("Attempting to verify invalid certificate chains...\n");
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma(
client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertB_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
- WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyCacheClear(client, &out_rc));
- WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
-#endif
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyDma(
client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, rootCertA_id, &out_rc));
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
diff --git a/wolfhsm/wh_server_cert_cache.h b/wolfhsm/wh_server_cert_cache.h
index 9a1f578be..af18a017d 100644
--- a/wolfhsm/wh_server_cert_cache.h
+++ b/wolfhsm/wh_server_cert_cache.h
@@ -25,9 +25,12 @@
* verify callback and (optionally) the trusted-cert verify cache.
* - whCertVerifyCacheContext: trusted-cert verify-result cache. Records
* SHA-256 hashes of DER-encoded certificates that have already been
- * successfully verified, so subsequent verifications encountering the
- * same cert can short-circuit the public-key signature check. Hits
- * apply across clients and across trusted roots.
+ * successfully verified under a specific trusted-root NVM ID, so
+ * subsequent verifications encountering the same cert under the same
+ * root can short-circuit the public-key signature check. Hits apply
+ * across clients but are scoped to the trusted root that anchored
+ * the prior verify; a cert cached under root A will not match a
+ * lookup under root B.
*
* Lives in its own header to avoid circular dependencies between wh_server.h
* and wh_server_cert.h.
@@ -43,6 +46,8 @@
#include
+#include "wolfhsm/wh_common.h" /* for whNvmId */
+
#include "wolfssl/ssl.h" /* for VerifyCallback */
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
@@ -55,7 +60,8 @@
typedef struct whCertVerifyCacheSlot {
uint8_t committed; /* 0 = empty, 1 = valid */
- uint8_t WH_PAD[3];
+ uint8_t WH_PAD[1];
+ whNvmId rootNvmId; /* trusted root NVM ID this entry is bound to */
uint8_t hash[WH_CERT_VERIFY_CACHE_HASH_LEN];
} whCertVerifyCacheSlot;
@@ -88,23 +94,27 @@ struct whServerContext_t;
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
/**
- * @brief Look up a cert hash in the verify cache.
+ * @brief Look up a cert hash in the verify cache, scoped to a trusted root.
* @param server The server context.
+ * @param rootNvmId NVM ID of the trusted root that must have anchored a prior
+ * successful verify of this cert.
* @param hash Pointer to a SHA-256 (32-byte) digest of the DER cert.
- * @return WH_ERROR_OK if the hash is present, WH_ERROR_NOTFOUND if absent,
- * WH_ERROR_BADARGS on invalid arguments.
+ * @return WH_ERROR_OK if the (rootNvmId, hash) pair is present,
+ * WH_ERROR_NOTFOUND if absent, WH_ERROR_BADARGS on invalid arguments.
*/
int wh_Server_CertVerifyCache_Lookup(struct whServerContext_t* server,
- const uint8_t* hash);
+ whNvmId rootNvmId, const uint8_t* hash);
/**
- * @brief Insert a cert hash into the verify cache. No-op if already present.
+ * @brief Insert a cert hash into the verify cache, bound to a trusted root.
+ * No-op if the (rootNvmId, hash) pair is already present.
* Uses FIFO ring overwrite when full.
* @param server The server context.
+ * @param rootNvmId NVM ID of the trusted root that anchored the verify.
* @param hash Pointer to a SHA-256 (32-byte) digest of the DER cert.
*/
void wh_Server_CertVerifyCache_Insert(struct whServerContext_t* server,
- const uint8_t* hash);
+ whNvmId rootNvmId, const uint8_t* hash);
/**
* @brief Clear all entries from the verify cache.
From 46df6856046d5c704d55a86425f0f8934f2aab86 Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Wed, 6 May 2026 11:30:12 -0600
Subject: [PATCH 5/7] add global trusted cert cache option
---
src/wh_nvm.c | 46 ++++++++++
src/wh_server_cert.c | 148 ++++++++++++++++++++++++++++-----
test/Makefile | 7 ++
test/wh_test_cert.c | 116 ++++++++++++++++++++++++++
wolfhsm/wh_nvm.h | 28 +++++++
wolfhsm/wh_server_cert_cache.h | 21 ++++-
6 files changed, 343 insertions(+), 23 deletions(-)
diff --git a/src/wh_nvm.c b/src/wh_nvm.c
index 371e4e79c..ec78909b7 100644
--- a/src/wh_nvm.c
+++ b/src/wh_nvm.c
@@ -104,13 +104,36 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
memset(&context->globalCache, 0, sizeof(context->globalCache));
#endif
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ /* Initialize the global cert verify cache */
+ memset(&context->globalCertVerifyCache, 0,
+ sizeof(context->globalCertVerifyCache));
+#endif
+
#ifdef WOLFHSM_CFG_THREADSAFE
/* Initialize lock (NULL lockConfig = no-op locking) */
rc = wh_Lock_Init(&context->lock, config->lockConfig);
if (rc != WH_ERROR_OK) {
return rc;
}
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ /* Initialize the global cert verify cache lock. Distinct lock from the
+ * NVM lock so cert-cache traffic and NVM I/O don't serialize each other.
+ * NULL config => no-op locking, same as the NVM lock above. */
+ rc = wh_Lock_Init(&context->globalCertVerifyCache.lock,
+ config->certVerifyCacheLockConfig);
+ if (rc != WH_ERROR_OK) {
+ (void)wh_Lock_Cleanup(&context->lock);
+ return rc;
+ }
#endif
+#endif /* WOLFHSM_CFG_THREADSAFE */
if (context->cb != NULL && context->cb->Init != NULL) {
rc = context->cb->Init(context->context, config->config);
@@ -118,6 +141,12 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
context->cb = NULL;
context->context = NULL;
#ifdef WOLFHSM_CFG_THREADSAFE
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ (void)wh_Lock_Cleanup(&context->globalCertVerifyCache.lock);
+#endif
(void)wh_Lock_Cleanup(&context->lock);
#endif
}
@@ -140,6 +169,17 @@ int wh_Nvm_Cleanup(whNvmContext* context)
memset(&context->globalCache, 0, sizeof(context->globalCache));
#endif
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ /* Clear cache slots/writeIdx but keep the embedded lock intact until its
+ * own cleanup below. */
+ memset(context->globalCertVerifyCache.slots, 0,
+ sizeof(context->globalCertVerifyCache.slots));
+ context->globalCertVerifyCache.writeIdx = 0;
+#endif
+
/* No callback? Return ABORTED */
if (context->cb->Cleanup == NULL) {
rc = WH_ERROR_ABORTED;
@@ -149,6 +189,12 @@ int wh_Nvm_Cleanup(whNvmContext* context)
}
#ifdef WOLFHSM_CFG_THREADSAFE
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ (void)wh_Lock_Cleanup(&context->globalCertVerifyCache.lock);
+#endif
(void)wh_Lock_Cleanup(&context->lock);
#endif
diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c
index f1518be15..1b13d0584 100644
--- a/src/wh_server_cert.c
+++ b/src/wh_server_cert.c
@@ -46,15 +46,55 @@
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
-int wh_Server_CertVerifyCache_Lookup(whServerContext* server, whNvmId rootNvmId,
- const uint8_t* hash)
+/* Resolve the verify cache for this server. In per-client mode the cache
+ * lives on the server context; in global mode it lives on the shared NVM
+ * context. Returns NULL if either pointer is missing. */
+static whCertVerifyCacheContext* _GetVerifyCache(whServerContext* server)
{
- int i;
- if ((server == NULL) || (hash == NULL)) {
- return WH_ERROR_BADARGS;
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
+ if ((server == NULL) || (server->nvm == NULL)) {
+ return NULL;
+ }
+ return &server->nvm->globalCertVerifyCache;
+#else
+ if (server == NULL) {
+ return NULL;
}
+ return &server->cert.cache;
+#endif
+}
+
+/* Lock helpers compile to no-ops when the cache has no embedded lock (i.e.
+ * outside global+threadsafe builds). */
+static int _LockVerifyCache(whCertVerifyCacheContext* cache)
+{
+#if defined(WOLFHSM_CFG_THREADSAFE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ return wh_Lock_Acquire(&cache->lock);
+#else
+ (void)cache;
+ return WH_ERROR_OK;
+#endif
+}
+
+static int _UnlockVerifyCache(whCertVerifyCacheContext* cache)
+{
+#if defined(WOLFHSM_CFG_THREADSAFE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ return wh_Lock_Release(&cache->lock);
+#else
+ (void)cache;
+ return WH_ERROR_OK;
+#endif
+}
+
+/* Internal slot scan, must be called with the cache lock held. */
+static int _LookupUnlocked(const whCertVerifyCacheContext* cache,
+ whNvmId rootNvmId, const uint8_t* hash)
+{
+ int i;
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
- const whCertVerifyCacheSlot* slot = &server->cert.cache.slots[i];
+ const whCertVerifyCacheSlot* slot = &cache->slots[i];
if (slot->committed && (slot->rootNvmId == rootNvmId) &&
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0)) {
return WH_ERROR_OK;
@@ -63,36 +103,87 @@ int wh_Server_CertVerifyCache_Lookup(whServerContext* server, whNvmId rootNvmId,
return WH_ERROR_NOTFOUND;
}
+int wh_Server_CertVerifyCache_Lookup(whServerContext* server, whNvmId rootNvmId,
+ const uint8_t* hash)
+{
+ whCertVerifyCacheContext* cache;
+ int rc;
+ int found;
+
+ if ((server == NULL) || (hash == NULL)) {
+ return WH_ERROR_BADARGS;
+ }
+ cache = _GetVerifyCache(server);
+ if (cache == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ rc = _LockVerifyCache(cache);
+ if (rc != WH_ERROR_OK) {
+ return rc;
+ }
+ found = _LookupUnlocked(cache, rootNvmId, hash);
+ (void)_UnlockVerifyCache(cache);
+ return found;
+}
+
void wh_Server_CertVerifyCache_Insert(whServerContext* server,
whNvmId rootNvmId, const uint8_t* hash)
{
- whCertVerifyCacheSlot* slot;
- uint16_t idx;
+ whCertVerifyCacheContext* cache;
+ whCertVerifyCacheSlot* slot;
+ uint16_t idx;
+ int rc;
if ((server == NULL) || (hash == NULL)) {
return;
}
- /* Skip if already present under this root */
- if (wh_Server_CertVerifyCache_Lookup(server, rootNvmId, hash) ==
- WH_ERROR_OK) {
+ cache = _GetVerifyCache(server);
+ if (cache == NULL) {
return;
}
- /* FIFO ring overwrite */
- idx = server->cert.cache.writeIdx;
- slot = &server->cert.cache.slots[idx];
- slot->rootNvmId = rootNvmId;
- memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
- slot->committed = 1;
- server->cert.cache.writeIdx =
- (uint16_t)((idx + 1) % WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT);
+
+ rc = _LockVerifyCache(cache);
+ if (rc != WH_ERROR_OK) {
+ return;
+ }
+ /* Dedup under the lock so concurrent inserts of the same hash collapse to
+ * a single slot. */
+ if (_LookupUnlocked(cache, rootNvmId, hash) != WH_ERROR_OK) {
+ idx = cache->writeIdx;
+ slot = &cache->slots[idx];
+ slot->rootNvmId = rootNvmId;
+ memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
+ slot->committed = 1;
+ cache->writeIdx =
+ (uint16_t)((idx + 1) % WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT);
+ }
+ (void)_UnlockVerifyCache(cache);
}
void wh_Server_CertVerifyCache_Clear(whServerContext* server)
{
+ whCertVerifyCacheContext* cache;
+ int rc;
+
if (server == NULL) {
return;
}
- memset(&server->cert.cache, 0, sizeof(server->cert.cache));
+ cache = _GetVerifyCache(server);
+ if (cache == NULL) {
+ return;
+ }
+
+ rc = _LockVerifyCache(cache);
+ if (rc != WH_ERROR_OK) {
+ return;
+ }
+ /* Clear payload only; the embedded lock (when present) must survive a
+ * Clear, otherwise the next operation would acquire an uninitialized
+ * lock. */
+ memset(cache->slots, 0, sizeof(cache->slots));
+ cache->writeIdx = 0;
+ (void)_UnlockVerifyCache(cache);
}
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
@@ -289,7 +380,12 @@ int wh_Server_CertInit(whServerContext* server)
#ifdef DEBUG_WOLFSSL
wolfSSL_Debugging_ON();
#endif
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ !defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ /* Per-client cache is owned by the server context and zeroed on each
+ * server init. Under _GLOBAL the cache lives in the NVM context and is
+ * initialized exactly once in wh_Nvm_Init — clearing it here would wipe
+ * entries populated by other clients. */
if (server != NULL) {
wh_Server_CertVerifyCache_Clear(server);
}
@@ -689,11 +785,21 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
case WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_CLEAR: {
whMessageCert_SimpleResponse resp = {0};
+#ifndef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
+ /* Per-client cache piggybacks on the NVM lock for serialization.
+ * Under _GLOBAL the cache has its own lock acquired internally by
+ * wh_Server_CertVerifyCache_Clear, so the NVM lock isn't needed
+ * (and acquiring it would needlessly block NVM I/O on cache
+ * clears). */
rc = WH_SERVER_NVM_LOCK(server);
if (rc == WH_ERROR_OK) {
wh_Server_CertVerifyCache_Clear(server);
(void)WH_SERVER_NVM_UNLOCK(server);
}
+#else
+ wh_Server_CertVerifyCache_Clear(server);
+ rc = WH_ERROR_OK;
+#endif
resp.rc = rc;
wh_MessageCert_TranslateSimpleResponse(
diff --git a/test/Makefile b/test/Makefile
index 8b8d15ba4..4274e16f6 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -170,6 +170,13 @@ ifeq ($(CERT_VERIFY_CACHE),1)
DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
endif
+# Use the cross-client (global) variant of the trusted-cert verify cache.
+# Implies CERT_VERIFY_CACHE.
+ifeq ($(CERT_VERIFY_CACHE_GLOBAL),1)
+ DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+ DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
+endif
+
## Project defines
# Option to build wolfcrypt tests
ifeq ($(TESTWOLFCRYPT),1)
diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c
index 4d817fea3..de9e90f45 100644
--- a/test/wh_test_cert.c
+++ b/test/wh_test_cert.c
@@ -202,6 +202,112 @@ static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
}
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+/* Counts callback invocations to detect cross-client cache hits. The verify
+ * callback fires only on a cold verify; a global-cache hit short-circuits the
+ * wolfSSL verify path and bypasses the callback. Two server contexts that
+ * share the same NVM context must share the cache, so the second context's
+ * verify of an already-cached chain must NOT increment this counter. */
+static int s_globalCacheCb_count = 0;
+static int whTest_globalCacheVerifyCb(int preverify,
+ WOLFSSL_X509_STORE_CTX* store)
+{
+ (void)store;
+ s_globalCacheCb_count++;
+ /* Mirror wolfSSL's verdict so cross-root verifies still fail. Returning a
+ * hard 1 would mask signature mismatches and break the cross-root
+ * regression check below. */
+ return preverify;
+}
+
+/* Cross-client cache hit test. Two whServerContext instances, both backed by
+ * the single whNvmContext owned by the test driver, must share the trusted
+ * cert verify cache: a chain verified on serverA must short-circuit when
+ * verified again on serverB. */
+static int whTest_CertServerVerifyCacheGlobalShared(whServerConfig* serverCfg)
+{
+ whServerContext serverA[1] = {0};
+ whServerContext serverB[1] = {0};
+ const whNvmId rootCertA = 1;
+ const whNvmId rootCertB = 2;
+ int beforeCount;
+
+ WH_TEST_PRINT(
+ "=== Server cert verify-cache global cross-client test ===\n");
+
+ /* Two independent server contexts, both pointing at the same NVM
+ * context via serverCfg. The cache lives on the NVM context in global
+ * mode, so both servers see the same slots. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(serverA, serverCfg));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(serverA));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_Init(serverB, serverCfg));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(serverB));
+
+ /* Register the same counting callback on both servers so we can detect
+ * which verify path actually executed wolfSSL's signature check vs.
+ * which one short-circuited via the global cache. */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Server_CertSetVerifyCb(serverA, whTest_globalCacheVerifyCb));
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Server_CertSetVerifyCb(serverB, whTest_globalCacheVerifyCb));
+
+ /* Trust both roots so the cross-root regression below has somewhere to
+ * land. */
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ serverA, rootCertA, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_A_CERT, ROOT_A_CERT_len));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
+ serverA, rootCertB, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
+ 0, ROOT_B_CERT, ROOT_B_CERT_len));
+
+ /* Make sure we start cold even if a prior test populated the global
+ * cache. wh_Server_CertInit no longer clears under _GLOBAL. */
+ wh_Server_CertVerifyCache_Clear(serverA);
+
+ /* 1. Cold verify on A populates the global cache. */
+ s_globalCacheCb_count = 0;
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ serverA, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_globalCacheCb_count > 0);
+
+ /* 2. Same chain re-verified on B must hit the cache populated by A —
+ * the callback must NOT fire. */
+ beforeCount = s_globalCacheCb_count;
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ serverB, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_globalCacheCb_count == beforeCount);
+
+ /* 3. Cross-root: chain A under rootB must still fail on B even though
+ * chain A was cached under rootA. The cache is keyed on (root, hash);
+ * a hit under one root must not satisfy a verify under another. */
+ WH_TEST_ASSERT_RETURN(WH_ERROR_CERT_VERIFY ==
+ wh_Server_CertVerify(serverB, RAW_CERT_CHAIN_A,
+ RAW_CERT_CHAIN_A_len, rootCertB,
+ WH_CERT_FLAGS_NONE,
+ WH_NVM_FLAGS_USAGE_ANY, NULL));
+
+ /* 4. Clear via serverA wipes the shared cache; serverB now cold-verifies
+ * again and the callback fires. */
+ wh_Server_CertVerifyCache_Clear(serverA);
+ beforeCount = s_globalCacheCb_count;
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
+ serverB, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
+ WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
+ WH_TEST_ASSERT_RETURN(s_globalCacheCb_count > beforeCount);
+
+ /* Reset cache so subsequent tests in the driver get a clean slate. */
+ wh_Server_CertVerifyCache_Clear(serverA);
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(serverA, rootCertA));
+ WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(serverA, rootCertB));
+ WH_TEST_PRINT("Server cert verify-cache global cross-client test PASSED\n");
+ return WH_ERROR_OK;
+}
+#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE && \
+ WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL */
+
/* State for the user-injectable verify callback test */
static int s_verifyCb_count = 0;
static int s_verifyCb_lastPreverify = -1;
@@ -929,6 +1035,16 @@ int whTest_CertRamSim(whTestNvmBackendType nvmType)
WH_ERROR_PRINT("Cert verify-cache tests failed: %d\n", rc);
}
}
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
+ if (rc == WH_ERROR_OK) {
+ rc = whTest_CertServerVerifyCacheGlobalShared(s_conf);
+ if (rc != WH_ERROR_OK) {
+ WH_ERROR_PRINT("Cert verify-cache global cross-client tests "
+ "failed: %d\n",
+ rc);
+ }
+ }
+#endif
#endif
if (rc == WH_ERROR_OK) {
diff --git a/wolfhsm/wh_nvm.h b/wolfhsm/wh_nvm.h
index e41f7f7c0..6b1e71beb 100644
--- a/wolfhsm/wh_nvm.h
+++ b/wolfhsm/wh_nvm.h
@@ -60,6 +60,12 @@
#include "wolfhsm/wh_common.h" /* For whNvm types */
#include "wolfhsm/wh_keycache.h" /* For whKeyCacheContext */
#include "wolfhsm/wh_lock.h"
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#include "wolfhsm/wh_server_cert_cache.h" /* For whCertVerifyCacheContext */
+#endif
/**
* @brief NVM backend callback table.
@@ -137,6 +143,15 @@ typedef struct whNvmContext_t {
#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_GLOBAL_KEYS)
whKeyCacheContext globalCache; /**< Global key cache (shared keys) */
#endif
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ whCertVerifyCacheContext globalCertVerifyCache; /**< Global cross-client
+ * trusted cert verify
+ * cache. Carries its own
+ * dedicated lock. */
+#endif
#ifdef WOLFHSM_CFG_THREADSAFE
whLock lock; /**< Lock for serializing NVM and global cache operations */
#endif
@@ -154,6 +169,19 @@ typedef struct whNvmConfig_t {
#ifdef WOLFHSM_CFG_THREADSAFE
whLockConfig*
lockConfig; /**< Lock configuration (NULL for no-op locking) */
+#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
+ !defined(WOLFHSM_CFG_NO_CRYPTO) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ whLockConfig* certVerifyCacheLockConfig; /**< Lock config for the global
+ * cert verify cache. Independent
+ * from lockConfig — pass a
+ * separate platform context (e.g.
+ * a distinct posixLockContext) so
+ * the two locks back distinct
+ * mutexes. NULL for no-op
+ * locking. */
+#endif
#endif
} whNvmConfig;
diff --git a/wolfhsm/wh_server_cert_cache.h b/wolfhsm/wh_server_cert_cache.h
index af18a017d..48799dfe2 100644
--- a/wolfhsm/wh_server_cert_cache.h
+++ b/wolfhsm/wh_server_cert_cache.h
@@ -47,6 +47,7 @@
#include
#include "wolfhsm/wh_common.h" /* for whNvmId */
+#include "wolfhsm/wh_lock.h" /* for whLock (global cache lock) */
#include "wolfssl/ssl.h" /* for VerifyCallback */
@@ -69,6 +70,13 @@ typedef struct whCertVerifyCacheContext {
whCertVerifyCacheSlot slots[WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT];
uint16_t writeIdx; /* FIFO ring write position */
uint8_t WH_PAD[6];
+#if defined(WOLFHSM_CFG_THREADSAFE) && \
+ defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+ /* Dedicated lock for the global verify cache. Independent from the NVM
+ * lock so cert-cache operations do not serialize behind NVM I/O. Only
+ * present when the cache lives in the shared NVM context. */
+ whLock lock;
+#endif
} whCertVerifyCacheContext;
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
@@ -81,10 +89,14 @@ typedef struct {
} whServerCertConfig;
/* Per-server cert subsystem context, embedded by value in whServerContext.
- * Holds the registered verify callback and (optionally) the verify cache. */
+ * Holds the registered verify callback and (optionally) the per-client verify
+ * cache. When WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL is defined the cache
+ * is relocated into the shared whNvmContext, so the per-client copy is
+ * omitted. */
typedef struct {
VerifyCallback verifyCb;
-#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ !defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
whCertVerifyCacheContext cache;
#endif
} whServerCertContext;
@@ -118,6 +130,11 @@ void wh_Server_CertVerifyCache_Insert(struct whServerContext_t* server,
/**
* @brief Clear all entries from the verify cache.
+ *
+ * In per-client mode (default) clears this server's cache only. In global
+ * mode (WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL) clears the shared
+ * cache for every connected client.
+ *
* @param server The server context.
*/
void wh_Server_CertVerifyCache_Clear(struct whServerContext_t* server);
From b2768396b511c0502cc4de62df0b7db12f7bcda2 Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Wed, 6 May 2026 13:04:18 -0600
Subject: [PATCH 6/7] restructure wh_settings.h macros and fix padding check to
use sentinel macro
---
src/wh_nvm.c | 25 +++--------
test/Makefile | 9 +++-
test/wh_test_check_struct_padding.c | 8 +++-
wolfhsm/wh_message_cert.h | 1 -
wolfhsm/wh_message_nvm.h | 1 -
wolfhsm/wh_nvm.h | 15 ++-----
wolfhsm/wh_settings.h | 68 ++++++++++++++++++++++-------
7 files changed, 73 insertions(+), 54 deletions(-)
diff --git a/src/wh_nvm.c b/src/wh_nvm.c
index ec78909b7..59557f0ac 100644
--- a/src/wh_nvm.c
+++ b/src/wh_nvm.c
@@ -104,10 +104,7 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
memset(&context->globalCache, 0, sizeof(context->globalCache));
#endif
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
/* Initialize the global cert verify cache */
memset(&context->globalCertVerifyCache, 0,
sizeof(context->globalCertVerifyCache));
@@ -119,10 +116,7 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
if (rc != WH_ERROR_OK) {
return rc;
}
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
/* Initialize the global cert verify cache lock. Distinct lock from the
* NVM lock so cert-cache traffic and NVM I/O don't serialize each other.
* NULL config => no-op locking, same as the NVM lock above. */
@@ -141,10 +135,7 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
context->cb = NULL;
context->context = NULL;
#ifdef WOLFHSM_CFG_THREADSAFE
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
(void)wh_Lock_Cleanup(&context->globalCertVerifyCache.lock);
#endif
(void)wh_Lock_Cleanup(&context->lock);
@@ -169,10 +160,7 @@ int wh_Nvm_Cleanup(whNvmContext* context)
memset(&context->globalCache, 0, sizeof(context->globalCache));
#endif
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
/* Clear cache slots/writeIdx but keep the embedded lock intact until its
* own cleanup below. */
memset(context->globalCertVerifyCache.slots, 0,
@@ -189,10 +177,7 @@ int wh_Nvm_Cleanup(whNvmContext* context)
}
#ifdef WOLFHSM_CFG_THREADSAFE
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
(void)wh_Lock_Cleanup(&context->globalCertVerifyCache.lock);
#endif
(void)wh_Lock_Cleanup(&context->lock);
diff --git a/test/Makefile b/test/Makefile
index 4274e16f6..ddfa78418 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -307,8 +307,13 @@ $(BUILD_DIR)/%.o: %.s
@echo "Compiling ASM file: $(notdir $<)"
$(CMD_ECHO) $(AS) $(ASFLAGS) $(DEF) $(INC) -c -o $@ $<
-# Add additional flag here to avoid pragma
-$(BUILD_DIR)/wh_test_check_struct_padding.o: CFLAGS+=-Wpadded -DWOLFHSM_CFG_NO_CRYPTO
+# Wire-format struct-padding audit. -Wpadded turns spurious padding into a
+# build error. WH_PADDING_CHECK is an internal sentinel honored by
+# wh_settings.h that suppresses external dependencies (wolfSSL etc.) so the
+# audit doesn't drag in third-party source whose layout could perturb the
+# result. Distinct from WOLFHSM_CFG_NO_CRYPTO so it doesn't disable user
+# features (e.g. the cert verify cache).
+$(BUILD_DIR)/wh_test_check_struct_padding.o: CFLAGS+=-Wpadded -DWH_PADDING_CHECK
$(BUILD_DIR)/%.o: %.c
@echo "Compiling C file: $(notdir $<)"
diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c
index dfc6d4913..0ff6e5576 100644
--- a/test/wh_test_check_struct_padding.c
+++ b/test/wh_test_check_struct_padding.c
@@ -86,7 +86,11 @@ whMessageKeystore_CacheDmaResponse keyCacheDmaRes;
whMessageKeystore_ExportDmaRequest keyExportDmaReq;
whMessageKeystore_ExportDmaResponse keyExportDmaRes;
-#ifndef WOLFHSM_CFG_NO_CRYPTO
+/* Crypto message structs are excluded from the audit because some of them
+ * have known padding issues that are out of scope to fix here. The previous
+ * scheme suppressed them via WOLFHSM_CFG_NO_CRYPTO; the WH_PADDING_CHECK
+ * sentinel preserves that exclusion without disturbing user-facing config. */
+#if !defined(WOLFHSM_CFG_NO_CRYPTO) && !defined(WH_PADDING_CHECK)
/* Include crypto message header for new crypto message structures */
#include "wolfhsm/wh_message_crypto.h"
whMessageCrypto_GenericRequestHeader cryptoGenericReqHeader;
@@ -136,7 +140,7 @@ whMessageCrypto_CmacAesDmaRequest cmacDmaReq;
whMessageCrypto_CmacAesDmaResponse cmacDmaRes;
#endif /* WOLFHSM_CFG_DMA */
-#endif /* !WOLFHSM_CFG_NO_CRYPTO */
+#endif /* !WOLFHSM_CFG_NO_CRYPTO && !WH_PADDING_CHECK */
#ifdef WOLFHSM_CFG_SHE_EXTENSION
/* Include SHE message header for SHE message structures */
diff --git a/wolfhsm/wh_message_cert.h b/wolfhsm/wh_message_cert.h
index 5c376a07f..a887ea454 100644
--- a/wolfhsm/wh_message_cert.h
+++ b/wolfhsm/wh_message_cert.h
@@ -31,7 +31,6 @@
#include "wolfhsm/wh_common.h"
#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_message.h"
-#include "wolfhsm/wh_nvm.h"
enum WH_MESSAGE_CERT_ACTION_ENUM {
WH_MESSAGE_CERT_ACTION_INIT = 0x1,
diff --git a/wolfhsm/wh_message_nvm.h b/wolfhsm/wh_message_nvm.h
index bf5a706fa..023158dd4 100644
--- a/wolfhsm/wh_message_nvm.h
+++ b/wolfhsm/wh_message_nvm.h
@@ -32,7 +32,6 @@
#include "wolfhsm/wh_common.h"
#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_message.h"
-#include "wolfhsm/wh_nvm.h"
enum WH_MESSAGE_NVM_ACTION_ENUM {
WH_MESSAGE_NVM_ACTION_INIT = 0x1,
diff --git a/wolfhsm/wh_nvm.h b/wolfhsm/wh_nvm.h
index 6b1e71beb..91506f148 100644
--- a/wolfhsm/wh_nvm.h
+++ b/wolfhsm/wh_nvm.h
@@ -60,10 +60,7 @@
#include "wolfhsm/wh_common.h" /* For whNvm types */
#include "wolfhsm/wh_keycache.h" /* For whKeyCacheContext */
#include "wolfhsm/wh_lock.h"
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
#include "wolfhsm/wh_server_cert_cache.h" /* For whCertVerifyCacheContext */
#endif
@@ -143,10 +140,7 @@ typedef struct whNvmContext_t {
#if !defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_GLOBAL_KEYS)
whKeyCacheContext globalCache; /**< Global key cache (shared keys) */
#endif
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
whCertVerifyCacheContext globalCertVerifyCache; /**< Global cross-client
* trusted cert verify
* cache. Carries its own
@@ -169,10 +163,7 @@ typedef struct whNvmConfig_t {
#ifdef WOLFHSM_CFG_THREADSAFE
whLockConfig*
lockConfig; /**< Lock configuration (NULL for no-op locking) */
-#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) && \
- !defined(WOLFHSM_CFG_NO_CRYPTO) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
- defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
+#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
whLockConfig* certVerifyCacheLockConfig; /**< Lock config for the global
* cert verify cache. Independent
* from lockConfig — pass a
diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h
index e9e54be88..321e854b6 100644
--- a/wolfhsm/wh_settings.h
+++ b/wolfhsm/wh_settings.h
@@ -162,7 +162,13 @@
#include
-#ifndef WOLFHSM_CFG_NO_CRYPTO
+/* WH_PADDING_CHECK is an internal sentinel set only by the wire-format
+ * struct-padding audit (test/wh_test_check_struct_padding.c). It suppresses
+ * external dependencies (wolfSSL headers, etc.) so that audit can compile
+ * without dragging in third-party source whose layout could perturb -Wpadded.
+ * It is NOT a public configuration flag — do not use it from application
+ * code. */
+#if !defined(WOLFHSM_CFG_NO_CRYPTO) && !defined(WH_PADDING_CHECK)
#ifdef WOLFSSL_USER_SETTINGS
#include "user_settings.h"
#else
@@ -174,7 +180,7 @@
#if defined(WOLFHSM_CFG_DEBUG) || defined(WOLFHSM_CFG_DEBUG_VERBOSE)
#define WOLFHSM_CFG_HEXDUMP
#endif
-#endif /* !WOLFHSM_CFG_NO_CRYPTO */
+#endif /* !WOLFHSM_CFG_NO_CRYPTO && !WH_PADDING_CHECK */
/* Platform system time access */
#if !defined WOLFHSM_CFG_NO_SYS_TIME && !defined(WOLFHSM_CFG_PORT_GETTIME)
@@ -373,7 +379,9 @@
#endif
/** Configuration checks */
-#ifndef WOLFHSM_CFG_NO_CRYPTO
+/* Skipped under WH_PADDING_CHECK because the wolfSSL feature macros
+ * referenced below are only defined when wolfssl/options.h is pulled. */
+#if !defined(WOLFHSM_CFG_NO_CRYPTO) && !defined(WH_PADDING_CHECK)
/* Crypto Cb is mandatory */
#ifndef WOLF_CRYPTO_CB
#error "wolfHSM requires wolfCrypt built with WOLF_CRYPTO_CB"
@@ -424,27 +432,55 @@
#endif /* !WOLFSSL_ACERT || !WOLFSSL_ASN_TEMPLATE */
#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT */
-#endif /* !WOLFHSM_CFG_NO_CRYPTO */
+#endif /* !WOLFHSM_CFG_NO_CRYPTO && !WH_PADDING_CHECK */
#if defined(WOLFHSM_CFG_NO_CRYPTO) && defined(WOLFHSM_CFG_KEYWRAP)
#error "WOLFHSM_CFG_KEYWRAP is incompatible with WOLFHSM_CFG_NO_CRYPTO"
#endif
+/* Trusted cert verify cache requires the certificate manager and crypto.
+ * Enforce here so downstream code can gate on
+ * WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE alone instead of repeating the full
+ * dependency chain at every site. */
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ !defined(WOLFHSM_CFG_CERTIFICATE_MANAGER)
+#error \
+ "WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE requires WOLFHSM_CFG_CERTIFICATE_MANAGER"
+#endif
+
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
+ defined(WOLFHSM_CFG_NO_CRYPTO)
+#error \
+ "WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE is incompatible with WOLFHSM_CFG_NO_CRYPTO"
+#endif
+
+/* The global cross-client verify cache is a layered option on top of the
+ * per-client cache. Enforce the dependency so downstream code can gate on
+ * WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL alone. */
+#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL) && \
+ !defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE)
+#error \
+ "WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL requires WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE"
+#endif
+
/** Cache flushing and memory fencing synchronization primitives */
/* Create a full sequential memory fence to ensure compiler memory ordering */
#ifndef XMEMFENCE
- #ifndef WOLFHSM_CFG_NO_CRYPTO
- #include "wolfssl/wolfcrypt/wc_port.h"
- #define XMEMFENCE() XFENCE()
- #else
- #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__)
- #include
- #define XMEMFENCE() atomic_thread_fence(memory_order_seq_cst)
- #elif defined(__GNUC__) || defined(__clang__)
- #define XMEMFENCE() __atomic_thread_fence(__ATOMIC_SEQ_CST)
- #else
- /* PPC32: __asm__ volatile ("sync" : : : "memory") */
- #define XMEMFENCE() do { } while (0)
+#if !defined(WOLFHSM_CFG_NO_CRYPTO) && !defined(WH_PADDING_CHECK)
+#include "wolfssl/wolfcrypt/wc_port.h"
+#define XMEMFENCE() XFENCE()
+#else
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \
+ !defined(__STDC_NO_ATOMICS__)
+#include
+#define XMEMFENCE() atomic_thread_fence(memory_order_seq_cst)
+#elif defined(__GNUC__) || defined(__clang__)
+#define XMEMFENCE() __atomic_thread_fence(__ATOMIC_SEQ_CST)
+#else
+/* PPC32: __asm__ volatile ("sync" : : : "memory") */
+#define XMEMFENCE() \
+ do { \
+ } while (0)
#warning "wolfHSM memory transports should have a functional XMEMFENCE"
#endif
#endif
From fe5cdfe4febd851621e9a73c4e7dd39749292e07 Mon Sep 17 00:00:00 2001
From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com>
Date: Wed, 6 May 2026 13:47:55 -0600
Subject: [PATCH 7/7] fix crypto message struct ordering and add coverage in
padding check
---
test/wh_test_check_struct_padding.c | 8 ++------
wolfhsm/wh_message_crypto.h | 17 +++++++++++------
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c
index 0ff6e5576..dfc6d4913 100644
--- a/test/wh_test_check_struct_padding.c
+++ b/test/wh_test_check_struct_padding.c
@@ -86,11 +86,7 @@ whMessageKeystore_CacheDmaResponse keyCacheDmaRes;
whMessageKeystore_ExportDmaRequest keyExportDmaReq;
whMessageKeystore_ExportDmaResponse keyExportDmaRes;
-/* Crypto message structs are excluded from the audit because some of them
- * have known padding issues that are out of scope to fix here. The previous
- * scheme suppressed them via WOLFHSM_CFG_NO_CRYPTO; the WH_PADDING_CHECK
- * sentinel preserves that exclusion without disturbing user-facing config. */
-#if !defined(WOLFHSM_CFG_NO_CRYPTO) && !defined(WH_PADDING_CHECK)
+#ifndef WOLFHSM_CFG_NO_CRYPTO
/* Include crypto message header for new crypto message structures */
#include "wolfhsm/wh_message_crypto.h"
whMessageCrypto_GenericRequestHeader cryptoGenericReqHeader;
@@ -140,7 +136,7 @@ whMessageCrypto_CmacAesDmaRequest cmacDmaReq;
whMessageCrypto_CmacAesDmaResponse cmacDmaRes;
#endif /* WOLFHSM_CFG_DMA */
-#endif /* !WOLFHSM_CFG_NO_CRYPTO && !WH_PADDING_CHECK */
+#endif /* !WOLFHSM_CFG_NO_CRYPTO */
#ifdef WOLFHSM_CFG_SHE_EXTENSION
/* Include SHE message header for SHE message structures */
diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h
index ac3417743..a5a3ef8da 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -149,14 +149,14 @@ int wh_MessageCrypto_TranslateRngResponse(
/*
* AES
*/
-/* AES CTR Request */
+/* AES CTR Request - fields ordered by size to keep padding trailing */
typedef struct {
uint32_t enc; /* 1 for encrypt, 0 for decrypt */
uint32_t keyLen; /* Length of key in bytes */
uint32_t sz; /* Size of input data */
- uint16_t keyId; /* Key ID if using stored key */
uint32_t left; /* unused bytes left from last call */
- uint8_t WH_PAD[2]; /* Padding for alignment */
+ uint16_t keyId; /* Key ID if using stored key */
+ uint8_t WH_PAD[2];
/* Data follows:
* uint8_t in[sz]
* uint8_t key[keyLen]
@@ -1068,13 +1068,16 @@ typedef struct {
uint32_t inSz;
} whMessageCrypto_Sha512DmaRequest;
-/* SHA2 DMA Response - carries updated state or final hash inline */
+/* SHA2 DMA Response - carries updated state or final hash inline.
+ * Fields ordered by size (8-byte-aligned struct first) to keep padding
+ * trailing. */
typedef struct {
+ whMessageCrypto_DmaAddrStatus
+ dmaAddrStatus; /* 8-byte aligned, place first */
+ uint8_t hash[64]; /* big enough for all SHA2 variants */
uint32_t hiLen;
uint32_t loLen;
- uint8_t hash[64]; /* big enough for all SHA2 variants */
uint32_t hashType;
- whMessageCrypto_DmaAddrStatus dmaAddrStatus;
uint8_t WH_PAD[4];
} whMessageCrypto_Sha2DmaResponse;
@@ -1138,6 +1141,7 @@ typedef struct {
typedef struct {
whMessageCrypto_DmaAddrStatus dmaAddrStatus;
uint32_t outSz;
+ uint8_t WH_PAD[4]; /* Round struct to 8-byte alignment */
} whMessageCrypto_AesEcbDmaResponse;
/* AES-ECB DMA translation functions */
@@ -1168,6 +1172,7 @@ typedef struct {
typedef struct {
whMessageCrypto_DmaAddrStatus dmaAddrStatus;
uint32_t outSz;
+ uint8_t WH_PAD[4]; /* Round struct to 8-byte alignment */
/* Trailing data: uint8_t iv[AES_IV_SIZE] */
} whMessageCrypto_AesCbcDmaResponse;