Commit 9138c69cee for openssl.org

commit 9138c69ceec002dac9d2e435afd8e626b6e33486
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Sat Sep 5 22:02:02 2026 +0200

    cms: reject signature alg OID as digestAlgorithm

    Since 4.0 the digest fetch by OID name no longer resolves signature
    algorithm OIDs such as sha256WithRSAEncryption, so end-to-end CMS and
    PKCS7 verification already rejects messages misusing them in
    digestAlgorithm.  However, CMS_SignerInfo_verify() still falls back
    to a legacy lookup which resolves such OIDs to the underlying digest
    through aliases, so the signed attributes signature of a SignerInfo
    violating RFC 5652 this way still verified successfully.  Require
    the resolved digest type to match the OID so only genuine digest
    OIDs are accepted.

    Add an API regression test for that and recipe tests pinning the
    rejection in the CMS and PKCS7 end-to-end verification.

    Fixes #23204

    Assisted-by: Claude:claude-fable-5
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Merge-date: Wed Sep 16 14:39:41 2026
    Merged-from: https://github.com/openssl/openssl/pull/32702

diff --git a/crypto/cms/cms_sd.c b/crypto/cms/cms_sd.c
index 87dbd32553..dec83eeeec 100644
--- a/crypto/cms/cms_sd.c
+++ b/crypto/cms/cms_sd.c
@@ -1335,10 +1335,15 @@ int CMS_SignerInfo_verify(CMS_SignerInfo *si)
     (void)ERR_set_mark();
     fetched_md = EVP_MD_fetch(libctx, name, propq);

-    if (fetched_md != NULL)
+    if (fetched_md != NULL) {
         md = fetched_md;
-    else
+    } else {
         md = EVP_get_digestbyobj(si->digestAlgorithm->algorithm);
+        /* Reject aliases such as signature algorithm OIDs */
+        if (md != NULL
+            && EVP_MD_get_type(md) != OBJ_obj2nid(si->digestAlgorithm->algorithm))
+            md = NULL;
+    }
     if (md == NULL) {
         (void)ERR_clear_last_mark();
         ERR_raise(ERR_LIB_CMS, CMS_R_UNKNOWN_DIGEST_ALGORITHM);
diff --git a/test/cmsapitest.c b/test/cmsapitest.c
index a0624646bc..62bbca842e 100644
--- a/test/cmsapitest.c
+++ b/test/cmsapitest.c
@@ -331,6 +331,40 @@ static int test_CMS_add1_cert(void)
     return ret;
 }

