Skip to content

Commit 14ebd3d

Browse files
authored
Merge pull request #10170 from embhorn/zd21566
Fix partial chain verification
2 parents 64c4203 + c873f3f commit 14ebd3d

2 files changed

Lines changed: 223 additions & 1 deletion

File tree

src/x509_str.c

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,53 @@ static int X509VerifyCertSetupRetry(WOLFSSL_X509_STORE_CTX* ctx,
552552
return ret;
553553
}
554554

555+
/* Returns 1 if cur and x509 have identical DER encodings, 0 otherwise. */
556+
static int X509DerEquals(WOLFSSL_X509* cur, WOLFSSL_X509* x509)
557+
{
558+
if (cur == NULL || cur->derCert == NULL ||
559+
x509 == NULL || x509->derCert == NULL) {
560+
return 0;
561+
}
562+
if (cur->derCert->length != x509->derCert->length)
563+
return 0;
564+
return XMEMCMP(cur->derCert->buffer, x509->derCert->buffer,
565+
x509->derCert->length) == 0;
566+
}
567+
568+
/* Returns 1 if x509's DER matches an entry in either origTrustedSk (an
569+
* immutable snapshot of the caller's trusted set captured before any
570+
* intermediates were injected for this verification call) or in
571+
* store->trusted. Returns 0 otherwise. Used by the
572+
* X509_V_FLAG_PARTIAL_CHAIN fallback to confirm that a chain actually
573+
* terminates at a caller-trusted certificate. */
574+
static int X509StoreCertIsTrusted(WOLFSSL_X509_STORE* store,
575+
WOLFSSL_X509* x509, WOLF_STACK_OF(WOLFSSL_X509)* origTrustedSk)
576+
{
577+
int i;
578+
int n;
579+
580+
if (x509 == NULL || x509->derCert == NULL)
581+
return 0;
582+
583+
if (origTrustedSk != NULL) {
584+
n = wolfSSL_sk_X509_num(origTrustedSk);
585+
for (i = 0; i < n; i++) {
586+
if (X509DerEquals(wolfSSL_sk_X509_value(origTrustedSk, i), x509))
587+
return 1;
588+
}
589+
}
590+
591+
if (store != NULL && store->trusted != NULL) {
592+
n = wolfSSL_sk_X509_num(store->trusted);
593+
for (i = 0; i < n; i++) {
594+
if (X509DerEquals(wolfSSL_sk_X509_value(store->trusted, i), x509))
595+
return 1;
596+
}
597+
}
598+
599+
return 0;
600+
}
601+
555602
/* Verifies certificate chain using WOLFSSL_X509_STORE_CTX
556603
* returns 1 on success or <= 0 on failure.
557604
*/
@@ -570,6 +617,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
570617
WOLF_STACK_OF(WOLFSSL_X509)* certs = NULL;
571618
WOLF_STACK_OF(WOLFSSL_X509)* certsToUse = NULL;
572619
WOLF_STACK_OF(WOLFSSL_X509)* failedCerts = NULL;
620+
WOLF_STACK_OF(WOLFSSL_X509)* origTrustedSk = NULL;
573621
WOLFSSL_ENTER("wolfSSL_X509_verify_cert");
574622

