PKCS7: tighten signature presence check in PKCS7_verify

Ensure a signer signature is actually verified before reporting a
PKCS7 SignedData object as verified, and add a regression test.
This commit is contained in:
Tobias Frauenschläger
2026-06-16 13:03:59 +02:00
parent c685293c92
commit d382439c7c
3 changed files with 110 additions and 0 deletions
+13
View File
@@ -816,6 +816,19 @@ int wolfSSL_PKCS7_verify(PKCS7* pkcs7, WOLFSSL_STACK* certs,
if (ret != 0)
return WOLFSSL_FAILURE;
/* Reject a degenerate (certs-only) PKCS#7 with no verified signer. Such an
* object has empty signerInfos, so wc_PKCS7_VerifySignedData() succeeds
* without authenticating the content. pkcs7.verifyCert is only set once a
* signer's signature has actually been verified, so a NULL value here means
* the content carries no valid signature and must not be reported as
* verified - regardless of PKCS7_NOVERIFY, which only suppresses signer
* certificate chain validation, not the requirement that a signature exist.
*/
if (p7->pkcs7.verifyCert == NULL) {
WOLFSSL_MSG("PKCS7 has no verified signer (degenerate/certs-only)");
return WOLFSSL_FAILURE;
}
if ((flags & PKCS7_NOVERIFY) != PKCS7_NOVERIFY) {
/* Verify signer certificates */
if (store == NULL || store->cm == NULL) {
+95
View File
@@ -675,6 +675,101 @@ int test_wolfSSL_PKCS7_verify_signer_forgery(void)
return EXPECT_RESULT();
}
/* A degenerate (certs-only) PKCS#7 - one with an empty signerInfos SET and
* therefore no signature at all - must NOT be reported as verified, even when
* the embedded certificate chains to a trusted CA, and even when
* PKCS7_NOVERIFY is set (which only suppresses signer-cert chain validation,
* not the requirement that a signature exist). */
int test_wolfSSL_PKCS7_verify_degenerate(void)
{
EXPECT_DECLS;
#if defined(OPENSSL_ALL) && defined(HAVE_PKCS7) && !defined(NO_BIO) && \
!defined(NO_FILESYSTEM) && !defined(NO_RSA)
const char* signerCertFile = "./certs/server-cert.pem";
const char* caFile = "./certs/ca-cert.pem";
const byte attackerContent[] = "ATTACKER-CONTENT";
PKCS7* encodeP7 = NULL;
PKCS7* p7 = NULL;
X509* signerCert = NULL;
X509* caCert = NULL;
STACK_OF(X509)* sk = NULL;
X509_STORE* store = NULL;
BIO* certBio = NULL;
BIO* caBio = NULL;
BIO* derBio = NULL;
BIO* inBio = NULL;
BIO* outBio = NULL;
const byte* der = NULL;
const byte* outData = NULL;
int derSz = 0;
/* ---- Build a degenerate certs-only PKCS#7 wrapping server-cert. ---- */
ExpectNotNull(certBio = BIO_new_file(signerCertFile, "r"));
ExpectNotNull(signerCert = PEM_read_bio_X509(certBio, NULL, 0, NULL));
ExpectNotNull(sk = sk_X509_new_null());
ExpectIntGT(sk_X509_push(sk, signerCert), 0);
if (EXPECT_SUCCESS())
signerCert = NULL; /* now owned by sk */
ExpectNotNull(encodeP7 = PKCS7_new());
if (encodeP7 != NULL) {
encodeP7->version = 1;
#ifdef NO_SHA
encodeP7->hashOID = SHA256h;
#else
encodeP7->hashOID = SHAh;
#endif
}
ExpectNotNull(derBio = BIO_new(BIO_s_mem()));
/* wolfSSL_PKCS7_encode_certs() takes ownership of sk (sets p7->certs, freed
* by PKCS7_free(encodeP7) below) whenever it is called with a valid PKCS7
* and BIO - success or failure. It only declines ownership when encodeP7 or
* derBio is NULL, in which case the test still owns sk and frees it in
* cleanup. */
ExpectIntEQ(wolfSSL_PKCS7_encode_certs(encodeP7, sk, derBio), 1);
if (encodeP7 != NULL && derBio != NULL)
sk = NULL; /* now owned by encodeP7 */
ExpectIntGT((derSz = BIO_get_mem_data(derBio, &der)), 0);
/* ---- Re-parse it; a degenerate bundle parses successfully. ---- */
ExpectNotNull(p7 = d2i_PKCS7(NULL, &der, derSz));
/* ---- Trust store contains the embedded cert's issuer (ca-cert). ---- */
ExpectNotNull(caBio = BIO_new_file(caFile, "r"));
ExpectNotNull(caCert = PEM_read_bio_X509(caBio, NULL, 0, NULL));
ExpectNotNull(store = X509_STORE_new());
ExpectIntEQ(X509_STORE_add_cert(store, caCert), 1);
/* ---- Attacker-supplied detached content + capture of any output. ---- */
ExpectNotNull(inBio = BIO_new_mem_buf(attackerContent,
(int)sizeof(attackerContent) - 1));
ExpectNotNull(outBio = BIO_new(BIO_s_mem()));
/* With a trust store (flags = 0): must be rejected, and the attacker
* content must NOT be emitted through the output BIO. */
ExpectIntEQ(PKCS7_verify(p7, NULL, store, inBio, outBio, 0),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
ExpectIntLE(BIO_get_mem_data(outBio, &outData), 0);
/* PKCS7_NOVERIFY must not turn a signature-less bundle into a pass. */
ExpectIntEQ(PKCS7_verify(p7, NULL, store, inBio, NULL, PKCS7_NOVERIFY),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
PKCS7_free(p7);
PKCS7_free(encodeP7); /* frees sk + signerCert once owned */
X509_STORE_free(store);
X509_free(caCert);
sk_X509_pop_free(sk, X509_free); /* no-op if ownership passed to encodeP7 */
X509_free(signerCert); /* no-op if pushed onto sk */
BIO_free(certBio);
BIO_free(caBio);
BIO_free(derBio);
BIO_free(inBio);
BIO_free(outBio);
#endif
return EXPECT_RESULT();
}
/* Exercise the SignerInfo-sid binding enforcement end-to-end.
*
* For both supported sid encodings (v1 = IssuerAndSerialNumber, v3 =
+2
View File
@@ -29,6 +29,7 @@ int test_wolfSSL_PKCS7_certs(void);
int test_wolfSSL_PKCS7_sign(void);
int test_wolfSSL_PKCS7_verify_signer_forgery(void);
int test_wolfSSL_PKCS7_verify_sid_binding(void);
int test_wolfSSL_PKCS7_verify_degenerate(void);
int test_wolfSSL_PKCS7_SIGNED_new(void);
int test_wolfSSL_PEM_write_bio_PKCS7(void);
int test_wolfSSL_PEM_write_bio_encryptedKey(void);
@@ -42,6 +43,7 @@ int test_wolfSSL_PKCS12(void);
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PKCS7_sign), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PKCS7_verify_signer_forgery), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PKCS7_verify_sid_binding), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PKCS7_verify_degenerate), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PKCS7_SIGNED_new), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PEM_write_bio_PKCS7), \
TEST_DECL_GROUP("ossl_p7", test_wolfSSL_PEM_write_bio_encryptedKey), \