Skip to content

Commit 2229ca2

Browse files
committed
refactor trusted cert cache to support multi root usage with root list subsets
1 parent 7d5fa9b commit 2229ca2

2 files changed

Lines changed: 163 additions & 144 deletions

File tree

src/wh_server_cert.c

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -88,54 +88,76 @@ static int _UnlockVerifyCache(whCertVerifyCacheContext* cache)
8888
#endif
8989
}
9090

91-
/* Internal slot scan, must be called with the cache lock held. */
92-
static int _LookupUnlocked(const whCertVerifyCacheContext* cache,
93-
whNvmId rootNvmId, const uint8_t* hash)
91+
/* Returns 1 if every element of `subset` appears in `superset`. The arrays
92+
* are unsorted but bounded by WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS, so the
93+
* O(N*M) scan is fine. */
94+
static int _IsSubsetOf(const whNvmId* subset, uint16_t subsetCount,
95+
const whNvmId* superset, uint16_t supersetCount)
96+
{
97+
uint16_t i, j;
98+
int found;
99+
for (i = 0; i < subsetCount; i++) {
100+
found = 0;
101+
for (j = 0; j < supersetCount; j++) {
102+
if (subset[i] == superset[j]) {
103+
found = 1;
104+
break;
105+
}
106+
}
107+
if (!found) {
108+
return 0;
109+
}
110+
}
111+
return 1;
112+
}
113+
114+
/* Internal slot scan, must be called with the cache lock held. Hit if any
115+
* committed slot's stored root set is a subset of the supplied root set
116+
* AND its hash matches. */
117+
static int _LookupSubsetUnlocked(const whCertVerifyCacheContext* cache,
118+
const whNvmId* rootNvmIds, uint16_t numRoots,
119+
const uint8_t* hash)
94120
{
95121
int i;
96122
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
97123
const whCertVerifyCacheSlot* slot = &cache->slots[i];
98-
if (slot->committed && (slot->rootNvmId == rootNvmId) &&
99-
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0)) {
124+
if (slot->committed &&
125+
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0) &&
126+
_IsSubsetOf(slot->rootNvmIds, slot->numRoots, rootNvmIds,
127+
numRoots)) {
100128
return WH_ERROR_OK;
101129
}
102130
}
103131
return WH_ERROR_NOTFOUND;
104132
}
105133

106-
int wh_Server_CertVerifyCache_Lookup(whServerContext* server, whNvmId rootNvmId,
107-
const uint8_t* hash)
134+
/* Internal exact-match scan for insert dedup. Two sets of equal size are
135+
* equal iff one is a subset of the other, so reuse _IsSubsetOf with a size
136+
* check rather than sorting. */
137+
static int _HasExactSlotUnlocked(const whCertVerifyCacheContext* cache,
138+
const whNvmId* rootNvmIds, uint16_t numRoots,
139+
const uint8_t* hash)
108140
{
109-
whCertVerifyCacheContext* cache;
110-
int rc;
111-
int found;
112-
113-
if ((server == NULL) || (hash == NULL)) {
114-
return WH_ERROR_BADARGS;
115-
}
116-
cache = _GetVerifyCache(server);
117-
if (cache == NULL) {
118-
return WH_ERROR_BADARGS;
119-
}
120-
121-
rc = _LockVerifyCache(cache);
122-
if (rc != WH_ERROR_OK) {
123-
return rc;
141+
int i;
142+
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
143+
const whCertVerifyCacheSlot* slot = &cache->slots[i];
144+
if (slot->committed && (slot->numRoots == numRoots) &&
145+
(memcmp(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN) == 0) &&
146+
_IsSubsetOf(slot->rootNvmIds, slot->numRoots, rootNvmIds,
147+
numRoots)) {
148+
return 1;
149+
}
124150
}
125-
found = _LookupUnlocked(cache, rootNvmId, hash);
126-
(void)_UnlockVerifyCache(cache);
127-
return found;
151+
return 0;
128152
}
129153

