]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: ssl: support SCTL in TLS 1.3 Certificate messages
authorWilliam Lallemand <wlallemand@haproxy.com>
Tue, 27 Oct 2026 15:13:40 +0000 (15:13 +0000)
committerWilliam Lallemand <wlallemand@haproxy.com>
Wed, 28 Oct 2026 13:55:51 +0000 (14:55 +0100)
SSL_CTX_add_server_custom_ext() only sends SCT lists in TLS 1.2 and earlier
ServerHello messages. Use SSL_CTX_add_custom_ext() on vanilla OpenSSL 1.1.1
and later to support TLS 1.3 Certificate messages as well. Retain the legacy
API for other libraries and older OpenSSL versions.

Send SCTs only on the leaf Certificate entry when requested by the client
and present for the selected certificate.

Keep SCT delivery disabled for QUIC: valgrind reports a use-after-free in
native OpenSSL 3.5.5 when SSL_set_SSL_CTX() merges transport parameters with
custom extensions. Also skip loading SCT lists into QUIC contexts,
including on AWS-LC.

Verified exact SCT payloads in TLS 1.2 and TLS 1.3 with a two-entry chain,
SNI isolation, omission without a client request, QUIC omission, and clean
config-check/deinit under valgrind.

This must be backported to all supported versions.

include/haproxy/openssl-compat.h
src/ssl_sock.c

index 4918024f59d7e673219dccd91c8d1fc470ea00cd..5064a83935d1bb35d1245c4222f63fb97fe23c7f 100644 (file)
@@ -146,6 +146,10 @@ enum ssl_encryption_level_t {
 #define HAVE_VANILLA_OPENSSL
 #endif
 
+#if defined(HAVE_VANILLA_OPENSSL) && defined(HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT) && (OPENSSL_VERSION_NUMBER >= 0x10101000L)
+#define HAVE_SSL_CTX_ADD_CUSTOM_EXT
+#endif
+
 #if ((defined(SN_ct_cert_scts) && !defined(OPENSSL_NO_TLSEXT)) || defined(OPENSSL_IS_AWSLC))
 /* AWS-LC supports SCT lists without defining the CT OID SN_ct_cert_scts. */
 #define HAVE_SSL_SCTL
index 3bcb70e791d574f8af6b6bc5b04cd599858d71eb..ff546670b2d99ab99191b36f68ca51072496a958 100644 (file)
@@ -1705,6 +1705,28 @@ static __maybe_unused int ssl_sock_sctl_parse_cbk(SSL *s, unsigned int ext_type,
        return 1;
 }
 
+#ifdef HAVE_SSL_CTX_ADD_CUSTOM_EXT
+/* Returns the selected context's SCT list via <out>/<outlen>. Returns 1
+ * to send it, or 0 if absent or <chainidx> is not the TLS 1.3 leaf entry.
+ */
+static int ssl_sock_sctl_add_cbk_ex(SSL *ssl, unsigned int ext_type, unsigned int context,
+                                  const unsigned char **out, size_t *outlen, X509 *x,
+                                  size_t chainidx, int *al, void *add_arg)
+{
+       if ((context & SSL_EXT_TLS1_3_CERTIFICATE) && chainidx != 0)
+               return 0;
+       return ssl_sock_sctl_add_cbk(ssl, ext_type, out, outlen, al, add_arg);
+}
+
+/* Accepts the client's SCT request through the legacy callback. Returns 1. */
+static int ssl_sock_sctl_parse_cbk_ex(SSL *ssl, unsigned int ext_type, unsigned int context,
+                                    const unsigned char *in, size_t inlen, X509 *x,
+                                    size_t chainidx, int *al, void *parse_arg)
+{
+       return ssl_sock_sctl_parse_cbk(ssl, ext_type, in, inlen, al, parse_arg);
+}
+#endif
+
 #endif /* HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT */
 
 /* Copies <sctl> into <ctx>, leaving ownership with the caller. Returns 0
@@ -1750,12 +1772,17 @@ static int ssl_sock_load_sctl(SSL_CTX *ctx, struct buffer *sctl)
 int ssl_sock_ctx_add_sctl_ext(struct bind_conf *bind_conf, SSL_CTX *ctx)
 {
 #ifdef HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT
-       /* xprt is not yet set to QUIC when certificates are loaded */
        if (bind_conf->options & BC_O_USE_SOCK_DGRAM)
                return 1;
-
+#ifdef HAVE_SSL_CTX_ADD_CUSTOM_EXT
+       if (!SSL_CTX_add_custom_ext(ctx, CT_EXTENSION_TYPE,
+                                  SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_CERTIFICATE,
+                                  ssl_sock_sctl_add_cbk_ex, NULL, NULL, ssl_sock_sctl_parse_cbk_ex, NULL))
+               return 0;
+#else
        if (!SSL_CTX_add_server_custom_ext(ctx, CT_EXTENSION_TYPE, ssl_sock_sctl_add_cbk, NULL, NULL, ssl_sock_sctl_parse_cbk, NULL))
                return 0;
+#endif
 #endif
        return 1;
 }
@@ -3229,7 +3256,7 @@ end:
 }
 
 
-/* Loads the info in ckch into ctx
+/* Loads <store> into <ctx> for <bind_conf>, omitting SCT delivery on QUIC.
  * Returns a bitfield containing the flags:
  *     ERR_FATAL in any fatal error case
  *     ERR_ALERT if the reason of the error is available in err
@@ -3237,7 +3264,7 @@ end:
  * The value 0 means there is no error nor warning and
  * the operation succeed.
  */
-static int ssl_sock_put_ckch_into_ctx(const char *path, struct ckch_store *store, SSL_CTX *ctx, char **err)
+static int ssl_sock_put_ckch_into_ctx(const char *path, struct ckch_store *store, SSL_CTX *ctx, struct bind_conf *bind_conf, char **err)
 {
        int errcode = 0;
        struct ckch_data *data = store->data;
@@ -3276,7 +3303,7 @@ static int ssl_sock_put_ckch_into_ctx(const char *path, struct ckch_store *store
 #endif
 
 #if defined(HAVE_SSL_CTX_ADD_SERVER_CUSTOM_EXT) || defined(HAVE_SSL_CTX_SET_SIGNED_CERT_TIMESTAMP_LIST)
-       if (data->sctl) {
+       if (data->sctl && !(bind_conf->options & BC_O_USE_SOCK_DGRAM)) {
 #ifdef HAVE_SSL_CTX_SET_SIGNED_CERT_TIMESTAMP_LIST
                /* A list rejected by AWS-LC only disables SCT delivery; warn and continue. */
                if (ssl_sock_load_sctl(ctx, data->sctl) < 0) {
@@ -3418,7 +3445,7 @@ int ckch_inst_new_load_store(const char *path, struct ckch_store *ckchs, struct
        if (global_ssl.security_level > -1)
                SSL_CTX_set_security_level(ctx, global_ssl.security_level);
 
-       errcode |= ssl_sock_put_ckch_into_ctx(path, ckchs, ctx, err);
+       errcode |= ssl_sock_put_ckch_into_ctx(path, ckchs, ctx, bind_conf, err);
        if (errcode & ERR_CODE)
                goto error;