diff --git a/src/x509_str.c b/src/x509_str.c index 7bd2eb22f11..bfb23578766 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -632,37 +632,6 @@ static int X509StoreMoveCert(WOLFSSL_STACK *certs_stack, return WOLFSSL_FAILURE; } -/* Remove the first node referencing `cert` (by pointer identity) from `stack`. - * The certificate object itself is not freed - the stack only holds a borrowed - * reference. Returns WOLFSSL_SUCCESS if a node was removed, WOLFSSL_FAILURE if - * `cert` was not present, or WOLFSSL_FATAL_ERROR if `stack`/`cert` is NULL. - * The only caller performs best-effort cleanup and intentionally ignores the - * return value. - * - * Walks the linked list once (O(n)) rather than indexing with - * wolfSSL_sk_X509_value() per position (which would re-walk from the head each - * time, O(n^2)). */ -static int X509StoreRemoveCert(WOLFSSL_STACK *stack, WOLFSSL_X509 *cert) { - WOLFSSL_STACK* node; - int idx; - int num; - - if (stack == NULL || cert == NULL) - return WOLFSSL_FATAL_ERROR; - - num = wolfSSL_sk_X509_num(stack); - for (node = stack, idx = 0; idx < num && node != NULL; - node = node->next, idx++) { - if (node->data.x509 == cert) { - (void)wolfSSL_sk_pop_node(stack, idx); - return WOLFSSL_SUCCESS; - } - } - - return WOLFSSL_FAILURE; -} - - /* Push x509 onto the ctx chain with its own reference, like OpenSSL. * The chain owns a reference to each of its certs. */ static int X509StoreChainPush(WOLF_STACK_OF(WOLFSSL_X509)* chain, @@ -679,6 +648,29 @@ static int X509StoreChainPush(WOLF_STACK_OF(WOLFSSL_X509)* chain, return ret; } +/* Returns 1 if `cert` (by pointer identity) is present in `stack`, else 0. + * Used to keep a candidate that already failed verification off the reported + * chain. */ +static int X509StoreCertInStack(WOLF_STACK_OF(WOLFSSL_X509)* stack, + WOLFSSL_X509* cert) +{ + int i; + int num; + + if (stack == NULL || cert == NULL) + return 0; + + /* Index by logical position like the other helpers in this file rather + * than walking raw nodes. */ + num = wolfSSL_sk_X509_num(stack); + for (i = 0; i < num; i++) { + if (wolfSSL_sk_X509_value(stack, i) == cert) + return 1; + } + + return 0; +} + /* Current certificate failed, but it is possible there is an * alternative cert with the same subject key which will work. * Retry until all possible candidate certs are exhausted. */ @@ -716,11 +708,15 @@ static int X509DerEquals(WOLFSSL_X509* cur, WOLFSSL_X509* x509) } /* Returns 1 if x509's DER matches an entry in either origTrustedSk (an - * immutable snapshot of the caller's trusted set captured before any - * intermediates were injected for this verification call) or in - * store->trusted. Returns 0 otherwise. Used by the - * X509_V_FLAG_PARTIAL_CHAIN fallback to confirm that a chain actually - * terminates at a caller-trusted certificate. */ + * immutable snapshot of the caller's trusted set - store->certs or the + * set0_trusted_stack override - captured before any intermediates were + * injected for this verification call) or in store->trusted. Returns 0 + * otherwise. Used by the X509_V_FLAG_PARTIAL_CHAIN fallback to confirm that + * a chain actually terminates at a caller-trusted certificate. + * NOTE: origTrustedSk is a private snapshot, but store->trusted is read live + * and unlocked here (as it is at the terminal issuer lookup); this mitigation + * is deliberately asymmetric, so a single X509_STORE must not be shared across + * threads verifying concurrently. */ static int X509StoreCertIsTrusted(WOLFSSL_X509_STORE* store, WOLFSSL_X509* x509, WOLF_STACK_OF(WOLFSSL_X509)* origTrustedSk) { @@ -859,13 +855,11 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); int done = 0; int added = 0; - int i = 0; - int numFailedCerts = 0; int depth = 0; int origDepth = 0; WOLFSSL_X509 *issuer = NULL; WOLFSSL_X509 *orig = NULL; - WOLF_STACK_OF(WOLFSSL_X509)* certs = NULL; + WOLF_STACK_OF(WOLFSSL_X509)* callerTrusted = NULL; WOLF_STACK_OF(WOLFSSL_X509)* certsToUse = NULL; WOLF_STACK_OF(WOLFSSL_X509)* failedCerts = NULL; WOLF_STACK_OF(WOLFSSL_X509)* origTrustedSk = NULL; @@ -876,51 +870,47 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) return WOLFSSL_FATAL_ERROR; } - certs = ctx->store->certs; - + /* Chain building mutates the working stack: caller-supplied intermediates + * are appended and X509VerifyCertSetupRetry moves failed certs out of it. + * store->certs is shared by every connection using this store and + * setTrustedSk is owned by the caller, so build a per-verification shallow + * copy (certsToUse) and leave both untouched. This removes the write-side + * corruption a concurrent verification used to inflict on those stacks, but + * it does NOT make concurrent use of one X509_STORE safe: the dup is itself + * an unlocked read, and store->trusted is still walked live (see the NOTE + * below). A single store must not be shared across threads that verify + * concurrently. + * + * The X509_V_FLAG_PARTIAL_CHAIN fallback needs the set of certs that were + * caller-trusted before any intermediates were injected. Snapshot it from + * certsToUse - the private copy, taken before addAllButSelfSigned() injects + * intermediates - rather than walking the shared stack a second time, so + * the two snapshots are provably identical. Both dups hold borrowed + * references and are shallow-freed at exit. */ + callerTrusted = ctx->store->certs; if (ctx->setTrustedSk != NULL) { - certs = ctx->setTrustedSk; + callerTrusted = ctx->setTrustedSk; } - if (certs == NULL && - wolfSSL_sk_X509_num(ctx->ctxIntermediates) > 0) { - certsToUse = wolfSSL_sk_X509_new_null(); - if (certsToUse == NULL) { + if (callerTrusted != NULL) { + certsToUse = wolfSSL_shallow_sk_dup(callerTrusted); + if (certsToUse != NULL) + origTrustedSk = wolfSSL_shallow_sk_dup(certsToUse); + if (origTrustedSk == NULL) { ret = WOLFSSL_FAILURE; goto exit; } - ret = addAllButSelfSigned(certsToUse, ctx->ctxIntermediates, NULL); - /* certsToUse holds only injected intermediates, none are trusted, so - * leave origTrustedSk NULL (empty snapshot). */ - certs = certsToUse; } else { - /* Snapshot the caller-trusted entries before injecting the - * caller-supplied untrusted intermediates. Only the entries already - * present count as trusted for the partial-chain check below, and - * we need a stable reference because X509VerifyCertSetupRetry may - * remove nodes from `certs` during chain building. */ - if (certs != NULL && wolfSSL_sk_X509_num(certs) > 0) { - int j; - int n = wolfSSL_sk_X509_num(certs); - origTrustedSk = wolfSSL_sk_X509_new_null(); - if (origTrustedSk == NULL) { - ret = WOLFSSL_FAILURE; - goto exit; - } - for (j = 0; j < n; j++) { - if (wolfSSL_sk_X509_push(origTrustedSk, - wolfSSL_sk_X509_value(certs, j)) <= 0) { - ret = WOLFSSL_FAILURE; - goto exit; - } - } - } - /* Add the intermediates provided on init to the list of untrusted - * intermediates to be used. They are removed again from `certs` in the - * exit cleanup (by identity, recomputed from ctxIntermediates). */ - ret = addAllButSelfSigned(certs, ctx->ctxIntermediates, NULL); + certsToUse = wolfSSL_sk_X509_new_null(); } + if (certsToUse == NULL) { + ret = WOLFSSL_FAILURE; + goto exit; + } + /* Add the intermediates provided on init to the list of untrusted + * intermediates to be used. */ + ret = addAllButSelfSigned(certsToUse, ctx->ctxIntermediates, NULL); if (ret != WOLFSSL_SUCCESS) { goto exit; } @@ -956,7 +946,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) issuer = NULL; /* Try to find an untrusted issuer first */ - ret = X509StoreGetIssuerEx(&issuer, certs, + ret = X509StoreGetIssuerEx(&issuer, certsToUse, ctx->current_cert); if (ret == WOLFSSL_SUCCESS) { if (ctx->current_cert == issuer) { @@ -991,9 +981,17 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) } } #endif + /* NOTE: this loads the caller-supplied intermediate into the + * shared ctx->store->cm as a WOLFSSL_TEMP_CA, and the unload paths + * drop *all* WOLFSSL_TEMP_CA signers in that CertManager, not only + * the ones added here. This copy removes the working-stack race, + * but two threads running X509_verify_cert() against the same + * X509_STORE still contend on store->cm. Concurrent verification + * on a single shared store therefore remains unsupported; callers + * needing it must use a store per thread. */ ret = X509StoreAddCa(ctx->store, issuer, WOLFSSL_TEMP_CA); if (ret != WOLFSSL_SUCCESS) { - X509VerifyCertSetupRetry(ctx, certs, failedCerts, + X509VerifyCertSetupRetry(ctx, certsToUse, failedCerts, &depth, origDepth); continue; } @@ -1002,7 +1000,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) if (ret != WOLFSSL_SUCCESS) { if ((origDepth - depth) <= 1) added = 0; - X509VerifyCertSetupRetry(ctx, certs, failedCerts, + X509VerifyCertSetupRetry(ctx, certsToUse, failedCerts, &depth, origDepth); continue; } @@ -1058,7 +1056,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) * above; the depth>0/done==0 success path accepts it. */ break; } else { - X509VerifyCertSetupRetry(ctx, certs, failedCerts, + X509VerifyCertSetupRetry(ctx, certsToUse, failedCerts, &depth, origDepth); continue; } @@ -1082,7 +1080,16 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ctx->setTrustedSk, ctx->current_cert); } #endif - if (issuer != NULL) { + /* A candidate that already failed verification (moved to + * failedCerts by the retry path) must not terminate the reported + * chain. setTrustedSk / store->trusted are searched by name+AKID, + * not by signature, and setTrustedSk is no longer pruned during + * chain building, so X509StoreGetIssuerEx can return a same-subject + * cert that was tried and rejected. + * Under WOLFSSL_SIGNER_DER_CERT the issuer above is a freshly + * allocated CM copy, never pointer-equal to a failedCerts entry, so + * this guard is a no-op on that path. */ + if (issuer != NULL && !X509StoreCertInStack(failedCerts, issuer)) { X509StoreChainPush(ctx->chain, issuer); } @@ -1115,40 +1122,12 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) } exit: - /* Copy back failed certs. */ - numFailedCerts = wolfSSL_sk_X509_num(failedCerts); - for (i = 0; i < numFailedCerts; i++) - { - wolfSSL_sk_X509_push(certs, wolfSSL_sk_X509_pop(failedCerts)); - } - wolfSSL_sk_X509_pop_free(failedCerts, NULL); + /* failedCerts, certsToUse and origTrustedSk hold only borrowed references; + * free the stack nodes, not the certs. All three are per-verification + * stacks (certsToUse/origTrustedSk are shallow dups of the caller's set), + * so none of the caller's own stacks are touched here. */ + wolfSSL_sk_X509_free(failedCerts); - /* Remove the caller-supplied intermediates that addAllButSelfSigned - * appended to `certs` during chain building, restoring it to its original - * contents. Remove them by pointer identity from the same stack they were - * added to (store->certs in the common case, or the caller's setTrustedSk - * via X509_STORE_CTX_set0_trusted_stack), recomputed from ctxIntermediates - * with the same self-signed filter as the add. - * - * Identity removal - not a saved count + positional pop - is required: - * X509VerifyCertSetupRetry reorders `certs` during chain building, so - * popping N entries off the top could drop a legitimate trusted entry and - * leave an injected intermediate behind, which a later verification reusing - * this store/ctx would then snapshot as a trust anchor. certsToUse is the - * throwaway certs==NULL path and is freed wholesale below, so skip it. */ - if (ctx != NULL && certsToUse == NULL && certs != NULL && - ctx->ctxIntermediates != NULL) { - int n = wolfSSL_sk_X509_num(ctx->ctxIntermediates); - for (i = 0; i < n; i++) { - WOLFSSL_X509* inter = - wolfSSL_sk_X509_value(ctx->ctxIntermediates, i); - if (inter != NULL && - wolfSSL_X509_NAME_cmp(&inter->issuer, &inter->subject) - != 0) { - X509StoreRemoveCert(certs, inter); - } - } - } /* Remove intermediates that were added to CM */ if (ctx != NULL) { if (ctx->store != NULL) { @@ -1160,13 +1139,8 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) ctx->current_cert = orig; } } - if (certsToUse != NULL) { - wolfSSL_sk_X509_free(certsToUse); - } - if (origTrustedSk != NULL) { - /* Shallow free: only the snapshot's stack nodes, not the X509s. */ - wolfSSL_sk_X509_free(origTrustedSk); - } + wolfSSL_sk_X509_free(certsToUse); + wolfSSL_sk_X509_free(origTrustedSk); /* Enforce hostname / IP verification from X509_VERIFY_PARAM if set. * Always check against the leaf (end-entity) certificate, captured in diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 2257fa5ed35..0dc15bd6680 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -1535,18 +1535,16 @@ static int test_untrusted_inter_depth_exhaustion(X509* leafDeep, X509* inter, return EXPECT_RESULT(); } -/* Intermediate-stack cleanup: the caller-supplied intermediates that the - * verifier temporarily appends to its working cert list must be removed from - * the exact stack they were added to once verification finishes. When a - * trusted_stack is in use (X509_STORE_CTX_set0_trusted_stack), they are - * appended to that caller-owned stack; if they are not removed again, a later - * verification reusing the stack/ctx would snapshot them as trust anchors. +/* Caller-owned trusted stack (X509_STORE_CTX_set0_trusted_stack): chain + * building appends the caller-supplied intermediates to an internal working + * copy, never to the caller's stack. If the caller's stack were modified and + * an intermediate left behind, a later verification reusing the stack/ctx + * would treat it as a trust anchor. * * leaf <- int-ca <- root, with root supplied via the trusted_stack. * * Verify the chain (which reaches root in the trusted stack), then assert the - * trusted stack is left exactly as the caller supplied it: only root, with the - * injected intermediate removed again. */ + * trusted stack is left exactly as the caller supplied it: only root. */ static int test_untrusted_inter_trusted_stack_cleanup(X509* leaf, X509* inter, X509* root) { @@ -1567,8 +1565,9 @@ static int test_untrusted_inter_trusted_stack_cleanup(X509* leaf, X509* inter, /* Chain reaches root in the trusted stack -> verifies. */ ExpectIntEQ(X509_verify_cert(ctx), 1); ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_OK); - /* The trusted stack must be restored: the injected intermediate appended - * during verification must have been removed, leaving only root. */ + /* The trusted stack must be left exactly as supplied: verification builds + * the chain on a private copy, so nothing is appended to or removed from + * the caller's stack - only root remains. */ ExpectIntEQ(sk_X509_num(trusted), 1); ExpectPtrEq(sk_X509_value(trusted, 0), root); X509_STORE_CTX_free(ctx); @@ -1578,6 +1577,62 @@ static int test_untrusted_inter_trusted_stack_cleanup(X509* leaf, X509* inter, return EXPECT_RESULT(); } +/* Trusted-stack counterpart of test_untrusted_inter_store_stack_unchanged: the + * caller's set0_trusted_stack must not be mutated - not even reordered - by the + * retry path. Put the tampered candidate ahead of root in the trusted stack so + * the verifier hits it first and takes X509VerifyCertSetupRetry (which moves + * failed candidates around on the internal copy), supply the genuine int-ca via + * the untrusted stack, then assert the trusted stack's exact contents and order + * after both a succeeding and a failing verification. */ +static int test_untrusted_inter_trusted_stack_unchanged(X509* leaf, X509* inter, + X509* tamperedInter, X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* trusted = NULL; + STACK_OF(X509)* untrusted = NULL; + + ExpectNotNull(store = X509_STORE_new()); + ExpectNotNull(trusted = sk_X509_new_null()); + /* Tampered candidate ahead of root forces the retry path over the trusted + * stack. */ + ExpectIntGT(sk_X509_push(trusted, tamperedInter), 0); + ExpectIntGT(sk_X509_push(trusted, root), 0); + + /* Succeeding verification: genuine int-ca arrives via the untrusted stack. */ + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, inter), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, untrusted), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + ExpectIntEQ(X509_verify_cert(ctx), 1); + X509_STORE_CTX_free(ctx); + ctx = NULL; + + /* Trusted stack unchanged in contents and order. */ + ExpectIntEQ(sk_X509_num(trusted), 2); + ExpectPtrEq(sk_X509_value(trusted, 0), tamperedInter); + ExpectPtrEq(sk_X509_value(trusted, 1), root); + + /* Failing verification on the same trusted stack: no genuine issuer. */ + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + X509_STORE_CTX_trusted_stack(ctx, trusted); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntNE(X509_STORE_CTX_get_error(ctx), X509_V_OK); + X509_STORE_CTX_free(ctx); + + ExpectIntEQ(sk_X509_num(trusted), 2); + ExpectPtrEq(sk_X509_value(trusted, 0), tamperedInter); + ExpectPtrEq(sk_X509_value(trusted, 1), root); + + X509_STORE_free(store); + sk_X509_free(untrusted); + sk_X509_free(trusted); + return EXPECT_RESULT(); +} + /* One mixed-candidate verification: leaf with both the tampered intermediate * (broken outer signature, same subject as int-ca) and the genuine int-ca in * the untrusted stack, in the given push order. The chain must verify - the @@ -1657,6 +1712,113 @@ static int test_untrusted_inter_retry(X509* leaf, X509* inter, sk_X509_free(badOnly); return EXPECT_RESULT(); } + +/* Retry-path chain integrity: a tampered same-subject candidate tried and + * rejected before the genuine intermediate succeeds must not appear in the + * reported chain. Drive the retry path (tampered candidate ahead of the + * genuine one), then confirm X509_STORE_CTX_get0_chain() contains the genuine + * intermediate and never the rejected sibling. Certs are compared by content + * (X509_cmp) since the chain need not hold the caller's pointers. + * NOTE: this exercises the retry/failedCerts machinery via the untrusted + * stack; it does not drive the terminal-anchor failedCerts guard (x509_str.c), + * which additionally needs a same-subject rejected *anchor* in the trusted + * terminal set - no such fixture exists yet. */ +static int test_untrusted_inter_chain_excludes_rejected(X509* leaf, X509* inter, + X509* tamperedInter, X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* mixed = NULL; + STACK_OF(X509)* chain = NULL; + int i; + int foundInter = 0; + int foundTampered = 0; + + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + + /* Tampered candidate first forces the verifier to try and reject it, + * moving it into the internal failedCerts list, before recovering with the + * genuine intermediate. */ + ExpectNotNull(mixed = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(mixed, tamperedInter), 0); + ExpectIntGT(sk_X509_push(mixed, inter), 0); + + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, mixed), 1); + ExpectIntEQ(X509_verify_cert(ctx), 1); + + ExpectNotNull(chain = X509_STORE_CTX_get0_chain(ctx)); + for (i = 0; i < sk_X509_num(chain); i++) { + X509* c = sk_X509_value(chain, i); + if (c != NULL && X509_cmp(c, inter) == 0) + foundInter = 1; + if (c != NULL && X509_cmp(c, tamperedInter) == 0) + foundTampered = 1; + } + ExpectIntEQ(foundInter, 1); + ExpectIntEQ(foundTampered, 0); + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(mixed); + return EXPECT_RESULT(); +} + +/* The store's cert stack is shared by every X509_STORE_CTX (and every SSL + * connection) using the store, so verification must not modify it. Chain + * building appends caller-supplied intermediates and moves failed retry + * candidates around on an internal copy only. Put a tampered candidate on + * store->certs ahead of the genuine one so the verifier takes the retry path + * (which used to reorder the stack), then assert the stack's exact contents + * and order after both a succeeding and a failing verification. */ +static int test_untrusted_inter_store_stack_unchanged(X509* leaf, X509* inter, + X509* tamperedInter, X509* inter2, X509* root) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* untrusted = NULL; + + ExpectNotNull(store = X509_STORE_new()); + ExpectIntEQ(X509_STORE_add_cert(store, root), 1); + /* Non-self-signed certs land on store->certs, in add order. */ + ExpectIntEQ(X509_STORE_add_cert(store, tamperedInter), 1); + ExpectIntEQ(X509_STORE_add_cert(store, inter2), 1); + ExpectIntEQ(sk_X509_num(store->certs), 2); + + /* Succeeding verification: the tampered candidate is hit first and the + * genuine intermediate arrives via the untrusted stack, forcing a retry. + * Only the return value is asserted; the error code after a recovered + * retry is order-dependent (worst-seen error persists). */ + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, inter), 0); + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, untrusted), 1); + ExpectIntEQ(X509_verify_cert(ctx), 1); + X509_STORE_CTX_free(ctx); + ctx = NULL; + + ExpectIntEQ(sk_X509_num(store->certs), 2); + ExpectPtrEq(sk_X509_value(store->certs, 0), tamperedInter); + ExpectPtrEq(sk_X509_value(store->certs, 1), inter2); + + /* Failing verification on the same store: no genuine issuer available. */ + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leaf, NULL), 1); + ExpectIntEQ(X509_verify_cert(ctx), 0); + ExpectIntNE(X509_STORE_CTX_get_error(ctx), X509_V_OK); + X509_STORE_CTX_free(ctx); + + ExpectIntEQ(sk_X509_num(store->certs), 2); + ExpectPtrEq(sk_X509_value(store->certs, 0), tamperedInter); + ExpectPtrEq(sk_X509_value(store->certs, 1), inter2); + + X509_STORE_free(store); + sk_X509_free(untrusted); + return EXPECT_RESULT(); +} #endif /* OPENSSL_EXTRA && !NO_RSA && !NO_CERTS && !NO_FILESYSTEM */ int test_X509_verify_cert_untrusted_inter(void) @@ -1681,7 +1843,10 @@ int test_X509_verify_cert_untrusted_inter(void) int noStaleRes = 0; int depthExhaustRes = 0; int trustedStackCleanupRes = 0; + int trustedStackUnchangedRes = 0; int retryRes = 0; + int chainExcludesRes = 0; + int storeStackRes = 0; ExpectNotNull(leaf = untrusted_inter_load(UA_CERT_DIR "leaf-cert.pem")); ExpectNotNull(leafDeep = @@ -1715,7 +1880,14 @@ int test_X509_verify_cert_untrusted_inter(void) inter, inter2, root); trustedStackCleanupRes = test_untrusted_inter_trusted_stack_cleanup( leaf, inter, root); + trustedStackUnchangedRes = + test_untrusted_inter_trusted_stack_unchanged( + leaf, inter, tamperedInter, root); retryRes = test_untrusted_inter_retry(leaf, inter, tamperedInter, root); + chainExcludesRes = test_untrusted_inter_chain_excludes_rejected(leaf, + inter, tamperedInter, root); + storeStackRes = test_untrusted_inter_store_stack_unchanged(leaf, inter, + tamperedInter, inter2, root); ExpectIntEQ(sanityRes, 1); ExpectIntEQ(twoLevelRes, 1); ExpectIntEQ(emptyStoreRes, 1); @@ -1725,7 +1897,10 @@ int test_X509_verify_cert_untrusted_inter(void) ExpectIntEQ(noStaleRes, 1); ExpectIntEQ(depthExhaustRes, 1); ExpectIntEQ(trustedStackCleanupRes, 1); + ExpectIntEQ(trustedStackUnchangedRes, 1); ExpectIntEQ(retryRes, 1); + ExpectIntEQ(chainExcludesRes, 1); + ExpectIntEQ(storeStackRes, 1); } X509_free(leaf); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 423f027c740..4bce561d85b 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2425,6 +2425,8 @@ WOLFSSL_API const unsigned char* wolfSSL_ASN1_INTEGER_get0_data( const WOLFSSL_ASN1_INTEGER* ai); WOLFSSL_API int wolfSSL_ASN1_STRING_copy(WOLFSSL_ASN1_STRING* dst, const WOLFSSL_ASN1_STRING* src); +/* NOTE: a single WOLFSSL_X509_STORE must not be shared across threads that + * verify concurrently; verification reads the store's trusted set live. */ WOLFSSL_API int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx); WOLFSSL_API const char* wolfSSL_X509_verify_cert_error_string(long err);