Commit d218dcf1bc for openssl.org
commit d218dcf1bc23916db58c386c656c213da193e0f6
Author: Igor Ustinov <igus@openssl.foundation>
Date: Tue Oct 20 12:39:47 2026 +0200
CMS_get0_signers() returns only the signers that passed verification
With CMS_VERIFY_PARTIAL, CMS_verify() succeeds as long as at least
one SignerInfo verifies, but CMS_get0_signers() still returned the
certificate of every SignerInfo, defeating "signed by X" or
"signed by N signers" policies. The same applied to the certificates
written by "openssl cms -verify -verify_partial -signer".
This commit fixes CMS_get0_signers() behaviour returning only
certificates of signers that passed verification.
Nothing changes if CMS_VERIFY_PARTIAL isn't set.
Reported-by: Anthropic OSS scan id ANT-2026-GF4OIPVH
Assisted-by: Claude:claude-opus-5-5
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Merge-date: Wed Oct 21 15:39:29 2026
Merged-from: https://github.com/openssl/openssl/pull/33279
diff --git a/crypto/cms/cms_sd.c b/crypto/cms/cms_sd.c
index f0d581e8ed..f0a2ee1229 100644
--- a/crypto/cms/cms_sd.c
+++ b/crypto/cms/cms_sd.c
@@ -847,7 +847,7 @@ STACK_OF(X509) *CMS_get0_signers(CMS_ContentInfo *cms)
sinfos = CMS_get0_SignerInfos(cms);
for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) {
si = sk_CMS_SignerInfo_value(sinfos, i);
- if (si->signer != NULL) {
+ if (si->signer != NULL && si->verify_result) {
if (!ossl_x509_add_cert_new(&signers, si->signer,
X509_ADD_FLAG_DEFAULT)) {
sk_X509_free(signers);
diff --git a/doc/man3/CMS_verify.pod b/doc/man3/CMS_verify.pod
index 92e1012210..958f6d87cb 100644
--- a/doc/man3/CMS_verify.pod
+++ b/doc/man3/CMS_verify.pod
@@ -60,6 +60,9 @@ are used when retrieving algorithms from providers.
CMS_get0_signers() retrieves the signer certificate(s) from I<cms>; it may only
be called after a successful CMS_verify() or CMS_SignedData_verify() operation.
+Only the certificates of signatures that passed verification are returned.
+This matters when B<CMS_VERIFY_PARTIAL> is set, where the verification may
+succeed even though some of the signatures failed.
CMS_SignerInfo_verify() verifies the signed attributes attached to the given
SignerInfo B<si>.
@@ -172,7 +175,8 @@ CMS_verify() returns 1 for a successful verification and 0 if an error occurred.
CMS_SignedData_verify() returns a memory BIO containing the verified content,
or NULL on error.
-CMS_get0_signers() returns all signers or NULL if an error occurred.
+CMS_get0_signers() returns the certificates of all signers whose signatures
+passed verification, or NULL if an error occurred.
The error can be obtained from L<ERR_get_error(3)>.
diff --git a/test/cmsapitest.c b/test/cmsapitest.c
index eff7884142..a38793c3c8 100644
--- a/test/cmsapitest.c
+++ b/test/cmsapitest.c
@@ -752,6 +752,7 @@ static int test_CMS_verify_result_partial_independence(void)
EVP_PKEY *pkey1 = NULL, *pkey2 = NULL;
X509 *cert1 = NULL, *local_cert2 = NULL;
STACK_OF(CMS_SignerInfo) *sinfos;
+ STACK_OF(X509) *signers = NULL;
CMS_SignerInfo *si1, *si2;
ASN1_OCTET_STRING *sig;
int ret = 0;
@@ -830,6 +831,12 @@ static int test_CMS_verify_result_partial_independence(void)
|| !TEST_size_t_gt(BIO_ctrl_pending(out), 0))
goto end;
+ /* Only the signer whose signature verified may be reported */
+ if (!TEST_ptr(signers = CMS_get0_signers(cms))
+ || !TEST_int_eq(sk_X509_num(signers), 1)
+ || !TEST_int_eq(X509_cmp(sk_X509_value(signers, 0), local_cert2), 0))
+ 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))
@@ -858,6 +865,7 @@ static int test_CMS_verify_result_partial_independence(void)
ret = 1;
end:
+ sk_X509_free(signers);
CMS_ContentInfo_free(cms);
X509_STORE_free(store);
X509_free(cert1);
diff --git a/test/recipes/80-test_cms.t b/test/recipes/80-test_cms.t
index 41cfcee278..2f5af7d871 100644
--- a/test/recipes/80-test_cms.t
+++ b/test/recipes/80-test_cms.t
@@ -1848,13 +1848,14 @@ subtest "PWRI missing keyDerivationAlgorithm regression" => sub {
};
subtest "sign and verify with multiple keys and -verify_partial" => sub {
- plan tests => 9;
+ plan tests => 10;
my $smrsa2 = catfile($smdir, "smrsa2.pem");
my $sig1 = "sig1.cms";
my $out1 = "out1.txt";
my $sig2 = "sig2.cms";
my $out2 = "out2.txt";
+ my $signers = "signers.pem";
ok(run(app(['openssl', 'cms',
@defaultprov,
@@ -1898,9 +1899,14 @@ subtest "sign and verify with multiple keys and -verify_partial" => sub {
'-verify', '-in', $sig2, '-inform', 'DER',
'-CAfile', $smrsa1, '-partial_chain',
'-verify_partial',
+ '-signer', $signers,
'-out', $out2,
])),
"verify two signatures with only rsa1");
+ # only the certificate of the verified signature may be written out
+ is(scalar(grep { /^-----BEGIN CERTIFICATE-----/ }
+ do { open(my $fh, '<', $signers) or die; <$fh> }), 1,
+ "only the verified signer is output");
# because the smrsa2 signature can be verified, overall verification succeeds
ok(run(app(['openssl', 'cms',