Skip to content

Commit 1f26be7

Browse files
committed
Add trusted cert cache enable/disable client API
1 parent 1e04f97 commit 1f26be7

9 files changed

Lines changed: 406 additions & 16 deletions

File tree

src/wh_client_cert.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,77 @@ int wh_Client_CertVerifyCacheClear(whClientContext* c, int32_t* out_rc)
726726

727727
return rc;
728728
}
729+
730+
int wh_Client_CertVerifyCacheSetEnabledRequest(whClientContext* c,
731+
uint8_t enable)
732+
{
733+
whMessageCert_SetEnabledRequest* req = NULL;
734+
735+
if (c == NULL) {
736+
return WH_ERROR_BADARGS;
737+
}
738+
739+
req = (whMessageCert_SetEnabledRequest*)wh_CommClient_GetDataPtr(c->comm);
740+
if (req == NULL) {
741+
return WH_ERROR_BADARGS;
742+
}
743+
memset(req, 0, sizeof(*req));
744+
req->enable = enable ? 1 : 0;
745+
746+
return wh_Client_SendRequest(
747+
c, WH_MESSAGE_GROUP_CERT,
748+
WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_SET_ENABLED, sizeof(*req),
749+
(uint8_t*)req);
750+
}
751+
752+
int wh_Client_CertVerifyCacheSetEnabledResponse(whClientContext* c,
753+
int32_t* out_rc)
754+
{
755+
int rc;
756+
uint16_t group;
757+
uint16_t action;
758+
uint16_t size;
759+
whMessageCert_SimpleResponse resp;
760+
761+
if (c == NULL) {
762+
return WH_ERROR_BADARGS;
763+
}
764+
765+
rc = wh_Client_RecvResponse(c, &group, &action, &size, &resp);
766+
if (rc == WH_ERROR_OK) {
767+
if ((group != WH_MESSAGE_GROUP_CERT) ||
768+
(action != WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_SET_ENABLED) ||
769+
(size != sizeof(resp))) {
770+
rc = WH_ERROR_ABORTED;
771+
}
772+
else if (out_rc != NULL) {
773+
*out_rc = resp.rc;
774+
}
775+
}
776+
return rc;
777+
}
778+
779+
int wh_Client_CertVerifyCacheSetEnabled(whClientContext* c, uint8_t enable,
780+
int32_t* out_rc)
781+
{
782+
int rc = WH_ERROR_OK;
783+
784+
if (c == NULL) {
785+
return WH_ERROR_BADARGS;
786+
}
787+
788+
do {
789+
rc = wh_Client_CertVerifyCacheSetEnabledRequest(c, enable);
790+
} while (rc == WH_ERROR_NOTREADY);
791+
792+
if (rc == WH_ERROR_OK) {
793+
do {
794+
rc = wh_Client_CertVerifyCacheSetEnabledResponse(c, out_rc);
795+
} while (rc == WH_ERROR_NOTREADY);
796+
}
797+
798+
return rc;
799+
}
729800
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
730801

731802
#ifdef WOLFHSM_CFG_DMA

src/wh_message_cert.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ int wh_MessageCert_TranslateSimpleResponse(
4545
return 0;
4646
}
4747

48+
int wh_MessageCert_TranslateSetEnabledRequest(
49+
uint16_t magic, const whMessageCert_SetEnabledRequest* src,
50+
whMessageCert_SetEnabledRequest* dest)
51+
{
52+
(void)magic;
53+
if ((src == NULL) || (dest == NULL)) {
54+
return WH_ERROR_BADARGS;
55+
}
56+
dest->enable = src->enable;
57+
return 0;
58+
}
59+
4860
int wh_MessageCert_TranslateAddTrustedRequest(
4961
uint16_t magic, const whMessageCert_AddTrustedRequest* src,
5062
whMessageCert_AddTrustedRequest* dest)

src/wh_nvm.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ int wh_Nvm_Init(whNvmContext* context, const whNvmConfig* config)
105105
#endif
106106

107107
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
108-
/* Initialize the global cert verify cache */
108+
/* Initialize the global cert verify cache. Default to enabled so a fresh
109+
* NVM context preserves pre-runtime-toggle behavior; clients can disable
110+
* via wh_Client_CertVerifyCacheSetEnabled. */
109111
memset(&context->globalCertVerifyCache, 0,
110112
sizeof(context->globalCertVerifyCache));
113+
context->globalCertVerifyCache.enabled = 1;
111114
#endif
112115

