diff --git a/src/x509_str.c b/src/x509_str.c index 90113caede..354f21c419 100644 --- a/src/x509_str.c +++ b/src/x509_str.c @@ -552,6 +552,48 @@ static int X509VerifyCertSetupRetry(WOLFSSL_X509_STORE_CTX* ctx, return ret; } +/* Returns 1 if x509's DER matches an entry in either the head [0, origNum) + * slice of trustedCerts (which excludes intermediates 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. */ +static int X509StoreCertIsTrusted(WOLFSSL_X509_STORE* store, + WOLFSSL_X509* x509, WOLF_STACK_OF(WOLFSSL_X509)* trustedCerts, + int origNum) +{ + int i; + int n; + WOLFSSL_X509* cur; + + if (x509 == NULL || x509->derCert == NULL) + return 0; + + for (i = 0; i < origNum; i++) { + cur = wolfSSL_sk_X509_value(trustedCerts, i); + if (cur != NULL && cur->derCert != NULL && + cur->derCert->length == x509->derCert->length && + XMEMCMP(cur->derCert->buffer, x509->derCert->buffer, + x509->derCert->length) == 0) { + return 1; + } + } + + if (store != NULL && store->trusted != NULL) { + n = wolfSSL_sk_X509_num(store->trusted); + for (i = 0; i < n; i++) { + cur = wolfSSL_sk_X509_value(store->trusted, i); + if (cur != NULL && cur->derCert != NULL && + cur->derCert->length == x509->derCert->length && + XMEMCMP(cur->derCert->buffer, x509->derCert->buffer, + x509->derCert->length) == 0) { + return 1; + } + } + } + + return 0; +} + /* Verifies certificate chain using WOLFSSL_X509_STORE_CTX * returns 1 on success or <= 0 on failure. */ @@ -565,6 +607,7 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) int numFailedCerts = 0; int depth = 0; int origDepth = 0; + int origCertsNum = 0; WOLFSSL_X509 *issuer = NULL; WOLFSSL_X509 *orig = NULL; WOLF_STACK_OF(WOLFSSL_X509)* certs = NULL; @@ -587,8 +630,14 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) wolfSSL_sk_X509_num(ctx->ctxIntermediates) > 0) { certsToUse = wolfSSL_sk_X509_new_null(); ret = addAllButSelfSigned(certsToUse, ctx->ctxIntermediates, NULL); + /* certsToUse holds only injected intermediates, none are trusted. */ + origCertsNum = 0; } else { + /* Snapshot the count of pre-existing entries before injecting the + * caller-supplied untrusted intermediates. Only the entries already + * present count as trusted for the partial-chain check below. */ + origCertsNum = (certs != NULL) ? wolfSSL_sk_X509_num(certs) : 0; /* Add the intermediates provided on init to the list of untrusted * intermediates to be used */ ret = addAllButSelfSigned(certs, ctx->ctxIntermediates, &numInterAdd); @@ -677,9 +726,17 @@ int wolfSSL_X509_verify_cert(WOLFSSL_X509_STORE_CTX* ctx) * a trusted CA in the CM */ ret = X509StoreVerifyCert(ctx); if (ret != WOLFSSL_SUCCESS) { + /* WOLFSSL_PARTIAL_CHAIN may only terminate the chain at a + * certificate the caller actually trusts. The previous + * "added == 1" guard merely confirmed that some untrusted + * intermediate had been temporarily loaded into the + * CertManager during chain building, which would accept + * chains that never reach a trust anchor. Verify that + * ctx->current_cert is itself in the original trust set. */ if (((ctx->flags & WOLFSSL_PARTIAL_CHAIN) || (ctx->store->param->flags & WOLFSSL_PARTIAL_CHAIN)) && - (added == 1)) { + X509StoreCertIsTrusted(ctx->store, ctx->current_cert, + certs, origCertsNum)) { wolfSSL_sk_X509_push(ctx->chain, ctx->current_cert); ret = WOLFSSL_SUCCESS; } else { diff --git a/tests/api/test_ossl_x509_str.c b/tests/api/test_ossl_x509_str.c index 99b82877c3..12f4977d6d 100644 --- a/tests/api/test_ossl_x509_str.c +++ b/tests/api/test_ossl_x509_str.c @@ -785,6 +785,42 @@ static int test_wolfSSL_X509_STORE_CTX_ex11(X509_STORE_test_data *testData) return EXPECT_RESULT(); } +static int test_wolfSSL_X509_STORE_CTX_ex_partial_chain_neg( + X509_STORE_test_data *testData) +{ + EXPECT_DECLS; + X509_STORE* store = NULL; + X509_STORE_CTX* ctx = NULL; + STACK_OF(X509)* untrusted = NULL; + + /* Negative partial-chain test: with X509_V_FLAG_PARTIAL_CHAIN set, the + * intermediates are supplied ONLY as untrusted (passed through the + * X509_STORE_CTX_init "chain" argument and never added to the store). + * No certificate in the chain is in the store, so verification must + * fail. Pre-fix, wolfSSL_X509_verify_cert would incorrectly accept + * this chain because its partial-chain fallback only checked that some + * intermediate had been temporarily loaded into the CertManager, not + * that any chain certificate was actually trusted. */ + ExpectNotNull(store = X509_STORE_new()); + /* Intentionally do NOT add x509CaInt, x509CaInt2, or x509Ca. */ + ExpectIntEQ(X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN), 1); + + ExpectNotNull(untrusted = sk_X509_new_null()); + ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt2), 0); + ExpectIntGT(sk_X509_push(untrusted, testData->x509CaInt), 0); + + ExpectNotNull(ctx = X509_STORE_CTX_new()); + ExpectIntEQ(X509_STORE_CTX_init(ctx, store, testData->x509Leaf, untrusted), + 1); + /* Must NOT verify: partial-chain does not relax the trust requirement. */ + ExpectIntNE(X509_verify_cert(ctx), 1); + + X509_STORE_CTX_free(ctx); + X509_STORE_free(store); + sk_X509_free(untrusted); + return EXPECT_RESULT(); +} + #ifdef HAVE_ECC static int test_wolfSSL_X509_STORE_CTX_ex12(void) { @@ -870,6 +906,7 @@ int test_wolfSSL_X509_STORE_CTX_ex(void) ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex9(&testData), 1); ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex10(&testData), 1); ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex11(&testData), 1); + ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex_partial_chain_neg(&testData), 1); #ifdef HAVE_ECC ExpectIntEQ(test_wolfSSL_X509_STORE_CTX_ex12(), 1); #endif