Commit 647a96676a for openssl.org

commit 647a96676a01b223b500fc14d7dcd93489576ee8
Author: Bob Beck <beck@openssl.org>
Date:   Thu Sep 3 16:48:44 2026 -0600

    Make the cached SHA-1 fingerprint internal-only

    X509::sha1_hash exists only so X509_cmp() can compare certificates by
    identity, yet X509_digest() handed it to callers when asked for SHA-1,
    and it was computed under the certificate's libctx/propq, tying an
    internal value to the context the certificate was parsed in.

    Compute it with the built-in SHA-1 over the DER encoding instead, via
    the new ossl_x509_internal_fingerprint(), so it is the same in every
    library context; the only remaining failure mode is an allocation
    failure. Drop the X509_digest() shortcut so the cached value is never
    visible to callers, and rename the field to fingerprint in X509 and
    X509_CRL. The CRL side gets the same treatment in a following commit.

    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Merge-date: Thu Sep 17 16:44:33 2026
    Merged-from: https://github.com/openssl/openssl/pull/32686

diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index 462e2f12c8..8e685103c5 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -518,7 +518,7 @@ static void scan_ext_flags(const X509 *x509, uint32_t *flags)
 /*
  * Cache info on various X.509v3 extensions and further derived information,
  * e.g., if cert 'x' is self-issued, in x->ex_flags and other internal fields.
- * x->sha1_hash is filled in, or else EXFLAG_NO_FINGERPRINT is set in x->flags.
+ * x->fingerprint is filled in, or else EXFLAG_NO_FINGERPRINT is set in x->flags.
  * X509_SIG_INFO_VALID is set in x->flags if x->siginf was filled successfully.
  * Set EXFLAG_INVALID and return 0 in case the certificate is invalid.
  *
@@ -537,7 +537,7 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     int i;
     int res;
     uint32_t tmp_ex_flags;
-    unsigned char tmp_sha1_hash[SHA_DIGEST_LENGTH];
+    unsigned char tmp_fingerprint[SHA_DIGEST_LENGTH];
     long tmp_ex_pathlen;
     long tmp_ex_pcpathlen;
     uint32_t tmp_ex_kusage;
@@ -570,8 +570,8 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)

     ERR_set_mark();

-    /* Cache the SHA1 digest of the cert */
-    if (!X509_digest(const_x, EVP_sha1(), tmp_sha1_hash, NULL))
+    if (!ossl_x509_internal_fingerprint(ASN1_ITEM_rptr(X509), const_x,
+            tmp_fingerprint, sizeof(tmp_fingerprint)))
         tmp_ex_flags |= EXFLAG_NO_FINGERPRINT;

     /* V1 should mean no extensions ... */
@@ -768,7 +768,7 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     ((X509 *)const_x)->ex_pathlen = tmp_ex_pathlen;
     ((X509 *)const_x)->ex_pcpathlen = tmp_ex_pcpathlen;
     if (!(tmp_ex_flags & EXFLAG_NO_FINGERPRINT))
-        memcpy(((X509 *)const_x)->sha1_hash, tmp_sha1_hash, SHA_DIGEST_LENGTH);
+        memcpy(((X509 *)const_x)->fingerprint, tmp_fingerprint, SHA_DIGEST_LENGTH);
     if (tmp_ex_flags & EXFLAG_KUSAGE)
         ((X509 *)const_x)->ex_kusage = tmp_ex_kusage;
     ((X509 *)const_x)->ex_xkusage = tmp_ex_xkusage;
diff --git a/crypto/x509/x509_cmp.c b/crypto/x509/x509_cmp.c
index 04d508ddc3..fb7e84f011 100644
--- a/crypto/x509/x509_cmp.c
+++ b/crypto/x509/x509_cmp.c
@@ -97,7 +97,7 @@ int X509_CRL_match(const X509_CRL *a, const X509_CRL *b)

     if ((a->flags & EXFLAG_NO_FINGERPRINT) == 0
         && (b->flags & EXFLAG_NO_FINGERPRINT) == 0)
-        rv = memcmp(a->sha1_hash, b->sha1_hash, SHA_DIGEST_LENGTH);
+        rv = memcmp(a->fingerprint, b->fingerprint, SHA_DIGEST_LENGTH);
     if (rv != 0)
         return rv < 0 ? -1 : 1;

@@ -189,7 +189,7 @@ int X509_cmp(const X509 *a, const X509 *b)

     if ((a->ex_flags & EXFLAG_NO_FINGERPRINT) == 0
         && (b->ex_flags & EXFLAG_NO_FINGERPRINT) == 0)
