]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: ssl: Add new 'pha' server option
authorRemi Tricot-Le Breton <rlebreton@haproxy.com>
Tue, 20 Oct 2026 14:16:40 +0000 (16:16 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Wed, 21 Oct 2026 14:31:48 +0000 (14:31 +0000)
This backend option allows post handshake authentication for the given
backend. This simply consists in adding a "post_handshake_auth"
extension in the ClientHello that tells the server that PHA is
supported. We only enable it for backends that have an explicit
certificate provided so that this certificate gets always used in
subsequent CertificateRequest messages sent by the server.
This does not work with AWS-LC (PHA unsupported) or WolfSSL which would
support it but only when WolfSSL is built with
WOLFSSL_POST_HANDSHAKE_AUTH defined (disabled by default).

doc/configuration.txt
include/haproxy/server-t.h
src/cfgparse-ssl.c
src/server.c
src/ssl_sock.c

index a2f0e56fdc574ddba3177e4ddbfaa12dade7e714..20ef09f42f5e5d96557a518323c4bc3ba7ff70ce 100644 (file)
@@ -19858,6 +19858,15 @@ no-check-ssl
   It may also be used as "default-server" setting to reset any previous
   "default-server" "check-ssl" setting.
 
+no-pha
+  May be used in the following contexts: tcp, http, log, peers, ring
+
+  This option may be used as "server" setting to reset any "pha" setting
+  which would have been inherited from "default-server" directive as default
+  value.
+  It may also be used as "default-server" setting to reset any previous
+  "default-server" "pha" setting.
+
 no-renegotiate
   May be used in the following contexts: tcp, http, log
 
@@ -20105,6 +20114,31 @@ on-marked-up <action>
 
   Actions are disabled by default
 
+pha
+  May be used in the following contexts: tcp, http, log, peers, ring
+
+  This setting is only available when support for OpenSSL was built in. It is
+  not supported by AWS-LC nor by WolfSSL. It enables post handshake
+  authentication (RFC 8446) for the given backend. It is a TLS1.3 feature that
+  consists in advertising the "post_handshake_auth" extension in the
+  ClientHello sent to the server, telling it that post handshake authentication
+  is supported. The certificate provided via the server's "crt" parameter is
+  then used to answer the "CertificateRequest" messages sent by the server
+  after the handshake completes, so this option requires that a certificate be
+  explicitly provided on the server line.
+  Considering the limited support of this option, it is advised to enable it
+  only when strictly necessary (to communicate with Windows Server 2022 IIS web
+  site for instance)
+
+  Post handshake authentication is not supported by HTTP/2 nor HTTP/3, so a
+  warning will be raised if an alpn containing other protocols than "http/1.1"
+  is specified, or if a "proto" option different than "h1" is used.
+  This warning also applies to QUIC servers using the default "h3" ALPN.
+
+  This option is disabled by default.
+
+  See also "no-pha", "crt", "alpn" and "proto".
+
 pool-conn-name <expr>
   May be used in the following contexts: http
 
index 0c6eda227966be934a04279c048248c9b5959712..40bfa910ebf30fee87d4a806627307c8e50b9c3e 100644 (file)
@@ -206,7 +206,8 @@ enum srv_init_state {
 
 /* server ssl options */
 #define SRV_SSL_O_NONE           0x0000
-#define SRV_SSL_O_NO_AUTO_SNI   0x0001  /* disable automatic SNI */
+#define SRV_SSL_O_NO_AUTO_SNI    0x0001 /* disable automatic SNI */
+#define SRV_SSL_O_PHA            0x0002 /* Post Handshake Authentication */
 #define SRV_SSL_O_NO_TLS_TICKETS 0x0100 /* disable session resumption tickets */
 #define SRV_SSL_O_NO_REUSE       0x200  /* disable session reuse */
 #define SRV_SSL_O_EARLY_DATA     0x400  /* Allow using early data */
index 9617945152b46410e7b9ad8c2dc78f12bd1c1b9b..d35e4ac520fb5f33a05d59f246443e0361d38a9c 100644 (file)
@@ -2148,6 +2148,24 @@ static int srv_parse_no_tls_tickets(char **args, int *cur_arg, struct proxy *px,
        newsrv->ssl_ctx.options |= SRV_SSL_O_NO_TLS_TICKETS;
        return 0;
 }
+
+/* parse the "pha" server keyword */
+static int srv_parse_pha(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
+{
+#if !defined(OPENSSL_IS_AWSLC) && !defined(USE_OPENSSL_WOLFSSL)
+       if (*args[*cur_arg] == 'n') {
+               /* "no-pha" */
+               newsrv->ssl_ctx.options &= ~SRV_SSL_O_PHA;
+       } else {
+               /* "pha" */
+               newsrv->ssl_ctx.options |= SRV_SSL_O_PHA;
+       }
+       return 0;
+#else
+       memprintf(err, "'%s' : library does not support post handshake authentication", args[*cur_arg]);
+       return ERR_ALERT | ERR_FATAL;
+#endif
+}
 /* parse the "send-proxy-v2-ssl" server keyword */
 static int srv_parse_send_proxy_ssl(char **args, int *cur_arg, struct proxy *px, struct server *newsrv, char **err)
 {
@@ -2817,6 +2835,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
        { "ktls",                    srv_parse_ktls,               1, 1, 1 }, /* enable or disable kTLS */
        { "no-check-sni-auto",       srv_parse_no_check_sni_auto,  0, 1, 1 }, /* disable automatic SNI selection for health checks */
        { "no-check-ssl",            srv_parse_no_check_ssl,       0, 1, 0 }, /* disable SSL for health checks */
+       { "no-pha",                  srv_parse_pha,                0, 1, 1 }, /* Disable Post Handshake Authentication */
        { "no-renegotiate",          srv_parse_renegotiate,        0, 1, 1 }, /* Disable renegotiation */
        { "no-send-proxy-v2-ssl",    srv_parse_no_send_proxy_ssl,  0, 1, 0 }, /* do not send PROXY protocol header v2 with SSL info */
        { "no-send-proxy-v2-ssl-cn", srv_parse_no_send_proxy_cn,   0, 1, 0 }, /* do not send PROXY protocol header v2 with CN */
@@ -2830,6 +2849,7 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
        { "no-tlsv13",               srv_parse_tls_method_options, 0, 0, 1 }, /* disable TLSv13 */
        { "no-tls-tickets",          srv_parse_no_tls_tickets,     0, 1, 1 }, /* disable session resumption tickets */
        { "npn",                     srv_parse_npn,                1, 1, 1 }, /* Set NPN supported protocols */
+       { "pha",                     srv_parse_pha,                0, 1, 1 }, /* Allow Post Handshake Authentication */
        { "renegotiate",             srv_parse_renegotiate,        0, 1, 1 }, /* Allow secure renegotiation */
        { "send-proxy-v2-ssl",       srv_parse_send_proxy_ssl,     0, 1, 1 }, /* send PROXY protocol header v2 with SSL info */
        { "send-proxy-v2-ssl-cn",    srv_parse_send_proxy_cn,      0, 1, 1 }, /* send PROXY protocol header v2 with CN */
index 76b0d9bfa2d01027158aefca8d4557770d0b689f..4b5d2a2012c0a1b390d7c042c0f1303e8f157a27 100644 (file)
@@ -4191,6 +4191,7 @@ static int _srv_parse_finalize(char **args, int cur_arg,
                                int parse_flags)
 {
        int ret;
+       int retval = 0;
        char *errmsg = NULL;
        struct srv_pp_tlv_list *srv_tlv = NULL;
 
@@ -4281,6 +4282,20 @@ static int _srv_parse_finalize(char **args, int cur_arg,
                }
        }
 
+       if (srv->use_ssl && (srv->ssl_ctx.options & SRV_SSL_O_PHA)) {
+               if (srv->ssl_ctx.alpn_str &&
+                    strcmp(srv->ssl_ctx.alpn_str, "\010http/1.1") != 0) {
+                       ha_warning("Post Handshake Authentication can only be used "
+                                  "with 'http/1.1' protocol (wrong 'alpn' value).\n");
+                       retval |= ERR_WARN;
+               } else if (srv->mux_proto &&
+                    !isteq(srv->mux_proto->mux_proto, ist("h1"))) {
+                       ha_warning("Post Handshake Authentication can only be used "
+                                  "with 'http/1.1' protocol (wrong 'proto' value).\n");
+                       retval |= ERR_WARN;
+               }
+       }
+
        if (!(srv->proxy->cap & PR_CAP_LB)) {
                /* No need to wait for effective proxy mode, it is already known:
                 * Only general purpose user-declared proxies ("listen", "frontend", "backend")
@@ -4292,7 +4307,7 @@ static int _srv_parse_finalize(char **args, int cur_arg,
 
        srv_lb_commit_status(srv);
 
-       return 0;
+       return retval;
 }
 
 int parse_server(const char *file, int linenum, char **args,
index f34ea6ddb96e9fd5b71c717a6e61e6a1804f64d5..08968b3ae5bed35cbca53e005d0f83575e469673 100644 (file)
@@ -5722,6 +5722,16 @@ static int ssl_sock_prepare_srv_ssl_ctx(const struct server *srv, SSL_CTX *ctx)
 #endif
 #endif /* defined(SSL_CTX_set1_curves_list) */
 
+#if !defined(OPENSSL_IS_AWSLC) && !defined(USE_OPENSSL_WOLFSSL)
+       if (srv->ssl_ctx.options & SRV_SSL_O_PHA) {
+               if (!srv->ssl_ctx.client_crt) {
+                       ha_alert("Proxy '%s': 'pha' option requires a 'crt' parameter.\n", curproxy->id);
+                       cfgerr++;
+               } else
+                       SSL_CTX_set_post_handshake_auth(ctx, 1);
+       }
+#endif
+
        return cfgerr;
 }