130-
int wh_Server_CertVerifyCache_LookupAnyRoot(whServerContext* server,
131-
const whNvmId* rootNvmIds,
132-
uint16_t numRoots,
133-
const uint8_t* hash)
154+
int wh_Server_CertVerifyCache_Lookup(whServerContext* server,
155+
const whNvmId* rootNvmIds,
156+
uint16_t numRoots, const uint8_t* hash)
134157
{
135158
whCertVerifyCacheContext* cache;
136159
int rc;
137-
int found = WH_ERROR_NOTFOUND;
138-
uint16_t i;
160+
int found;
139161

140162
if ((server == NULL) || (hash == NULL) || (rootNvmIds == NULL) ||
141163
(numRoots == 0)) {
@@ -150,30 +172,23 @@ int wh_Server_CertVerifyCache_LookupAnyRoot(whServerContext* server,
150172
if (rc != WH_ERROR_OK) {
151173
return rc;
152174
}
153-
/* Hit if the cert is cached under ANY of the supplied roots. The
154-
* binding to a real root preserves the security property that
155-
* cache hits cannot bypass trust-anchor checks: if the cached
156-
* binding root is in the supplied set, the cached verify was
157-
* anchored to a root the caller currently trusts. */
158-
for (i = 0; i < numRoots; i++) {
159-
if (_LookupUnlocked(cache, rootNvmIds[i], hash) == WH_ERROR_OK) {
160-
found = WH_ERROR_OK;
161-
break;
162-
}
163-
}
175+
found = _LookupSubsetUnlocked(cache, rootNvmIds, numRoots, hash);
164176
(void)_UnlockVerifyCache(cache);
165177
return found;
166178
}
167179

168180
void wh_Server_CertVerifyCache_Insert(whServerContext* server,
169-
whNvmId rootNvmId, const uint8_t* hash)
181+
const whNvmId* rootNvmIds,
182+
uint16_t numRoots, const uint8_t* hash)
170183
{
171184
whCertVerifyCacheContext* cache;
172185
whCertVerifyCacheSlot* slot;
173186
uint16_t idx;
187+
uint16_t k;
174188
int rc;
175189

176-
if ((server == NULL) || (hash == NULL)) {
190+
if ((server == NULL) || (hash == NULL) || (rootNvmIds == NULL) ||
191+
(numRoots == 0) || (numRoots > WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS)) {
177192
return;
178193
}
179194
cache = _GetVerifyCache(server);
@@ -185,12 +200,17 @@ void wh_Server_CertVerifyCache_Insert(whServerContext* server,
185200
if (rc != WH_ERROR_OK) {
186201
return;
187202
}
188-
/* Dedup under the lock so concurrent inserts of the same hash collapse to
189-
* a single slot. */
190-
if (_LookupUnlocked(cache, rootNvmId, hash) != WH_ERROR_OK) {
191-
idx = cache->writeIdx;
192-
slot = &cache->slots[idx];
193-
slot->rootNvmId = rootNvmId;
203+
/* Dedup on exact (set, hash) match under the lock so concurrent inserts
204+
* of the same verify collapse to a single slot. Differing-set entries
205+
* for the same hash coexist: each is an independent claim about a
206+
* distinct verify, and dropping either could lose hit coverage. */
207+
if (!_HasExactSlotUnlocked(cache, rootNvmIds, numRoots, hash)) {
208+
idx = cache->writeIdx;
209+
slot = &cache->slots[idx];
210+
slot->numRoots = (uint8_t)numRoots;
211+
for (k = 0; k < numRoots; k++) {
212+
slot->rootNvmIds[k] = rootNvmIds[k];
213+
}
194214
memcpy(slot->hash, hash, WH_CERT_VERIFY_CACHE_HASH_LEN);
195215
slot->committed = 1;
196216
cache->writeIdx =
@@ -243,13 +263,22 @@ void wh_Server_CertVerifyCache_EvictRoot(whServerContext* server,
243263
if (rc != WH_ERROR_OK) {
244264
return;
245265
}
246-
/* Walk every slot and zero those bound to the affected root. writeIdx is
247-
* left alone: the FIFO ring is sparse but still well-formed, and pruning
248-
* here would otherwise need to compact entries belonging to other roots. */
266+
/* Drop any slot whose stored root set contains the evicted root. We
267+
* cannot safely strip the root from the set and keep the entry: the
268+
* original verify may have anchored at the now-departed root, so the
269+
* remaining set is no longer a sound claim. writeIdx is left alone:
270+
* the FIFO ring is sparse but still well-formed, and pruning here
271+
* would otherwise need to compact entries belonging to other roots. */
249272
for (i = 0; i < WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT; i++) {
250273
whCertVerifyCacheSlot* slot = &cache->slots[i];
251-
if (slot->committed && (slot->rootNvmId == rootNvmId)) {
252-
memset(slot, 0, sizeof(*slot));
274+
if (slot->committed) {
275+
uint16_t k;
276+
for (k = 0; k < slot->numRoots; k++) {
277+
if (slot->rootNvmIds[k] == rootNvmId) {
278+
memset(slot, 0, sizeof(*slot));
279+
break;
280+
}
281+
}
253282
}
254283
}
255284
(void)_UnlockVerifyCache(cache);
@@ -299,15 +328,6 @@ _verifyChainAgainstCmStore(whServerContext* server, WOLFSSL_CERT_MANAGER* cm,
299328
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
300329
uint8_t certHash[WH_CERT_VERIFY_CACHE_HASH_LEN];
301330
int hashed = 0;
302-
/* Single-root callers know which root anchors the verify and can both
303-
* read and write the cache under that root binding. Multi-root callers
304-
* cannot identify the anchoring root post-verify, so the cache is
305-
* read-only for them: lookups OR across the supplied roots (a hit
306-
* means the cert was previously verified under a root the caller
307-
* currently trusts), but no entries are inserted. The cache is still
308-
* populated by single-root verifies, which multi-root verifies can
309-
* benefit from. */
310-
int boundCacheMode = (numRoots == 1);
311331
#else
312332
(void)trustedRootNvmIds;
313333
(void)numRoots;
@@ -349,17 +369,9 @@ _verifyChainAgainstCmStore(whServerContext* server, WOLFSSL_CERT_MANAGER* cm,
349369
}
350370
hashed = 1;
351371
{
352-
int hit;
353-
if (boundCacheMode) {
354-
hit = (wh_Server_CertVerifyCache_Lookup(
355-
server, trustedRootNvmIds[0], certHash) ==
356-
WH_ERROR_OK);
357-
}
358-
else {
359-
hit = (wh_Server_CertVerifyCache_LookupAnyRoot(
372+
int hit = (wh_Server_CertVerifyCache_Lookup(
360373
server, trustedRootNvmIds, numRoots, certHash) ==
361374
WH_ERROR_OK);
362-
}
363375
if (hit) {
364376
rc = WOLFSSL_SUCCESS;
365377
}
@@ -449,27 +461,26 @@ _verifyChainAgainstCmStore(whServerContext* server, WOLFSSL_CERT_MANAGER* cm,
449461
}
450462
}
451463
#ifdef WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE
452-
/* Only insert CA certs into the verify cache, and only in
453-
* single-root (bound) mode. Two restrictions:
454-
*
455-
* - Leaves are not cached: a cache hit on a leaf cert during a
456-
* future "leaf alone" verify would short-circuit the wolfSSL
457-
* signature check that would otherwise have failed (the
458-
* leaf's issuer is not in the cert manager when the leaf is
459-
* supplied without its intermediates). CA caching is sound
460-
* because the chain walk loads each verified CA into the
461-
* cert manager before the next cert is processed.
464+
/* Insert only CA certs into the verify cache. Leaves are not
465+
* cached: a cache hit on a leaf during a future "leaf alone"
466+
* verify would short-circuit the wolfSSL signature check that
467+
* would otherwise have failed (the leaf's issuer is not in the
468+
* cert manager when the leaf is supplied without its
469+
* intermediates). CA caching is sound because the chain walk
470+
* loads each verified CA into the cert manager before the next
471+
* cert is processed.
462472
*
463-
* - Multi-root verifies do not insert: the anchoring root
464-
* cannot be identified post-verify, and inserting under any
465-
* non-anchoring root from the supplied set could be matched
466-
* by a future single-root verify under that wrong root,
467-
* causing a false positive. Multi-root callers still
468-
* benefit from cache entries populated by single-root
469-
* verifies via the OR-across-roots lookup above. */
470-
if (hashed && dc.isCA && boundCacheMode) {
471-
wh_Server_CertVerifyCache_Insert(server, trustedRootNvmIds[0],
472-
certHash);
473+
* The slot's binding is the loaded root set passed in. Under
474+
* subset-lookup semantics, a future verify hits this entry
475+
* only when its loaded set is a superset, which by X.509
476+
* verify monotonicity guarantees the cached chain still
477+
* validates. Single-root callers produce one-element entries
478+
* (broadest reuse); multi-root callers produce wider entries
479+
* that are still useful when later traffic presents at least
480+
* the same roots. */
481+
if (hashed && dc.isCA) {
482+
wh_Server_CertVerifyCache_Insert(server, trustedRootNvmIds,
483+
numRoots, certHash);
473484
}
474485
#endif
475486
wc_FreeDecodedCert(&dc);

0 commit comments

Comments
 (0)