From: William Lallemand Date: Tue, 27 Oct 2026 15:13:40 +0000 (+0000) Subject: BUG/MINOR: ssl: support SCTL in TLS 1.3 Certificate messages X-Git-Tag: v3.5-dev10~6 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=0302a32017507852fd26d19876f416a3106e40bd;p=haproxy.git BUG/MINOR: ssl: support SCTL in TLS 1.3 Certificate messages 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. --- diff --git a/include/haproxy/openssl-compat.h b/include/haproxy/openssl-compat.h index 4918024f5..5064a8393 100644 --- a/include/haproxy/openssl-compat.h +++ b/include/haproxy/openssl-compat.h @@ -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 diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 3bcb70e79..ff546670b 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -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 /. Returns 1 + * to send it, or 0 if absent or 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 into , 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 into for , 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;