@@ -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
168180void 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