+static int test_CMS_SignerInfo_verify_sigalg_oid(void)
+{
+    static const char msg[] = "Hello World!\r\n";
+    BIO *msgbio = NULL;
+    CMS_ContentInfo *cms = NULL;
+    CMS_SignerInfo *si;
+    X509_ALGOR *dalg = NULL;
+    int ret = 0;
+
+    if (!TEST_ptr(msgbio = BIO_new_mem_buf(msg, sizeof(msg) - 1))
+        || !TEST_ptr(cms = CMS_sign(NULL, NULL, NULL, NULL, CMS_PARTIAL))
+        || !TEST_ptr(si = CMS_add1_signer(cms, cert, privkey, EVP_sha256(), 0))
+        || !TEST_true(CMS_final(cms, msgbio, NULL, 0)))
+        goto end;
+
+    if (!TEST_int_gt(CMS_SignerInfo_verify(si), 0))
+        goto end;
+
+    /* A signature algorithm OID as digestAlgorithm must not verify */
+    CMS_SignerInfo_get0_algs(si, NULL, NULL, &dalg, NULL);
+    if (!TEST_true(X509_ALGOR_set0(dalg,
+            OBJ_nid2obj(NID_sha256WithRSAEncryption),
+            V_ASN1_UNDEF, NULL))
+        || !TEST_int_le(CMS_SignerInfo_verify(si), 0))
+        goto end;
+
+    ret = 1;
+end:
+    ERR_clear_error();
+    CMS_ContentInfo_free(cms);
+    BIO_free(msgbio);
+    return ret;
+}
+
 static int test_CMS_add1_signer_ed448(const EVP_MD *md, unsigned int flags,
     int expect_success)
 {
@@ -1001,6 +1035,7 @@ int setup_tests(void)
     ADD_TEST(test_short_mac_on_auth_envelope_data);
     ADD_TEST(test_CMS_add_standard_smimecap_ex);
     ADD_TEST(test_CMS_add1_cert);
+    ADD_TEST(test_CMS_SignerInfo_verify_sigalg_oid);
     ADD_TEST(test_d2i_CMS_bio_NULL);
     ADD_TEST(test_CMS_set1_key_mem_leak);
     ADD_TEST(test_encrypted_data);
diff --git a/test/recipes/80-test_cms.t b/test/recipes/80-test_cms.t
index e92c9fcc9e..634ddbb6e3 100644
--- a/test/recipes/80-test_cms.t
+++ b/test/recipes/80-test_cms.t
@@ -13,7 +13,7 @@ use warnings;
 use POSIX;
 use File::Spec::Functions qw/catfile/;
 use File::Compare qw/compare_text compare/;
-use OpenSSL::Test qw/:DEFAULT srctop_dir srctop_file bldtop_dir bldtop_file with data_file/;
+use OpenSSL::Test qw/:DEFAULT srctop_dir srctop_file bldtop_dir bldtop_file with data_file slurp_file/;

 use OpenSSL::Test::Utils;

@@ -56,7 +56,7 @@ my ($no_des, $no_dh, $no_dsa, $no_ec, $no_ec2m, $no_rc2, $no_zlib)

 $no_rc2 = 1 if disabled("legacy");

-plan tests => 42;
+plan tests => 43;

 ok(run(test(["pkcs7_test", srctop_file("test", "certs", "servercert.pem"),
              srctop_file("test", "certs", "serverkey.pem")])), "test pkcs7");
@@ -1093,6 +1093,44 @@ subtest "CMS parse authenticatedData authAttrs and unauthAttrs\n" => sub {
        "unauthAttrs parsed as SET OF Attribute");
 };

+# Replace all occurrences of a DER encoded OID in a file
+sub replace_der_oid {
+    my ($file, $from_hex, $to_hex) = @_;
+    my $from = pack("H*", $from_hex);
+    my $to = pack("H*", $to_hex);
+    my $der = slurp_file($file, binary => 1);
+
+    $der =~ s/\Q$from\E/$to/g;
+    open(my $fh, ">", $file) or die "Cannot write $file: $!";
+    binmode $fh;
+    print $fh $der;
+    close($fh);
+}
+
+subtest "reject signature algorithm OID as digestAlgorithm\n" => sub {
+    plan tests => 8;
+
+    foreach my $app ("cms", "smime") {
+        foreach my $attrs ("attrs", "noattr") {
+            my @noattr = $attrs eq "noattr" ? ("-noattr") : ();
+            my $sig = "digalg-$app-$attrs.der";
+
+            ok(run(app(["openssl", $app, @defaultprov, "-sign", "-in", $smcont,
+                        "-outform", "DER", "-nodetach", "-md", "sha256",
+                        @noattr, "-signer", $smrsa1, "-out", $sig])),
+               "sign ($app, $attrs)");
+
+            # Replace the sha256 OID with sha256WithRSAEncryption
+            replace_der_oid($sig, "608648016503040201", "2a864886f70d01010b");
+
+            ok(!run(app(["openssl", $app, @defaultprov, "-verify", "-noverify",
+                         "-in", $sig, "-inform", "DER",
+                         "-out", "$sig.txt"])),
+               "must not verify with signature algorithm OID ($app, $attrs)");
+        }
+    }
+};
+
 subtest "CAdES <=> CAdES consistency tests\n" => sub {
     plan tests => (scalar @smime_cms_cades_tests);