diff options
| author | Igor Ustinov <[email protected]> | 2026-10-20 12:39:47 +0200 |
|---|---|---|
| committer | Tomas Mraz <[email protected]> | 2026-10-21 17:38:13 +0200 |
| commit | d218dcf1bc23916db58c386c656c213da193e0f6 (patch) | |
| tree | 9b2aa05c4916eb0c9e6f26fefac1fa1c4dc30028 | |
| parent | CMS_verify() stops at the first failure without CMS_VERIFY_PARTIAL (diff) | |
| download | openssl-d218dcf1bc23916db58c386c656c213da193e0f6.tar.gz openssl-d218dcf1bc23916db58c386c656c213da193e0f6.zip | |
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 <[email protected]>
Reviewed-by: Matt Caswell <[email protected]>
Merge-date: Wed Oct 21 15:39:29 2026
Merged-from: https://github.com/openssl/openssl/pull/33279
| -rw-r--r-- | crypto/cms/cms_sd.c | 2 | ||||
| -rw-r--r-- | doc/man3/CMS_verify.pod | 6 | ||||
| -rw-r--r-- | test/cmsapitest.c | 8 | ||||
| -rw-r--r-- | test/recipes/80-test_cms.t | 8 |
4 files changed, 21 insertions, 3 deletions
diff --git a/crypto/cms/cms_sd.c b/crypto/cms/cms_sd.c index f0d581e8eda..f0a2ee1229e 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 92e1012210b..958f6d87cbe 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 eff78841424..a38793c3c8a 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 41cfcee2781..2f5af7d8717 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', |