113116
#ifdef WOLFHSM_CFG_THREADSAFE

src/wh_server.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config)
128128
if (config->certConfig != NULL) {
129129
server->cert.verifyCb = config->certConfig->verifyCb;
130130
}
131+
#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
132+
!defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL)
133+
/* Cache defaults to enabled so a fresh server preserves pre-runtime-toggle
134+
* behavior. Clients can disable via wh_Client_CertVerifyCacheSetEnabled. */
135+
server->cert.cache.enabled = 1;
136+
#endif
131137
#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */
132138

133139
/* Log the server startup */

src/wh_server_cert.c

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,15 @@ int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
172172
if (rc != WH_ERROR_OK) {
173173
return rc;
174174
}
175-
found = _LookupSubsetUnlocked(cache, rootNvmIds, numRoots, hash);
175+
if (!cache->enabled) {
176+
/* Runtime-disabled cache: always miss, regardless of slot contents.
177+
* Slots are cleared at disable time, so the scan would miss anyway,
178+
* but short-circuiting keeps disabled-cache verify cost predictable. */
179+
found = WH_ERROR_NOTFOUND;
180+
}
181+
else {
182+
found = _LookupSubsetUnlocked(cache, rootNvmIds, numRoots, hash);
183+
}
176184
(void)_UnlockVerifyCache(cache);
177185
return found;
178186
}
@@ -200,6 +208,13 @@ void wh_Server_CertVerifyCache_Insert(whServerContext* server,
200208
if (rc != WH_ERROR_OK) {
201209
return;
202210
}
211+
/* Runtime-disabled cache: drop the insert silently. The slot array is
212+
* already empty (cleared on disable) and stays that way until re-enable,
213+
* so dropping here preserves "no new entries while disabled". */
214+
if (!cache->enabled) {
215+
(void)_UnlockVerifyCache(cache);
216+
return;
217+
}
203218
/* Dedup on exact (set, hash) match under the lock so concurrent inserts
204219
* of the same verify collapse to a single slot. Differing-set entries
205220
* for the same hash coexist: each is an independent claim about a
@@ -244,6 +259,37 @@ void wh_Server_CertVerifyCache_Clear(whServerContext* server)
244259
(void)_UnlockVerifyCache(cache);
245260
}
246261

262+
int wh_Server_CertVerifyCache_SetEnabled(whServerContext* server,
263+
uint8_t enable)
264+
{
265+
whCertVerifyCacheContext* cache;
266+
int rc;
267+
268+
if (server == NULL) {
269+
return WH_ERROR_BADARGS;
270+
}
271+
cache = _GetVerifyCache(server);
272+
if (cache == NULL) {
273+
return WH_ERROR_BADARGS;
274+
}
275+
276+
rc = _LockVerifyCache(cache);
277+
if (rc != WH_ERROR_OK) {
278+
return rc;
279+
}
280+
/* Flush on transition to disabled so a future re-enable starts from a
281+
* clean state rather than reviving entries that pre-dated the disable.
282+
* Mirrors the payload-only clear done by wh_Server_CertVerifyCache_Clear:
283+
* the embedded lock (when present) must survive. */
284+
if (!enable) {
285+
memset(cache->slots, 0, sizeof(cache->slots));
286+
cache->writeIdx = 0;
287+
}
288+
cache->enabled = enable ? 1 : 0;
289+
(void)_UnlockVerifyCache(cache);
290+
return WH_ERROR_OK;
291+
}
292+
247293
void wh_Server_CertVerifyCache_EvictRoot(whServerContext* server,
248294
whNvmId rootNvmId)
249295
{
@@ -1066,6 +1112,35 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic,
10661112
magic, &resp, (whMessageCert_SimpleResponse*)resp_packet);
10671113
*out_resp_size = sizeof(resp);
10681114
}; break;
1115+
1116+
case WH_MESSAGE_CERT_ACTION_VERIFY_CACHE_SET_ENABLED: {
1117+
whMessageCert_SetEnabledRequest req = {0};
1118+
whMessageCert_SimpleResponse resp = {0};
1119+
1120+
if (req_size != sizeof(req)) {
1121+
resp.rc = WH_ERROR_ABORTED;
1122+
}
1123+
else {
1124+
wh_MessageCert_TranslateSetEnabledRequest(
1125+
magic, (whMessageCert_SetEnabledRequest*)req_packet, &req);
1126+
#ifndef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
1127+
/* Same locking rationale as VERIFY_CACHE_CLEAR above. */
1128+
rc = WH_SERVER_NVM_LOCK(server);
1129+
if (rc == WH_ERROR_OK) {
1130+
rc = wh_Server_CertVerifyCache_SetEnabled(server,
1131+
req.enable);
1132+
(void)WH_SERVER_NVM_UNLOCK(server);
1133+
}
1134+
#else
1135+
rc = wh_Server_CertVerifyCache_SetEnabled(server, req.enable);
1136+
#endif
1137+
resp.rc = rc;
1138+
}
1139+
1140+
wh_MessageCert_TranslateSimpleResponse(
1141+
magic, &resp, (whMessageCert_SimpleResponse*)resp_packet);
1142+
*out_resp_size = sizeof(resp);
1143+
}; break;
10691144
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
10701145

