@@ -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
789913static 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