]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
REGTESTS: ssl: add basic 0rtt tests for TLSv1.2, TLSv1.3 and QUIC
authorWilly Tarreau <w@1wt.eu>
Wed, 19 Nov 2025 10:58:21 +0000 (11:58 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 19 Nov 2025 21:30:21 +0000 (22:30 +0100)
These tests try all the combinations of {0,1}rtt <-> {0,1}rtt with
stateless and stateful tickets. They take into consideration the TLS
version to decide whether or not 0rtt should work. Since we cannot
use environment variables in the client, the tests are run in haproxy
itself where the frontends set a "x-early-rcvd-test" response header
that the client checks. At this stage, the test only verifies that
*some* early data were received.

Note that the tests are a bit complex because we need 4 listeners
for the various combinations of 0rtt/tickets, then we have to set
expectations based on the TLS version (1.2 vs 1.3), as well as the
session resumption status.

We have to set alpn on the server lines because currently our frontends
expect it for 0-rtt to work.

reg-tests/quic/tls13_0rtt.vtc [new file with mode: 0644]
reg-tests/ssl/ssl-0rtt.vtci [new file with mode: 0644]
reg-tests/ssl/tls12_0rtt.vtc [new file with mode: 0644]
reg-tests/ssl/tls13_0rtt.vtc [new file with mode: 0644]

diff --git a/reg-tests/quic/tls13_0rtt.vtc b/reg-tests/quic/tls13_0rtt.vtc
new file mode 100644 (file)
index 0000000..90024dc
--- /dev/null
@@ -0,0 +1,14 @@
+#REGTEST_TYPE=devel
+
+# This reg-test tests 8 scenarios with and without tickets, with various
+# combinations of settings for allow-0rtt, with QUIC/TLSv1.3. Each client will
+# try to established a connection, then try to reconnect 10 times resuming,
+# and check for which combination(s) 0-rtt is used and if they are expected.
+
+varnishtest "Test if the SSL session/ticket reuse works correctly for QUIC"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC) && !feature(QUIC_OPENSSL_COMPAT) && !feature(OPENSSL_WOLFSSL) && ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)'"
+
+setenv VTC_SOCK_TYPE quic
+setenv TLSV TLSv1.3
+setenv ALPN h3
+include ${testdir}/../ssl/ssl-0rtt.vtci
diff --git a/reg-tests/ssl/ssl-0rtt.vtci b/reg-tests/ssl/ssl-0rtt.vtci
new file mode 100644 (file)
index 0000000..8797808
--- /dev/null
@@ -0,0 +1,238 @@
+# Uses VTC_SOCK_TYPE (quic / stream) TLSV (TLSv1.2 / TLSv1.3)
+
+feature ignore_unknown_macro
+
+haproxy h1 -conf {
+   global
+    .if streq("$VTC_SOCK_TYPE",quic)
+        # required for backend connections
+        expose-experimental-directives
+    .endif
+    .if feature(THREAD)
+        thread-groups 1
+    .endif
+    .if streq("$TLSV",TLSv1.3)
+        setenv ZRTT_SUPP 1
+    .else
+        setenv ZRTT_SUPP 0
+    .endif
+
+      # forced to 1 here, because there is a cached session per thread
+      nbthread 1
+
+
+    defaults
+        mode http
+        option httplog
+        option logasap
+        log stderr local0 debug err
+        option httpclose
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    # combinations: cl_{1r,0r}_{sf,st}_{1r,0r}
+    # sf/st: stateful/stateless tickets
+    # 1r/0r: 1rtt/0rtt, left: client, right: server
+    # we force ALPN to "$ALPN" because the next layer's SSL listener needs it.
+    listen cl_1r_sf_1r
+        bind "fd@${cl_1r_sf_1r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sf_1r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com)
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_1r_sl_1r
+        bind "fd@${cl_1r_sl_1r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sl_1r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com)
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_1r_sf_0r
+        bind "fd@${cl_1r_sf_0r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sf_0r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com)
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_1r_sl_0r
+        bind "fd@${cl_1r_sl_0r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sl_0r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com)
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_0r_sf_1r
+        bind "fd@${cl_0r_sf_1r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sf_1r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com) allow-0rtt
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_0r_sl_1r
+        bind "fd@${cl_0r_sl_1r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sl_1r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com) allow-0rtt
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+
+    listen cl_0r_sf_0r
+        bind "fd@${cl_0r_sf_0r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sf_0r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com) allow-0rtt
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) "$ZRTT_SUPP" }
+
+    listen cl_0r_sl_0r
+        bind "fd@${cl_0r_sl_0r}"
+        retry-on 0rtt-rejected
+        http-request add-header x-from %[be_name]
+        server s1 "${VTC_SOCK_TYPE}+${h1_sv_sl_0r_sock}" ssl verify none alpn "${ALPN}" sni str(www.test1.com) allow-0rtt
+        http-response add-header x-early-rcvd-test OK if !{ ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) 0 }
+        http-response add-header x-early-rcvd-test OK if  { ssl_bc_is_resumed } { res.hdr(x-ssl-early-rcvd) "$ZRTT_SUPP" }
+
+    listen ssl
+        # socket names indicate their capabilities and are used below in regex
+        # (0r means 0rtt OK, 1r means 0rtt not accepted)
+        bind "${VTC_SOCK_TYPE}+fd@${sv_sf_1r}" name sf_1r ssl crt ${testdir}/common.pem ssl-min-ver "${TLSV}" ssl-max-ver "${TLSV}"
+        bind "${VTC_SOCK_TYPE}+fd@${sv_sl_1r}" name sl_1r ssl crt ${testdir}/common.pem ssl-min-ver "${TLSV}" ssl-max-ver "${TLSV}" no-tls-tickets
+        bind "${VTC_SOCK_TYPE}+fd@${sv_sf_0r}" name sf_0r ssl crt ${testdir}/common.pem ssl-min-ver "${TLSV}" ssl-max-ver "${TLSV}" allow-0rtt
+        bind "${VTC_SOCK_TYPE}+fd@${sv_sl_0r}" name sl_0r ssl crt ${testdir}/common.pem ssl-min-ver "${TLSV}" ssl-max-ver "${TLSV}" allow-0rtt no-tls-tickets
+
+        # this is the application server behind us
+        server s1 ${h1_srv_sock}
+
+        # this one is only set for debugging
+        http-response add-header x-ssl-early-rcvd %[ssl_fc_early_rcvd]
+
+    frontend srv
+        bind "fd@${srv}"
+        http-request return status 200
+} -start
+
+
+# 1r -> 1r
+client cl_1r_sf_1r -connect ${h1_cl_1r_sf_1r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sf_1r -connect ${h1_cl_1r_sf_1r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sl_1r -connect ${h1_cl_1r_sl_1r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sl_1r -connect ${h1_cl_1r_sl_1r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+
+# 1r -> 0r
+client cl_1r_sf_0r -connect ${h1_cl_1r_sf_0r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sf_0r -connect ${h1_cl_1r_sf_0r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sl_0r -connect ${h1_cl_1r_sl_0r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_1r_sl_0r -connect ${h1_cl_1r_sl_0r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+# 0r -> 1r
+client cl_0r_sf_1r -connect ${h1_cl_0r_sf_1r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sf_1r -connect ${h1_cl_0r_sf_1r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sl_1r -connect ${h1_cl_0r_sl_1r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sl_1r -connect ${h1_cl_0r_sl_1r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+
+# 0r -> 0r: must work for TLSv1.3
+client cl_0r_sf_0r -connect ${h1_cl_0r_sf_0r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sf_0r -connect ${h1_cl_0r_sf_0r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sl_0r -connect ${h1_cl_0r_sl_0r_sock} {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
+
+client cl_0r_sl_0r -connect ${h1_cl_0r_sl_0r_sock} -repeat 10 {
+    txreq
+    rxresp
+    expect resp.status == 200
+    expect resp.http.x-early-rcvd-test == OK
+} -run
diff --git a/reg-tests/ssl/tls12_0rtt.vtc b/reg-tests/ssl/tls12_0rtt.vtc
new file mode 100644 (file)
index 0000000..f95c607
--- /dev/null
@@ -0,0 +1,15 @@
+#REGTEST_TYPE=devel
+
+# This reg-test tests 8 scenarios with and without tickets, with various
+# combinations of settings for allow-0rtt, with TLSv1.2. Each client will try
+# to established a connection, then try to reconnect 10 times resuming, and
+# check for which combination(s) 0-rtt is used and fail if any does so since
+# it's not expected to work with 1.2.
+
+varnishtest "Test if the SSL session/ticket reuse works correctly for TLSv1.2"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL_WOLFSSL) || feature(OPENSSL) && ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)'"
+
+setenv VTC_SOCK_TYPE stream
+setenv TLSV TLSv1.2
+setenv ALPN http/1.1
+include ${testdir}/../ssl/ssl-0rtt.vtci
diff --git a/reg-tests/ssl/tls13_0rtt.vtc b/reg-tests/ssl/tls13_0rtt.vtc
new file mode 100644 (file)
index 0000000..f211144
--- /dev/null
@@ -0,0 +1,14 @@
+#REGTEST_TYPE=devel
+
+# This reg-test tests 8 scenarios with and without tickets, with various
+# combinations of settings for allow-0rtt, with TLSv1.3. Each client will try
+# to established a connection, then try to reconnect 10 times resuming, and
+# check for which combination(s) 0-rtt is used and if they are expected.
+
+varnishtest "Test if the SSL session/ticket reuse works correctly for TLSv1.3"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && ssllib_name_startswith(OpenSSL) && openssl_version_atleast(1.1.1)'"
+
+setenv VTC_SOCK_TYPE stream
+setenv TLSV TLSv1.3
+setenv ALPN http/1.1
+include ${testdir}/../ssl/ssl-0rtt.vtci