SSL_SET_QUIC_TLS_CBS(3SSL)
π SSL_SET_QUIC_TLS_CBS β bestie's guide to using OpenSSL's TLS brain inside somebody else's QUIC π
okay so this is the 3SSL manual page, and it's about letting OpenSSL do the TLS part of QUIC while your QUIC stack does all the QUIC parts. split the work, share the credit, iconic behaviour truly.
β¨ NAME β¨
the whole squad of names in this page:
OSSL_FUNC_SSL_QUIC_TLS_crypto_send_fnOSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fnOSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fnOSSL_FUNC_SSL_QUIC_TLS_yield_secret_fnOSSL_FUNC_SSL_QUIC_TLS_got_transport_params_fnOSSL_FUNC_SSL_QUIC_TLS_alert_fnSSL_set_quic_tls_cbsSSL_set_quic_tls_transport_paramsSSL_set_quic_tls_early_data_enabled
and what do they all do together? they let you use the OpenSSL TLS implementation for a third party QUIC implementation. that's it, that's the whole vibe. π
π¦ SYNOPSIS π¦
#include <openssl/ssl.h>
/* QUIC TLS callbacks available via an OSSL_DISPATCH table */
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_crypto_send_fn)(SSL *s,
const unsigned char *buf,
size_t buf_len,
size_t *consumed,
void *arg);
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fn)(SSL *s,
const unsigned char **buf,
size_t *bytes_read,
void *arg);
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fn)(SSL *,
size_t bytes_read,
void *arg);
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_yield_secret_fn)(SSL *s,
uint32_t prot_level,
int direction,
const unsigned char *secret,
size_t secret_len,
void *arg);
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_got_transport_params_fn)(SSL *s,
const unsigned char *params,
size_t params_len,
void *arg);
/* Function id: OSSL_FUNC_SSL_QUIC_TLS_ALERT */
typedef int (*OSSL_FUNC_SSL_QUIC_TLS_alert_fn)(SSL *s,
unsigned char alert_code,
void *arg);
int SSL_set_quic_tls_cbs(SSL *s, const OSSL_DISPATCH *qtdis, void *arg);
int SSL_set_quic_tls_transport_params(SSL *s,
const unsigned char *params,
size_t params_len);
int SSL_set_quic_tls_early_data_enabled(SSL *s, int enabled);
πΈ DESCRIPTION πΈ
SSL_set_quic_tls_cbs() is the main character here, ngl. it lets you replace the standard TLS record layer with a custom record layer, specifically so a third party QUIC implementation can ride along. for the given SSL object s, you hand over a whole set of callbacks inside an OSSL_DISPATCH table via qtdis. and that arg parameter? it gets passed right back to you as an argument every time one of those callbacks fires. so it's basically your little backpack of state. π
these callbacks get invoked, as needed, by SSL_do_handshake() and SSL_read() β and yes, that includes SSL_read_ex, SSL_peek, and SSL_peek_ex, they're all in the group chat. once the SSL handshake is complete, the QUIC stack must arrange to call one of the SSL_read() variants whenever a post-handshake CRYPTO frame shows up. oh and the number of bytes requested may be zero. zero bytes requested, still a whole function call. it's giving "just checking in". π
an OSSL_DISPATCH table should be an array of OSSL_DISPATCH entries, where every entry is a function id plus a function pointer. and you terminate the array with an empty entry β aka a 0 function id and a NULL function pointer. do not forget the terminator bestie, that's the closing bracket of your friendship. π
calling SSL_set_quic_tls_cbs() will also switch off the SSL_OP_ENABLE_MIDDLEBOX_COMPAT option (if it was set). see SSL_set_options(3). and on top of that, the minimum TLS protocol version gets set to TLS1_3_VERSION. also, super important: it is an error to call this function with anything other than a TLS connection SSL object. no DTLS, no exceptions, no cap. π«
the callbacks, one by one, with love π
- the
OSSL_FUNC_SSL_QUIC_TLS_crypto_send_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND) gets called when CRYPTO frame data needs to be sent to the peer. the data lives in the bufferbuf, and that buffer isbuf_lenlong. the callback is allowed to consume less data than you gave it. when it succeeds, you populate theconsumedparameter with how much data the callback actually consumed β and that number should be less than or equal to the value inbuf_len. the CRYPTO data should be sent using the most recent write encryption level set via theOSSL_FUNC_SSL_QUIC_TLS_yield_secret_fncallback (that is, if it has been called). β¨ - the
OSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD) is your receive path β it's how CRYPTO frame data comes in from the peer. when OpenSSL wants to read data from the peer, boom, this callback fires. it should populate*bufwith a pointer to a buffer containing CRYPTO data received from the peer, and populate the size of that buffer into*bytes_read. the buffer has to stay valid until OpenSSL calls theOSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fncallback. and CRYPTO frame data is assumed to have been decrypted using the most recent read protection level set via theyield_secret_cbcallback (if it has been called). π¦ - the
OSSL_FUNC_SSL_QUIC_TLS_crypto_release_rcd_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD) fires when data previously read viaOSSL_FUNC_SSL_QUIC_TLS_crypto_recv_rcd_fnis no longer required. thebytes_readargument is always equal to the size of the buffer that was previously provided in thecrypto_receive_rcd_cbcallback. and only one record at a time will ever be read by OpenSSL. one at a time. monogamous reading. π - the
OSSL_FUNC_SSL_QUIC_TLS_yield_secret_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET) fires when a new secret has been established. theprot_levelargument tells you which TLS protection level it is, and it'll be one ofOSSL_RECORD_PROTECTION_LEVEL_NONE,OSSL_RECORD_PROTECTION_LEVEL_EARLY,OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE, orOSSL_RECORD_PROTECTION_LEVEL_APPLICATION. thedirectionwill either be0(for the read secret) or1(for the write secret). and the secret itself is in the buffer pointed to bysecret, which is of lengthsecret_len. π - the
OSSL_FUNC_SSL_QUIC_TLS_got_transport_params_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS) fires when transport parameters have been received from the peer. those parameters live in theparamsbuffer, which isparams_lenlong. π - the
OSSL_FUNC_SSL_QUIC_TLS_alert_fncallback (function idOSSL_FUNC_SSL_QUIC_TLS_ALERT) fires when OpenSSL is attempting to send an alert to the peer. the code for the alert comes inalert_code. π¨
the two extra setters π
SSL_set_quic_tls_transport_params() sets the transport parameters to be sent by this endpoint. the parameters are in the params buffer, which should be params_len long. the buffer holding those parameters must remain valid until after the parameters have been sent (yes, really, keep it alive). and this function must have been called by the time the transport parameters need to go out. for a client, that means before the connection has been initiated. for a server, this might typically happen during the got_transport_params_cb. β
SSL_set_quic_tls_early_data_enabled() is the one that turns on the 0-RTT feature for a third party QUIC implementation. 0-RTT!! early data!! we love a head start. π
π RETURN VALUES π
these functions return 1 on success and 0 on failure. simple as.
all of the callbacks should also return 1 on success and 0 on failure. and heads up, bestie:
a failure response is fatal to the connection. like, not "oops try again" fatal. capital-F Fatal. π
π«Ά EXAMPLES π«Ά
a call to SSL_set_quic_tls_cbs() might look something like the following, assuming you've got suitable definitions of the various callback functions:
const OSSL_DISPATCH qtdis[] = {
{OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND, (void (*)(void))crypto_send_cb},
{OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD,
(void (*)(void))crypto_recv_rcd_cb},
{OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD,
(void (*)(void))crypto_release_rcd_cb},
{OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET,
(void (*)(void))yield_secret_cb},
{OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS,
(void (*)(void))got_transport_params_cb},
{OSSL_FUNC_SSL_QUIC_TLS_ALERT, (void (*)(void))alert_cb},
{0, NULL}
};
if (!SSL_set_quic_tls_cbs(ssl, qtdis, NULL))
goto err;
see that {0, NULL} at the end? that's the terminator. never skip the terminator. π
π HISTORY π
these functions were added in OpenSSL 3.5. fresh out the box, lowkey historic. π§
π COPYRIGHT π
Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at https://www.openssl.org/source/license.html.
and that's the whole page, bestie!! no cap, every flag, callback and caveat accounted for. this was OpenSSL 4.0.3, dated 2026-09-29, and the manual page is SSL_SET_QUIC_TLS_CBS(3SSL). go forth and hand OpenSSL your record layer, she's got this. slay πβ¨π
π¦ where this page comes from
- ubuntu stonking: libssl-doc 4.0.3-1ubuntu1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms