--- /dev/null
+#REGTEST_TYPE=bug
+varnishtest "Test for per-certificate ciphersuites in a crt-list over QUIC"
+feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.8)'"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(QUIC) && !feature(QUIC_OPENSSL_COMPAT) && !feature(OPENSSL_WOLFSSL) && !ssllib_name_startswith(LibreSSL) && !ssllib_name_startswith(BoringSSL)'"
+# This test checks that the ciphersuites set on a crt-list line are enforced
+# over QUIC, even when the bind line allows more of them.
+# See GitHub issue #3500.
+
+setenv VTC_SOCK_TYPE quic
+include ${testdir}/../ssl/ssl_crt-list_ciphersuites.vtci
--- /dev/null
+common.pem [ciphers ECDHE-RSA-AES128-GCM-SHA256] restricted.bug3500.domain.tld
+common.pem open.bug3500.domain.tld
--- /dev/null
+common.pem [ciphersuites TLS_AES_128_GCM_SHA256] restricted.bug3500.domain.tld
+common.pem open.bug3500.domain.tld
--- /dev/null
+#REGTEST_TYPE=bug
+varnishtest "Test for per-certificate ciphers in a crt-list"
+feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.8)'"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && !feature(OPENSSL_WOLFSSL)'"
+# This test checks that the ciphers set on a crt-list line are enforced, even
+# when the bind line allows more of them. It uses TLSv1.2, so there is no QUIC
+# variant.
+# See GitHub issue #3500.
+feature ignore_unknown_macro
+
+server s1 -repeat 2 {
+ rxreq
+ txresp
+} -start
+
+haproxy h1 -conf {
+ global
+ .if feature(THREAD)
+ thread-groups 1
+ .endif
+ crt-base ${testdir}/certs
+
+ defaults
+ mode http
+ option httplog
+ retries 0
+ log stderr local0 debug err
+ option logasap
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ listen clear-lst
+ bind "fd@${clearlst}"
+ use_backend allowed if { path /allowed }
+ use_backend excluded if { path /excluded }
+ use_backend open if { path /open }
+
+ # cipher allowed by the crt-list line
+ backend allowed
+ server s1 ${h1_ssllst_sock} ssl verify none sni str(restricted.bug3500.domain.tld) ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.2 ciphers ECDHE-RSA-AES128-GCM-SHA256
+
+ # cipher allowed by the bind line but excluded by the crt-list line
+ backend excluded
+ server s1 ${h1_ssllst_sock} ssl verify none sni str(restricted.bug3500.domain.tld) ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.2 ciphers ECDHE-RSA-AES256-GCM-SHA384
+
+ # same excluded cipher, but for a certificate without restriction
+ backend open
+ server s1 ${h1_ssllst_sock} ssl verify none sni str(open.bug3500.domain.tld) ssl-min-ver TLSv1.2 ssl-max-ver TLSv1.2 ciphers ECDHE-RSA-AES256-GCM-SHA384
+
+ listen ssl-lst
+ http-response add-header x-cipher %[ssl_fc_cipher]
+ bind "fd@${ssllst}" ssl strict-sni crt-list ${testdir}/certs/ciphers.crt-list ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
+ server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/allowed"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-cipher == ECDHE-RSA-AES128-GCM-SHA256
+} -run
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/excluded"
+ rxresp
+ expect resp.status == 503
+} -run
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/open"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-cipher == ECDHE-RSA-AES256-GCM-SHA384
+} -run
--- /dev/null
+#REGTEST_TYPE=bug
+varnishtest "Test for per-certificate ciphersuites in a crt-list"
+feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.8)'"
+feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && !ssllib_name_startswith(LibreSSL) && !ssllib_name_startswith(BoringSSL) && !feature(OPENSSL_WOLFSSL)'"
+# This test checks that the ciphersuites set on a crt-list line are enforced,
+# even when the bind line allows more of them.
+# See GitHub issue #3500.
+
+setenv VTC_SOCK_TYPE stream
+include ${testdir}/ssl_crt-list_ciphersuites.vtci
--- /dev/null
+feature ignore_unknown_macro
+
+server s1 -repeat 2 {
+ rxreq
+ txresp
+} -start
+
+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
+ crt-base ${testdir}/certs
+
+ defaults
+ mode http
+ option httplog
+ retries 0
+ log stderr local0 debug err
+ option logasap
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ listen clear-lst
+ bind "fd@${clearlst}"
+ use_backend allowed if { path /allowed }
+ use_backend excluded if { path /excluded }
+ use_backend open if { path /open }
+
+ # ciphersuite allowed by the crt-list line
+ backend allowed
+ server s1 ${VTC_SOCK_TYPE}+${h1_ssllst_sock} ssl verify none sni str(restricted.bug3500.domain.tld) ssl-min-ver TLSv1.3 ciphersuites TLS_AES_128_GCM_SHA256
+
+ # ciphersuite allowed by the bind line but excluded by the crt-list line
+ backend excluded
+ server s1 ${VTC_SOCK_TYPE}+${h1_ssllst_sock} ssl verify none sni str(restricted.bug3500.domain.tld) ssl-min-ver TLSv1.3 ciphersuites TLS_AES_256_GCM_SHA384
+
+ # same excluded ciphersuite, but for a certificate without restriction
+ backend open
+ server s1 ${VTC_SOCK_TYPE}+${h1_ssllst_sock} ssl verify none sni str(open.bug3500.domain.tld) ssl-min-ver TLSv1.3 ciphersuites TLS_AES_256_GCM_SHA384
+
+ listen ssl-lst
+ http-response add-header x-cipher %[ssl_fc_cipher]
+ bind ${VTC_SOCK_TYPE}+fd@${ssllst} ssl strict-sni crt-list ${testdir}/certs/ciphersuites.crt-list ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
+ server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/allowed"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-cipher == TLS_AES_128_GCM_SHA256
+} -run
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/excluded"
+ rxresp
+ expect resp.status == 503
+} -run
+
+client c1 -connect ${h1_clearlst_sock} {
+ txreq -url "/open"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-cipher == TLS_AES_256_GCM_SHA384
+} -run
#include <import/ebpttree.h>
#include <import/ebsttree.h>
+#include <haproxy/errors.h>
+#include <haproxy/init.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/proto_tcp.h>
#include <haproxy/quic_conn.h>
return SSL_TLSEXT_ERR_NOACK;
}
+#if defined(OPENSSL_IS_AWSLC)
+/* Set to 1 when the SSL object holds its own TLSv1.3 ciphersuites, which is
+ * the case from AWS-LC 1.46 on. Before that SSL_set_ciphersuites() and
+ * SSL_set_cipher_list() write the same field, and the last call wins.
+ */
+static int awslc_ssl_owns_cipher_lists;
+
+/* Checks whether SSL_set_ciphersuites() keeps what SSL_set_cipher_list() set
+ * on the same SSL object. AWSLC_API_VERSION cannot tell, it stays at 32 from
+ * 1.44 to 1.49 while the behaviour changed in 1.46.
+ * Always returns 0, a failed check just leaves the ciphersuites of a crt-list
+ * line unapplied.
+ */
+static int ssl_sock_check_awslc_cipher_lists(void)
+{
+ SSL_CTX *ctx;
+ SSL *ssl = NULL;
+ int before = 0, after = 0;
+
+ ctx = SSL_CTX_new(SSLv23_server_method());
+ if (ctx)
+ ssl = SSL_new(ctx);
+
+ if (ssl) {
+ while (SSL_get_cipher_list(ssl, before))
+ before++;
+ }
+
+ if (before < 2 || !SSL_set_ciphersuites(ssl, "TLS_AES_128_GCM_SHA256")) {
+ ha_warning("Cannot check how AWS-LC keeps the cipher lists of an SSL object. "
+ "The 'ciphersuites' option of a crt-list line will be ignored.\n");
+ }
+ else {
+ while (SSL_get_cipher_list(ssl, after))
+ after++;
+
+ /* More than one entry left means the TLSv1.2 ciphers survived
+ * the call above. The two lists then use separate fields.
+ */
+ if (after > 1)
+ awslc_ssl_owns_cipher_lists = 1;
+ }
+
+ SSL_free(ssl);
+ SSL_CTX_free(ctx);
+ return 0;
+}
+
+REGISTER_POST_CHECK(ssl_sock_check_awslc_cipher_lists);
+
+/* Sets the ciphers and the ciphersuites of the crt-list line <conf> on <ssl>.
+ * AWS-LC copies the cipher lists of the SSL_CTX into the SSL object at
+ * SSL_new() time. SSL_set_SSL_CTX() does not update this copy. So they must be
+ * set again on the SSL object after a context switch. An option not set in
+ * <conf> keeps the bind line value copied at SSL_new() time.
+ * Returns 0 on success, -1 on failure.
+ */
+static int ssl_sock_switchctx_set_ciphers(SSL *ssl, struct ssl_bind_conf *conf)
+{
+ if (conf->ciphers && !SSL_set_cipher_list(ssl, conf->ciphers))
+ return -1;
+
+ /* On the versions where both cipher lists share the same field, this
+ * call would erase the ciphers set above.
+ */
+ if (awslc_ssl_owns_cipher_lists &&
+ conf->ciphersuites && !SSL_set_ciphersuites(ssl, conf->ciphersuites))
+ return -1;
+
+ return 0;
+}
+#endif /* OPENSSL_IS_AWSLC */
+
#if defined(SSL_CTX_set1_curves_list)
/* Sets the curves of the crt-list line <conf> on <ssl>.
* The SSL libraries copy the curves of the SSL_CTX into the SSL object at
* SSL_new() time. SSL_set_SSL_CTX() does not update this copy. So they must be
- * set again on the connection after a context switch.
+ * set again on the SSL object after a context switch.
* The value is picked in the same order as when the SSL_CTX is prepared:
* <curves> of the crt-list line, else <curves> of the bind line, else
* <ecdhe> of the crt-list line, else <ecdhe> of the bind line.
methodVersions[conf->ssl_methods.max].ssl_set_version(ssl, SET_MAX);
if (conf->early_data)
allow_early = 1;
+#if defined(OPENSSL_IS_AWSLC)
+ if (ssl_sock_switchctx_set_ciphers(ssl, conf) < 0) {
+ HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
+ TRACE_ERROR("Cannot set the crt-list ciphers on the SSL object",
+ SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
+ goto abort;
+ }
+#endif
#if defined(SSL_CTX_set1_curves_list)
if (ssl_sock_switchctx_set_curves(ssl, s, conf) < 0) {
HA_RWLOCK_RDUNLOCK(SNI_LOCK, &s->sni_lock);
- TRACE_ERROR("Cannot set the crt-list curves on the connection",
+ TRACE_ERROR("Cannot set the crt-list curves on the SSL object",
SSL_EV_CONN_SWITCHCTX_CB|SSL_EV_CONN_ERR, conn);
goto abort;
}