Commit d0113f1089 for openssl.org
commit d0113f108951c3fe408bffda7061c61c034fac70
Author: Bob Beck <beck@openssl.org>
Date: Sat Sep 5 11:26:42 2026 -0600
Treat a modified certificate or CRL as equal only to itself
X509_cmp() and X509_CRL_match() skipped the encoding comparison when
either object had been modified since it was signed or decoded, and
returned the result of comparing cached hashes that a modification does
not refresh. A modified copy of a certificate therefore compared equal
to the original.
Return unequal as soon as either object is modified, with the modified
one sorting last, and always compare the encodings otherwise.
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Merge-date: Thu Sep 17 16:44:32 2026
Merged-from: https://github.com/openssl/openssl/pull/32686
diff --git a/crypto/x509/x509_cmp.c b/crypto/x509/x509_cmp.c
index 89ada3a6bb..04d508ddc3 100644
--- a/crypto/x509/x509_cmp.c
+++ b/crypto/x509/x509_cmp.c
@@ -88,6 +88,13 @@ int X509_CRL_match(const X509_CRL *a, const X509_CRL *b)
{
int rv = 0;
+ if (a == b)
+ return 0;
+
+ /* A CRL modified since it was signed or decoded is equal only to itself */
+ if (a->crl.enc.modified || b->crl.enc.modified)
+ return a->crl.enc.modified ? 1 : -1;
+
if ((a->flags & EXFLAG_NO_FINGERPRINT) == 0
&& (b->flags & EXFLAG_NO_FINGERPRINT) == 0)
rv = memcmp(a->sha1_hash, b->sha1_hash, SHA_DIGEST_LENGTH);
@@ -95,20 +102,18 @@ int X509_CRL_match(const X509_CRL *a, const X509_CRL *b)
return rv < 0 ? -1 : 1;
/* Check for match against stored encoding too */
- if (!a->crl.enc.modified && !b->crl.enc.modified) {
- if (a->crl.enc.len < b->crl.enc.len)
- return -1;
- if (a->crl.enc.len > b->crl.enc.len)
- return 1;
- rv = memcmp(a->crl.enc.enc, b->crl.enc.enc, a->crl.enc.len);
- if (rv != 0)
- return rv < 0 ? -1 : 1;
- /* Same TBS: the signature algorithm and signature must match too */
- rv = X509_ALGOR_cmp(&a->sig_alg, &b->sig_alg);
- if (rv != 0)
- return rv < 0 ? -1 : 1;
- rv = ASN1_STRING_cmp(&a->signature, &b->signature);
- }
+ if (a->crl.enc.len < b->crl.enc.len)
+ return -1;
+ if (a->crl.enc.len > b->crl.enc.len)
+ return 1;
+ rv = memcmp(a->crl.enc.enc, b->crl.enc.enc, a->crl.enc.len);
+ if (rv != 0)
+ return rv < 0 ? -1 : 1;
+ /* Same TBS: the signature algorithm and signature must match too */
+ rv = X509_ALGOR_cmp(&a->sig_alg, &b->sig_alg);
+ if (rv != 0)
+ return rv < 0 ? -1 : 1;
+ rv = ASN1_STRING_cmp(&a->signature, &b->signature);
return rv < 0 ? -1 : rv > 0;
}
@@ -171,6 +176,13 @@ int X509_cmp(const X509 *a, const X509 *b)
if (a == b) /* for efficiency */
return 0;
+ /*
+ * A certificate modified since it was signed or decoded is equal only
+ * to itself
+ */
+ if (a->cert_info.enc.modified || b->cert_info.enc.modified)
+ return a->cert_info.enc.modified ? 1 : -1;
+
/* attempt to compute cert hash */
(void)X509_check_purpose((X509 *)a, -1, 0);
(void)X509_check_purpose((X509 *)b, -1, 0);
@@ -182,21 +194,19 @@ int X509_cmp(const X509 *a, const X509 *b)
return rv < 0 ? -1 : 1;
/* Check for match against stored encoding too */
- if (!a->cert_info.enc.modified && !b->cert_info.enc.modified) {
- if (a->cert_info.enc.len < b->cert_info.enc.len)
- return -1;
- if (a->cert_info.enc.len > b->cert_info.enc.len)
- return 1;
- rv = memcmp(a->cert_info.enc.enc,
- b->cert_info.enc.enc, a->cert_info.enc.len);
- if (rv != 0)
- return rv < 0 ? -1 : 1;
- /* Same TBS: the signature algorithm and signature must match too */
- rv = X509_ALGOR_cmp(&a->sig_alg, &b->sig_alg);
- if (rv != 0)
- return rv < 0 ? -1 : 1;
- rv = ASN1_STRING_cmp(&a->signature, &b->signature);
- }
+ if (a->cert_info.enc.len < b->cert_info.enc.len)
+ return -1;
+ if (a->cert_info.enc.len > b->cert_info.enc.len)
+ return 1;
+ rv = memcmp(a->cert_info.enc.enc,
+ b->cert_info.enc.enc, a->cert_info.enc.len);
+ if (rv != 0)
+ return rv < 0 ? -1 : 1;
+ /* Same TBS: the signature algorithm and signature must match too */
+ rv = X509_ALGOR_cmp(&a->sig_alg, &b->sig_alg);
+ if (rv != 0)
+ return rv < 0 ? -1 : 1;
+ rv = ASN1_STRING_cmp(&a->signature, &b->signature);
return rv < 0 ? -1 : rv > 0;
}
diff --git a/doc/man3/X509_cmp.pod b/doc/man3/X509_cmp.pod
index cd939cab02..c5e95394a5 100644
--- a/doc/man3/X509_cmp.pod
+++ b/doc/man3/X509_cmp.pod
@@ -27,6 +27,9 @@ certificates, X509 CRL objects and various values in an X509 certificate.
The X509_cmp() function compares two B<X509> objects indicated by parameters
I<a> and I<b>. The comparison is based on the B<memcmp> result of the hash
values of two B<X509> objects and the canonical (DER) encoding values.
+A certificate that has been modified since it was signed or decoded, and
+not signed again, compares equal only to itself, and sorts after any
+certificate that has not been modified.
The X509_NAME_cmp() function compares two B<X509_NAME> objects indicated by
parameters I<a> and I<b>, any of which may be NULL.
@@ -48,7 +51,9 @@ objects, respectively.
The X509_CRL_match() function compares two B<X509_CRL> objects. Unlike the
X509_CRL_cmp() function, this function compares the whole CRL content instead
-of just the issuer name.
+of just the issuer name. A CRL that has been modified since it was signed or
+decoded, and not signed again, compares equal only to itself, and sorts after
+any CRL that has not been modified.
=head1 RETURN VALUES
diff --git a/test/x509_internal_test.c b/test/x509_internal_test.c
index d74846e5c4..b8c613a493 100644
--- a/test/x509_internal_test.c
+++ b/test/x509_internal_test.c
@@ -1355,9 +1355,76 @@ err:
return ret;
}
+/* A modified, unsigned certificate or CRL is equal only to itself */
+static int test_cmp_modified(void)
+{
+ EVP_PKEY *pkey = NULL;
+ X509_NAME *name = NULL;
+ X509 *cert = NULL, *copy = NULL;
+ X509_CRL *crl = NULL, *crl_copy = NULL;
+ ASN1_INTEGER *serial = NULL;
+ int ret = 0;
+
+ if (!TEST_ptr(pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)2048))
+ || !TEST_ptr(name = X509_NAME_new())
+ || !TEST_true(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+ (const unsigned char *)"cmp test", -1, -1, 0))
+ || !TEST_ptr(serial = ASN1_INTEGER_new())
+ || !TEST_true(ASN1_INTEGER_set(serial, 2)))
+ goto err;
+
+ if (!TEST_ptr(cert = X509_new())
+ || !TEST_true(X509_set_subject_name(cert, name))
+ || !TEST_true(X509_set_issuer_name(cert, name))
+ || !TEST_ptr(X509_gmtime_adj(X509_getm_notBefore(cert), 0))
+ || !TEST_ptr(X509_gmtime_adj(X509_getm_notAfter(cert), 3600))
+ || !TEST_true(X509_set_pubkey(cert, pkey))
+ || !TEST_int_gt(X509_sign(cert, pkey, EVP_sha256()), 0)
+ || !TEST_ptr(copy = X509_dup(cert))
+ || !TEST_int_eq(X509_cmp(cert, copy), 0)
+ || !TEST_int_eq(X509_cmp(cert, cert), 0)
+ /* The copy is modified but still equal to itself */
+ || !TEST_true(X509_set_serialNumber(copy, serial))
+ || !TEST_int_eq(X509_cmp(copy, copy), 0)
+ || !TEST_int_eq(X509_cmp(cert, copy), -1)
+ || !TEST_int_eq(X509_cmp(copy, cert), 1)
+ /* Both modified: unequal */
+ || !TEST_true(X509_set_serialNumber(cert, serial))
+ || !TEST_int_ne(X509_cmp(cert, copy), 0)
+ /* Signing again makes them comparable and equal */
+ || !TEST_int_gt(X509_sign(cert, pkey, EVP_sha256()), 0)
+ || !TEST_int_gt(X509_sign(copy, pkey, EVP_sha256()), 0)
+ || !TEST_int_eq(X509_cmp(cert, copy), 0))
+ goto err;
+
+ if (!TEST_ptr(crl = X509_CRL_new())
+ || !TEST_true(X509_CRL_set_issuer_name(crl, name))
+ || !TEST_true(X509_CRL_set1_lastUpdate(crl, X509_getm_notBefore(cert)))
+ || !TEST_int_gt(X509_CRL_sign(crl, pkey, EVP_sha256()), 0)
+ || !TEST_ptr(crl_copy = X509_CRL_dup(crl))
+ || !TEST_int_eq(X509_CRL_match(crl, crl), 0)
+ || !TEST_true(X509_CRL_set_version(crl_copy, X509_CRL_VERSION_2))
+ || !TEST_int_eq(X509_CRL_match(crl_copy, crl_copy), 0)
+ || !TEST_int_eq(X509_CRL_match(crl, crl_copy), -1)
+ || !TEST_int_eq(X509_CRL_match(crl_copy, crl), 1))
+ goto err;
+
+ ret = 1;
+err:
+ X509_CRL_free(crl_copy);
+ X509_CRL_free(crl);
+ X509_free(copy);
+ X509_free(cert);
+ ASN1_INTEGER_free(serial);
+ X509_NAME_free(name);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
int setup_tests(void)
{
ADD_TEST(test_sign_caches_encoding);
+ ADD_TEST(test_cmp_modified);
ADD_TEST(test_standard_exts);
ADD_ALL_TESTS(test_a2i_ipaddress, OSSL_NELEM(a2i_ipaddress_tests));
ADD_ALL_TESTS(test_ipaddr_to_asc, OSSL_NELEM(ipaddr_to_asc_tests));