#32611·openssl

[4.1] CMS_verify can return success with a certificate verification error still queued

Author: idrassiCreated Aug 31, 2026Updated Sep 16, 2026
Labelsbranch: mastertriaged: bug

I noticed this while reviewing #22285 and testing CMS_VERIFY_PARTIAL with a CMS object that has two signers. One signer verifies against the supplied store. The other doesn't.

CMS_verify returns 1, which is the result I would expect when partial verification finds one acceptable signer. The surprising part is that the rejected signer leaves CMS_R_CERTIFICATE_VERIFY_ERROR on the current thread error queue.

I cleared the queue immediately before the call to rule out an older error. ERR_peek_error was still nonzero immediately after the successful return.

I reproduced this on current master at commit acde7bc05a.

This defect was introduced by the partial verification changes in #27604. It affects the OpenSSL 4.1 development code. OpenSSL 4.0 and earlier release branches aren't affected because they don't have CMS_VERIFY_PARTIAL.

Reproducer

I created a CMS object with attached content and two embedded signer certificates. Both certificates permit CMS signing. The store passed below trusts only signer1.

The CMS object can be made with:

bash
openssl cms -sign -binary -nodetach \
    -signer signer1.pem -inkey signer1.key \
    -signer signer2.pem -inkey signer2.key \
    -outform DER -out two_signers.der < message.txt

The relevant part of my test is:

c
X509_STORE *store = X509_STORE_new();
BIO *out = BIO_new(BIO_s_null());
unsigned long queued_error;
int ret;

X509_STORE_add_cert(store, signer1);

ERR_clear_error();
ret = CMS_verify(cms, NULL, store, NULL, out,
                 CMS_BINARY | CMS_VERIFY_PARTIAL);
queued_error = ERR_peek_error();

printf("return=%d\n", ret);
printf("queued_error=%s\n",
       queued_error == 0 ? "none" : ERR_error_string(queued_error, NULL));

This prints:

return=1
queued_error=error:17000064:CMS routines::certificate verify error

The numeric error value can vary between builds, but the reason is CMS_R_CERTIFICATE_VERIFY_ERROR.

What seems to be happening

The certificate check queues an error before the signer loop decides that this particular failure can be tolerated. If another signer succeeds, the partial verification return path reports success but doesn't remove the error raised for the rejected signer.

Expected result

When partial verification returns 1, an error produced only by a signer whose failure was tolerated shouldn't remain on the error queue. An error that was already queued before CMS_verify should remain untouched.

This matters because the leftover entry can later be reported as the reason for an unrelated failure. It also makes a successful CMS_verify call look as though it failed when the queue is inspected for diagnostics.

An error queue mark around each signer check may be one way to handle this. I haven't tried to turn that idea into a patch yet.