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;
}