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;