From: William Lallemand Date: Tue, 18 Aug 2026 07:10:49 +0000 (+0000) Subject: BUG/MINOR: ssl: isolate TLS session resumption per crt-list filter X-Git-Tag: v3.5-dev5~19 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=52bdd0d02f04d50a6cedd00a9483ee16788a41fd;p=haproxy.git BUG/MINOR: ssl: isolate TLS session resumption per crt-list filter ssl_set_session_id_context() folds the frontend's own leaf certificate identity into sid_ctx, but a crt-list can point several entries at the exact same certificate file, distinguished only by their SNI filter, e.g.: cert.pem foo.example.com cert.pem bar.example.com Each entry gets its own SSL_CTX/ckch_inst, but since both share the same certificate, they still end up with an identical sid_ctx, and a session established while connecting to "foo.example.com" can be resumed under "bar.example.com" (or any other filter sharing that certificate) without the servername callback/SNI match ever running again on the resumed handshake. Fix the issue by adding a ckch_inst parameter in order to hash the filters provided by the crt-list. Must be backported in every stable branches. --- diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 71d8cd473..48af62efd 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -4660,12 +4660,34 @@ static int ssl_ctx_hash_X509(EVP_MD_CTX *mdctx, X509 *cert) return 1; } +/* hash SNI filters into the session ID context */ +static int ssl_ctx_hash_filters(EVP_MD_CTX *mdctx, char **filters, unsigned int fcount) +{ + unsigned int i; + + if (EVP_DigestUpdate(mdctx, &fcount, sizeof(fcount)) != 1) + return 0; + + for (i = 0; i < fcount; i++) { + size_t len = strlen(filters[i]); + + /* hash the len to avoid collision like "ab""c" = "abc" */ + if (EVP_DigestUpdate(mdctx, &len, sizeof(len)) != 1) + return 0; + if (len && EVP_DigestUpdate(mdctx, filters[i], len) != 1) + return 0; + } + + return 1; +} + /* Replace the sid_ctx of a SSL_CTX */ -static int ssl_set_session_id_context(SSL_CTX *ctx) +static int ssl_set_session_id_context(SSL_CTX *ctx, struct ckch_inst *ckch_inst) { EVP_MD_CTX *mdctx = NULL; unsigned char sid_ctx[EVP_MAX_MD_SIZE]; unsigned int sid_ctx_len = 0; + struct crtlist_entry *entry = ckch_inst ? ckch_inst->crtlist_entry : NULL; int ret = 0; mdctx = EVP_MD_CTX_new(); @@ -4673,6 +4695,7 @@ static int ssl_set_session_id_context(SSL_CTX *ctx) EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL) != 1 || EVP_DigestUpdate(mdctx, SHCTX_APPNAME, sizeof(SHCTX_APPNAME)) != 1 || !ssl_ctx_hash_X509(mdctx, SSL_CTX_get0_certificate(ctx)) || + !ssl_ctx_hash_filters(mdctx, entry ? entry->filters : NULL, entry ? entry->fcount : 0) || EVP_DigestFinal_ex(mdctx, sid_ctx, &sid_ctx_len) != 1 || sid_ctx_len > SSL_MAX_SID_CTX_LENGTH || SSL_CTX_set_session_id_context(ctx, sid_ctx, sid_ctx_len) != 1) @@ -4797,7 +4820,8 @@ error: * This function applies the SSL configuration on a SSL_CTX * It returns an error code and fills the buffer */ -static int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf, SSL_CTX *ctx, char **err) +static int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_conf *ssl_conf, SSL_CTX *ctx, + struct ckch_inst *ckch_inst, char **err) { struct proxy *curproxy = bind_conf->frontend; int cfgerr = 0; @@ -4915,7 +4939,7 @@ static int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, struct ssl_bind_con ssl_set_shctx(ctx); #ifndef USE_OPENSSL_WOLFSSL - if (!ssl_set_session_id_context(ctx)) { + if (!ssl_set_session_id_context(ctx, ckch_inst)) { memprintf(err, "%sProxy '%s': unable to set the SSL session context for bind '%s' at [%s:%d].\n", err && *err ? *err : "", bind_conf->frontend->id, bind_conf->arg, bind_conf->file, bind_conf->line); cfgerr |= ERR_ALERT | ERR_FATAL; @@ -5090,7 +5114,7 @@ int ssl_sock_prep_ctx_and_inst(struct bind_conf *bind_conf, struct ssl_bind_conf { int errcode = 0; - errcode |= ssl_sock_prepare_ctx(bind_conf, ssl_conf, ctx, err); + errcode |= ssl_sock_prepare_ctx(bind_conf, ssl_conf, ctx, ckch_inst, err); if (!errcode && ckch_inst) ckch_inst_add_cafile_link(ckch_inst, bind_conf, ssl_conf, NULL);