575623
if (ctx == NULL || ctx->store == NULL || ctx->store->cm == NULL
@@ -586,9 +634,37 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
586634
if (certs == NULL &&
587635
wolfSSL_sk_X509_num(ctx->ctxIntermediates) > 0) {
588636
certsToUse = wolfSSL_sk_X509_new_null();
637+
if (certsToUse == NULL) {
638+
ret = WOLFSSL_FAILURE;
639+
goto exit;
640+
}
589641
ret = addAllButSelfSigned(certsToUse, ctx->ctxIntermediates, NULL);
642+
/* certsToUse holds only injected intermediates, none are trusted, so
643+
* leave origTrustedSk NULL (empty snapshot). */
644+
certs = certsToUse;
590645
}
591646
else {
647+
/* Snapshot the caller-trusted entries before injecting the
648+
* caller-supplied untrusted intermediates. Only the entries already
649+
* present count as trusted for the partial-chain check below, and
650+
* we need a stable reference because X509VerifyCertSetupRetry may
651+
* remove nodes from `certs` during chain building. */
652+
if (certs != NULL && wolfSSL_sk_X509_num(certs) > 0) {
653+
int j;
654+
int n = wolfSSL_sk_X509_num(certs);
655+
origTrustedSk = wolfSSL_sk_X509_new_null();
656+
if (origTrustedSk == NULL) {
657+
ret = WOLFSSL_FAILURE;
658+
goto exit;
659+
}
660+
for (j = 0; j < n; j++) {
661+
if (wolfSSL_sk_X509_push(origTrustedSk,
662+
wolfSSL_sk_X509_value(certs, j)) <= 0) {
663+
ret = WOLFSSL_FAILURE;
664+
goto exit;
665+
}
666+
}
667+
}
592668
/* Add the intermediates provided on init to the list of untrusted
593669
* intermediates to be used */
594670
ret = addAllButSelfSigned(certs, ctx->ctxIntermediates, &numInterAdd);
@@ -677,10 +753,22 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
677753
* a trusted CA in the CM */
678754
ret = X509StoreVerifyCert(ctx);
679755
if (ret != WOLFSSL_SUCCESS) {
756+
/* WOLFSSL_PARTIAL_CHAIN may only terminate the chain at a
757+
* certificate the caller actually trusts. The previous
758+
* "added == 1" guard merely confirmed that some untrusted
759+
* intermediate had been temporarily loaded into the
760+
* CertManager during chain building, which would accept
761+
* chains that never reach a trust anchor. Verify that
762+
* ctx->current_cert is itself in the original trust set. */
680763
if (((ctx->flags & WOLFSSL_PARTIAL_CHAIN) ||
681764
(ctx->store->param->flags & WOLFSSL_PARTIAL_CHAIN)) &&
682-
(added == 1)) {
765+
X509StoreCertIsTrusted(ctx->store, ctx->current_cert,
766+
origTrustedSk)) {
683767
wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert);
768+
/* Clear error set by the failed X509StoreVerifyCert
769+
* attempt; the partial-chain fallback accepted the
770+
* chain at a caller-trusted certificate. */
771+
ctx->error = 0;
684772
ret = WOLFSSL_SUCCESS;
685773
} else {
686774
X509VerifyCertSetupRetry(ctx, certs, failedCerts,
@@ -749,6 +837,10 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx)
749837
if (certsToUse != NULL) {
750838
wolfSSL_sk_X509_free(certsToUse);
751839
}
840+
if (origTrustedSk != NULL) {
841+
/* Shallow free: only the snapshot's stack nodes, not the X509s. */
842+
wolfSSL_sk_X509_free(origTrustedSk);
843+
}
752844

753845
/* Enforce hostname / IP verification from X509_VERIFY_PARAM if set.
754846
* Always check against the leaf (end-entity) certificate, captured in

tests/api/test_ossl_x509_str.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,130 @@ static int test_wolfSSL_X509_STORE_CTX_ex11(X509_STORE_test_data *testData)
785785
return EXPECT_RESULT();
786786
}
787787

788+
static int test_wolfSSL_X509_STORE_CTX_ex_partial_chain_neg(
789+
X509_STORE_test_data *testData)
790+
{
791+
EXPECT_DECLS;
792+
X509_STORE* store = NULL;
793+
X509_STORE_CTX* ctx = NULL;
794+
STACK_OF(X509)* untrusted = NULL;
795+
796+
/* Negative partial-chain test: with X509_V_FLAG_PARTIAL_CHAIN set, the
797+
* intermediates are supplied ONLY as untrusted (passed through the
798+
* X509_STORE_CTX_init "chain" argument and never added to the store).
799+
* No certificate in the chain is in the store, so verification must
800+
* fail. Pre-fix, wolfSSL_X509_verify_cert would incorrectly accept
801+
* this chain because its partial-chain fallback only checked that some
802+
* intermediate had been temporarily loaded into the CertManager, not
803+
* that any chain certificate was actually trusted. */
804+
ExpectNotNull(store = X509_STORE_new());
805+
/* Intentionally do NOT add x509CaInt, x509CaInt2, or x509Ca. */
806+
ExpectIntEQ(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN), 1);
807+
808+
ExpectNotNull(untrusted = sk_X509_new_null());
809+
ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt2), 0);
810+
ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt), 0);
811+
812+
ExpectNotNull(ctx = X509_STORE_CTX_new());
813+
ExpectIntEQ(X509_STORE_CTX_init(ctx, store, testData->x509Leaf, untrusted),
814+
1);
815+
/* Must NOT verify: partial-chain does not relax the trust requirement. */
816+
ExpectIntNE(X509_verify_cert(ctx), 1);
817+
/* Verify the failure is specifically due to missing trust anchor, not
818+
* some unrelated error. */
819+
ExpectIntEQ(X509_STORE_CTX_get_error(ctx),
820+
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY);
821+
822+
X509_STORE_CTX_free(ctx);
823+
X509_STORE_free(store);
824+
sk_X509_free(untrusted);
825+
return EXPECT_RESULT();
826+
}
827+
828+
static int test_wolfSSL_X509_STORE_CTX_ex_partial_chain_mixed(
829+
X509_STORE_test_data *testData)
830+
{
831+
EXPECT_DECLS;
832+
X509_STORE* store = NULL;
833+
X509_STORE_CTX* ctx = NULL;
834+
STACK_OF(X509)* untrusted = NULL;
835+
836+
/* Mixed trusted-store + untrusted-chain partial-chain test: the store
837+
* trusts an intermediate (x509CaInt2, the leaf's direct issuer), while
838+
* an additional intermediate (x509CaInt) is supplied only as untrusted
839+
* via the chain argument. With X509_V_FLAG_PARTIAL_CHAIN, verification
840+
* must succeed by terminating at the trusted intermediate. This test
841+
* exercises the snapshot-based trust check in X509StoreCertIsTrusted:
842+
* the untrusted intermediate injected during verification must not be
843+
* treated as a trust anchor, but the intermediate already in the store
844+
* must be. */
845+
ExpectNotNull(store = X509_STORE_new());
846+
ExpectIntEQ(X509_STORE_add_cert(store, testData->x509CaInt2), 1);
847+
ExpectIntEQ(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN), 1);
848+
849+
ExpectNotNull(untrusted = sk_X509_new_null());
850+
ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt), 0);
851+
852+
ExpectNotNull(ctx = X509_STORE_CTX_new());
853+
ExpectIntEQ(X509_STORE_CTX_init(ctx, store, testData->x509Leaf, untrusted),
854+
1);
855+
/* Must verify: chain terminates at trusted intermediate in the store. */
856+
ExpectIntEQ(X509_verify_cert(ctx), 1);
857+
ExpectIntEQ(X509_STORE_CTX_get_error(ctx), X509_V_OK);
858+
859+
X509_STORE_CTX_free(ctx);
860+
X509_STORE_free(store);
861+
sk_X509_free(untrusted);
862+
return EXPECT_RESULT();
863+
}
864+
865+
static int test_wolfSSL_X509_STORE_CTX_ex_partial_chain_untrusted_terminal(
866+
X509_STORE_test_data *testData)
867+
{
868+
EXPECT_DECLS;
869+
X509_STORE* store = NULL;
870+
X509_STORE_CTX* ctx = NULL;
871+
STACK_OF(X509)* untrusted = NULL;
872+
873+
/* Partial-chain boundary test: the store trusts a CA (x509Ca) that is
874+
* NOT reachable from the leaf given the supplied untrusted intermediates,
875+
* and an untrusted intermediate (x509CaInt2) IS the terminal of the
876+
* (truncated) chain. With X509_V_FLAG_PARTIAL_CHAIN set, verification
877+
* must FAIL because the chain terminates at an untrusted certificate.
878+
*
879+
* This test specifically targets the snapshot-based trust check in
880+
* X509StoreCertIsTrusted. Before addAllButSelfSigned injects
881+
* x509CaInt2, origTrustedSk is snapshotted from the caller-trusted set
882+
* and contains only x509Ca. When the chain terminates at x509CaInt2,
883+
* the trust check consults origTrustedSk (not the mutated working
884+
* stack) and correctly finds no match. A regression that consulted
885+
* the post-injection working stack instead of the snapshot would
886+
* incorrectly mark x509CaInt2 as trusted and cause verification to
887+
* succeed. */
888+
ExpectNotNull(store = X509_STORE_new());
889+
ExpectIntEQ(X509_STORE_add_cert(store, testData->x509Ca), 1);
890+
ExpectIntEQ(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN), 1);
891+
892+
/* Only x509CaInt2 supplied as untrusted; x509CaInt is intentionally
893+
* withheld so the chain cannot actually reach the trusted x509Ca. */
894+
ExpectNotNull(untrusted = sk_X509_new_null());
895+
ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt2), 0);
896+
897+
ExpectNotNull(ctx = X509_STORE_CTX_new());
898+
ExpectIntEQ(X509_STORE_CTX_init(ctx, store, testData->x509Leaf, untrusted),
899+
1);
900+
/* Must NOT verify: the chain terminal (x509CaInt2) is not in the
901+
* original trust set, even though the store is non-empty. */
902+
ExpectIntNE(X509_verify_cert(ctx), 1);
903+
ExpectIntEQ(X509_STORE_CTX_get_error(ctx),
904+
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY);
905+
906+
X509_STORE_CTX_free(ctx);
907+
X509_STORE_free(store);
908+
sk_X509_free(untrusted);
909+
return EXPECT_RESULT();
910+
}
911+
788912
#ifdef HAVE_ECC
789913
static int test_wolfSSL_X509_STORE_CTX_ex12(void)
790914
{
@@ -870,6 +994,12 @@ int test_wolfSSL_X509_STORE_CTX_ex(void)
870994
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex9(&testData), 1);
871995
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex10(&testData), 1);
872996
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex11(&testData), 1);
997+
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex_partial_chain_neg(&testData), 1);
998+
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex_partial_chain_mixed(&testData),
999+
1);
1000+
ExpectIntEQ(
1001+
test_wolfSSL_X509_STORE_CTX_ex_partial_chain_untrusted_terminal(
1002+
&testData), 1);
8731003
#ifdef HAVE_ECC
8741004
ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex12(), 1);
8751005
#endif

0 commit comments

Comments
 (0)