-        rv = memcmp(a->sha1_hash, b->sha1_hash, SHA_DIGEST_LENGTH);
+        rv = memcmp(a->fingerprint, b->fingerprint, SHA_DIGEST_LENGTH);
     if (rv != 0)
         return rv < 0 ? -1 : 1;

diff --git a/crypto/x509/x_all.c b/crypto/x509/x_all.c
index ef16a7fc88..e394bfe0d5 100644
--- a/crypto/x509/x_all.c
+++ b/crypto/x509/x_all.c
@@ -30,6 +30,7 @@
 #include "crypto/x509.h"
 #include "crypto/x509_acert.h"
 #include "crypto/rsa.h"
+#include "crypto/sha.h"
 #include "x509_local.h"

 static void *RSA_new_thunk(void)
@@ -643,17 +644,27 @@ int X509_pubkey_digest(const X509 *data, const EVP_MD *type,
     return EVP_Digest(key->data, key->length, md, len, type, NULL);
 }

+int ossl_x509_internal_fingerprint(const ASN1_ITEM *it, const void *val,
+    unsigned char *hash, size_t hash_size)
+{
+    unsigned char *der = NULL;
+    int der_len;
+
+    if (!ossl_assert(hash_size >= SHA_DIGEST_LENGTH)) {
+        ERR_raise(ERR_LIB_X509, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    der_len = ASN1_item_i2d((const ASN1_VALUE *)val, &der, it);
+    if (der_len < 0)
+        return 0;
+    ossl_sha1(der, (size_t)der_len, hash);
+    OPENSSL_free(der);
+    return 1;
+}
+
 int X509_digest(const X509 *cert, const EVP_MD *md, unsigned char *data,
     unsigned int *len)
 {
-    if (EVP_MD_is_a(md, SN_sha1) && (cert->ex_flags & EXFLAG_SET) != 0
-        && (cert->ex_flags & EXFLAG_NO_FINGERPRINT) == 0) {
-        /* Asking for SHA1 and we already computed it. */
-        if (len != NULL)
-            *len = sizeof(cert->sha1_hash);
-        memcpy(data, cert->sha1_hash, sizeof(cert->sha1_hash));
-        return 1;
-    }
     return ossl_asn1_item_digest_ex(ASN1_ITEM_rptr(X509), md, (char *)cert,
         data, len, cert->libctx, cert->propq);
 }
@@ -764,8 +775,8 @@ int X509_CRL_digest(const X509_CRL *data, const EVP_MD *type,
         && (data->flags & EXFLAG_NO_FINGERPRINT) == 0) {
         /* Asking for SHA1; always computed in CRL d2i. */
         if (len != NULL)
-            *len = sizeof(data->sha1_hash);
-        memcpy(md, data->sha1_hash, sizeof(data->sha1_hash));
+            *len = sizeof(data->fingerprint);
+        memcpy(md, data->fingerprint, sizeof(data->fingerprint));
         return 1;
     }
     return ossl_asn1_item_digest_ex(ASN1_ITEM_rptr(X509_CRL), type, (char *)data,
diff --git a/crypto/x509/x_crl.c b/crypto/x509/x_crl.c
index 33c18147aa..6a1a3778fb 100644
--- a/crypto/x509/x_crl.c
+++ b/crypto/x509/x_crl.c
@@ -245,7 +245,7 @@ static int crl_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
         break;

     case ASN1_OP_D2I_POST:
-        if (!X509_CRL_digest(crl, EVP_sha1(), crl->sha1_hash, NULL))
+        if (!X509_CRL_digest(crl, EVP_sha1(), crl->fingerprint, NULL))
             crl->flags |= EXFLAG_NO_FINGERPRINT;
         crl->idp = X509_CRL_get_ext_d2i(crl, NID_issuing_distribution_point, &i, NULL);
         if (crl->idp == NULL && i != -1) {
diff --git a/doc/internal/man3/x509v3_cache_extensions.pod b/doc/internal/man3/x509v3_cache_extensions.pod
index cc0aeb6079..8f3dffc859 100644
--- a/doc/internal/man3/x509v3_cache_extensions.pod
+++ b/doc/internal/man3/x509v3_cache_extensions.pod
@@ -16,9 +16,13 @@ x509v3_cache_extensions
 This function processes any X509v3 extensions present in an X509 object I<x>
 and caches the result of that processing as well as further derived info,
 for instance whether the certificate is self-issued or has version X.509v1.
-It computes the SHA1 digest of the certificate using the default library context
-and property query string and stores the result in x->sha1_hash,
-or on failure sets B<EXFLAG_NO_FINGERPRINT> in x->flags.
+It computes an internal-use SHA1 fingerprint of the certificate, used only by
+L<X509_cmp(3)> and never exposed to callers, with the built-in SHA1
+implementation, independent of any library context or property query string,
+and stores the result in x->fingerprint;
+on failure (the certificate cannot be DER encoded, for instance because it is
+still under construction, or memory allocation failed) it sets
+B<EXFLAG_NO_FINGERPRINT> in x->flags instead.
 It sets B<X509_SIG_INFO_VALID> in x->flags if x->siginf was filled successfully,
 which may not be possible if a referenced algorithm is unknown or not available.
 Many OpenSSL functions that use an X509 object call this function implicitly.
diff --git a/doc/man3/X509_get_extension_flags.pod b/doc/man3/X509_get_extension_flags.pod
index bc2ec51ecc..21bf952e75 100644
--- a/doc/man3/X509_get_extension_flags.pod
+++ b/doc/man3/X509_get_extension_flags.pod
@@ -86,8 +86,9 @@ ASN1 object itself.

 =item B<EXFLAG_NO_FINGERPRINT>

-Failed to compute the internal SHA1 hash value of the certificate or CRL.
-This may be due to malloc failure or because no SHA1 implementation was found.
+Failed to compute the internal fingerprint of the certificate or CRL,
+because it could not be DER encoded (for instance a certificate still under
+construction) or on memory allocation failure.

 =item B<EXFLAG_INVALID_POLICY>

diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index 4925131180..bb575b57a5 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -119,8 +119,8 @@ struct X509_crl_st {
     ASN1_INTEGER *crl_number;
     ASN1_INTEGER *base_crl_number;
     STACK_OF(GENERAL_NAMES) *issuers;
-    /* hash of CRL */
-    unsigned char sha1_hash[SHA_DIGEST_LENGTH];
+    /* internal-use fingerprint for X509_CRL_match(), see ossl_x509_internal_fingerprint() */
+    unsigned char fingerprint[SHA_DIGEST_LENGTH];
     /* alternative method to handle this CRL */
     const X509_CRL_METHOD *meth;
     void *meth_data;
@@ -198,7 +198,8 @@ struct x509_st {
     STACK_OF(IPAddressFamily) *rfc3779_addr;
     struct ASIdentifiers_st *rfc3779_asid;
 #endif
-    unsigned char sha1_hash[SHA_DIGEST_LENGTH];
+    /* internal-use fingerprint for X509_cmp(), see ossl_x509_internal_fingerprint() */
+    unsigned char fingerprint[SHA_DIGEST_LENGTH];
     X509_CERT_AUX *aux;
     CRYPTO_RWLOCK *lock;
     volatile int ex_cached;
@@ -319,6 +320,29 @@ int ossl_x509_print_ex_brief(BIO *bio, const X509 *cert, unsigned long neg_cflag
 int ossl_x509v3_cache_extensions(const X509 *x);
 int ossl_x509_init_sig_info(const X509 *x, X509_SIG_INFO *info);

+/**
+ * @brief Compute the internal-use fingerprint of a DER-encodable object.
+ *
+ * The fingerprint is cached in X509 / X509_CRL fingerprint and used only for
+ * internal identity comparison (X509_cmp(), X509_CRL_match()); it is never
+ * returned to callers, so the algorithm is an implementation detail
+ * (currently SHA-1). It uses the built-in implementation directly rather
+ * than fetching one, so the result depends on neither the library context
+ * nor the property query string of the object and stays valid if the object
+ * is moved to another library context. It fails only if the object cannot be
+ * DER encoded, for instance a certificate still under construction, or on
+ * an allocation failure in the encoder.
+ *
+ * @param it the ASN1_ITEM describing @p val
+ * @param val the object to encode and hash
+ * @param hash output buffer for the fingerprint
+ * @param hash_size size in bytes of @p hash, must be at least
+ *                  SHA_DIGEST_LENGTH
+ * @returns 1 on success, 0 on failure
+ */
+int ossl_x509_internal_fingerprint(const ASN1_ITEM *it, const void *val,
+    unsigned char *hash, size_t hash_size);
+
 int ossl_x509_set0_libctx(X509 *x, OSSL_LIB_CTX *libctx, const char *propq);
 int ossl_x509_crl_set0_libctx(X509_CRL *x, OSSL_LIB_CTX *libctx,
     const char *propq);