x509: reject depth-exhausted chains in wolfSSL_X509_verify_cert()

Fail compatibility-layer verification when the path-building loop runs
out of its depth budget before reaching a configured trust anchor,
instead of accepting the last verified link. Add a regression test.
This commit is contained in:
Tobias Frauenschläger
2026-06-11 16:07:04 +02:00
parent 3e2c46001e
commit 2d76a68925
2 changed files with 53 additions and 1 deletions
+13 -1
View File
@@ -826,6 +826,18 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
depth--;
}
/* Success requires the path to have reached a configured trust anchor
* (done == 1) or to have terminated at a caller-trusted self-signed
* certificate via the break above (done == 0 with depth still > 0). A
* loop that instead ran out of its depth budget (depth <= 0) without
* completing must fail closed: ret may still be WOLFSSL_SUCCESS from the
* last link, but no trust anchor was reached. */
if (ret == WOLFSSL_SUCCESS && done == 0 && depth <= 0) {
SetupStoreCtxError_ex(ctx, WOLFSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG,
wolfSSL_sk_X509_num(ctx->chain));
ret = WOLFSSL_FAILURE;
}
exit:
/* Copy back failed certs. */
numFailedCerts = wolfSSL_sk_X509_num(failedCerts);
@@ -1044,7 +1056,7 @@ int wolfSSL_X509_STORE_CTX_set_ex_data_with_cleanup(
}
#endif /* HAVE_EX_DATA_CLEANUP_HOOKS */
#if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_ALL)
#if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_EXTRA)
void wolfSSL_X509_STORE_CTX_set_depth(WOLFSSL_X509_STORE_CTX* ctx, int depth)
{
WOLFSSL_ENTER("wolfSSL_X509_STORE_CTX_set_depth");
+40
View File
@@ -1265,6 +1265,42 @@ static int test_untrusted_inter_no_stale_anchor(X509* leaf, X509* inter,
sk_X509_free(chain);
return EXPECT_RESULT();
}
/* Depth exhaustion: a chain that cannot be walked to the trusted anchor within
* the configured path-building depth budget must be rejected,.
*
* leaf-deep <- int-ca2 <- int-ca <- root (root trusted)
*
* The chain is genuine and verifies at the default depth (covered by
* test_untrusted_inter_two_level); here the depth is capped below the chain
* length so the budget is consumed before the trusted root is reached. The
* fix must report it as "certificate chain too long". */
static int test_untrusted_inter_depth_exhaustion(X509* leafDeep, X509* inter,
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);
ExpectNotNull(untrusted = sk_X509_new_null());
ExpectIntGT(sk_X509_push(untrusted, inter), 0);
ExpectIntGT(sk_X509_push(untrusted, inter2), 0);
ExpectNotNull(ctx = X509_STORE_CTX_new());
ExpectIntEQ(X509_STORE_CTX_init(ctx, store, leafDeep, untrusted), 1);
/* Cap the path-building budget below the chain length so the walk runs
* out of depth before it can reach the trusted root. */
X509_STORE_CTX_set_depth(ctx, 1);
ExpectIntEQ(X509_verify_cert(ctx), 0);
ExpectIntEQ(X509_STORE_CTX_get_error(ctx),
X509_V_ERR_CERT_CHAIN_TOO_LONG);
X509_STORE_CTX_free(ctx);
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)
@@ -1287,6 +1323,7 @@ int test_X509_verify_cert_untrusted_inter(void)
int tamperedRes = 0;
int reusedStoreRes = 0;
int noStaleRes = 0;
int depthExhaustRes = 0;
ExpectNotNull(leaf = untrusted_inter_load(UA_CERT_DIR "leaf-cert.pem"));
ExpectNotNull(leafDeep =
@@ -1316,6 +1353,8 @@ int test_X509_verify_cert_untrusted_inter(void)
tamperedInter, root);
noStaleRes = test_untrusted_inter_no_stale_anchor(leaf, inter,
root);
depthExhaustRes = test_untrusted_inter_depth_exhaustion(leafDeep,
inter, inter2, root);
ExpectIntEQ(sanityRes, 1);
ExpectIntEQ(twoLevelRes, 1);
ExpectIntEQ(emptyStoreRes, 1);
@@ -1323,6 +1362,7 @@ int test_X509_verify_cert_untrusted_inter(void)
ExpectIntEQ(tamperedRes, 1);
ExpectIntEQ(reusedStoreRes, 1);
ExpectIntEQ(noStaleRes, 1);
ExpectIntEQ(depthExhaustRes, 1);
}
X509_free(leaf);