10711146
#ifdef WOLFHSM_CFG_DMA

test/wh_test_cert.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,114 @@ static int whTest_CertServerVerifyCache(whServerConfig* serverCfg)
311311
WH_TEST_PRINT("Server cert verify-cache test PASSED\n");
312312
return WH_ERROR_OK;
313313
}
314+
315+
/* Counts verify-callback invocations so we can tell a cold verify (callback
316+
* fires for every cert) from a warm verify (callback skipped on cache hits).
317+
* Used by the SetEnabled test below to confirm that disabling actually
318+
* suppresses cache hits. */
319+
static int s_setEnabledCb_count = 0;
320+
static int whTest_setEnabledVerifyCb(int preverify,
321+
WOLFSSL_X509_STORE_CTX* store)
322+
{
323+
(void)store;
324+
s_setEnabledCb_count++;
325+
return preverify;
326+
}
327+
328+
/* Exercises wh_Server_CertVerifyCache_SetEnabled:
329+
* - disable flushes existing entries and suppresses subsequent cache hits
330+
* (a re-verify after disable runs cold, callback fires the full count)
331+
* - re-enable resumes caching (a verify after re-enable populates, the next
332+
* re-verify warms — callback count drops back down)
333+
* - default state is enabled (covered implicitly by the cold/warm counts) */
334+
static int whTest_CertServerVerifyCacheSetEnabled(whServerConfig* serverCfg)
335+
{
336+
whServerContext server[1] = {0};
337+
whServerCertConfig certCfg = {.verifyCb = whTest_setEnabledVerifyCb};
338+
whServerCertConfig* savedCertConfig;
339+
const whNvmId rootCertA = 1;
340+
int coldCount;
341+
int warmCount;
342+
int afterDisableCount;
343+
344+
WH_TEST_PRINT("=== Server cert verify-cache set-enabled test ===\n");
345+
346+
/* Inject the counting callback so we can detect cache hits by absence of
347+
* callback fires. Restore on exit. */
348+
savedCertConfig = serverCfg->certConfig;
349+
serverCfg->certConfig = &certCfg;
350+
351+
WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, serverCfg));
352+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server));
353+
354+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted(
355+
server, rootCertA, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONMODIFIABLE, NULL,
356+
0, ROOT_A_CERT, ROOT_A_CERT_len));
357+
358+
/* Start cold under the global-shared cache mode where prior tests may
359+
* have populated entries. Per-client mode is already clean. */
360+
wh_Server_CertVerifyCache_Clear(server);
361+
362+
/* 1. Cold verify under default-enabled cache: callback fires for every
363+
* cert in the chain. */
364+
s_setEnabledCb_count = 0;
365+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
366+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
367+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
368+
coldCount = s_setEnabledCb_count;
369+
WH_TEST_ASSERT_RETURN(coldCount > 0);
370+
371+
/* 2. Re-verify with cache still enabled: CA cache hits skip the callback,
372+
* so the count is strictly less than cold. */
373+
s_setEnabledCb_count = 0;
374+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
375+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
376+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
377+
warmCount = s_setEnabledCb_count;
378+
WH_TEST_ASSERT_RETURN(warmCount > 0);
379+
WH_TEST_ASSERT_RETURN(warmCount < coldCount);
380+
381+
/* 3. Disable the cache. Entries from steps 1-2 must be flushed and new
382+
* inserts suppressed: the next verify should be cold again (count back
383+
* up to coldCount). */
384+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyCache_SetEnabled(server, 0));
385+
s_setEnabledCb_count = 0;
386+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
387+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
388+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
389+
afterDisableCount = s_setEnabledCb_count;
390+
WH_TEST_ASSERT_RETURN(afterDisableCount == coldCount);
391+
392+
/* 4. Second verify while still disabled — also cold (no caching). */
393+
s_setEnabledCb_count = 0;
394+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
395+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
396+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
397+
WH_TEST_ASSERT_RETURN(s_setEnabledCb_count == coldCount);
398+
399+
/* 5. Re-enable. The post-disable verify did not populate the cache, so
400+
* the first re-enabled verify is still cold (and populates). */
401+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyCache_SetEnabled(server, 1));
402+
s_setEnabledCb_count = 0;
403+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
404+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
405+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
406+
WH_TEST_ASSERT_RETURN(s_setEnabledCb_count == coldCount);
407+
408+
/* 6. Subsequent verify warms, matching step 2's warmCount. */
409+
s_setEnabledCb_count = 0;
410+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify(
411+
server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA,
412+
WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL));
413+
WH_TEST_ASSERT_RETURN(s_setEnabledCb_count == warmCount);
414+
415+
/* Clean up: leave the cache empty for downstream tests. */
416+
wh_Server_CertVerifyCache_Clear(server);
417+
WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertA));
418+
serverCfg->certConfig = savedCertConfig;
419+
WH_TEST_PRINT("Server cert verify-cache set-enabled test PASSED\n");
420+
return WH_ERROR_OK;
421+
}
314422
#endif /* WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE */
315423

