From: William Lallemand Date: Tue, 18 Aug 2026 07:26:53 +0000 (+0000) Subject: BUG/MEDIUM: ssl: isolate TLS session resumption per authentication policy X-Git-Tag: v3.5-dev5~18 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=4a8f762204bba85064a8b1c4355f56d4b857787c;p=haproxy.git BUG/MEDIUM: ssl: isolate TLS session resumption per authentication policy The session id context OpenSSL uses to allow or reject session resumption) was so far scoped only to the frontend's own certificate. Two SSL_CTXs presenting the same certificate but different client-certificate policies (verify mode, ca-file, crl-file, ca/crt-ignore-err) still shared that sid_ctx, so a session or ticket obtained on a weakly-verified listener could be resumed on another listener enforcing stricter mutual TLS, without ever re-presenting a certificate. Fix this by also hashing the verify mode, every CA certificate and CRL loaded in the SSL_CTX's X509_STORE (ssl_ctx_hash_store()), and the ca/crt-ignore-err bitfields into sid_ctx. CA certificates are hashed the same way as leaf certificate (issuer/subject/serial); CRLs are hashed by issuer and CRL Number extension. Must be backported to every stable branch. Reported-by: Yiyi Wang Reported-by: Chris Jarrett-Davies --- diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 48af62efd..e898a9f22 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -4681,8 +4681,86 @@ static int ssl_ctx_hash_filters(EVP_MD_CTX *mdctx, char **filters, unsigned int return 1; } +/* hash the trusted store of the SSL_CTX (X509 + CRL) */ +static int ssl_ctx_hash_store(EVP_MD_CTX *mdctx, SSL_CTX *ctx) +{ + STACK_OF(X509_OBJECT) *objects; + int count; + int i; + int ret = 0; + +#if (HA_OPENSSL_VERSION_NUMBER >= 0x40000000L) && !defined(OPENSSL_IS_AWSLC) && \ + !defined(LIBRESSL_VERSION_NUMBER) + objects = X509_STORE_get1_objects(SSL_CTX_get_cert_store(ctx)); +#else + objects = X509_STORE_get0_objects(SSL_CTX_get_cert_store(ctx)); +#endif + if (!objects) + return 0; + + count = sk_X509_OBJECT_num(objects); + if (EVP_DigestUpdate(mdctx, &count, sizeof(count)) != 1) + goto out; + + for (i = 0; i < count; i++) { + X509_OBJECT *object = sk_X509_OBJECT_value(objects, i); + int type = X509_OBJECT_get_type(object); + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int digest_len = 0; + + if (EVP_DigestUpdate(mdctx, &type, sizeof(type)) != 1) + goto out; + + switch (type) { + case X509_LU_X509: + if (!ssl_ctx_hash_X509(mdctx, X509_OBJECT_get0_X509(object))) + goto out; + break; + case X509_LU_CRL: { + X509_CRL *crl = X509_OBJECT_get0_X509_CRL(object); + ASN1_INTEGER *crl_number; + int crl_number_len; + + if (X509_NAME_digest(X509_CRL_get_issuer(crl), EVP_sha256(), digest, &digest_len) != 1) + goto out; + if (EVP_DigestUpdate(mdctx, digest, digest_len) != 1) + goto out; + + crl_number = X509_CRL_get_ext_d2i(crl, NID_crl_number, NULL, NULL); + crl_number_len = crl_number ? ASN1_STRING_length(crl_number) : 0; + + if (EVP_DigestUpdate(mdctx, &crl_number_len, sizeof(crl_number_len)) != 1) { + ASN1_INTEGER_free(crl_number); + goto out; + } + if (crl_number_len && EVP_DigestUpdate(mdctx, ASN1_STRING_get0_data(crl_number), crl_number_len) != 1) { + ASN1_INTEGER_free(crl_number); + goto out; + } + ASN1_INTEGER_free(crl_number); + break; + } + default: + break; + } + } + + ret = 1; + +out: +#if (HA_OPENSSL_VERSION_NUMBER >= 0x40000000L) && !defined(OPENSSL_IS_AWSLC) && \ + !defined(LIBRESSL_VERSION_NUMBER) + sk_X509_OBJECT_pop_free(objects, X509_OBJECT_free); +#endif + return ret; +} + /* Replace the sid_ctx of a SSL_CTX */ -static int ssl_set_session_id_context(SSL_CTX *ctx, struct ckch_inst *ckch_inst) +static int ssl_set_session_id_context(SSL_CTX *ctx, + const struct bind_conf *bind_conf, + const struct ssl_bind_conf *ssl_conf, + int verify, + struct ckch_inst *ckch_inst) { EVP_MD_CTX *mdctx = NULL; unsigned char sid_ctx[EVP_MAX_MD_SIZE]; @@ -4696,6 +4774,12 @@ static int ssl_set_session_id_context(SSL_CTX *ctx, struct ckch_inst *ckch_inst) 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_DigestUpdate(mdctx, &verify, sizeof(verify)) != 1 || + !ssl_ctx_hash_store(mdctx, ctx) || + EVP_DigestUpdate(mdctx, bind_conf->ca_ignerr_bitfield, + sizeof(bind_conf->ca_ignerr_bitfield)) != 1 || + EVP_DigestUpdate(mdctx, bind_conf->crt_ignerr_bitfield, + sizeof(bind_conf->crt_ignerr_bitfield)) != 1 || 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) @@ -4939,7 +5023,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, ckch_inst)) { + if (!ssl_set_session_id_context(ctx, bind_conf, ssl_conf, verify, 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;