Commit 90c3f5110f for openssl.org

commit 90c3f5110fc66dce374512738f2667c29eca2c5e
Author: Bob Beck <beck@openssl.org>
Date:   Thu Sep 3 17:51:43 2026 -0600

    Use SipHash for the internal fingerprint

    Replace SHA-1 with SipHash-2-4 (fixed key, 64-bit output) for the
    internal X509 and X509_CRL fingerprint.

    Always build crypto/siphash; no-siphash now only removes the provider
    MAC.

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

diff --git a/Configure b/Configure
index aa7323cb50..549d14a833 100755
--- a/Configure
+++ b/Configure
@@ -2060,8 +2060,11 @@ foreach my $what (sort keys %disabled) {
             }
         }

+        # siphash is used internally by libcrypto (X509 fingerprints), so
+        # no-siphash only removes the provider algorithm.
         $skipdir{"crypto/$skipdir"} = $what
-            unless $what eq 'async' || $what eq 'err' || $what eq 'dso' || $what eq 'http';
+            unless $what eq 'async' || $what eq 'err' || $what eq 'dso'
+                || $what eq 'http' || $what eq 'siphash';
     }
 }

diff --git a/crypto/x509/v3_purp.c b/crypto/x509/v3_purp.c
index e49ac312f5..3ee4369b2a 100644
--- a/crypto/x509/v3_purp.c
+++ b/crypto/x509/v3_purp.c
@@ -518,7 +518,8 @@ 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->fingerprint 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->ex_flags.
  * Set EXFLAG_INVALID and return 0 in case the certificate is invalid.
  *
  * This is usually called by side-effect on objects, and forces us to keep
@@ -536,7 +537,7 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     int i;
     int res;
     uint32_t tmp_ex_flags;
-    unsigned char tmp_fingerprint[SHA_DIGEST_LENGTH];
+    unsigned char tmp_fingerprint[OSSL_X509_FINGERPRINT_SIZE];
     long tmp_ex_pathlen;
     long tmp_ex_pcpathlen;
     uint32_t tmp_ex_kusage;
@@ -569,7 +570,7 @@ int ossl_x509v3_cache_extensions(const X509 *const_x)
     ERR_set_mark();

     if (!ossl_x509_internal_fingerprint(ASN1_ITEM_rptr(X509), const_x,
-            tmp_fingerprint, sizeof(tmp_fingerprint)))
+            tmp_fingerprint))
         tmp_ex_flags |= EXFLAG_NO_FINGERPRINT;

     /* V1 should mean no extensions ... */
@@ -763,7 +764,8 @@ 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)->fingerprint, tmp_fingerprint, SHA_DIGEST_LENGTH);
+        memcpy(((X509 *)const_x)->fingerprint, tmp_fingerprint,
+            sizeof(tmp_fingerprint));
     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 fb7e84f011..996ab10707 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->fingerprint, b->fingerprint, SHA_DIGEST_LENGTH);
+        rv = memcmp(a->fingerprint, b->fingerprint, sizeof(a->fingerprint));
     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->fingerprint, b->fingerprint, SHA_DIGEST_LENGTH);
+        rv = memcmp(a->fingerprint, b->fingerprint, sizeof(a->fingerprint));
     if (rv != 0)
         return rv < 0 ? -1 : 1;

diff --git a/crypto/x509/x_all.c b/crypto/x509/x_all.c
index 3be6654d04..6612dd06f9 100644
--- a/crypto/x509/x_all.c
+++ b/crypto/x509/x_all.c
@@ -30,7 +30,7 @@
 #include "crypto/x509.h"
 #include "crypto/x509_acert.h"
 #include "crypto/rsa.h"
-#include "crypto/sha.h"
+#include "crypto/siphash.h"
 #include "x509_local.h"

 static void *RSA_new_thunk(void)
@@ -645,19 +645,20 @@ int X509_pubkey_digest(const X509 *data, const EVP_MD *type,
 }

 int ossl_x509_internal_fingerprint(const ASN1_ITEM *it, const void *val,
-    unsigned char *hash, size_t hash_size)
+    unsigned char *hash)
 {
+    static const unsigned char key[SIPHASH_KEY_SIZE] = { 0 };
+    SIPHASH ctx = { 0 };
     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);
+    (void)SipHash_set_hash_size(&ctx, OSSL_X509_FINGERPRINT_SIZE);
+    (void)SipHash_Init(&ctx, key, 0, 0);
+    SipHash_Update(&ctx, der, (size_t)der_len);
+    (void)SipHash_Final(&ctx, hash, OSSL_X509_FINGERPRINT_SIZE);
     OPENSSL_free(der);
     return 1;
 }
