Compare commits

...

7 Commits

Author SHA1 Message Date
Frederic Lecaille
4fe959c2c1 DOC: update INSTALL for QUIC with OpenSSL 3.5 usages
Update the QUIC sections which mention the OpenSSL library use cases.
2025-05-19 19:03:51 +02:00
Frederic Lecaille
86636a5d71 MINOR: quic: OpenSSL 3.5 trick to support 0-RTT
For an unidentified reason, SSL_do_hanshake() succeeds at its first call when 0-RTT
is enabled for the connection. This behavior looks very similar by the one encountered
by AWS-LC stack. That said, it was documented by AWS-LC. This issue leads the
connection to stop sending handshake packets after having release the handshake
encryption level. In fact, no handshake packets could even been sent leading
the handshake to always fail.

To fix this, this patch simulates a "handshake in progress" state waiting
for the application level read secret to be established by the TLS stack.
This may happen only after the QUIC listener has completed/confirmed the handshake
upon handshake CRYPTO data receipt from the peer.
2025-05-19 19:03:51 +02:00
Frederic Lecaille
653cb95d54 MINOR: quic: OpenSSL 3.5 internal QUIC custom extension for transport parameters reset
A QUIC must sent its transport parameter using a TLS custom extention. This
extension is reset by SSL_set_SSL_CTX(). It can be restored calling
quic_ssl_set_tls_cbs() (which calls SSL_set_quic_tls_cbs()).
2025-05-19 19:03:44 +02:00
Frederic Lecaille
5075a21fe1 MINOR: quic: implement all remaining callbacks for OpenSSL 3.5 QUIC API
The quic_conn struct is modified for two reasons. The first one is to store
the encoded version of the local tranport parameter as this is done for
USE_QUIC_OPENSSL_COMPAT. Indeed, the local transport parameter "should remain
valid until after the parameters have been sent" as mentionned by
SSL_set_quic_tls_cbs(3) manual. In our case, the buffer is a static buffer
attached to the quic_conn object. qc_ssl_set_quic_transport_params() function
whose role is to call SSL_set_tls_quic_transport_params() (aliased by
SSL_set_quic_transport_params() to set these local tranport parameter into
the TLS stack from the buffer attached to the quic_conn struct.

The second quic_conn struct modification is the addition of the  new ->prot_level
(SSL protection level) member is added to the quic_conn strut to store "the most
recent write encryption level set via the OSSL_FUNC_SSL_QUIC_TLS_yield_secret_fn
callback (if it has been called)" as mentionned by SSL_set_quic_tls_cbs(3) manual.

This patches finally implements the five remaining callacks to make the haproxy
QUIC implementation work.

OSSL_FUNC_SSL_QUIC_TLS_crypto_send_fn() (ha_quic_ossl_crypto_send) is easy to
implement. It calls ha_quic_add_handshake_data() after having converted
qc->prot_level TLS protection level value to the correct ssl_encryption_level_t
(boringSSL API/quictls) value.

OSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fn() (ha_quic_ossl_crypto_recv_rcd())
provide the non-contiguous addresses to the TLS stack, without releasing
them.

OSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fn() (ha_quic_ossl_crypto_release_rcd())
release these non-contiguous buffer relying on the fact that the list of
encryption level (qc->qel_list) is correctly ordered by SSL protection level
secret establishements order (by the TLS stack).

OSSL_FUNC_SSL_QUIC_TLS_yield_secret_fn() (ha_quic_ossl_got_transport_params())
is a simple wrapping function over ha_quic_set_encryption_secrets() which is used
by boringSSL/quictls API.

OSSL_FUNC_SSL_QUIC_TLS_got_transport_params_fn() (ha_quic_ossl_got_transport_params())
role is to store the peer received transport parameters. It simply calls
quic_transport_params_store() and set them into the TLS stack calling
qc_ssl_set_quic_transport_params().

Also add some comments for all the OpenSSL 3.5 QUIC API callbacks.

This patch have no impact on the other use of QUIC API provided by the others TLS
stacks.
2025-05-19 17:50:24 +02:00
Frederic Lecaille
f0c833f334 MINOR: quic: Add useful error traces about qc_ssl_sess_init() failures
There were no traces to diagnose qc_ssl_sess_init() failures from QUIC traces.
This patch add calls to TRACE_DEVEL() into qc_ssl_sess_init() and its caller
(qc_alloc_ssl_sock_ctx()). This was useful at least to diagnose SSL context
initialization failures when porting QUIC to the new OpenSSL 3.5 QUIC API.

Should be easily backported as far as 2.6.
2025-05-19 17:50:24 +02:00
Frederic Lecaille
d96368b1a4 MINOR: quic: Allow the use of the new OpenSSL 3.5.0 QUIC TLS API (to be completed)
This patch allows the use of the new OpenSSL 3.5.0 QUIC TLS API when it is
available and detected at compilation time. The detection relies on the presence of the
OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND macro from openssl-compat.h. Indeed this
macro is defined by OpenSSL since 3.5.0 version. It is not defined by quictls.
This helps in distinguishing these two TLS stacks. When the detection succeeds,
HAVE_OPENSSL_QUIC is also defined by openssl-compat.h. Then, this is this new macro
which is used to detect the availability of the new OpenSSL 3.5.0 QUIC TLS API.

Note that this detection is done only if USE_QUIC_OPENSSL_COMPAT is not asked.
So, USE_QUIC_OPENSSL_COMPAT and HAVE_OPENSSL_QUIC are exclusive.

At the same location, from openssl-compat.h, ssl_encryption_level_t enum is
defined. This enum was defined by quictls and expansively used by the haproxy
QUIC implementation. SSL_set_quic_transport_params() is replaced by
SSL_set_quic_tls_transport_params. SSL_set_quic_early_data_enabled() (quictls) is also replaced
by SSL_set_quic_tls_early_data_enabled() (OpenSSL). SSL_quic_read_level() (quictls)
is not defined by OpenSSL. It is only used by the traces to log the current
TLS stack decryption level (read). A macro makes it return -1 which is an
usused values.

The most of the differences between quictls and OpenSSL QUI APIs are in quic_ssl.c
where some callbacks must be defined for these two APIs. This is why this
patch modifies quic_ssl.c to define an array of OSSL_DISPATCH structs: <ha_quic_dispatch>.
Each element of this arry defines a callback. So, this patch implements these
six callabcks:

  - ha_quic_ossl_crypto_send()
  - ha_quic_ossl_crypto_recv_rcd()
  - ha_quic_ossl_crypto_release_rcd()
  - ha_quic_ossl_yield_secret()
  - ha_quic_ossl_got_transport_params() and
  - ha_quic_ossl_alert().

But at this time, these implementations which must return an int return 0 interpreted
as a failure by the OpenSSL QUIC API, except for ha_quic_ossl_alert() which
is implemented the same was as for quictls. The five remaining functions above
will be implemented by the next patches to come.

ha_quic_set_encryption_secrets() and ha_quic_add_handshake_data() have been moved
to be defined for both quictls and OpenSSL QUIC API.

These callbacks are attached to the SSL objects (sessions) calling qc_ssl_set_cbs()
new function. This latter callback the correct function to attached the correct
callbacks to the SSL objects (defined by <ha_quic_method> for quictls, and
<ha_quic_dispatch> for OpenSSL).

The calls to SSL_provide_quic_data() and SSL_process_quic_post_handshake()
have been also disabled. These functions are not defined by OpenSSL QUIC API.
At this time, the functions which call them are still defined when HAVE_OPENSSL_QUIC
is defined.
2025-05-19 17:50:24 +02:00
Frederic Lecaille
5ec237528d CLEANUP: quic: Useless BIO_METHOD initialization
This code is there from QUIC implementation start. It was supposed to
initialize <ha_quic_meth> as a BIO_METHOD static object. But this
BIO_METHOD is not used at all!

Should be backported as far as 2.6 to help integrate the next patches to come.
2025-05-19 17:50:24 +02:00
10 changed files with 414 additions and 55 deletions

31
INSTALL
View File

@ -259,10 +259,10 @@ reported to work as well. While there are some efforts from the community to
ensure they work well, OpenSSL remains the primary target and this means that
in case of conflicting choices, OpenSSL support will be favored over other
options. Note that QUIC is not fully supported when haproxy is built with
OpenSSL. In this case, QUICTLS is the preferred alternative. As of writing
this, the QuicTLS project follows OpenSSL very closely and provides update
simultaneously, but being a volunteer-driven project, its long-term future does
not look certain enough to convince operating systems to package it, so it
OpenSSL < 3.5 version. In this case, QUICTLS is the preferred alternative.
As of writing this, the QuicTLS project follows OpenSSL very closely and provides
update simultaneously, but being a volunteer-driven project, its long-term future
does not look certain enough to convince operating systems to package it, so it
needs to be build locally. See the section about QUIC in this document.
A fifth option is wolfSSL (https://github.com/wolfSSL/wolfssl). It is the only
@ -500,10 +500,11 @@ QUIC is the new transport layer protocol and is required for HTTP/3. This
protocol stack is currently supported as an experimental feature in haproxy on
the frontend side. In order to enable it, use "USE_QUIC=1 USE_OPENSSL=1".
Note that QUIC is not fully supported by the OpenSSL library. Indeed QUIC 0-RTT
cannot be supported by OpenSSL contrary to others libraries with full QUIC
support. The preferred option is to use QUICTLS. This is a fork of OpenSSL with
a QUIC-compatible API. Its repository is available at this location:
Note that QUIC is not always fully supported by the OpenSSL library depending on
its version. Indeed QUIC 0-RTT cannot be supported by OpenSSL for versions before
3.5 contrary to others libraries with full QUIC support. The preferred option is
to use QUICTLS. This is a fork of OpenSSL with a QUIC-compatible API. Its
repository is available at this location:
https://github.com/quictls/openssl
@ -531,14 +532,18 @@ way assuming that wolfSSL was installed in /opt/wolfssl-5.6.0 as shown in 4.5:
SSL_INC=/opt/wolfssl-5.6.0/include SSL_LIB=/opt/wolfssl-5.6.0/lib
LDFLAGS="-Wl,-rpath,/opt/wolfssl-5.6.0/lib"
As last resort, haproxy may be compiled against OpenSSL as follows:
As last resort, haproxy may be compiled against OpenSSL as follows from 3.5
version with 0-RTT support:
$ make TARGET=generic USE_OPENSSL=1 USE_QUIC=1
or as follows for all OpenSSL versions but without O-RTT support:
$ make TARGET=generic USE_OPENSSL=1 USE_QUIC=1 USE_QUIC_OPENSSL_COMPAT=1
Note that QUIC 0-RTT is not supported by haproxy QUIC stack when built against
OpenSSL. In addition to this compilation requirements, the QUIC listener
bindings must be explicitly enabled with a specific QUIC tuning parameter.
(see "limited-quic" global parameter of haproxy Configuration Manual).
In addition to this requirements, the QUIC listener bindings must be explicitly
enabled with a specific QUIC tuning parameter. (see "limited-quic" global
parameter of haproxy Configuration Manual).
5) How to build HAProxy

View File

@ -46,7 +46,25 @@
#ifdef USE_QUIC_OPENSSL_COMPAT
#include <haproxy/quic_openssl_compat.h>
#else
#if defined(OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND)
/* This macro is defined by the new OpenSSL 3.5.0 QUIC TLS API and it is not
* defined by quictls.
*/
#define HAVE_OPENSSL_QUIC
#define SSL_set_quic_transport_params SSL_set_quic_tls_transport_params
#define SSL_set_quic_early_data_enabled SSL_set_quic_tls_early_data_enabled
#define SSL_quic_read_level(arg) -1
enum ssl_encryption_level_t {
ssl_encryption_initial = 0,
ssl_encryption_early_data,
ssl_encryption_handshake,
ssl_encryption_application
};
#endif
#endif /* USE_QUIC_OPENSSL_COMPAT */
#if defined(OPENSSL_IS_AWSLC)
#define OPENSSL_NO_DH

View File

@ -334,7 +334,7 @@ struct quic_conn {
int tps_tls_ext;
int state;
enum qc_mux_state mux_state; /* status of the connection/mux layer */
#ifdef USE_QUIC_OPENSSL_COMPAT
#if defined(USE_QUIC_OPENSSL_COMPAT) || defined(HAVE_OPENSSL_QUIC)
unsigned char enc_params[QUIC_TP_MAX_ENCLEN]; /* encoded QUIC transport parameters */
size_t enc_params_len;
#endif
@ -345,6 +345,9 @@ struct quic_conn {
*/
uint64_t hash64;
#ifdef HAVE_OPENSSL_QUIC
uint32_t prot_level;
#endif
/* Initial encryption level */
struct quic_enc_level *iel;
/* 0-RTT encryption level */

View File

@ -36,6 +36,7 @@
int ssl_quic_initial_ctx(struct bind_conf *bind_conf);
int qc_alloc_ssl_sock_ctx(struct quic_conn *qc);
int qc_ssl_provide_all_quic_data(struct quic_conn *qc, struct ssl_sock_ctx *ctx);
int quic_ssl_set_tls_cbs(SSL *ssl);
static inline void qc_free_ssl_sock_ctx(struct ssl_sock_ctx **ctx)
{

View File

@ -291,6 +291,29 @@ static inline struct quic_enc_level **ssl_to_qel_addr(struct quic_conn *qc,
}
}
#ifdef HAVE_OPENSSL_QUIC
/* Simple helper function which translate an OpenSSL SSL protection level
* to a quictls SSL encryption. This way the code which use the OpenSSL QUIC API
* may use the code which uses the quictls API.
*/
static inline enum ssl_encryption_level_t ssl_prot_level_to_enc_level(struct quic_conn *qc,
uint32_t prot_level)
{
switch (prot_level) {
case OSSL_RECORD_PROTECTION_LEVEL_NONE:
return ssl_encryption_initial;
case OSSL_RECORD_PROTECTION_LEVEL_EARLY:
return ssl_encryption_early_data;
case OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE:
return ssl_encryption_handshake;
case OSSL_RECORD_PROTECTION_LEVEL_APPLICATION:
return ssl_encryption_application;
default:
return -1;
}
}
#endif
/* Return the address of the QUIC TLS encryption level associated to <level> internal
* encryption level and attached to <qc> QUIC connection if succeeded, or
* NULL if failed.

View File

@ -99,5 +99,6 @@ struct quic_rx_crypto_frm {
#define QUIC_EV_CONN_KP (1ULL << 50)
#define QUIC_EV_CONN_SSL_COMPAT (1ULL << 51)
#define QUIC_EV_CONN_BIND_TID (1ULL << 52)
#define QUIC_EV_CONN_RELEASE_RCD (1ULL << 53)
#endif /* _HAPROXY_QUIC_TRACE_T_H */

View File

@ -1109,6 +1109,9 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
quic_tls_ku_reset(&qc->ku.nxt_rx);
quic_tls_ku_reset(&qc->ku.nxt_tx);
#ifdef HAVE_OPENSSL_QUIC
qc->prot_level = OSSL_RECORD_PROTECTION_LEVEL_NONE;
#endif
/* Encryption levels */
qc->iel = qc->eel = qc->hel = qc->ael = NULL;
LIST_INIT(&qc->qel_list);

View File

@ -10,8 +10,6 @@
#include <haproxy/ssl_sock.h>
#include <haproxy/trace.h>
static BIO_METHOD *ha_quic_meth;
DECLARE_POOL(pool_head_quic_ssl_sock_ctx, "quic_ssl_sock_ctx", sizeof(struct ssl_sock_ctx));
/* Set the encoded version of the transport parameter into the TLS
@ -23,7 +21,7 @@ static int qc_ssl_set_quic_transport_params(struct quic_conn *qc,
const struct quic_version *ver, int server)
{
int ret = 0;
#ifdef USE_QUIC_OPENSSL_COMPAT
#if defined(USE_QUIC_OPENSSL_COMPAT) || defined(HAVE_OPENSSL_QUIC)
unsigned char *in = qc->enc_params;
size_t insz = sizeof qc->enc_params;
size_t *enclen = &qc->enc_params_len;
@ -327,27 +325,6 @@ write:
return ret;
}
#if defined(OPENSSL_IS_AWSLC)
/* compatibility function for split read/write encryption secrets to be used
* with the API which uses 2 callbacks. */
static inline int ha_quic_set_read_secret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len)
{
return ha_quic_set_encryption_secrets(ssl, level, secret, NULL, secret_len);
}
static inline int ha_quic_set_write_secret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len)
{
return ha_quic_set_encryption_secrets(ssl, level, NULL, secret, secret_len);
}
#endif
/* ->add_handshake_data QUIC TLS callback used by the QUIC TLS stack when it
* wants to provide the QUIC layer with CRYPTO data.
* Returns 1 if succeeded, 0 if not.
@ -391,6 +368,291 @@ static int ha_quic_add_handshake_data(SSL *ssl, enum ssl_encryption_level_t leve
return ret;
}
#ifdef HAVE_OPENSSL_QUIC
/************************** OpenSSL QUIC TLS API (>= 3.5.0) *******************/
/* Callback called by OpenSSL when it needs to send CRYPTO data to the peer.
* This is done from <buf> buffer with <buf_len> as number of bytes to be sent.
* This callback must set <*consumed> to the number of bytes which could be
* consumed (buffered in our case) before being sent to the peer. This is always
* <buf_len> when this callback succeeds, or 0 when it fails.
* Return 1 if succeeded, 0 if not.
*/
static int ha_quic_ossl_crypto_send(SSL *ssl,
const unsigned char *buf, size_t buf_len,
size_t *consumed, void *arg)
{
int ret = 0;
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
enum ssl_encryption_level_t level = ssl_prot_level_to_enc_level(qc, qc->prot_level);
TRACE_ENTER(QUIC_EV_CONN_ADDDATA, qc);
if (!ha_quic_add_handshake_data(ssl, level, buf, buf_len))
goto err;
*consumed = buf_len;
TRACE_DEVEL("CRYPTO data buffered", QUIC_EV_CONN_ADDDATA, qc, &level, &buf_len);
ret = 1;
leave:
TRACE_LEAVE(QUIC_EV_CONN_ADDDATA, qc);
return ret;
err:
*consumed = 0;
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_ADDDATA, qc);
goto leave;
}
/* Callback to provide CRYPTO data from the peer to the TLS stack. It must set
* <buf> to the address of the buffer which contains the CRYPTO data.
* <*byte_read> value must be the number of bytes of CRYPTO data received.
* Never fail, always return 1.
*/
static int ha_quic_ossl_crypto_recv_rcd(SSL *ssl,
const unsigned char **buf,
size_t *bytes_read,
void *arg)
{
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
struct quic_enc_level *qel;
struct ncbuf *ncbuf = NULL;
struct quic_cstream *cstream = NULL;
ncb_sz_t data = 0;
TRACE_ENTER(QUIC_EV_CONN_SSLDATA, qc);
list_for_each_entry(qel, &qc->qel_list, list) {
cstream = qel->cstream;
if (!cstream)
continue;
ncbuf = &cstream->rx.ncbuf;
if (ncb_is_null(ncbuf))
continue;
data = ncb_data(ncbuf, 0);
if (data)
break;
}
if (data) {
const unsigned char *cdata;
BUG_ON(ncb_is_null(ncbuf) || !cstream);
/* <ncbuf> must not be released at this time. */
cdata = (const unsigned char *)ncb_head(ncbuf);
cstream->rx.offset += data;
TRACE_DEVEL("buffered crypto data were provided to TLS stack",
QUIC_EV_CONN_PHPKTS, qc, qel);
*buf = cdata;
*bytes_read = data;
}
else {
*buf = NULL;
*bytes_read = 0;
}
TRACE_LEAVE(QUIC_EV_CONN_SSLDATA, qc);
return 1;
}
/* Callback to release the CRYPT data buffer which have been received
* by ha_quic_ossl_crypto_recv_rcd().
* Return 0 if failed, this means no buffer could be released, or 1 if
* succeeded.
*/
static int ha_quic_ossl_crypto_release_rcd(SSL *ssl,
size_t bytes_read, void *arg)
{
int ret = 0;
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
struct quic_enc_level *qel;
TRACE_ENTER(QUIC_EV_CONN_RELEASE_RCD, qc);
list_for_each_entry(qel, &qc->qel_list, list) {
struct quic_cstream *cstream = qel->cstream;
struct ncbuf *ncbuf;
ncb_sz_t data;
if (!cstream)
continue;
ncbuf = &cstream->rx.ncbuf;
if (ncb_is_null(ncbuf))
continue;
data = ncb_data(ncbuf, 0);
if (!data)
continue;
data = data > bytes_read ? bytes_read : data;
ncb_advance(ncbuf, data);
bytes_read -= data;
if (ncb_is_empty(ncbuf)) {
TRACE_DEVEL("freeing crypto buf", QUIC_EV_CONN_PHPKTS, qc, qel);
quic_free_ncbuf(ncbuf);
}
ret = 1;
if (bytes_read == 0)
break;
}
if (!ret)
goto err;
leave:
TRACE_LEAVE(QUIC_EV_CONN_RELEASE_RCD, qc);
return ret;
err:
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_RELEASE_RCD, qc);
goto leave;
}
/* Callback called by OpenSSL when <secret> has been established at
* <prot_level> SSL protection level. <direction> value is 0 for a read secret,
* 1 for a write secret.
* Return 1 if succeeded, 0 if not.
*/
static int ha_quic_ossl_yield_secret(SSL *ssl, uint32_t prot_level, int direction,
const unsigned char *secret, size_t secret_len,
void *arg)
{
int ret = 0;
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
enum ssl_encryption_level_t level = ssl_prot_level_to_enc_level(qc, prot_level);
TRACE_ENTER(QUIC_EV_CONN_RWSEC, qc);
BUG_ON(level == -1);
if (!direction) {
/* read secret */
if (!ha_quic_set_encryption_secrets(ssl, level, secret, NULL, secret_len))
goto err;
}
else {
/* write secret */
if (!ha_quic_set_encryption_secrets(ssl, level, NULL, secret, secret_len))
goto err;
qc->prot_level = prot_level;
}
ret = 1;
leave:
TRACE_LEAVE(QUIC_EV_CONN_RWSEC, qc);
return ret;
err:
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_RWSEC, qc);
goto leave;
}
/* Callback called by OpenSSL when the peer transport parameters have been
* received.
* Return 1 if succeeded, 0 if not.
*/
static int ha_quic_ossl_got_transport_params(SSL *ssl, const unsigned char *params,
size_t params_len, void *arg)
{
int ret = 0;
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
const struct quic_version *ver =
qc->negotiated_version ? qc->negotiated_version : qc->original_version;
TRACE_ENTER(QUIC_EV_TRANSP_PARAMS, qc);
if (qc->flags & QUIC_FL_CONN_TX_TP_RECEIVED) {
TRACE_PROTO("peer transport parameters already received",
QUIC_EV_TRANSP_PARAMS, qc);
ret = 1;
}
else {
if (!quic_transport_params_store(qc, 0, params, params + params_len) ||
!qc_ssl_set_quic_transport_params(qc, ver, 1))
goto err;
}
ret = 1;
leave:
TRACE_LEAVE(QUIC_EV_TRANSP_PARAMS, qc);
return ret;
err:
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_RWSEC, qc);
goto leave;
}
/* Callback called by OpenSSL when it needs to send a TLS to the peer with
* <alert_code> as value.
* Always succeeds.
*/
static int ha_quic_ossl_alert(SSL *ssl, unsigned char alert_code, void *arg)
{
int ret = 1, alert = alert_code;
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
TRACE_ENTER(QUIC_EV_CONN_SSLALERT, qc);
TRACE_PROTO("Received TLS alert", QUIC_EV_CONN_SSLALERT, qc, &alert);
quic_set_tls_alert(qc, alert_code);
TRACE_LEAVE(QUIC_EV_CONN_SSLALERT, qc);
return ret;
}
static const OSSL_DISPATCH ha_quic_dispatch[] = {
{
OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND,
(OSSL_FUNC)ha_quic_ossl_crypto_send,
},
{
OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD,
(OSSL_FUNC)ha_quic_ossl_crypto_recv_rcd,
},
{
OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD,
(OSSL_FUNC)ha_quic_ossl_crypto_release_rcd,
},
{
OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET,
(OSSL_FUNC)ha_quic_ossl_yield_secret,
},
{
OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS,
(OSSL_FUNC)ha_quic_ossl_got_transport_params,
},
{
OSSL_FUNC_SSL_QUIC_TLS_ALERT,
(OSSL_FUNC)ha_quic_ossl_alert,
},
OSSL_DISPATCH_END,
};
#else /* !HAVE_OPENSSL_QUIC */
/***************************** QUICTLS QUIC API ******************************/
#if defined(OPENSSL_IS_AWSLC)
/* compatibility function for split read/write encryption secrets to be used
* with the API which uses 2 callbacks. */
static inline int ha_quic_set_read_secret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len)
{
return ha_quic_set_encryption_secrets(ssl, level, secret, NULL, secret_len);
}
static inline int ha_quic_set_write_secret(SSL *ssl, enum ssl_encryption_level_t level,
const SSL_CIPHER *cipher, const uint8_t *secret,
size_t secret_len)
{
return ha_quic_set_encryption_secrets(ssl, level, NULL, secret, secret_len);
}
#endif
static int ha_quic_flush_flight(SSL *ssl)
{
struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index);
@ -434,6 +696,7 @@ static SSL_QUIC_METHOD ha_quic_method = {
.send_alert = ha_quic_send_alert,
};
#endif
#endif /* HAVE_OPENSSL_QUIC */
/* Initialize the TLS context of a listener with <bind_conf> as configuration.
* Returns an error count.
@ -535,11 +798,13 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
TRACE_ENTER(QUIC_EV_CONN_SSLDATA, qc);
#ifndef HAVE_OPENSSL_QUIC
if (SSL_provide_quic_data(ctx->ssl, level, data, len) != 1) {
TRACE_ERROR("SSL_provide_quic_data() error",
QUIC_EV_CONN_SSLDATA, qc, NULL, NULL, ctx->ssl);
goto leave;
}
#endif
state = qc->state;
if (state < QUIC_HS_ST_COMPLETE) {
@ -609,7 +874,27 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
}
#endif
#ifndef HAVE_OPENSSL_QUIC
TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state);
#else
/* Hack to support O-RTT with the OpenSSL 3.5 QUIC API.
* SSL_do_handshake() succeeds at the first call. Why? |-(
* This prevents the handshake CRYPTO data to be sent.
* To overcome this, ensure one does not consider the handshake is
* successful if the read application level secrets have not been
* provided by the stack. This happens after having received the peer
* handshake level CRYPTO data which are validated by the TLS stack.
*/
if (qc->li->bind_conf->ssl_conf.early_data &&
(!qc->ael || !qc->ael->tls_ctx.rx.secret)) {
TRACE_PROTO("SSL handshake in progress",
QUIC_EV_CONN_IO_CB, qc, &state, &ssl_err);
goto out;
}
else {
TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state);
}
#endif
/* Check the alpn could be negotiated */
if (!qc->app_ops) {
@ -647,7 +932,9 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
TRACE_ERROR("quic_tls_key_update() failed", QUIC_EV_CONN_IO_CB, qc);
goto leave;
}
} else {
}
#ifndef HAVE_OPENSSL_QUIC
else {
ssl_err = SSL_process_quic_post_handshake(ctx->ssl);
if (ssl_err != 1) {
ssl_err = SSL_get_error(ctx->ssl, ssl_err);
@ -664,6 +951,7 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf,
TRACE_STATE("SSL post handshake succeeded", QUIC_EV_CONN_IO_CB, qc, &state);
}
#endif
out:
ret = 1;
@ -735,6 +1023,16 @@ int qc_ssl_provide_all_quic_data(struct quic_conn *qc, struct ssl_sock_ctx *ctx)
return ret;
}
/* Simple helper to set the specifig OpenSSL/quictls QUIC API callbacks */
int quic_ssl_set_tls_cbs(SSL *ssl)
{
#ifdef HAVE_OPENSSL_QUIC
return SSL_set_quic_tls_cbs(ssl, ha_quic_dispatch, NULL);
#else
return SSL_set_quic_method(ssl, &ha_quic_method);
#endif
}
/* Try to allocate the <*ssl> SSL session object for <qc> QUIC connection
* with <ssl_ctx> as SSL context inherited settings. Also set the transport
* parameters of this session.
@ -754,18 +1052,18 @@ static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl)
*ssl = SSL_new(ssl_ctx);
if (!*ssl) {
if (!retry--)
goto leave;
goto err;
pool_gc(NULL);
goto retry;
}
if (!SSL_set_ex_data(*ssl, ssl_qc_app_data_index, qc) ||
!SSL_set_quic_method(*ssl, &ha_quic_method)) {
!quic_ssl_set_tls_cbs(*ssl)) {
SSL_free(*ssl);
*ssl = NULL;
if (!retry--)
goto leave;
goto err;
pool_gc(NULL);
goto retry;
@ -775,6 +1073,9 @@ static int qc_ssl_sess_init(struct quic_conn *qc, SSL_CTX *ssl_ctx, SSL **ssl)
leave:
TRACE_LEAVE(QUIC_EV_CONN_NEW, qc);
return ret;
err:
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_NEW, qc);
goto leave;
}
#ifdef HAVE_SSL_0RTT_QUIC
@ -872,18 +1173,7 @@ int qc_alloc_ssl_sock_ctx(struct quic_conn *qc)
return !ret;
err:
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_NEW, qc);
pool_free(pool_head_quic_ssl_sock_ctx, ctx);
goto leave;
}
static void __quic_conn_init(void)
{
ha_quic_meth = BIO_meth_new(0x666, "ha QUIC methods");
}
INITCALL0(STG_REGISTER, __quic_conn_init);
static void __quic_conn_deinit(void)
{
BIO_meth_free(ha_quic_meth);
}
REGISTER_POST_DEINIT(__quic_conn_deinit);

View File

@ -16,6 +16,7 @@
#include <haproxy/proto_tcp.h>
#include <haproxy/quic_conn.h>
#include <haproxy/quic_openssl_compat.h>
#include <haproxy/quic_ssl.h>
#include <haproxy/quic_tp.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_gencert.h>
@ -28,6 +29,9 @@ static void ssl_sock_switchctx_set(SSL *ssl, SSL_CTX *ctx)
SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx), ssl_sock_bind_verifycbk);
SSL_set_client_CA_list(ssl, SSL_dup_CA_list(SSL_CTX_get_client_CA_list(ctx)));
SSL_set_SSL_CTX(ssl, ctx);
#if defined(USE_QUIC) && defined(HAVE_OPENSSL_QUIC)
quic_ssl_set_tls_cbs(ssl);
#endif
}
/*

View File

@ -12,6 +12,7 @@
#include <haproxy/errors.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/quic_ssl.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_sock.h>
#include <haproxy/xxhash.h>
@ -284,8 +285,12 @@ SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind
HA_RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_ckch->cert, 0);
if (lru && lru->domain) {
if (ssl)
if (ssl) {
SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data);
#if defined(USE_QUIC) && defined(HAVE_OPENSSL_QUIC)
quic_ssl_set_tls_cbs(ssl);
#endif
}
HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return (SSL_CTX *)lru->data;
}
@ -354,12 +359,18 @@ int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
}
SSL_set_SSL_CTX(ssl, ssl_ctx);
#if defined(USE_QUIC) && defined(HAVE_OPENSSL_QUIC)
quic_ssl_set_tls_cbs(ssl);
#endif
HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
return 1;
}
else {
ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl);
SSL_set_SSL_CTX(ssl, ssl_ctx);
#if defined(USE_QUIC) && defined(HAVE_OPENSSL_QUIC)
quic_ssl_set_tls_cbs(ssl);
#endif
/* No LRU cache, this CTX will be released as soon as the session dies */
SSL_CTX_free(ssl_ctx);
return 1;