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.
This commit is contained in:
Frederic Lecaille 2025-05-19 17:04:21 +02:00
parent f0c833f334
commit 5075a21fe1
4 changed files with 194 additions and 11 deletions

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

@ -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

@ -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

@ -21,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;
@ -371,68 +371,222 @@ static int ha_quic_add_handshake_data(SSL *ssl, enum ssl_encryption_level_t leve
#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);
TRACE_LEAVE(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,
const unsigned char **buf,
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;
struct ncbuf *ncbuf = NULL;
struct quic_cstream *cstream = NULL;
ncb_sz_t data = 0;
TRACE_ENTER(QUIC_EV_CONN_SSLDATA, qc);
TRACE_LEAVE(QUIC_EV_CONN_SSLDATA, qc);
return ret;
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;
}
static int ha_quic_ossl_crypto_release_rcd(SSL *ssl, size_t bytes_read, void *arg)
/* 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);
TRACE_LEAVE(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);
TRACE_LEAVE(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);
TRACE_LEAVE(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;