diff --git a/crypto/x509/x_crl.c b/crypto/x509/x_crl.c
index 8254aa30ba..9693ad16a3 100644
--- a/crypto/x509/x_crl.c
+++ b/crypto/x509/x_crl.c
@@ -247,7 +247,7 @@ static int crl_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,

     case ASN1_OP_D2I_POST:
         if (!ossl_x509_internal_fingerprint(ASN1_ITEM_rptr(X509_CRL), crl,
-                crl->fingerprint, sizeof(crl->fingerprint)))
+                crl->fingerprint))
             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 ecd7818f17..f01d1993a1 100644
--- a/doc/internal/man3/x509v3_cache_extensions.pod
+++ b/doc/internal/man3/x509v3_cache_extensions.pod
@@ -16,9 +16,9 @@ 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 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,
+It computes an internal-use fingerprint of the certificate, used only by
+L<X509_cmp(3)> and never exposed to callers,
+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
diff --git a/include/crypto/x509.h b/include/crypto/x509.h
index 515ea15530..e89f2fed50 100644
--- a/include/crypto/x509.h
+++ b/include/crypto/x509.h
@@ -19,6 +19,19 @@
 #include "crypto/types.h"

 #include <crypto/asn1.h>
+#include <crypto/siphash.h>
+
+/*
+ * Size in bytes of the internal X509 / X509_CRL fingerprint, see
+ * ossl_x509_internal_fingerprint(). The fingerprint only short-circuits
+ * X509_cmp() and X509_CRL_match(), which compare the encodings on a match,
+ * so a collision costs one extra memcmp. With 64 bits a collision among
+ * n objects has probability about n^2 / 2^65: one in 10^12 for 10,000
+ * certificates, and even odds only at around 2^32 of them. The SipHash key
+ * is fixed, so an attacker can craft certificates that collide, but gains
+ * only that memcmp per colliding pair on certificates they had to supply.
+ */
+#define OSSL_X509_FINGERPRINT_SIZE SIPHASH_MIN_DIGEST_SIZE

 /* Internal X509 structures and functions: not for application use */

@@ -119,8 +132,12 @@ struct X509_crl_st {
     ASN1_INTEGER *crl_number;
     ASN1_INTEGER *base_crl_number;
     STACK_OF(GENERAL_NAMES) *issuers;
-    /* internal-use fingerprint for X509_CRL_match(), see ossl_x509_internal_fingerprint() */
-    unsigned char fingerprint[SHA_DIGEST_LENGTH];
+    /*
+     * Internal-use fingerprint for X509_CRL_match(), see
+     * ossl_x509_internal_fingerprint(). Not cryptographically secure and
+     * not collision free: a match is confirmed by comparing the CRLs.
+     */
+    unsigned char fingerprint[OSSL_X509_FINGERPRINT_SIZE];
     /* alternative method to handle this CRL */
     const X509_CRL_METHOD *meth;
     void *meth_data;
@@ -197,8 +214,12 @@ struct x509_st {
     STACK_OF(IPAddressFamily) *rfc3779_addr;
     struct ASIdentifiers_st *rfc3779_asid;
 #endif
-    /* internal-use fingerprint for X509_cmp(), see ossl_x509_internal_fingerprint() */
-    unsigned char fingerprint[SHA_DIGEST_LENGTH];
+    /*
+     * Internal-use fingerprint for X509_cmp(), see
+     * ossl_x509_internal_fingerprint(). Not cryptographically secure and
+     * not collision free: a match is confirmed by comparing the certificates.
+     */
+    unsigned char fingerprint[OSSL_X509_FINGERPRINT_SIZE];
     X509_CERT_AUX *aux;
     CRYPTO_RWLOCK *lock;
     volatile int ex_cached;
@@ -324,22 +345,23 @@ int ossl_x509v3_cache_extensions(const X509 *x);
  * 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
+ * (currently SipHash-2-4 with a fixed key and 64-bit output; a collision
+ * only costs the callers a fall through to their encoding comparison).
+ * Callers hash the whole signed object (X509, X509_CRL). No algorithm
+ * is fetched, 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
+ * @param hash output buffer for the fingerprint, OSSL_X509_FINGERPRINT_SIZE
+ *             bytes
  * @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);
+    unsigned char *hash);

 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,
diff --git a/ssl/build.info b/ssl/build.info
index e45db959cc..e94b1591d4 100644
--- a/ssl/build.info
+++ b/ssl/build.info
@@ -52,11 +52,7 @@ ENDIF
 # siphash.c is needed by dtls_conn_lookup.c (DTLS) and quic_lcidm.c (QUIC)
 # for hash-flooding resistant connection lookup
 IF[{- !$disabled{dtls} || !$disabled{quic} -}]
-  IF[{- $disabled{siphash} -}]
-    SOURCE[../libssl]=../crypto/siphash/siphash.c
-  ELSE
-    SHARED_SOURCE[../libssl]=../crypto/siphash/siphash.c
-  ENDIF
+  SHARED_SOURCE[../libssl]=../crypto/siphash/siphash.c
 ENDIF

 IF[{- $target{needs_c99_snprintf_compat} -}]