316424
#if defined(WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE) && \
@@ -873,6 +981,32 @@ int whTest_CertClient(whClientContext* client)
873981
rootCertB_id_c, &out_rc));
874982
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
875983

984+
/* Exercise the SetEnabled RPC path. The cross-root-must-fail
985+
* invariant has to hold regardless of cache state, so re-run it
986+
* after disable and after re-enable to confirm the RPC neither
987+
* crashes nor breaks correctness. Behavioral observation (cache
988+
* hits vs misses) is covered by whTest_CertServerVerifyCacheSetEnabled;
989+
* here we just confirm the wire path round-trips. */
990+
WH_TEST_RETURN_ON_FAIL(
991+
wh_Client_CertVerifyCacheSetEnabled(client, 0, &out_rc));
992+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
993+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
994+
RAW_CERT_CHAIN_A_len,
995+
rootCertA_id_c, &out_rc));
996+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
997+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
998+
RAW_CERT_CHAIN_A_len,
999+
rootCertB_id_c, &out_rc));
1000+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY);
1001+
1002+
WH_TEST_RETURN_ON_FAIL(
1003+
wh_Client_CertVerifyCacheSetEnabled(client, 1, &out_rc));
1004+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
1005+
WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerify(client, RAW_CERT_CHAIN_A,
1006+
RAW_CERT_CHAIN_A_len,
1007+
rootCertA_id_c, &out_rc));
1008+
WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK);
1009+
8761010
/* Cleanup */
8771011
WH_TEST_RETURN_ON_FAIL(
8781012
wh_Client_CertEraseTrusted(client, rootCertA_id_c, &out_rc));
@@ -1394,6 +1528,13 @@ int whTest_CertRamSim(whTestNvmBackendType nvmType)
13941528
WH_ERROR_PRINT("Cert verify-cache tests failed: %d\n", rc);
13951529
}
13961530
}
1531+
if (rc == WH_ERROR_OK) {
1532+
rc = whTest_CertServerVerifyCacheSetEnabled(s_conf);
1533+
if (rc != WH_ERROR_OK) {
1534+
WH_ERROR_PRINT("Cert verify-cache set-enabled tests failed: %d\n",
1535+
rc);
1536+
}
1537+
}
13971538
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL
13981539
if (rc == WH_ERROR_OK) {
13991540
rc = whTest_CertServerVerifyCacheGlobalShared(s_conf);

0 commit comments

Comments
 (0)