Commit 7a441852a2 for openssl.org

commit 7a441852a23a123977ba3b5f8fc93dd42ac49d7e
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Mon May 18 20:41:54 2026 +1000

    x509: check inner/outer signatureAlgorithm match in X509_CRL_verify

    RFC 5280 section 5.1.1.2 requires the signatureAlgorithm in the outer
    CertificateList wrapper to be identical to the signature field inside
    the signed TBSCertList.  def_crl_verify() did not enforce this, unlike
    X509_verify() and X509_ACERT_verify() which both carry an X509_ALGOR_cmp
    guard.

    Add the same guard to def_crl_verify().  A mismatch raises
    X509_R_CRL_SIGNATURE_ALGORITHM_MISMATCH.  No known attack results from
    the missing check; this is a conformance and hardening fix only.

    Add a regression test: a CRL with a valid RSA-SHA256 signature over a
    TBSCertList whose inner signatureAlgorithm claims ecdsaWithSHA256 is
    now rejected.

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    MergeDate: Thu Jun 11 08:45:41 2026
    (Merged from https://github.com/openssl/openssl/pull/31213)

diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index ba558f5a9a..d993f02d69 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -1850,6 +1850,7 @@ X509_R_CANT_CHECK_DH_KEY:114:can't check dh key
 X509_R_CERTIFICATE_VERIFICATION_FAILED:139:certificate verification failed
 X509_R_CERT_ALREADY_IN_HASH_TABLE:101:cert already in hash table
 X509_R_CRL_ALREADY_DELTA:127:crl already delta
+X509_R_CRL_SIGNATURE_ALGORITHM_MISMATCH:147:crl signature algorithm mismatch
 X509_R_CRL_VERIFY_FAILURE:131:crl verify failure
 X509_R_DUPLICATE_ATTRIBUTE:140:duplicate attribute
 X509_R_ERROR_GETTING_MD_BY_NID:141:error getting md by nid
diff --git a/crypto/x509/x509_err.c b/crypto/x509/x509_err.c
index 7e21e4c707..b78f210fd4 100644
--- a/crypto/x509/x509_err.c
+++ b/crypto/x509/x509_err.c
@@ -26,6 +26,8 @@ static const ERR_STRING_DATA X509_str_reasons[] = {
     { ERR_PACK(ERR_LIB_X509, 0, X509_R_CERT_ALREADY_IN_HASH_TABLE),
         "cert already in hash table" },
     { ERR_PACK(ERR_LIB_X509, 0, X509_R_CRL_ALREADY_DELTA), "crl already delta" },
+    { ERR_PACK(ERR_LIB_X509, 0, X509_R_CRL_SIGNATURE_ALGORITHM_MISMATCH),
+        "crl signature algorithm mismatch" },
     { ERR_PACK(ERR_LIB_X509, 0, X509_R_CRL_VERIFY_FAILURE),
         "crl verify failure" },
     { ERR_PACK(ERR_LIB_X509, 0, X509_R_DUPLICATE_ATTRIBUTE),
diff --git a/crypto/x509/x_crl.c b/crypto/x509/x_crl.c
index dbcd43415f..e19f0e181d 100644
--- a/crypto/x509/x_crl.c
+++ b/crypto/x509/x_crl.c
@@ -466,6 +466,10 @@ int X509_CRL_get0_by_cert(X509_CRL *crl, X509_REVOKED **ret, const X509 *x)

 static int def_crl_verify(X509_CRL *crl, EVP_PKEY *r)
 {
+    if (X509_ALGOR_cmp(&crl->sig_alg, &crl->crl.sig_alg) != 0) {
+        ERR_raise(ERR_LIB_X509, X509_R_CRL_SIGNATURE_ALGORITHM_MISMATCH);
+        return 0;
+    }
     return ASN1_item_verify_ex(ASN1_ITEM_rptr(X509_CRL_INFO),
         &crl->sig_alg, &crl->signature, &crl->crl, NULL,
         r, crl->libctx, crl->propq);
diff --git a/include/openssl/x509err.h b/include/openssl/x509err.h
index 250ffa21ec..fa00e4143f 100644
--- a/include/openssl/x509err.h
+++ b/include/openssl/x509err.h
@@ -27,6 +27,7 @@
 #define X509_R_CERTIFICATE_VERIFICATION_FAILED 139
 #define X509_R_CERT_ALREADY_IN_HASH_TABLE 101
 #define X509_R_CRL_ALREADY_DELTA 127
+#define X509_R_CRL_SIGNATURE_ALGORITHM_MISMATCH 147
 #define X509_R_CRL_VERIFY_FAILURE 131
 #define X509_R_DUPLICATE_ATTRIBUTE 140
 #define X509_R_ERROR_GETTING_MD_BY_NID 141
diff --git a/test/crltest.c b/test/crltest.c
index f5fb2b67c4..1b06c261b7 100644
--- a/test/crltest.c
+++ b/test/crltest.c
@@ -772,6 +772,52 @@ static const char *kCrlIDPWrongTag2[] = {
     NULL
 };

+/*
+ * A well-formed CRL issued by kRoot (sha256WithRSAEncryption, inner and
+ * outer signatureAlgorithm identical), used as the positive test case in
+ * test_crl_sigalg_mismatch.
+ */
+static const char *kCrlRootCA[] = {
+    "-----BEGIN X509 CRL-----\n",
+    "MIIB2jCBwwIBATANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCVVMxEzARBgNV\n",
+    "BAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTATBgNVBAoM\n",
+    "DEV4YW1wbGUgQ29ycDEeMBwGA1UECwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR0w\n",
+    "GwYDVQQDDBRFeGFtcGxlIENvcnAgUm9vdCBDQRcNMjYwMTAxMDAwMDAwWhcNMjcw\n",
+    "MTAxMDAwMDAwWjANBgkqhkiG9w0BAQsFAAOCAQEAjLDGYBswRZpuaRh9qVXrP4i0\n",
+    "wttPikYZkkUk07/KU1zN6pS21Dqx1sEofrkqwRnKXq/hsoCz3sd7QFIv30v2iZwM\n",
+    "ioaksAjcGnaLqe8vuKVtIyiOpDSJR89l84BZr2I9+6osTYnPgroMHQ/7OUt+PKdE\n",
+    "1VAkA137tLMRw2qGPELdCyHA7LXr0gI6jeyLPLtb1blQrMzznp3y/trNWa+DKq6h\n",
+    "SflQrixmLeXTMBD/DDUd8Kj9HHmejbJNAsgaNHv9mtIhUVEspRM0020b3AeJyfTP\n",
+    "3oN/y4fgQ8q5v9i8lDbe8moCo+W0rS4ksWvB6SuYYj/NkUE4EtoIreSVtcz8JA==\n",
+    "-----END X509 CRL-----\n",
+    NULL
+};
+
+/*
+ * kCrlMismatchedSigAlg is issued by kRoot with a deliberately inconsistent
+ * pair of signatureAlgorithm fields: the inner (signed) copy inside
+ * TBSCertList claims ecdsaWithSHA256, while the outer wrapper carries
+ * sha256WithRSAEncryption -- and the actual signature is a valid RSA-SHA256
+ * signature over that TBSCertList.  Without the inner/outer comparison,
+ * X509_CRL_verify() would accept this CRL because the RSA signature checks
+ * out.  RFC 5280 section 5.1.1.2 requires the two fields to be identical.
+ */
+static const char *kCrlMismatchedSigAlg[] = {
+    "-----BEGIN X509 CRL-----\n",
+    "MIIB1zCBwAIBATAKBggqhkjOPQQDAjCBkDELMAkGA1UEBhMCVVMxEzARBgNVBAgM\n",
+    "CkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTATBgNVBAoMDEV4\n",
+    "YW1wbGUgQ29ycDEeMBwGA1UECwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR0wGwYD\n",
+    "VQQDDBRFeGFtcGxlIENvcnAgUm9vdCBDQRcNMjYwMTAxMDAwMDAwWhcNMjcwMTAx\n",
+    "MDAwMDAwWjANBgkqhkiG9w0BAQsFAAOCAQEAcle5SUuN1XIx5amjddTqDPyEm9pP\n",
+    "sNeBwR+TQi19pWHtQ5anr6PBIAxHC5uxhVpZDScZu0TlodWigo+1bfAJRyrIm/6+\n",
+    "AbmAyNC4txpNsOHgCFGW7q9T8OutaOhUw+jC6i3bxUQZ64L1sXuy2nZMzU19+Aro\n",
+    "TxSWYkIJg65SKwM/8ggyd5G7TXkv7w19+W/7Y9JV0c+kPueUZSgEGUG/GJF/Nrrc\n",
+    "TRfvqz7Qs9H9+hUiQl5K7tF9gj6aU3p1s1IZKR2x0lv4wDRUUgIjrvRzfQSGjhgf\n",
+    "6rBILI3EIxPN/PoZ3mHLYkhH5IyNj9R2GlMle52isNdW8BiNlePLx0/Jzg==\n",
+    "-----END X509 CRL-----\n",
+    NULL
+};
+
 /*
  * Verify |leaf| certificate (chained up to |root|).  |crls| if
  * not NULL, is a list of CRLs to include in the verification. It is
@@ -1461,6 +1507,53 @@ err:
     return ret;
 }

+/*
+ * Check that X509_CRL_verify() rejects a CRL where the outer
+ * signatureAlgorithm does not match the inner copy inside TBSCertList.
+ * RFC 5280 section 5.1.1.2 requires the two to be identical; X509_verify()
+ * and X509_ACERT_verify() enforce this, and so must X509_CRL_verify().
+ *
+ * Both CRLs are issued by kRoot (RSA-2048).  kCrlMismatchedSigAlg carries a
+ * valid RSA-SHA256 signature over a TBSCertList whose inner signatureAlgorithm
+ * claims ecdsaWithSHA256, while the outer wrapper carries the correct
+ * sha256WithRSAEncryption.  Without the inner/outer comparison the signature
+ * would verify and the CRL would be accepted.
+ */
+static int test_crl_sigalg_mismatch(void)
+{
+    X509 *root = X509_from_strings(kRoot);
+    X509_CRL *good = CRL_from_strings(kCrlRootCA);
+    X509_CRL *bad = CRL_from_strings(kCrlMismatchedSigAlg);
+    EVP_PKEY *pkey = NULL;
+    int ret = 0;
+
+    if (!TEST_ptr(root) || !TEST_ptr(good) || !TEST_ptr(bad))
+        goto end;
+
+    pkey = X509_get0_pubkey(root);
+    if (!TEST_ptr(pkey))
+        goto end;
+
+    /* Well-formed CRL: inner and outer algorithms match; verify succeeds. */
+    if (!TEST_int_eq(X509_CRL_verify(good, pkey), 1))
+        goto end;
+
+    /*
+     * Mismatched CRL: inner signatureAlgorithm is ecdsaWithSHA256, outer is
+     * sha256WithRSAEncryption, RSA signature is valid.  X509_ALGOR_cmp()
+     * must catch the mismatch before the signature is checked.
+     */
+    if (!TEST_int_eq(X509_CRL_verify(bad, pkey), 0))
+        goto end;
+
+    ret = 1;
+end:
+    X509_CRL_free(good);
+    X509_CRL_free(bad);
+    X509_free(root);
+    return ret;
+}
+
 int setup_tests(void)
 {
     ADD_TEST(test_private_keys);
@@ -1489,5 +1582,6 @@ int setup_tests(void)
     ADD_TEST(test_crl_extension_duplicate_serial);
     ADD_ALL_TESTS(test_reuse_crl, 6);
     ADD_MFAIL_TEST(test_crl_diff_mfail);
+    ADD_TEST(test_crl_sigalg_mismatch);
     return 1;
 }