QUIC_LCIDM *lcidm;
/* SRTM to register SRTs with. */
QUIC_SRTM *srtm;
+ OSSL_QRX *qrx;
int is_server;
SSL *tls;
/* Whether to use qlog. */
int use_qlog;
+ int is_tserver_ch;
+
/* Title to use for the qlog session, or NULL. */
const char *qlog_title;
} QUIC_CHANNEL_ARGS;
*/
QUIC_CHANNEL *ossl_quic_channel_alloc(const QUIC_CHANNEL_ARGS *args);
int ossl_quic_channel_init(QUIC_CHANNEL *ch);
+void ossl_quic_channel_bind_qrx(QUIC_CHANNEL *tserver_ch, OSSL_QRX *qrx);
/* No-op if ch is NULL. */
typedef struct quic_reactor_wait_ctx_st QUIC_REACTOR_WAIT_CTX;
typedef struct ossl_statm_st OSSL_STATM;
typedef struct quic_demux_st QUIC_DEMUX;
+typedef struct ossl_qrx_st OSSL_QRX;
typedef struct ossl_qrx_pkt_st OSSL_QRX_PKT;
typedef struct ossl_qtx_pkt_st OSSL_QTX_PKT;
typedef struct quic_tick_result_st QUIC_TICK_RESULT;
* QUIC Record Layer - RX
* ======================
*/
-typedef struct ossl_qrx_st OSSL_QRX;
typedef struct ossl_qrx_args_st {
OSSL_LIB_CTX *libctx;
* establish a new connection.
*/
void ossl_qrx_inject_urxe(OSSL_QRX *qrx, QUIC_URXE *e);
+int ossl_qrx_validate_initial_packet(OSSL_QRX *qrx, QUIC_URXE *urxe,
+ const QUIC_CONN_ID *dcid);
/*
* Decryption of 1-RTT packets must be explicitly enabled by calling this
ossl_quic_tx_packetiser_set_ack_tx_cb(ch->txp, ch_on_txp_ack_tx, ch);
- qrx_args.libctx = ch->port->engine->libctx;
- qrx_args.demux = ch->port->demux;
- qrx_args.short_conn_id_len = rx_short_dcid_len;
- qrx_args.max_deferred = 32;
+ /*
+ * qrx does not exist yet, then we must be dealing with client channel
+ * (QUIC connection initiator).
+ * If qrx exists already, then we are dealing with server channel which
+ * qrx gets created by port_default_packet_handler() before
+ * port_default_packet_handler() accepts connection and creates channel
+ * for it.
+ * The exception here is tserver which always creates channel,
+ * before the first packet is ever seen.
+ */
+ if (ch->qrx == NULL && ch->is_tserver_ch == 0) {
+ /* we are regular client, create channel */
+ qrx_args.libctx = ch->port->engine->libctx;
+ qrx_args.demux = ch->port->demux;
+ qrx_args.short_conn_id_len = rx_short_dcid_len;
+ qrx_args.max_deferred = 32;
+
+ if ((ch->qrx = ossl_qrx_new(&qrx_args)) == NULL)
+ goto err;
+ }
- if ((ch->qrx = ossl_qrx_new(&qrx_args)) == NULL)
- goto err;
+ if (ch->qrx != NULL) {
+ /*
+ * callbacks for channels associated with tserver's port
+ * are set up later when we call ossl_quic_channel_bind_qrx()
+ * in port_default_packet_handler()
+ */
+ if (!ossl_qrx_set_late_validation_cb(ch->qrx,
+ rx_late_validate,
+ ch))
+ goto err;
- if (!ossl_qrx_set_late_validation_cb(ch->qrx,
- rx_late_validate,
- ch))
- goto err;
+ if (!ossl_qrx_set_key_update_cb(ch->qrx,
+ rxku_detected,
+ ch))
+ goto err;
+ }
- if (!ossl_qrx_set_key_update_cb(ch->qrx,
- rxku_detected,
- ch))
- goto err;
for (pn_space = QUIC_PN_SPACE_INITIAL; pn_space < QUIC_PN_SPACE_NUM; ++pn_space) {
ch->crypto_recv[pn_space] = ossl_quic_rstream_new(NULL, NULL, 0);
return ch_init(ch);
}
+void ossl_quic_channel_bind_qrx(QUIC_CHANNEL *tserver_ch, OSSL_QRX *qrx)
+{
+ if (tserver_ch->qrx == NULL && tserver_ch->is_tserver_ch == 1) {
+ tserver_ch->qrx = qrx;
+ ossl_qrx_set_late_validation_cb(tserver_ch->qrx, rx_late_validate,
+ tserver_ch);
+ ossl_qrx_set_key_update_cb(tserver_ch->qrx, rxku_detected,
+ tserver_ch);
+ }
+}
+
QUIC_CHANNEL *ossl_quic_channel_alloc(const QUIC_CHANNEL_ARGS *args)
{
QUIC_CHANNEL *ch = NULL;
if ((ch = OPENSSL_zalloc(sizeof(*ch))) == NULL)
return NULL;
- ch->port = args->port;
- ch->is_server = args->is_server;
- ch->tls = args->tls;
- ch->lcidm = args->lcidm;
- ch->srtm = args->srtm;
+ ch->port = args->port;
+ ch->is_server = args->is_server;
+ ch->tls = args->tls;
+ ch->lcidm = args->lcidm;
+ ch->srtm = args->srtm;
+ ch->qrx = args->qrx;
+ ch->is_tserver_ch = args->is_tserver_ch;
#ifndef OPENSSL_NO_QLOG
ch->use_qlog = args->use_qlog;
size_t ossl_quic_channel_get_short_header_conn_id_len(QUIC_CHANNEL *ch)
{
- return ossl_qrx_get_short_hdr_conn_id_len(ch->qrx);
+ return ossl_quic_port_get_rx_short_dcid_len(ch->port);
}
QUIC_STREAM *ossl_quic_channel_get_stream_by_id(QUIC_CHANNEL *ch,
ossl_qtx_set_qlog_cb(ch->qtx, ch_get_qlog_cb, ch);
ossl_quic_tx_packetiser_set_qlog_cb(ch->txp, ch_get_qlog_cb, ch);
- /* Plug in secrets for the Initial EL. */
+ /*
+ * Plug in secrets for the Initial EL. secrets for QRX were created in
+ * port_default_packet_handler() already.
+ */
if (!ossl_quic_provide_initial_secret(ch->port->engine->libctx,
ch->port->engine->propq,
&ch->init_dcid,
/*is_server=*/1,
- ch->qrx, ch->qtx))
+ NULL, ch->qtx))
return 0;
/* Register the peer ODCID in the LCIDM. */
ossl_qtx_set_msg_callback(ch->qtx, msg_callback, msg_callback_ssl);
ossl_quic_tx_packetiser_set_msg_callback(ch->txp, msg_callback,
msg_callback_ssl);
- ossl_qrx_set_msg_callback(ch->qrx, msg_callback, msg_callback_ssl);
+ /*
+ * postpone msg callback setting for tserver until port calls
+ * port_bind_channel().
+ */
+ if (ch->is_tserver_ch == 0)
+ ossl_qrx_set_msg_callback(ch->qrx, msg_callback, msg_callback_ssl);
}
void ossl_quic_channel_set_msg_callback_arg(QUIC_CHANNEL *ch,
ch->msg_callback_arg = msg_callback_arg;
ossl_qtx_set_msg_callback_arg(ch->qtx, msg_callback_arg);
ossl_quic_tx_packetiser_set_msg_callback_arg(ch->txp, msg_callback_arg);
- ossl_qrx_set_msg_callback_arg(ch->qrx, msg_callback_arg);
+
+ /*
+ * postpone msg callback setting for tserver until port calls
+ * port_bind_channel().
+ */
+ if (ch->is_tserver_ch == 0)
+ ossl_qrx_set_msg_callback_arg(ch->qrx, msg_callback_arg);
}
void ossl_quic_channel_set_txku_threshold_override(QUIC_CHANNEL *ch,
/* Has qlog been requested? */
unsigned int use_qlog : 1;
+ /* Has qlog been requested? */
+ unsigned int is_tserver_ch : 1;
+
/* Saved error stack in case permanent error was encountered */
ERR_STATE *err_state;
return tls;
}
-static QUIC_CHANNEL *port_make_channel(QUIC_PORT *port, SSL *tls, int is_server)
+static QUIC_CHANNEL *port_make_channel(QUIC_PORT *port, SSL *tls, OSSL_QRX *qrx,
+ int is_server, int is_tserver)
{
QUIC_CHANNEL_ARGS args = {0};
QUIC_CHANNEL *ch;
- args.port = port;
- args.is_server = is_server;
- args.lcidm = port->lcidm;
- args.srtm = port->srtm;
+ args.port = port;
+ args.is_server = is_server;
+ args.lcidm = port->lcidm;
+ args.srtm = port->srtm;
+ args.qrx = qrx;
+ args.is_tserver_ch = is_tserver;
/*
* Creating a a new channel is made a bit tricky here as there is a
QUIC_CHANNEL *ossl_quic_port_create_outgoing(QUIC_PORT *port, SSL *tls)
{
- return port_make_channel(port, tls, /*is_server=*/0);
+ return port_make_channel(port, tls, NULL, /* is_server= */ 0,
+ /* is_tserver= */ 0);
}
QUIC_CHANNEL *ossl_quic_port_create_incoming(QUIC_PORT *port, SSL *tls)
assert(port->tserver_ch == NULL);
- ch = port_make_channel(port, tls, /*is_server=*/1);
+ /*
+ * pass -1 for qrx to indicate port will create qrx
+ * later in port_default_packet_handler() when calling port_bind_channel().
+ */
+ ch = port_make_channel(port, tls, NULL, /* is_server= */ 1,
+ /* is_tserver_ch */ 1);
port->tserver_ch = ch;
port->allow_incoming = 1;
return ch;
*/
static void port_bind_channel(QUIC_PORT *port, const BIO_ADDR *peer,
const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid,
- const QUIC_CONN_ID *odcid, QUIC_CHANNEL **new_ch)
+ const QUIC_CONN_ID *odcid, OSSL_QRX *qrx,
+ QUIC_CHANNEL **new_ch)
{
QUIC_CHANNEL *ch;
if (port->tserver_ch != NULL) {
ch = port->tserver_ch;
port->tserver_ch = NULL;
+ ossl_quic_channel_bind_qrx(ch, qrx);
+ ossl_qrx_set_msg_callback(ch->qrx, ch->msg_callback,
+ ch->msg_callback_ssl);
+ ossl_qrx_set_msg_callback_arg(ch->qrx, ch->msg_callback_arg);
} else {
- ch = port_make_channel(port, NULL, /* is_server= */1);
+ ch = port_make_channel(port, NULL, qrx, /* is_server= */ 1,
+ /* is_tserver */ 0);
}
if (ch == NULL)
QUIC_CHANNEL *ch = NULL, *new_ch = NULL;
QUIC_CONN_ID odcid, scid;
uint8_t gen_new_token = 0;
+ OSSL_QRX *qrx = NULL;
+ OSSL_QRX_ARGS qrx_args = {0};
uint64_t cause_flags = 0;
/* Don't handle anything if we are no longer running. */
odcid.id_len = 0;
+ /*
+ * Create qrx now so we can check integrity of packet
+ * which does not belong to any channel.
+ */
+ qrx_args.libctx = port->engine->libctx;
+ qrx_args.demux = port->demux;
+ qrx_args.short_conn_id_len = dcid->id_len;
+ qrx_args.max_deferred = 32;
+ qrx = ossl_qrx_new(&qrx_args);
+ if (qrx == NULL)
+ goto undesirable;
+
+ /*
+ * Derive secrets for qrx only.
+ */
+ if (!ossl_quic_provide_initial_secret(port->engine->libctx,
+ port->engine->propq,
+ &hdr.dst_conn_id,
+ /* is_server */ 1,
+ qrx, NULL))
+ goto undesirable;
+
+ if (ossl_qrx_validate_initial_packet(qrx, e, (const QUIC_CONN_ID *)dcid) == 0)
+ goto undesirable;
+
/*
* TODO(QUIC FUTURE): there should be some logic similar to accounting half-open
* states in TCP. If we reach certain threshold, then we want to
*/
if (port->validate_addr == 1 && hdr.token == NULL) {
port_send_retry(port, &e->peer, &hdr);
+ /*
+ * This is a kind of bummer because we forget secrets for initial
+ * level encryption. The secrets costs us CPU to compute. What we can
+ * do here is to store them within retry token. Then we can retrieve them
+ * from initial packet which will carry our retry token to validate
+ * client's address.
+ */
goto undesirable;
}
* the request is valid
*/
if (port->validate_addr == 1) {
+ /*
+ * Again: we should consider saving initial encryption level
+ * secrets to token here to save some CPU cycles.
+ */
port_send_retry(port, &e->peer, &hdr);
goto undesirable;
}
}
port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id,
- &odcid, &new_ch);
+ &odcid, qrx, &new_ch);
+
+ /*
+ * if packet validates it gets moved to channel, we've just bound
+ * to port.
+ */
+ if (new_ch == NULL)
+ goto undesirable;
/*
* Generate a token for sending in a later NEW_TOKEN frame
generate_new_token(new_ch, &e->peer);
/*
- * The channel will do all the LCID registration needed, but as an
- * optimization inject this packet directly into the channel's QRX for
- * processing without going through the DEMUX again.
+ * The qrx belongs to channel now, so don't free it.
+ */
+ qrx = NULL;
+
+ /*
+ * If function reaches this place, then packet got validated in
+ * ossl_qrx_validate_initial_packet(). Keep in mind the function
+ * ossl_qrx_validate_initial_packet() decrypts the packet to validate it.
+ * If packet validation was successful (and it was because we are here),
+ * then the function puts the packet to qrx->rx_pending. We must not call
+ * ossl_qrx_inject_urxe() here now, because we don't want to insert
+ * the packet to qrx->urx_pending which keeps packet waiting for decryption.
+ *
+ * We are going to call ossl_quic_demux_release_urxe() to dispose buffer
+ * which still holds encrypted data.
*/
- if (new_ch != NULL) {
- ossl_qrx_inject_urxe(new_ch->qrx, e);
- return;
- }
undesirable:
+ ossl_qrx_free(qrx);
ossl_quic_demux_release_urxe(port->demux, e);
}
SSL *msg_callback_ssl;
};
+static RXE *qrx_ensure_free_rxe(OSSL_QRX *qrx, size_t alloc_len);
+static int qrx_validate_hdr_early(OSSL_QRX *qrx, RXE *rxe,
+ const QUIC_CONN_ID *first_dcid);
+static int qrx_relocate_buffer(OSSL_QRX *qrx, RXE **prxe, size_t *pi,
+ const unsigned char **pptr, size_t buf_len);
+static int qrx_validate_hdr(OSSL_QRX *qrx, RXE *rxe);
+static RXE *qrx_reserve_rxe(RXE_LIST *rxl, RXE *rxe, size_t n);
+static int qrx_decrypt_pkt_body(OSSL_QRX *qrx, unsigned char *dst,
+ const unsigned char *src,
+ size_t src_len, size_t *dec_len,
+ const unsigned char *aad, size_t aad_len,
+ QUIC_PN pn, uint32_t enc_level,
+ unsigned char key_phase_bit,
+ uint64_t *rx_key_epoch);
+static int qrx_validate_hdr_late(OSSL_QRX *qrx, RXE *rxe);
+static uint32_t rxe_determine_pn_space(RXE *rxe);
+static void ignore_res(int x);
+
OSSL_QRX *ossl_qrx_new(const OSSL_QRX_ARGS *args)
{
OSSL_QRX *qrx;
qrx->msg_callback_arg);
}
+/*
+ * qrx_validate_initial_pkt() is derived from qrx_process_pkt(). Unlike
+ * qrx_process_pkt() the qrx_validate_initial_pkt() function can process
+ * initial packet only. All other packets should be discarded. This allows
+ * port_default_packet_handler() to validate incoming packet. If packet
+ * is not valid, then port_default_packet_handler() must discard the
+ * packet instead of creating a new channel for it.
+ */
+static int qrx_validate_initial_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
+ const QUIC_CONN_ID *first_dcid,
+ size_t datagram_len)
+{
+ PACKET pkt, orig_pkt;
+ RXE *rxe;
+ size_t i = 0, aad_len = 0, dec_len = 0;
+ const unsigned char *sop;
+ unsigned char *dst;
+ QUIC_PKT_HDR_PTRS ptrs;
+ uint32_t pn_space;
+ OSSL_QRL_ENC_LEVEL *el = NULL;
+ uint64_t rx_key_epoch = UINT64_MAX;
+
+ if (!PACKET_buf_init(&pkt, ossl_quic_urxe_data(urxe), urxe->data_len))
+ return 0;
+
+ orig_pkt = pkt;
+ sop = PACKET_data(&pkt);
+
+ /*
+ * Get a free RXE. If we need to allocate a new one, use the packet length
+ * as a good ballpark figure.
+ */
+ rxe = qrx_ensure_free_rxe(qrx, PACKET_remaining(&pkt));
+ if (rxe == NULL)
+ return 0;
+
+ /*
+ * we expect INITIAL packet only, therefore it is OK to pass
+ * short_conn_id_len as 0.
+ */
+ if (!ossl_quic_wire_decode_pkt_hdr(&pkt,
+ 0, /* short_conn_id_len */
+ 1, /* need second decode */
+ 0, /* nodata -> want to read data */
+ &rxe->hdr, &ptrs,
+ NULL))
+ goto malformed;
+
+ if (rxe->hdr.type != QUIC_PKT_TYPE_INITIAL)
+ goto malformed;
+
+ if (!qrx_validate_hdr_early(qrx, rxe, NULL))
+ goto malformed;
+
+ if (ossl_qrl_enc_level_set_have_el(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL) != 1)
+ goto malformed;
+
+ if (rxe->hdr.type == QUIC_PKT_TYPE_INITIAL) {
+ const unsigned char *token = rxe->hdr.token;
+
+ /*
+ * This may change the value of rxe and change the value of the token
+ * pointer as well. So we must make a temporary copy of the pointer to
+ * the token, and then copy it back into the new location of the rxe
+ */
+ if (!qrx_relocate_buffer(qrx, &rxe, &i, &token, rxe->hdr.token_len))
+ goto malformed;
+
+ rxe->hdr.token = token;
+ }
+
+ pkt = orig_pkt;
+
+ el = ossl_qrl_enc_level_set_get(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL, 1);
+ assert(el != NULL); /* Already checked above */
+
+ if (!ossl_quic_hdr_protector_decrypt(&el->hpr, &ptrs))
+ goto malformed;
+
+ /*
+ * We have removed header protection, so don't attempt to do it again if
+ * the packet gets deferred and processed again.
+ */
+ pkt_mark(&urxe->hpr_removed, 0);
+
+ /* Decode the now unprotected header. */
+ if (ossl_quic_wire_decode_pkt_hdr(&pkt, 0,
+ 0, 0, &rxe->hdr, NULL, NULL) != 1)
+ goto malformed;
+
+ /* Validate header and decode PN. */
+ if (!qrx_validate_hdr(qrx, rxe))
+ goto malformed;
+
+ /*
+ * The AAD data is the entire (unprotected) packet header including the PN.
+ * The packet header has been unprotected in place, so we can just reuse the
+ * PACKET buffer. The header ends where the payload begins.
+ */
+ aad_len = rxe->hdr.data - sop;
+
+ /* Ensure the RXE buffer size is adequate for our payload. */
+ if ((rxe = qrx_reserve_rxe(&qrx->rx_free, rxe, rxe->hdr.len + i)) == NULL)
+ goto malformed;
+
+ /*
+ * We decrypt the packet body to immediately after the token at the start of
+ * the RXE buffer (where present).
+ *
+ * Do the decryption from the PACKET (which points into URXE memory) to our
+ * RXE payload (single-copy decryption), then fixup the pointers in the
+ * header to point to our new buffer.
+ *
+ * If decryption fails this is considered a permanent error; we defer
+ * packets we don't yet have decryption keys for above, so if this fails,
+ * something has gone wrong with the handshake process or a packet has been
+ * corrupted.
+ */
+ dst = (unsigned char *)rxe_data(rxe) + i;
+ if (!qrx_decrypt_pkt_body(qrx, dst, rxe->hdr.data, rxe->hdr.len,
+ &dec_len, sop, aad_len, rxe->pn, QUIC_ENC_LEVEL_INITIAL,
+ rxe->hdr.key_phase, &rx_key_epoch))
+ goto malformed;
+
+ /*
+ * -----------------------------------------------------
+ * IMPORTANT: ANYTHING ABOVE THIS LINE IS UNVERIFIED
+ * AND MUST BE TIMING-CHANNEL SAFE.
+ * -----------------------------------------------------
+ *
+ * At this point, we have successfully authenticated the AEAD tag and no
+ * longer need to worry about exposing the PN, PN length or Key Phase bit in
+ * timing channels. Invoke any configured validation callback to allow for
+ * rejection of duplicate PNs.
+ */
+ if (!qrx_validate_hdr_late(qrx, rxe))
+ goto malformed;
+
+ pkt_mark(&urxe->processed, 0);
+
+ /*
+ * Update header to point to the decrypted buffer, which may be shorter
+ * due to AEAD tags, block padding, etc.
+ */
+ rxe->hdr.data = dst;
+ rxe->hdr.len = dec_len;
+ rxe->data_len = dec_len;
+ rxe->datagram_len = datagram_len;
+ rxe->key_epoch = rx_key_epoch;
+
+ /* We processed the PN successfully, so update largest processed PN. */
+ pn_space = rxe_determine_pn_space(rxe);
+ if (rxe->pn > qrx->largest_pn[pn_space])
+ qrx->largest_pn[pn_space] = rxe->pn;
+
+ /* Copy across network addresses and RX time from URXE to RXE. */
+ rxe->peer = urxe->peer;
+ rxe->local = urxe->local;
+ rxe->time = urxe->time;
+ rxe->datagram_id = urxe->datagram_id;
+
+ /*
+ * The packet is decrypted, we are going to move it from
+ * rx_pending queue where it waits to be further processed
+ * by ch_rx().
+ */
+ ossl_list_rxe_remove(&qrx->rx_free, rxe);
+ ossl_list_rxe_insert_tail(&qrx->rx_pending, rxe);
+
+ return 1;
+
+malformed:
+ /* caller (port_default_packet_handler()) should discard urxe */
+ return 0;
+}
+
+int ossl_qrx_validate_initial_packet(OSSL_QRX *qrx, QUIC_URXE *urxe,
+ const QUIC_CONN_ID *dcid)
+{
+ urxe->processed = 0;
+ urxe->hpr_removed = 0;
+ urxe->deferred = 0;
+
+ return qrx_validate_initial_pkt(qrx, urxe, dcid, urxe->data_len);
+}
+
static void qrx_requeue_deferred(OSSL_QRX *qrx)
{
QUIC_URXE *e;