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