Commit 9da8d79621 for openssl.org
commit 9da8d796212c762818e9186c61a970b0ff7eeef8
Author: Sergio C.C. <sergiocarrillocobacho@gmail.com>
Date: Thu Sep 17 13:55:53 2026 +0800
Reset only verify_result across all CMS SignerInfos on failure
Fixes #32612.
CMS_verify() fails with CMS_R_SIGNER_CERTIFICATE_NOT_FOUND when a
signer certificate can't be found for one of several SignerInfos, and
in that case 'scount' ends up smaller than the total number of
SignerInfos. The err: cleanup loop only reset verify_result for the
first 'scount' SignerInfos, so the trailing ones -- never reached by
the per-signer verification loops -- kept the verify_result = 1
("so far, fine") value set by the initialization loop earlier in the
function, and were reported as verified even though their certificates
were never looked at. Fixed by iterating the reset over all
SignerInfos instead of just the first 'scount'.
CMS_verify() can also fail before any per-signer verification is
attempted, when check_content() fails on a CMS_ContentInfo that a
previous call already fully verified: in that case verify_result,
cert_verified, attr_verified and content_verified all still hold that
earlier call's genuine results and must be cleared in full. That reset
already happens separately, at the top of the function.
During review the err: cleanup loop was changed to also clear
cert_verified/attr_verified/content_verified for every SignerInfo
whenever the overall result was 0, to address the same kind of
staleness for the early-exit path above. That was too broad: those
three fields are per-signer facts established during the current
call, and clearing them for every signer whenever CMS_verify()'s
overall result is 0 makes one signer's certificate/attribute/content
result depend on whether other signers, or the all-vs-partial policy,
also succeeded. With CMS_VERIFY_PARTIAL and two independently trusted
signers, breaking the first signer's signature while the second still
verifies correctly must not disturb the second signer's fields; if the
second signer's signature is also later broken, so the whole call now
fails, both signers' cert_verified must still reflect that their
certificates genuinely re-verified during that call. The err: loop
now resets only verify_result.
Adds test_CMS_verify_result_partial_independence, covering exactly
that scenario, alongside the test_CMS_verify_result_no_signer_cert,
test_CMS_verify_result_partial_signer_cert and
test_CMS_verify_reused_after_check_content_failure regression tests
added earlier during review.
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Wed Sep 30 08:15:46 2026
Merged-from: https://github.com/openssl/openssl/pull/32643
diff --git a/crypto/cms/cms_smime.c b/crypto/cms/cms_smime.c
index 5ca8e6606c..9315c35d1c 100644
--- a/crypto/cms/cms_smime.c
+++ b/crypto/cms/cms_smime.c
@@ -355,8 +355,25 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
int cadesVerify = (flags & CMS_CADES) != 0;
const CMS_CTX *ctx = ossl_cms_get0_cmsctx(cms);
- if (dcont == NULL && !check_content(cms))
+ if (dcont == NULL && !check_content(cms)) {
+ /*
+ * A CMS_ContentInfo can be verified more than once (e.g. retried
+ * with a different store). If a previous call left verify_result
+ * == 1 ("so far, fine") or any of the *_verified flags set on a
+ * SignerInfo, this early failure must still be reflected there
+ * instead of silently keeping the stale "verified" state from the
+ * earlier call.
+ */
+ sinfos = CMS_get0_SignerInfos(cms);
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) {
+ si = sk_CMS_SignerInfo_value(sinfos, i);
+ si->verify_result = 0;
+ si->cert_verified = 0;
+ si->attr_verified = 0;
+ si->content_verified = 0;
+ }
return 0;
+ }
/* Set a mark so that we can clear any new errors on success. */
(void)ERR_set_mark();
if (dcont != NULL && !(flags & CMS_BINARY)) {
@@ -470,7 +487,7 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
tmpin = (len == 0) ? dcont : BIO_new_mem_buf(ptr, len);
if (tmpin == NULL) {
ERR_raise(ERR_LIB_CMS, ERR_R_BIO_LIB);
- goto err2;
+ goto err;
}
} else {
tmpin = dcont;
@@ -537,9 +554,14 @@ int CMS_verify(CMS_ContentInfo *cms, const STACK_OF(X509) *certs,
else
ret = n == scount; /* All must be successful */
err:
- if (!ret)
- for (i = 0; i < scount; i++)
+ /*
+ * On overall failure, verify_result must not stand at 1 ("so far, fine")
+ * for any SignerInfo.
+ */
+ if (!ret) {
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++)
sk_CMS_SignerInfo_value(sinfos, i)->verify_result = 0;
+ }
if (!(flags & SMIME_BINARY) && dcont) {
do_free_upto(cmsbio, tmpout);
if (tmpin != dcont)
@@ -556,7 +578,6 @@ err:
if (out != tmpout)
BIO_free_all(tmpout);
-err2:
if (si_chains != NULL) {
for (i = 0; i < scount; ++i)
OSSL_STACK_OF_X509_free(si_chains[i]);
diff --git a/test/cmsapitest.c b/test/cmsapitest.c
index ac34f7ef9e..f30a0be8c6 100644
--- a/test/cmsapitest.c
+++ b/test/cmsapitest.c
@@ -7,11 +7,13 @@
* https://www.openssl.org/source/license.html
*/
+#include <stdint.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/cms.h>
#include <openssl/bio.h>
+#include <openssl/err.h>
#include <openssl/x509.h>
#include "../crypto/cms/cms_local.h" /* for d.signedData and d.envelopedData */
@@ -411,6 +413,438 @@ end:
return ret;
}
+/*
+ * Regression test for GH #32612: when CMS_verify() bails out early because a
+ * signer certificate cannot be found, every SignerInfo must report
+ * CMS_VERIFY_RESULT == 0. A two-signer message with no embedded certificates,
+ * verified against an empty store, used to leave the trailing signer(s) at 1
+ * ("so far, fine") because the cleanup loop only reset the first 'scount'
+ * (== signers whose cert was found == 0 here) entries.
+ */
+static int test_CMS_verify_result_no_signer_cert(void)
+{
+ CMS_ContentInfo *cms = NULL, *cms2 = NULL;
+ BIO *in = NULL, *out = NULL, *der = NULL;
+ X509_STORE *store = NULL;
+ STACK_OF(CMS_SignerInfo) *sinfos;
+ const unsigned char *p;
+ unsigned char *derbuf = NULL;
+ long derlen;
+ int i, ret = 0;
+
+ if (!TEST_ptr(in = BIO_new_mem_buf("Hello World\n", -1))
+ || !TEST_ptr(out = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(der = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(store = X509_STORE_new()))
+ goto end;
+
+ /* two signers, neither certificate embedded */
+ if (!TEST_ptr(cms = CMS_sign(NULL, NULL, NULL, in,
+ CMS_BINARY | CMS_PARTIAL | CMS_NOCERTS))
+ || !TEST_ptr(CMS_add1_signer(cms, cert, privkey, NULL, CMS_NOCERTS))
+ || !TEST_ptr(CMS_add1_signer(cms, cert, privkey, NULL, CMS_NOCERTS))
+ || !TEST_true(CMS_final(cms, in, NULL, CMS_BINARY)))
+ goto end;
+
+ /* round-trip through DER so no in-memory signer cert pointers linger */
+ if (!TEST_true(i2d_CMS_bio(der, cms)))
+ goto end;
+ derlen = BIO_get_mem_data(der, &derbuf);
+ p = derbuf;
+ if (!TEST_ptr(cms2 = d2i_CMS_ContentInfo(NULL, &p, derlen)))
+ goto end;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms2, NULL, store, NULL, out,
+ CMS_BINARY | CMS_VERIFY_PARTIAL),
+ 0)
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ CMS_R_SIGNER_CERTIFICATE_NOT_FOUND))
+ goto end;
+
+ sinfos = CMS_get0_SignerInfos(cms2);
+ if (!TEST_int_eq(sk_CMS_SignerInfo_num(sinfos), 2))
+ goto end;
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) {
+ CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sinfos, i);
+
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si,
+ CMS_VERIFY_RESULT),
+ 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si,
+ CMS_VERIFY_CERT),
+ 0))
+ goto end;
+ }
+
+ ret = 1;
+end:
+ ERR_clear_error();
+ CMS_ContentInfo_free(cms);
+ CMS_ContentInfo_free(cms2);
+ X509_STORE_free(store);
+ BIO_free(in);
+ BIO_free(out);
+ BIO_free(der);
+ return ret;
+}
+
+/* self-signed throwaway cert for the second signer in the test below */
+static X509 *make_self_signed_cert(EVP_PKEY *pkey, const char *cn)
+{
+ X509 *newcert = NULL, *ret = NULL;
+ X509_NAME *name = NULL;
+ ASN1_INTEGER *serial = NULL;
+
+ if (!TEST_ptr(newcert = X509_new())
+ || !TEST_true(X509_set_version(newcert, X509_VERSION_3)))
+ goto err;
+
+ if (!TEST_ptr(serial = ASN1_INTEGER_new())
+ || !TEST_true(ASN1_INTEGER_set(serial, 1))
+ || !TEST_true(X509_set_serialNumber(newcert, serial)))
+ goto err;
+
+ if (!TEST_ptr(X509_gmtime_adj(X509_getm_notBefore(newcert), 0))
+ || !TEST_ptr(X509_gmtime_adj(X509_getm_notAfter(newcert),
+ 60L * 60L * 24L * 365L)))
+ goto err;
+
+ if (!TEST_true(X509_set_pubkey(newcert, pkey)))
+ goto err;
+
+ if (!TEST_ptr(name = X509_NAME_new())
+ || !TEST_true(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+ (const unsigned char *)cn, -1, -1, 0))
+ || !TEST_true(X509_set_subject_name(newcert, name))
+ || !TEST_true(X509_set_issuer_name(newcert, name)))
+ goto err;
+
+ if (!TEST_int_gt(X509_sign(newcert, pkey, EVP_sha256()), 0))
+ goto err;
+
+ ret = newcert;
+ newcert = NULL;
+err:
+ X509_free(newcert);
+ X509_NAME_free(name);
+ ASN1_INTEGER_free(serial);
+ return ret;
+}
+
+/*
+ * Companion to test_CMS_verify_result_no_signer_cert() above, covering the
+ * mixed case (scount == 1): a two-signer message where only *one* signer's
+ * certificate is available at verification time. Both SignerInfos must
+ * report verify_result == 0. Also checks that supplying both certs makes
+ * CMS_verify() succeed.
+ */
+static int test_CMS_verify_result_partial_signer_cert(void)
+{
+ CMS_ContentInfo *cms = NULL, *cms2 = NULL;
+ BIO *in = NULL, *out = NULL, *der = NULL;
+ X509_STORE *store = NULL;
+ EVP_PKEY *pkey2 = NULL;
+ X509 *local_cert2 = NULL;
+ STACK_OF(X509) *both_certs = NULL;
+ STACK_OF(CMS_SignerInfo) *sinfos;
+ const unsigned char *p;
+ unsigned char *derbuf = NULL;
+ long derlen;
+ int i, found_verified = 0, found_unverified = 0, ret = 0;
+
+ if (!TEST_ptr(in = BIO_new_mem_buf("Hello World\n", -1))
+ || !TEST_ptr(out = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(der = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(store = X509_STORE_new())
+ || !TEST_ptr(both_certs = sk_X509_new_null()))
+ goto end;
+
+ if (!TEST_ptr(pkey2 = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)2048))
+ || !TEST_ptr(local_cert2 = make_self_signed_cert(pkey2, "second-signer")))
+ goto end;
+
+ /* two signers with two *different* certs, neither embedded */
+ if (!TEST_ptr(cms = CMS_sign(NULL, NULL, NULL, in,
+ CMS_BINARY | CMS_PARTIAL | CMS_NOCERTS))
+ || !TEST_ptr(CMS_add1_signer(cms, cert, privkey, NULL, CMS_NOCERTS))
+ || !TEST_ptr(CMS_add1_signer(cms, local_cert2, pkey2, NULL, CMS_NOCERTS))
+ || !TEST_true(CMS_final(cms, in, NULL, CMS_BINARY)))
+ goto end;
+
+ /* round-trip through DER so no in-memory signer cert pointers linger */
+ if (!TEST_true(i2d_CMS_bio(der, cms)))
+ goto end;
+ derlen = BIO_get_mem_data(der, &derbuf);
+ p = derbuf;
+ if (!TEST_ptr(cms2 = d2i_CMS_ContentInfo(NULL, &p, derlen)))
+ goto end;
+
+ /* only 'cert' (the 1st signer's) is supplied -> scount == 1, not 0 or 2 */
+ if (!TEST_int_ge(sk_X509_push(both_certs, cert), 1))
+ goto end;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms2, both_certs, store, NULL, out,
+ CMS_BINARY | CMS_VERIFY_PARTIAL),
+ 0)
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_last_error()),
+ CMS_R_SIGNER_CERTIFICATE_NOT_FOUND))
+ goto end;
+
+ sinfos = CMS_get0_SignerInfos(cms2);
+ if (!TEST_int_eq(sk_CMS_SignerInfo_num(sinfos), 2))
+ goto end;
+ /*
+ * Neither signer must report success: CMS_verify() overall failed with
+ * CMS_R_SIGNER_CERTIFICATE_NOT_FOUND before any content/attr verification
+ * ran, so both entries must still be at verify_result == 0, regardless
+ * of whether that particular signer's own cert was found.
+ */
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) {
+ CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sinfos, i);
+
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si,
+ CMS_VERIFY_RESULT),
+ 0))
+ goto end;
+ }
+
+ /* now supply both certs: verification of both signers must succeed */
+ if (!TEST_int_ge(sk_X509_push(both_certs, local_cert2), 1))
+ goto end;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms2, both_certs, store, NULL, out,
+ CMS_BINARY | CMS_VERIFY_PARTIAL
+ | CMS_NO_SIGNER_CERT_VERIFY),
+ 1))
+ goto end;
+
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) {
+ CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sinfos, i);
+ int r = CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_RESULT);
+
+ if (r)
+ found_verified++;
+ else
+ found_unverified++;
+ }
+ if (!TEST_int_eq(found_verified, 2) || !TEST_int_eq(found_unverified, 0))
+ goto end;
+
+ ret = 1;
+end:
+ CMS_ContentInfo_free(cms);
+ CMS_ContentInfo_free(cms2);
+ X509_STORE_free(store);
+ /* both_certs does not own cert/local_cert2 (no _UP_REF push), just free the stack */
+ sk_X509_free(both_certs);
+ X509_free(local_cert2);
+ EVP_PKEY_free(pkey2);
+ BIO_free(in);
+ BIO_free(out);
+ BIO_free(der);
+ return ret;
+}
+
+/*
+ * A CMS_ContentInfo that verified successfully once (all four per-SignerInfo
+ * fields left at 1) must not keep reporting that state if it is verified
+ * again and that second call fails via the early check_content() path at the
+ * top of CMS_verify() -- reached when the content turns out to be missing
+ * and dcont is NULL.
+ */
+static int test_CMS_verify_reused_after_check_content_failure(void)
+{
+ CMS_ContentInfo *cms = NULL;
+ BIO *in = NULL, *out = NULL;
+ X509_STORE *store = NULL;
+ EVP_PKEY *pkey2 = NULL;
+ X509 *local_cert2 = NULL;
+ CMS_SignerInfo *si;
+ STACK_OF(CMS_SignerInfo) *sinfos;
+ ASN1_OCTET_STRING *saved_econtent = NULL;
+ int ret = 0;
+
+ /*
+ * Self-signed cert added directly to the store: chain building succeeds
+ * trivially, so the first CMS_verify() below genuinely sets
+ * cert_verified (unlike the other two tests above, which use
+ * CMS_NO_SIGNER_CERT_VERIFY and never actually exercise that field).
+ */
+ if (!TEST_ptr(in = BIO_new_mem_buf("Hello World\n", -1))
+ || !TEST_ptr(out = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(store = X509_STORE_new())
+ || !TEST_ptr(pkey2 = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)2048))
+ || !TEST_ptr(local_cert2 = make_self_signed_cert(pkey2, "reused-cms-signer"))
+ || !TEST_true(X509_STORE_add_cert(store, local_cert2)))
+ goto end;
+
+ /* one signer, cert embedded, content embedded (not detached) */
+ if (!TEST_ptr(cms = CMS_sign(local_cert2, pkey2, NULL, in, CMS_BINARY)))
+ goto end;
+
+ /*
+ * First verification succeeds on this exact in-memory CMS_ContentInfo
+ * (no DER round-trip): 'cert' is directly in the trust store, so
+ * verify_result, cert_verified, attr_verified and content_verified all
+ * land on 1 for the single signer.
+ */
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out, CMS_BINARY), 1))
+ goto end;
+
+ sinfos = CMS_get0_SignerInfos(cms);
+ if (!TEST_int_eq(sk_CMS_SignerInfo_num(sinfos), 1))
+ goto end;
+ si = sk_CMS_SignerInfo_value(sinfos, 0);
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_RESULT), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_CERT), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_ATTR), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_CONTENT), 1))
+ goto end;
+
+ /*
+ * Corrupt the embedded content in place on the same, still-"verified"
+ * object so the next call takes the check_content() early-return path
+ * (dcont == NULL and no eContent) instead of the main body / err label.
+ */
+ saved_econtent = cms->d.signedData->encapContentInfo->eContent;
+ cms->d.signedData->encapContentInfo->eContent = NULL;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out, CMS_BINARY), 0))
+ goto end;
+
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_RESULT), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_CERT), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_ATTR), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si, CMS_VERIFY_CONTENT), 0))
+ goto end;
+
+ ret = 1;
+end:
+ /* put the real content back so CMS_ContentInfo_free() doesn't leak it */
+ if (cms != NULL && saved_econtent != NULL)
+ cms->d.signedData->encapContentInfo->eContent = saved_econtent;
+ CMS_ContentInfo_free(cms);
+ X509_STORE_free(store);
+ X509_free(local_cert2);
+ EVP_PKEY_free(pkey2);
+ BIO_free(in);
+ BIO_free(out);
+ return ret;
+}
+
+/*
+ * With two independently trusted signers and CMS_VERIFY_PARTIAL, one
+ * signer's signature failure must not affect the *other* signer's per-field
+ * results -- including once that other signer also fails and the overall
+ * call fails too.
+ */
+static int test_CMS_verify_result_partial_independence(void)
+{
+ CMS_ContentInfo *cms = NULL;
+ BIO *in = NULL, *out = NULL;
+ X509_STORE *store = NULL;
+ EVP_PKEY *pkey1 = NULL, *pkey2 = NULL;
+ X509 *cert1 = NULL, *local_cert2 = NULL;
+ STACK_OF(CMS_SignerInfo) *sinfos;
+ CMS_SignerInfo *si1, *si2;
+ ASN1_OCTET_STRING *sig;
+ int ret = 0;
+
+ /*
+ * Two independent, self-signed certs added directly to the trust store:
+ * chain building for each is trivial (issuer == self, already trusted),
+ * so both genuinely get cert_verified == 1, unlike the global 'cert'
+ * (issued by a separate test CA whose issuer isn't in this store).
+ */
+ if (!TEST_ptr(in = BIO_new_mem_buf("Hello World\n", -1))
+ || !TEST_ptr(out = BIO_new(BIO_s_mem()))
+ || !TEST_ptr(store = X509_STORE_new())
+ || !TEST_ptr(pkey1 = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)2048))
+ || !TEST_ptr(cert1 = make_self_signed_cert(pkey1, "first-independent-signer"))
+ || !TEST_ptr(pkey2 = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)2048))
+ || !TEST_ptr(local_cert2 = make_self_signed_cert(pkey2, "second-independent-signer"))
+ || !TEST_true(X509_STORE_add_cert(store, cert1))
+ || !TEST_true(X509_STORE_add_cert(store, local_cert2)))
+ goto end;
+
+ /* two signers, both certs embedded, both directly trusted */
+ if (!TEST_ptr(cms = CMS_sign(NULL, NULL, NULL, in, CMS_BINARY | CMS_PARTIAL))
+ || !TEST_ptr(CMS_add1_signer(cms, cert1, pkey1, NULL, 0))
+ || !TEST_ptr(CMS_add1_signer(cms, local_cert2, pkey2, NULL, 0))
+ || !TEST_true(CMS_final(cms, in, NULL, CMS_BINARY)))
+ goto end;
+
+ sinfos = CMS_get0_SignerInfos(cms);
+ if (!TEST_int_eq(sk_CMS_SignerInfo_num(sinfos), 2))
+ goto end;
+ si1 = sk_CMS_SignerInfo_value(sinfos, 0);
+ si2 = sk_CMS_SignerInfo_value(sinfos, 1);
+
+ /* break signer 1's signature over the signed attributes */
+ if (!TEST_ptr(sig = CMS_SignerInfo_get0_signature(si1))
+ || !TEST_size_t_gt(ASN1_STRING_get_length(sig), 0))
+ goto end;
+ ((uint8_t *)ASN1_STRING_get0_data(sig))[0] ^= 0xff;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out,
+ CMS_BINARY | CMS_VERIFY_PARTIAL),
+ 1))
+ goto end;
+
+ /*
+ * Signer 1 failed and signer 2 succeeded (partial verify), but signer
+ * 1's certificate genuinely re-verified during this call and must
+ * still report so.
+ */
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_RESULT), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_CERT), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_RESULT), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1))
+ goto end;
+
+ /* now also break signer 2's signature: the overall call must now fail */
+ if (!TEST_ptr(sig = CMS_SignerInfo_get0_signature(si2))
+ || !TEST_size_t_gt(ASN1_STRING_get_length(sig), 0))
+ goto end;
+ ((uint8_t *)ASN1_STRING_get0_data(sig))[0] ^= 0xff;
+
+ ERR_clear_error();
+ if (!TEST_int_eq(CMS_verify(cms, NULL, store, NULL, out,
+ CMS_BINARY | CMS_VERIFY_PARTIAL),
+ 0))
+ goto end;
+
+ /*
+ * Both signers now fail overall (verify_result == 0), but both
+ * certificates were genuinely re-verified during this same call: that
+ * per-signer fact must not be erased just because the all-vs-partial
+ * policy made the whole call fail.
+ */
+ if (!TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_RESULT), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si1, CMS_VERIFY_CERT), 1)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_RESULT), 0)
+ || !TEST_int_eq(CMS_SignerInfo_get_verification_result(si2, CMS_VERIFY_CERT), 1))
+ goto end;
+
+ ret = 1;
+end:
+ CMS_ContentInfo_free(cms);
+ X509_STORE_free(store);
+ X509_free(cert1);
+ EVP_PKEY_free(pkey1);
+ X509_free(local_cert2);
+ EVP_PKEY_free(pkey2);
+ BIO_free(in);
+ BIO_free(out);
+ return ret;
+}
+
static int test_CMS_add1_signer_ed448(const EVP_MD *md, unsigned int flags,
int expect_success)
{
@@ -1094,6 +1528,10 @@ int setup_tests(void)
ADD_TEST(test_decrypt_with_wrong_key);
ADD_TEST(test_CMS_add1_cert);
ADD_TEST(test_CMS_SignerInfo_verify_sigalg_oid);
+ ADD_TEST(test_CMS_verify_result_no_signer_cert);
+ ADD_TEST(test_CMS_verify_result_partial_signer_cert);
+ ADD_TEST(test_CMS_verify_reused_after_check_content_failure);
+ ADD_TEST(test_CMS_verify_result_partial_independence);
ADD_TEST(test_d2i_CMS_bio_NULL);
ADD_TEST(test_CMS_set1_key_mem_leak);
ADD_TEST(test_encrypted_data);