aboutsummaryrefslogtreecommitdiffstats
path: root/ssl
diff options
context:
space:
mode:
authorHugo Landau <hlandau@openssl.org>2022-07-22 13:08:38 +0100
committerTomas Mraz <tomas@openssl.org>2022-09-02 10:03:55 +0200
commitec279ac21105a85d9f11eed984eb64405811425d (patch)
treef793d4635eece923228d2a9d91aaaa91d134f612 /ssl
parentfc2be2d07acc0cfe954320c2491b8c5461cbef09 (diff)
downloadopenssl-ec279ac21105a85d9f11eed984eb64405811425d.tar.gz
QUIC Demuxer and Record Layer (RX Side)
Reviewed-by: Paul Dale <pauli@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from https://github.com/openssl/openssl/pull/18949)
Diffstat (limited to 'ssl')
-rw-r--r--ssl/quic/build.info2
-rw-r--r--ssl/quic/quic_demux.c523
-rw-r--r--ssl/quic/quic_record.c1315
-rw-r--r--ssl/quic/quic_record_util.c141
-rw-r--r--ssl/quic/quic_wire.c8
-rw-r--r--ssl/quic/quic_wire_pkt.c678
-rw-r--r--ssl/ssl_local.h10
-rw-r--r--ssl/tls13_enc.c48
8 files changed, 2701 insertions, 24 deletions
diff --git a/ssl/quic/build.info b/ssl/quic/build.info
index 9e411011e7..482338be95 100644
--- a/ssl/quic/build.info
+++ b/ssl/quic/build.info
@@ -1,3 +1,3 @@
$LIBSSL=../../libssl
-SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c cc_dummy.c
+SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c cc_dummy.c quic_demux.c quic_record.c quic_record_util.c quic_wire_pkt.c
diff --git a/ssl/quic/quic_demux.c b/ssl/quic/quic_demux.c
new file mode 100644
index 0000000000..3eb4f6dfb4
--- /dev/null
+++ b/ssl/quic/quic_demux.c
@@ -0,0 +1,523 @@
+#include "internal/quic_demux.h"
+#include "internal/quic_wire_pkt.h"
+#include "internal/common.h"
+#include <openssl/lhash.h>
+
+#define OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL 32
+
+void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+ /* Must be in list currently. */
+ OPENSSL_assert((e->prev != NULL || l->head == e)
+ && (e->next != NULL || l->tail == e));
+
+ if (e->prev != NULL)
+ e->prev->next = e->next;
+ if (e->next != NULL)
+ e->next->prev = e->prev;
+
+ if (e == l->head)
+ l->head = e->next;
+ if (e == l->tail)
+ l->tail = e->prev;
+
+ e->next = e->prev = NULL;
+}
+
+void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+ /* Must not be in list. */
+ OPENSSL_assert(e->prev == NULL && e->next == NULL);
+
+ if (l->head == NULL) {
+ l->head = l->tail = e;
+ e->next = e->prev = NULL;
+ return;
+ }
+
+ l->head->prev = e;
+ e->next = l->head;
+ e->prev = NULL;
+ l->head = e;
+}
+
+void ossl_quic_urxe_insert_tail(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+ /* Must not be in list. */
+ OPENSSL_assert(e->prev == NULL && e->next == NULL);
+
+ if (l->tail == NULL) {
+ l->head = l->tail = e;
+ e->next = e->prev = NULL;
+ return;
+ }
+
+ l->tail->next = e;
+ e->prev = l->tail;
+ e->next = NULL;
+ l->tail = e;
+}
+
+/* Structure used to track a given connection ID. */
+typedef struct quic_demux_conn_st QUIC_DEMUX_CONN;
+
+struct quic_demux_conn_st {
+ QUIC_DEMUX_CONN *next; /* used when unregistering only */
+ QUIC_CONN_ID dst_conn_id;
+ ossl_quic_demux_cb_fn *cb;
+ void *cb_arg;
+};
+
+DEFINE_LHASH_OF_EX(QUIC_DEMUX_CONN);
+
+static unsigned long demux_conn_hash(const QUIC_DEMUX_CONN *conn)
+{
+ size_t i;
+ unsigned long v = 0;
+
+ assert(conn->dst_conn_id.id_len <= QUIC_MAX_CONN_ID_LEN);
+
+ for (i = 0; i < conn->dst_conn_id.id_len; ++i)
+ v ^= ((unsigned long)conn->dst_conn_id.id[i])
+ << ((i * 8) % (sizeof(unsigned long) * 8));
+
+ return v;
+}
+
+static int demux_conn_cmp(const QUIC_DEMUX_CONN *a, const QUIC_DEMUX_CONN *b)
+{
+ return !ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id);
+}
+
+struct quic_demux_st {
+ /* The underlying transport BIO with datagram semantics. */
+ BIO *net_bio;
+
+ /*
+ * QUIC short packets do not contain the length of the connection ID field,
+ * therefore it must be known contextually. The demuxer requires connection
+ * IDs of the same length to be used for all incoming packets.
+ */
+ size_t short_conn_id_len;
+
+ /* Default URXE buffer size in bytes. */
+ size_t default_urxe_alloc_len;
+
+ /* Hashtable mapping connection IDs to QUIC_DEMUX_CONN structures. */
+ LHASH_OF(QUIC_DEMUX_CONN) *conns_by_id;
+
+ /*
+ * List of URXEs which are not currently in use (i.e., not filled with
+ * unconsumed data). These are moved to the pending list as they are filled.
+ */
+ QUIC_URXE_LIST urx_free;
+ size_t num_urx_free;
+
+ /*
+ * List of URXEs which are filled with received encrypted data. These are
+ * removed from this list as we invoke the callbacks for each of them. They
+ * are then not on any list managed by us; we forget about them until our
+ * user calls ossl_quic_demux_release_urxe to return the URXE to us, at
+ * which point we add it to the free list.
+ */
+ QUIC_URXE_LIST urx_pending;
+
+ /* Whether to use local address support. */
+ char use_local_addr;
+};
+
+QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
+ size_t short_conn_id_len,
+ size_t default_urxe_alloc_len)
+{
+ QUIC_DEMUX *demux;
+
+ demux = OPENSSL_zalloc(sizeof(QUIC_DEMUX));
+ if (demux == NULL)
+ return NULL;
+
+ demux->net_bio = net_bio;
+ demux->short_conn_id_len = short_conn_id_len;
+ demux->default_urxe_alloc_len = default_urxe_alloc_len;
+
+ demux->conns_by_id
+ = lh_QUIC_DEMUX_CONN_new(demux_conn_hash, demux_conn_cmp);
+ if (demux->conns_by_id == NULL) {
+ OPENSSL_free(demux);
+ return NULL;
+ }
+
+ if (net_bio != NULL
+ && BIO_dgram_get_local_addr_cap(net_bio)
+ && BIO_dgram_set_local_addr_enable(net_bio, 1))
+ demux->use_local_addr = 1;
+
+ return demux;
+}
+
+static void demux_free_conn_it(QUIC_DEMUX_CONN *conn, void *arg)
+{
+ OPENSSL_free(conn);
+}
+
+static void demux_free_urxl(QUIC_URXE_LIST *l)
+{
+ QUIC_URXE *e, *enext;
+
+ for (e = l->head; e != NULL; e = enext) {
+ enext = e->next;
+ OPENSSL_free(e);
+ }
+
+ l->head = l->tail = NULL;
+}
+
+void ossl_quic_demux_free(QUIC_DEMUX *demux)
+{
+ if (demux == NULL)
+ return;
+
+ /* Free all connection structures. */
+ lh_QUIC_DEMUX_CONN_doall_arg(demux->conns_by_id, demux_free_conn_it, NULL);
+ lh_QUIC_DEMUX_CONN_free(demux->conns_by_id);
+
+ /* Free all URXEs we are holding. */
+ demux_free_urxl(&demux->urx_free);
+ demux_free_urxl(&demux->urx_pending);
+
+ OPENSSL_free(demux);
+}
+
+static QUIC_DEMUX_CONN *demux_get_by_conn_id(QUIC_DEMUX *demux,
+ const QUIC_CONN_ID *dst_conn_id)
+{
+ QUIC_DEMUX_CONN key;
+
+ if (dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+
+ key.dst_conn_id = *dst_conn_id;
+ return lh_QUIC_DEMUX_CONN_retrieve(demux->conns_by_id, &key);
+}
+
+int ossl_quic_demux_register(QUIC_DEMUX *demux,
+ const QUIC_CONN_ID *dst_conn_id,
+ ossl_quic_demux_cb_fn *cb, void *cb_arg)
+{
+ QUIC_DEMUX_CONN *conn;
+
+ if (dst_conn_id == NULL
+ || dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN
+ || cb == NULL)
+ return 0;
+
+ /* Ensure not already registered. */
+ if (demux_get_by_conn_id(demux, dst_conn_id) != NULL)
+ /* Handler already registered with this connection ID. */
+ return 0;
+
+ conn = OPENSSL_zalloc(sizeof(QUIC_DEMUX_CONN));
+ if (conn == NULL)
+ return 0;
+
+ conn->dst_conn_id = *dst_conn_id;
+ conn->cb = cb;
+ conn->cb_arg = cb_arg;
+
+ lh_QUIC_DEMUX_CONN_insert(demux->conns_by_id, conn);
+ return 1;
+}
+
+static void demux_unregister(QUIC_DEMUX *demux,
+ QUIC_DEMUX_CONN *conn)
+{
+ lh_QUIC_DEMUX_CONN_delete(demux->conns_by_id, conn);
+ OPENSSL_free(conn);
+}
+
+int ossl_quic_demux_unregister(QUIC_DEMUX *demux,
+ const QUIC_CONN_ID *dst_conn_id)
+{
+ QUIC_DEMUX_CONN *conn;
+
+ if (dst_conn_id == NULL
+ || dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+
+ conn = demux_get_by_conn_id(demux, dst_conn_id);
+ if (conn == NULL)
+ return 0;
+
+ demux_unregister(demux, conn);
+ return 1;
+}
+
+struct unreg_arg {
+ ossl_quic_demux_cb_fn *cb;
+ void *cb_arg;
+ QUIC_DEMUX_CONN *head;
+};
+
+static void demux_unregister_by_cb(QUIC_DEMUX_CONN *conn, void *arg_)
+{
+ struct unreg_arg *arg = arg_;
+
+ if (conn->cb == arg->cb && conn->cb_arg == arg->cb_arg) {
+ conn->next = arg->head;
+ arg->head = conn;
+ }
+}
+
+void ossl_quic_demux_unregister_by_cb(QUIC_DEMUX *demux,
+ ossl_quic_demux_cb_fn *cb,
+ void *cb_arg)
+{
+ QUIC_DEMUX_CONN *conn, *cnext;
+ struct unreg_arg arg = {0};
+ arg.cb = cb;
+ arg.cb_arg = cb_arg;
+
+ lh_QUIC_DEMUX_CONN_doall_arg(demux->conns_by_id,
+ demux_unregister_by_cb, &arg);
+
+ for (conn = arg.head; conn != NULL; conn = cnext) {
+ cnext = conn->next;
+ demux_unregister(demux, conn);
+ }
+}
+
+static QUIC_URXE *demux_alloc_urxe(size_t alloc_len)
+{
+ QUIC_URXE *e;
+
+ if (alloc_len >= SIZE_MAX - sizeof(QUIC_URXE))
+ return NULL;
+
+ e = OPENSSL_malloc(sizeof(QUIC_URXE) + alloc_len);
+ if (e == NULL)
+ return NULL;
+
+ e->prev = e->next = NULL;
+ e->alloc_len = alloc_len;
+ e->data_len = 0;
+ return e;
+}
+
+static int demux_ensure_free_urxe(QUIC_DEMUX *demux, size_t min_num_free)
+{
+ QUIC_URXE *e;
+
+ while (demux->num_urx_free < min_num_free) {
+ e = demux_alloc_urxe(demux->default_urxe_alloc_len);
+ if (e == NULL)
+ return 0;
+
+ ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+ ++demux->num_urx_free;
+ }
+
+ return 1;
+}
+
+/*
+ * Receive datagrams from network, placing them into URXEs.
+ *
+ * Returns 1 on success or 0 on failure.
+ *
+ * Precondition: at least one URXE is free
+ * Precondition: there are no pending URXEs
+ */
+static int demux_recv(QUIC_DEMUX *demux)
+{
+ BIO_MSG msg[OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL];
+ ossl_ssize_t rd, i;
+ QUIC_URXE *urxe = demux->urx_free.head, *unext;
+
+ /* This should never be called when we have any pending URXE. */
+ assert(demux->urx_pending.head == NULL);
+
+ if (demux->net_bio == NULL)
+ return 0;
+
+ /*
+ * Opportunistically receive as many messages as possible in a single
+ * syscall, determined by how many free URXEs are available.
+ */
+ for (i = 0; i < (ossl_ssize_t)OSSL_NELEM(msg); ++i, urxe = urxe->next) {
+ if (urxe == NULL) {
+ /* We need at least one URXE to receive into. */
+ if (!ossl_assert(i > 0))
+ return 0;
+
+ break;
+ }
+
+ /* Ensure we zero any fields added to BIO_MSG at a later date. */
+ memset(&msg[i], 0, sizeof(BIO_MSG));
+ msg[i].data = ossl_quic_urxe_data(urxe);
+ msg[i].data_len = urxe->alloc_len;
+ msg[i].peer = &urxe->peer;
+ if (demux->use_local_addr)
+ msg[i].local = &urxe->local;
+ else
+ BIO_ADDR_clear(&urxe->local);
+ }
+
+ rd = BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0);
+ if (rd <= 0)
+ return 0;
+
+ urxe = demux->urx_free.head;
+ for (i = 0; i < rd; ++i, urxe = unext) {
+ unext = urxe->next;
+ /* Set URXE with actual length of received datagram. */
+ urxe->data_len = msg[i].data_len;
+ /* Move from free list to pending list. */
+ ossl_quic_urxe_remove(&demux->urx_free, urxe);
+ --demux->num_urx_free;
+ ossl_quic_urxe_insert_tail(&demux->urx_pending, urxe);
+ }
+
+ return 1;
+}
+
+/* Extract destination connection ID from the first packet in a datagram. */
+static int demux_identify_conn_id(QUIC_DEMUX *demux,
+ QUIC_URXE *e,
+ QUIC_CONN_ID *dst_conn_id)
+{
+ return ossl_quic_wire_get_pkt_hdr_dst_conn_id(ossl_quic_urxe_data(e),
+ e->data_len,
+ demux->short_conn_id_len,
+ dst_conn_id);
+}
+
+/* Identify the connection structure corresponding to a given URXE. */
+static QUIC_DEMUX_CONN *demux_identify_conn(QUIC_DEMUX *demux, QUIC_URXE *e)
+{
+ QUIC_CONN_ID dst_conn_id;
+
+ if (!demux_identify_conn_id(demux, e, &dst_conn_id))
+ /*
+ * Datagram is so badly malformed we can't get the DCID from the first
+ * packet in it, so just give up.
+ */
+ return NULL;
+
+ return demux_get_by_conn_id(demux, &dst_conn_id);
+}
+
+/* Process a single pending URXE. */
+static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
+{
+ QUIC_DEMUX_CONN *conn;
+
+ /* The next URXE we process should be at the head of the pending list. */
+ OPENSSL_assert(e == demux->urx_pending.head);
+
+ conn = demux_identify_conn(demux, e);
+ if (conn == NULL) {
+ /*
+ * We could not identify a connection. We will never be able to process
+ * this datagram, so get rid of it.
+ */
+ ossl_quic_urxe_remove(&demux->urx_pending, e);
+ ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+ ++demux->num_urx_free;
+ return 1; /* keep processing pending URXEs */
+ }
+
+ /*
+ * Remove from list and invoke callback. The URXE now belongs to the
+ * callback. (QUIC_DEMUX_CONN never has non-NULL cb.)
+ */
+ ossl_quic_urxe_remove(&demux->urx_pending, e);
+ conn->cb(e, conn->cb_arg);
+ return 1;
+}
+
+/* Process pending URXEs to generate callbacks. */
+static int demux_process_pending_urxl(QUIC_DEMUX *demux)
+{
+ QUIC_URXE *e;
+
+ while ((e = demux->urx_pending.head) != NULL)
+ if (!demux_process_pending_urxe(demux, e))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Drain the pending URXE list, processing any pending URXEs by making their
+ * callbacks. If no URXEs are pending, a network read is attempted first.
+ */
+int ossl_quic_demux_pump(QUIC_DEMUX *demux)
+{
+ int ret;
+
+ if (demux->urx_pending.head == NULL) {
+ ret = demux_ensure_free_urxe(demux, OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL);
+ if (ret != 1)
+ return 0;
+
+ ret = demux_recv(demux);
+ if (ret != 1)
+ return 0;
+
+ /*
+ * If demux_recv returned successfully, we should always have something.
+ */
+ assert(demux->urx_pending.head != NULL);
+ }
+
+ return demux_process_pending_urxl(demux);
+}
+
+/* Artificially inject a packet into the demuxer for testing purposes. */
+int ossl_quic_demux_inject(QUIC_DEMUX *demux,
+ const unsigned char *buf,
+ size_t buf_len,
+ const BIO_ADDR *peer,
+ const BIO_ADDR *local)
+{
+ int ret;
+ QUIC_URXE *urxe;
+
+ ret = demux_ensure_free_urxe(demux, 1);
+ if (ret != 1)
+ return 0;
+
+ urxe = demux->urx_free.head;
+ if (buf_len > urxe->alloc_len)
+ return 0;
+
+ memcpy(ossl_quic_urxe_data(urxe), buf, buf_len);
+ urxe->data_len = buf_len;
+
+ if (peer != NULL)
+ urxe->peer = *peer;
+ else
+ BIO_ADDR_clear(&urxe->local);
+
+ if (local != NULL)
+ urxe->local = *local;
+ else
+ BIO_ADDR_clear(&urxe->local);
+
+ /* Move from free list to pending list. */
+ ossl_quic_urxe_remove(&demux->urx_free, urxe);
+ --demux->num_urx_free;
+ ossl_quic_urxe_insert_tail(&demux->urx_pending, urxe);
+
+ return demux_process_pending_urxl(demux);
+}
+
+/* Called by our user to return a URXE to the free list. */
+void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
+ QUIC_URXE *e)
+{
+ OPENSSL_assert(e->prev == NULL && e->next == NULL);
+ ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+ ++demux->num_urx_free;
+}
diff --git a/ssl/quic/quic_record.c b/ssl/quic/quic_record.c
new file mode 100644
index 0000000000..95044d2836
--- /dev/null
+++ b/ssl/quic/quic_record.c
@@ -0,0 +1,1315 @@
+/*
+ * Copyright 2022 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
+ */
+
+#include "internal/quic_record.h"
+#include "internal/common.h"
+#include "../ssl_local.h"
+
+/*
+ * Mark a packet in a bitfield.
+ *
+ * pkt_idx: index of packet within datagram.
+ */
+static ossl_inline void pkt_mark(uint64_t *bitf, size_t pkt_idx)
+{
+ assert(pkt_idx < QUIC_MAX_PKT_PER_URXE);
+ *bitf |= ((uint64_t)1) << pkt_idx;
+}
+
+/* Returns 1 if a packet is in the bitfield. */
+static ossl_inline int pkt_is_marked(const uint64_t *bitf, size_t pkt_idx)
+{
+ assert(pkt_idx < QUIC_MAX_PKT_PER_URXE);
+ return (*bitf & (((uint64_t)1) << pkt_idx)) != 0;
+}
+
+/*
+ * RXE
+ * ===
+ *
+ * RX Entries (RXEs) store processed (i.e., decrypted) data received from the
+ * network. One RXE is used per received QUIC packet.
+ */
+typedef struct rxe_st RXE;
+
+struct rxe_st {
+ RXE *prev, *next;
+ size_t data_len, alloc_len;
+
+ /* Extra fields for per-packet information. */
+ QUIC_PKT_HDR hdr; /* data/len are decrypted payload */
+
+ /* Decoded packet number. */
+ QUIC_PN pn;
+
+ /* Addresses copied from URXE. */
+ BIO_ADDR peer, local;
+
+ /* Total length of the datagram which contained this packet. */
+ size_t datagram_len;
+};
+
+typedef struct ossl_qrl_rxe_list_st {
+ RXE *head, *tail;
+} RXE_LIST;
+
+static ossl_inline unsigned char *rxe_data(const RXE *e)
+{
+ return (unsigned char *)(e + 1);
+}
+
+static void rxe_remove(RXE_LIST *l, RXE *e)
+{
+ if (e->prev != NULL)
+ e->prev->next = e->next;
+ if (e->next != NULL)
+ e->next->prev = e->prev;
+
+ if (e == l->head)
+ l->head = e->next;
+ if (e == l->tail)
+ l->tail = e->prev;
+
+ e->next = e->prev = NULL;
+}
+
+static void rxe_insert_tail(RXE_LIST *l, RXE *e)
+{
+ if (l->tail == NULL) {
+ l->head = l->tail = e;
+ e->next = e->prev = NULL;
+ return;
+ }
+
+ l->tail->next = e;
+ e->prev = l->tail;
+ e->next = NULL;
+ l->tail = e;
+}
+
+/*
+ * QRL
+ * ===
+ */
+
+/* (Encryption level, direction)-specific state. */
+typedef struct ossl_qrl_enc_level_st {
+ /* Hash function used for key derivation. */
+ EVP_MD *md;
+ /* Context used for packet body ciphering. */
+ EVP_CIPHER_CTX *cctx;
+ /* IV used to construct nonces used for AEAD packet body ciphering. */
+ unsigned char iv[EVP_MAX_IV_LENGTH];
+ /* Have we permanently discarded this encryption level? */
+ unsigned char discarded;
+ /* QRL_SUITE_* value. */
+ uint32_t suite_id;
+ /* Length of authentication tag. */
+ uint32_t tag_len;
+ /*
+ * Cryptographic context used to apply and remove header protection from
+ * packet headers.
+ */
+ QUIC_HDR_PROTECTOR hpr;
+} OSSL_QRL_ENC_LEVEL;
+
+struct ossl_qrl_st {
+ OSSL_LIB_CTX *libctx;
+ const char *propq;
+
+ /* Demux to receive datagrams from. */
+ QUIC_DEMUX *rx_demux;
+
+ /* Length of connection IDs used in short-header packets in bytes. */
+ size_t short_conn_id_len;
+
+ /*
+ * List of URXEs which are filled with received encrypted data.
+ * These are returned to the DEMUX's free list as they are processed.
+ */
+ QUIC_URXE_LIST urx_pending;
+
+ /*
+ * List of URXEs which we could not decrypt immediately and which are being
+ * kept in case they can be decrypted later.
+ */
+ QUIC_URXE_LIST urx_deferred;
+
+ /*
+ * List of RXEs which are not currently in use. These are moved
+ * to the pending list as they are filled.
+ */
+ RXE_LIST rx_free;
+
+ /*
+ * List of RXEs which are filled with decrypted packets ready to be passed
+ * to the user. A RXE is removed from all lists inside the QRL when passed
+ * to the user, then returned to the free list when the user returns it.
+ */
+ RXE_LIST rx_pending;
+
+ /* Largest PN we have received and processed in a given PN space. */
+ QUIC_PN rx_largest_pn[QUIC_PN_SPACE_NUM];
+
+ /* Per encryption-level state. */
+ OSSL_QRL_ENC_LEVEL rx_el[QUIC_ENC_LEVEL_NUM];
+ OSSL_QRL_ENC_LEVEL tx_el[QUIC_ENC_LEVEL_NUM];
+
+ /* Bytes we have received since this counter was last cleared. */
+ uint64_t bytes_received;
+
+ /* Validation callback. */
+ ossl_qrl_early_rx_validation_cb *rx_validation_cb;
+ void *rx_validation_cb_arg;
+};
+
+static void qrl_on_rx(QUIC_URXE *urxe, void *arg);
+
+/* Constants used for key derivation in QUIC v1. */
+static const unsigned char quic_client_in_label[] = {
+ 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e /* "client in" */
+};
+static const unsigned char quic_server_in_label[] = {
+ 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x69, 0x6e /* "server in" */
+};
+static const unsigned char quic_v1_iv_label[] = {
+ 0x71, 0x75, 0x69, 0x63, 0x20, 0x69, 0x76 /* "quic iv" */
+};
+static const unsigned char quic_v1_key_label[] = {
+ 0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79 /* "quic key" */
+};
+static const unsigned char quic_v1_hp_label[] = {
+ 0x71, 0x75, 0x69, 0x63, 0x20, 0x68, 0x70 /* "quic hp" */
+};
+/* Salt used to derive Initial packet protection keys (RFC 9001 Section 5.2). */
+static const unsigned char quic_v1_initial_salt[] = {
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,
+ 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a
+};
+
+static ossl_inline OSSL_QRL_ENC_LEVEL *qrl_get_el(OSSL_QRL *qrl,
+ uint32_t enc_level,
+ int is_tx)
+{
+ if (!ossl_assert(enc_level < QUIC_ENC_LEVEL_NUM))
+ return NULL;
+ return is_tx ? &qrl->tx_el[enc_level] : &qrl->rx_el[enc_level];
+}
+
+/*
+ * Returns 1 if we have key material for a given encryption level, 0 if we do
+ * not yet have material and -1 if the EL is discarded.
+ */
+static int qrl_have_el(OSSL_QRL *qrl, uint32_t enc_level, int is_tx)
+{
+ OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+
+ if (el->cctx != NULL)
+ return 1;
+ if (el->discarded)
+ return -1;
+ return 0;
+}
+
+/* Drops keying material for a given encryption level. */
+static void qrl_el_discard(OSSL_QRL *qrl, uint32_t enc_level,
+ int is_tx, int final)
+{
+ OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+
+ if (el->discarded)
+ return;
+
+ if (el->cctx != NULL) {
+ ossl_quic_hdr_protector_destroy(&el->hpr);
+
+ EVP_CIPHER_CTX_free(el->cctx);
+ el->cctx = NULL;
+
+ EVP_MD_free(el->md);
+ el->md = NULL;
+ }
+
+ /* Zeroise IV. */
+ OPENSSL_cleanse(el->iv, sizeof(el->iv));
+
+ if (final)
+ el->discarded = 1;
+}
+
+/*
+ * Sets up cryptographic state for a given encryption level and direction by
+ * deriving "quic iv", "quic key" and "quic hp" values from a given secret.
+ *
+ * md is a hash function used for key derivation. If it is NULL, this function
+ * fetches the necessary hash function itself. If it is non-NULL, this function
+ * can reuse the caller's reference to a suitable EVP_MD; the EVP_MD provided
+ * must match the suite.
+ *
+ * On success where md is non-NULL, takes ownership of the caller's reference to
+ * md.
+ */
+static int qrl_el_set_secret(OSSL_QRL *qrl, uint32_t enc_level,
+ uint32_t suite_id, EVP_MD *md,
+ int is_tx,
+ const unsigned char *secret,
+ size_t secret_len)
+{
+ OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+ unsigned char key[EVP_MAX_KEY_LENGTH], hpr_key[EVP_MAX_KEY_LENGTH];
+ size_t key_len = 0, hpr_key_len = 0, iv_len = 0;
+ const char *cipher_name = NULL, *md_name = NULL;
+ EVP_CIPHER *cipher = NULL;
+ EVP_CIPHER_CTX *cctx = NULL;
+ int own_md = 0, have_hpr = 0;
+
+ if (el->discarded)
+ /* Should not be trying to reinitialise an EL which was discarded. */
+ return 0;
+
+ cipher_name = ossl_qrl_get_suite_cipher_name(suite_id);
+ iv_len = ossl_qrl_get_suite_cipher_iv_len(suite_id);
+ key_len = ossl_qrl_get_suite_cipher_key_len(suite_id);
+ hpr_key_len = ossl_qrl_get_suite_hdr_prot_key_len(suite_id);
+ if (cipher_name == NULL)
+ return 0;
+
+ if (secret_len != ossl_qrl_get_suite_secret_len(suite_id))
+ return 0;
+
+ if (md == NULL) {
+ md_name = ossl_qrl_get_suite_md_name(suite_id);
+
+ if ((md = EVP_MD_fetch(qrl->libctx,
+ md_name, qrl->propq)) == NULL)
+ return 0;
+
+ own_md = 1;
+ }
+
+ /* Derive "quic iv" key. */
+ if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+ md,
+ secret,
+ quic_v1_iv_label,
+ sizeof(quic_v1_iv_label),
+ NULL, 0,
+ el->iv, iv_len, 0))
+ goto err;
+
+ /* Derive "quic key" key. */
+ if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+ md,
+ secret,
+ quic_v1_key_label,
+ sizeof(quic_v1_key_label),
+ NULL, 0,
+ key, key_len, 0))
+ goto err;
+
+ /* Derive "quic hp" key. */
+ if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+ md,
+ secret,
+ quic_v1_hp_label,
+ sizeof(quic_v1_hp_label),
+ NULL, 0,
+ hpr_key, hpr_key_len, 0))
+ goto err;
+
+ /* Free any old context which is using old keying material. */
+ if (el->cctx != NULL) {
+ ossl_quic_hdr_protector_destroy(&el->hpr);
+ EVP_CIPHER_CTX_free(el->cctx);
+ el->cctx = NULL;
+ }
+
+ /* Setup header protection context. */
+ if (!ossl_quic_hdr_protector_init(&el->hpr,
+ qrl->libctx,
+ qrl->propq,
+ ossl_qrl_get_suite_hdr_prot_cipher_id(suite_id),
+ hpr_key,
+ hpr_key_len))
+ goto err;
+
+ have_hpr = 1;
+
+ /* Create and initialise cipher context. */
+ if ((cipher = EVP_CIPHER_fetch(qrl->libctx, cipher_name,
+ qrl->propq)) == NULL)
+ goto err;
+
+ if (!ossl_assert(iv_len == (size_t)EVP_CIPHER_get_iv_length(cipher))
+ || !ossl_assert(key_len == (size_t)EVP_CIPHER_get_key_length(cipher)))
+ goto err;
+
+ if ((cctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ /* IV will be changed on RX so we don't need to use a real value here. */
+ if (!EVP_CipherInit_ex(cctx, cipher, NULL, key, el->iv, 0))
+ goto err;
+
+ el->suite_id = suite_id;
+ el->cctx = cctx;
+ el->md = md;
+ el->tag_len = ossl_qrl_get_suite_cipher_tag_len(suite_id);
+
+ /* Zeroize intermediate keys. */
+ OPENSSL_cleanse(key, sizeof(key));
+ OPENSSL_cleanse(hpr_key, sizeof(hpr_key));
+ EVP_CIPHER_free(cipher);
+ return 1;
+
+err:
+ if (have_hpr)
+ ossl_quic_hdr_protector_destroy(&el->hpr);
+ EVP_CIPHER_CTX_free(cctx);
+ EVP_CIPHER_free(cipher);
+ if (own_md)
+ EVP_MD_free(md);
+ return 0;
+}
+
+OSSL_QRL *ossl_qrl_new(const OSSL_QRL_ARGS *args)
+{
+ OSSL_QRL *qrl;
+ size_t i;
+
+ if (args->rx_demux == NULL)
+ return 0;
+
+ qrl = OPENSSL_zalloc(sizeof(OSSL_QRL));
+ if (qrl == NULL)
+ return 0;
+
+ for (i = 0; i < OSSL_NELEM(qrl->rx_largest_pn); ++i)
+ qrl->rx_largest_pn[i] = args->rx_init_largest_pn[i];
+
+ qrl->libctx = args->libctx;
+ qrl->propq = args->propq;
+ qrl->rx_demux = args->rx_demux;
+ qrl->short_conn_id_len = args->short_conn_id_len;
+ return qrl;
+}
+
+static void qrl_cleanup_rxl(RXE_LIST *l)
+{
+ RXE *e, *enext;
+ for (e = l->head; e != NULL; e = enext) {
+ enext = e->next;
+ OPENSSL_free(e);
+ }
+ l->head = l->tail = NULL;
+}
+
+static void qrl_cleanup_urxl(OSSL_QRL *qrl, QUIC_URXE_LIST *l)
+{
+ QUIC_URXE *e, *enext;
+ for (e = l->head; e != NULL; e = enext) {
+ enext = e->next;
+ ossl_quic_demux_release_urxe(qrl->rx_demux, e);
+ }
+ l->head = l->tail = NULL;
+}
+
+void ossl_qrl_free(OSSL_QRL *qrl)
+{
+ uint32_t i;
+
+ /* Unregister from the RX DEMUX. */
+ ossl_quic_demux_unregister_by_cb(qrl->rx_demux, qrl_on_rx, qrl);
+
+ /* Free RXE queue data. */
+ qrl_cleanup_rxl(&qrl->rx_free);
+ qrl_cleanup_rxl(&qrl->rx_pending);
+ qrl_cleanup_urxl(qrl, &qrl->urx_pending);
+ qrl_cleanup_urxl(qrl, &qrl->urx_deferred);
+
+ /* Drop keying material and crypto resources. */
+ for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i) {
+ qrl_el_discard(qrl, i, 0, 1);
+ qrl_el_discard(qrl, i, 1, 1);
+ }
+
+ OPENSSL_free(qrl);
+}
+
+static void qrl_on_rx(QUIC_URXE *urxe, void *arg)
+{
+ OSSL_QRL *qrl = arg;
+
+ /* Initialize our own fields inside the URXE and add to the pending list. */
+ urxe->processed = 0;
+ urxe->hpr_removed = 0;
+ ossl_quic_urxe_insert_tail(&qrl->urx_pending, urxe);
+}
+
+int ossl_qrl_add_dst_conn_id(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id)
+{
+ return ossl_quic_demux_register(qrl->rx_demux,
+ dst_conn_id,
+ qrl_on_rx,
+ qrl);
+}
+
+int ossl_qrl_remove_dst_conn_id(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id)
+{
+ return ossl_quic_demux_unregister(qrl->rx_demux, dst_conn_id);
+}
+
+static void qrl_requeue_deferred(OSSL_QRL *qrl)
+{
+ QUIC_URXE *e;
+
+ while ((e = qrl->urx_deferred.head) != NULL) {
+ ossl_quic_urxe_remove(&qrl->urx_deferred, e);
+ ossl_quic_urxe_insert_head(&qrl->urx_pending, e);
+ }
+}
+
+int ossl_qrl_provide_rx_secret(OSSL_QRL *qrl, uint32_t enc_level,
+ uint32_t suite_id,
+ const unsigned char *secret, size_t secret_len)
+{
+ if (enc_level == QUIC_ENC_LEVEL_INITIAL || enc_level >= QUIC_ENC_LEVEL_NUM)
+ return 0;
+
+ if (!qrl_el_set_secret(qrl, enc_level, suite_id, NULL,
+ /*is_tx=*/0, secret, secret_len))
+ return 0;
+
+ /*
+ * Any packets we previously could not decrypt, we may now be able to
+ * decrypt, so move any datagrams containing deferred packets from the
+ * deferred to the pending queue.
+ */
+ qrl_requeue_deferred(qrl);
+ return 1;
+}
+
+/* Initialise key material for the INITIAL encryption level. */
+int ossl_qrl_provide_rx_secret_initial(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id)
+{
+ unsigned char initial_secret[32];
+ unsigned char client_initial_secret[32], server_initial_secret[32];
+ EVP_MD *sha256;
+ int have_rx = 0;
+
+ /* Initial encryption always uses SHA-256. */
+ if ((sha256 = EVP_MD_fetch(qrl->libctx,
+ "SHA256", qrl->propq)) == NULL)
+ return 0;
+
+ /* Derive initial secret from destination connection ID. */
+ if (!ossl_quic_hkdf_extract(qrl->libctx, qrl->propq,
+ sha256,
+ quic_v1_initial_salt,
+ sizeof(quic_v1_initial_salt),
+ dst_conn_id->id,
+ dst_conn_id->id_len,
+ initial_secret,
+ sizeof(initial_secret)))
+ goto err;
+
+ /* Derive "client in" secret. */
+ if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+ sha256,
+ initial_secret,
+ quic_client_in_label,
+ sizeof(quic_client_in_label),
+ NULL, 0,
+ client_initial_secret,
+ sizeof(client_initial_secret), 0))
+ goto err;
+
+ /* Derive "server in" secret. */
+ if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+ sha256,
+ initial_secret,
+ quic_server_in_label,
+ sizeof(quic_server_in_label),
+ NULL, 0,
+ server_initial_secret,
+ sizeof(server_initial_secret), 0))
+ goto err;
+
+ /* Setup RX cipher. Initial encryption always uses AES-128-GCM. */
+ if (!qrl_el_set_secret(qrl, QUIC_ENC_LEVEL_INITIAL,
+ QRL_SUITE_AES128GCM,
+ sha256,
+ /*is_tx=*/0,
+ server_initial_secret,
+ sizeof(server_initial_secret)))
+ goto err;
+
+ have_rx = 1;
+
+ /*
+ * qrl_el_set_secret takes ownership of our ref to SHA256, so get a new ref
+ * for the following call for the TX side.
+ */
+ if (!EVP_MD_up_ref(sha256)) {
+ sha256 = NULL;
+ goto err;
+ }
+
+ /* Setup TX cipher. */
+ if (!qrl_el_set_secret(qrl, QUIC_ENC_LEVEL_INITIAL,
+ QRL_SUITE_AES128GCM,
+ sha256,
+ /*is_tx=*/1,
+ client_initial_secret,
+ sizeof(client_initial_secret)))
+ goto err;
+
+ /*
+ * Any packets we previously could not decrypt, we may now be able to
+ * decrypt, so move any datagrams containing deferred packets from the
+ * deferred to the pending queue.
+ */
+ qrl_requeue_deferred(qrl);
+ return 1;
+
+err:
+ if (have_rx)
+ qrl_el_discard(qrl, QUIC_ENC_LEVEL_INITIAL, /*is_tx=*/0, 0);
+
+ EVP_MD_free(sha256);
+ return 0;
+}
+
+int ossl_qrl_discard_enc_level(OSSL_QRL *qrl, uint32_t enc_level)
+{
+ if (enc_level >= QUIC_ENC_LEVEL_NUM)
+ return 0;
+
+ qrl_el_discard(qrl, enc_level, 0, 1);
+ return 1;
+}
+
+/* Returns 1 if there are one or more pending RXEs. */
+int ossl_qrl_processed_read_pending(OSSL_QRL *qrl)
+{
+ return qrl->rx_pending.head != NULL;
+}
+
+/* Returns 1 if there are yet-unprocessed packets. */
+int ossl_qrl_unprocessed_read_pending(OSSL_QRL *qrl)
+{
+ return qrl->urx_pending.head != NULL || qrl->urx_deferred.head != NULL;
+}
+
+/* Pop the next pending RXE. Returns NULL if no RXE is pending. */
+static RXE *qrl_pop_pending_rxe(OSSL_QRL *qrl)
+{
+ RXE *rxe = qrl->rx_pending.head;
+
+ if (rxe == NULL)
+ return NULL;
+
+ rxe_remove(&qrl->rx_pending, rxe);
+ return rxe;
+}
+
+/* Allocate a new RXE. */
+static RXE *qrl_alloc_rxe(size_t alloc_len)
+{
+ RXE *rxe;
+
+ if (alloc_len >= SIZE_MAX - sizeof(RXE))
+ return NULL;
+
+ rxe = OPENSSL_malloc(sizeof(RXE) + alloc_len);
+ if (rxe == NULL)
+ return NULL;
+
+ rxe->prev = rxe->next = NULL;
+ rxe->alloc_len = alloc_len;
+ rxe->data_len = 0;
+ return rxe;
+}
+
+/*
+ * Ensures there is at least one RXE in the RX free list, allocating a new entry
+ * if necessary. The returned RXE is in the RX free list; it is not popped.
+ *
+ * alloc_len is a hint which may be used to determine the RXE size if allocation
+ * is necessary. Returns NULL on allocation failure.
+ */
+static RXE *qrl_ensure_free_rxe(OSSL_QRL *qrl, size_t alloc_len)
+{
+ RXE *rxe;
+
+ if (qrl->rx_free.head != NULL)
+ return qrl->rx_free.head;
+
+ rxe = qrl_alloc_rxe(alloc_len);
+ if (rxe == NULL)
+ return NULL;
+
+ rxe_insert_tail(&qrl->rx_free, rxe);
+ return rxe;
+}
+
+/*
+ * Resize the data buffer attached to an RXE to be n bytes in size. The address
+ * of the RXE might change; the new address is returned, or NULL on failure, in
+ * which case the original RXE remains valid.
+ */
+static RXE *qrl_resize_rxe(RXE_LIST *rxl, RXE *rxe, size_t n)
+{
+ RXE *rxe2;
+
+ /* Should never happen. */
+ if (rxe == NULL)
+ return NULL;
+
+ if (n >= SIZE_MAX - sizeof(RXE))
+ return NULL;
+
+ /*
+ * NOTE: We do not clear old memory, although it does contain decrypted
+ * data.
+ */
+ rxe2 = OPENSSL_realloc(rxe, sizeof(RXE) + n);
+ if (rxe2 == NULL)
+ /* original RXE is still in tact unchanged */
+ return NULL;
+
+ if (rxe != rxe2) {
+ if (rxl->head == rxe)
+ rxl->head = rxe2;
+ if (rxl->tail == rxe)
+ rxl->tail = rxe2;
+ if (rxe->prev != NULL)
+ rxe->prev->next = rxe2;
+ if (rxe->next != NULL)
+ rxe->next->prev = rxe2;
+ }
+
+ rxe2->alloc_len = n;
+ return rxe2;
+}
+
+/*
+ * Ensure the data buffer attached to an RXE is at least n bytes in size.
+ * Returns NULL on failure.
+ */
+static RXE *qrl_reserve_rxe(RXE_LIST *rxl,
+ RXE *rxe, size_t n)
+{
+ if (rxe->alloc_len >= n)
+ return rxe;
+
+ return qrl_resize_rxe(rxl, rxe, n);
+}
+
+/* Return a RXE handed out to the user back to our freelist. */
+static void qrl_recycle_rxe(OSSL_QRL *qrl, RXE *rxe)
+{
+ /* RXE should not be in any list */
+ assert(rxe->prev == NULL && rxe->next == NULL);
+ rxe_insert_tail(&qrl->rx_free, rxe);
+}
+
+/*
+ * Given a pointer to a pointer pointing to a buffer and the size of that
+ * buffer, copy the buffer into *prxe, expanding the RXE if necessary (its
+ * pointer may change due to realloc). *pi is the offset in bytes to copy the
+ * buffer to, and on success is updated to be the offset pointing after the
+ * copied buffer. *pptr is updated to point to the new location of the buffer.
+ */
+static int qrl_relocate_buffer(OSSL_QRL *qrl, RXE **prxe, size_t *pi,
+ const unsigned char **pptr, size_t buf_len)
+{
+ RXE *rxe;
+ unsigned char *dst;
+
+ if (!buf_len)
+ return 1;
+
+ if ((rxe = qrl_reserve_rxe(&qrl->rx_free, *prxe, *pi + buf_len)) == NULL)
+ return 0;
+
+ *prxe = rxe;
+ dst = (unsigned char *)rxe_data(rxe) + *pi;
+
+ memcpy(dst, *pptr, buf_len);
+ *pi += buf_len;
+ *pptr = dst;
+ return 1;
+}
+
+static uint32_t qrl_determine_enc_level(const QUIC_PKT_HDR *hdr)
+{
+ switch (hdr->type) {
+ case QUIC_PKT_TYPE_INITIAL:
+ return QUIC_ENC_LEVEL_INITIAL;
+ case QUIC_PKT_TYPE_HANDSHAKE:
+ return QUIC_ENC_LEVEL_HANDSHAKE;
+ case QUIC_PKT_TYPE_0RTT:
+ return QUIC_ENC_LEVEL_0RTT;
+ case QUIC_PKT_TYPE_1RTT:
+ return QUIC_ENC_LEVEL_1RTT;
+
+ default:
+ assert(0);
+ case QUIC_PKT_TYPE_RETRY:
+ case QUIC_PKT_TYPE_VERSION_NEG:
+ return QUIC_ENC_LEVEL_INITIAL; /* not used */
+ }
+}
+
+static uint32_t rxe_determine_pn_space(RXE *rxe)
+{
+ uint32_t enc_level;
+
+ enc_level = qrl_determine_enc_level(&rxe->hdr);
+ return ossl_quic_enc_level_to_pn_space(enc_level);
+}
+
+static int qrl_validate_hdr_early(OSSL_QRL *qrl, RXE *rxe,
+ RXE *first_rxe)
+{
+ /* Ensure version is what we want. */
+ if (rxe->hdr.version != QUIC_VERSION_1
+ && rxe->hdr.version != QUIC_VERSION_NONE)
+ return 0;
+
+ /* Clients should never receive 0-RTT packets. */
+ if (rxe->hdr.type == QUIC_PKT_TYPE_0RTT)
+ return 0;
+
+ /* Version negotiation and retry packets must be the first packet. */
+ if (first_rxe != NULL && (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
+ || rxe->hdr.type == QUIC_PKT_TYPE_RETRY))
+ return 0;
+
+ /*
+ * If this is not the first packet in a datagram, the destination connection
+ * ID must match the one in that packet.
+ */
+ if (first_rxe != NULL &&
+ !ossl_quic_conn_id_eq(&first_rxe->hdr.dst_conn_id,
+ &rxe->hdr.dst_conn_id))
+ return 0;
+
+ return 1;
+}
+
+/* Validate header and decode PN. */
+static int qrl_validate_hdr(OSSL_QRL *qrl, RXE *rxe)
+{
+ int pn_space = rxe_determine_pn_space(rxe);
+
+ if (!ossl_quic_wire_decode_pkt_hdr_pn(rxe->hdr.pn, rxe->hdr.pn_len,
+ qrl->rx_largest_pn[pn_space],
+ &rxe->pn))
+ return 0;
+
+ /*
+ * Allow our user to decide whether to discard the packet before we try and
+ * decrypt it.
+ */
+ if (qrl->rx_validation_cb != NULL
+ && !qrl->rx_validation_cb(rxe->pn, pn_space, qrl->rx_validation_cb_arg))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Tries to decrypt a packet payload.
+ *
+ * Returns 1 on success or 0 on failure (which is permanent). The payload is
+ * decrypted from src and written to dst. The buffer dst must be of at least
+ * src_len bytes in length. The actual length of the output in bytes is written
+ * to *dec_len on success, which will always be equal to or less than (usually
+ * less than) src_len.
+ */
+static int qrl_decrypt_pkt_body(OSSL_QRL *qrl, 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)
+{
+ int l = 0, l2 = 0;
+ unsigned char nonce[EVP_MAX_IV_LENGTH];
+ size_t nonce_len, i;
+ OSSL_QRL_ENC_LEVEL *el = &qrl->rx_el[enc_level];
+
+ if (src_len > INT_MAX || aad_len > INT_MAX || el->tag_len >= src_len)
+ return 0;
+
+ /* We should not have been called if we do not have key material. */
+ if (!ossl_assert(qrl_have_el(qrl, enc_level, /*is_tx=*/0) == 1))
+ return 0;
+
+ /* Construct nonce (nonce=IV ^ PN). */
+ nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
+ if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
+ return 0;
+
+ memcpy(nonce, el->iv, nonce_len);
+ for (i = 0; i < sizeof(QUIC_PN); ++i)
+ nonce[nonce_len - i - 1] ^= (unsigned char)(pn >> (i * 8));
+
+ /* type and key will already have been setup; feed the IV. */
+ if (EVP_CipherInit_ex(el->cctx, NULL,
+ NULL, NULL, nonce, /*enc=*/0) != 1)
+ return 0;
+
+ /* Feed the AEAD tag we got so the cipher can validate it. */
+ if (EVP_CIPHER_CTX_ctrl(el->cctx, EVP_CTRL_AEAD_SET_TAG,
+ el->tag_len,
+ (unsigned char *)src + src_len - el->tag_len) != 1)
+ return 0;
+
+ /* Feed AAD data. */
+ if (EVP_CipherUpdate(el->cctx, NULL, &l, aad, aad_len) != 1)
+ return 0;
+
+ /* Feed encrypted packet body. */
+ if (EVP_CipherUpdate(el->cctx, dst, &l, src, src_len - el->tag_len) != 1)
+ return 0;
+
+ /* Ensure authentication succeeded. */
+ if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1)
+ return 0;
+
+ *dec_len = l;
+ return 1;
+}
+
+static ossl_inline void ignore_res(int x)
+{
+ /* No-op. */
+}
+
+/* Process a single packet in a datagram. */
+static int qrl_process_pkt(OSSL_QRL *qrl, QUIC_URXE *urxe,
+ PACKET *pkt, size_t pkt_idx,
+ RXE **first_rxe,
+ size_t datagram_len)
+{
+ RXE *rxe;
+ const unsigned char *eop = NULL;
+ size_t i, aad_len = 0, dec_len = 0;
+ PACKET orig_pkt = *pkt;
+ const unsigned char *sop = PACKET_data(pkt);
+ unsigned char *dst;
+ char need_second_decode = 0, already_processed = 0;
+ QUIC_PKT_HDR_PTRS ptrs;
+ uint32_t pn_space, enc_level;
+
+ /*
+ * Get a free RXE. If we need to allocate a new one, use the packet length
+ * as a good ballpark figure.
+ */
+ rxe = qrl_ensure_free_rxe(qrl, PACKET_remaining(pkt));
+ if (rxe == NULL)
+ return 0;
+
+ /* Have we already processed this packet? */
+ if (pkt_is_marked(&urxe->processed, pkt_idx))
+ already_processed = 1;
+
+ /*
+ * Decode the header into the RXE structure. We first decrypt and read the
+ * unprotected part of the packet header (unless we already removed header
+ * protection, in which case we decode all of it).
+ */
+ need_second_decode = !pkt_is_marked(&urxe->hpr_removed, pkt_idx);
+ if (!ossl_quic_wire_decode_pkt_hdr(pkt,
+ qrl->short_conn_id_len,
+ need_second_decode, &rxe->hdr, &ptrs))
+ goto malformed;
+
+ /*
+ * Our successful decode above included an intelligible length and the
+ * PACKET is now pointing to the end of the QUIC packet.
+ */
+ eop = PACKET_data(pkt);
+
+ /*
+ * Make a note of the first RXE so we can later ensure the destination
+ * connection IDs of all packets in a datagram mater.
+ */
+ if (pkt_idx == 0)
+ *first_rxe = rxe;
+
+ /*
+ * Early header validation. Since we now know the packet length, we can also
+ * now skip over it if we already processed it.
+ */
+ if (already_processed
+ || !qrl_validate_hdr_early(qrl, rxe, pkt_idx == 0 ? NULL : *first_rxe))
+ goto malformed;
+
+ if (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
+ || rxe->hdr.type == QUIC_PKT_TYPE_RETRY) {
+ /*
+ * Version negotiation and retry packets are a special case. They do not
+ * contain a payload which needs decrypting and have no header
+ * protection.
+ */
+
+ /* Just copy the payload from the URXE to the RXE. */
+ if ((rxe = qrl_reserve_rxe(&qrl->rx_free, rxe, rxe->hdr.len)) == NULL)
+ /*
+ * Allocation failure. EOP will be pointing to the end of the
+ * datagram so processing of this datagram will end here.
+ */
+ goto malformed;
+
+ /* We are now committed to returning the packet. */
+ memcpy(rxe_data(rxe), rxe->hdr.data, rxe->hdr.len);
+ pkt_mark(&urxe->processed, pkt_idx);
+
+ rxe->hdr.data = rxe_data(rxe);
+
+ /* Move RXE to pending. */
+ rxe_remove(&qrl->rx_free, rxe);
+ rxe_insert_tail(&qrl->rx_pending, rxe);
+ return 0; /* success, did not defer */
+ }
+
+ /* Determine encryption level of packet. */
+ enc_level = qrl_determine_enc_level(&rxe->hdr);
+
+ /* If we do not have keying material for this encryption level yet, defer. */
+ switch (qrl_have_el(qrl, enc_level, /*is_tx=*/0)) {
+ case 1:
+ /* We have keys. */
+ break;
+ case 0:
+ /* No keys yet. */
+ goto cannot_decrypt;
+ default:
+ /* We already discarded keys for this EL, we will never process this.*/
+ goto malformed;
+ }
+
+ /*
+ * We will copy any token included in the packet to the start of our RXE
+ * data buffer (so that we don't reference the URXE buffer any more and can
+ * recycle it). Track our position in the RXE buffer by index instead of
+ * pointer as the pointer may change as reallocs occur.
+ */
+ i = 0;
+
+ /*
+ * rxe->hdr.data is now pointing at the (encrypted) packet payload. rxe->hdr
+ * also has fields pointing into the PACKET buffer which will be going away
+ * soon (the URXE will be reused for another incoming packet).
+ *
+ * Firstly, relocate some of these fields into the RXE as needed.
+ *
+ * Relocate token buffer and fix pointer.
+ */
+ if (rxe->hdr.type == QUIC_PKT_TYPE_INITIAL
+ && !qrl_relocate_buffer(qrl, &rxe, &i, &rxe->hdr.token,
+ rxe->hdr.token_len))
+ goto malformed;
+
+ /* Now remove header protection. */
+ *pkt = orig_pkt;
+
+ if (need_second_decode) {
+ if (!ossl_quic_hdr_protector_decrypt(&qrl->rx_el[enc_level].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, pkt_idx);
+
+ /* Decode the now unprotected header. */
+ if (ossl_quic_wire_decode_pkt_hdr(pkt, qrl->short_conn_id_len,
+ 0, &rxe->hdr, NULL) != 1)
+ goto malformed;
+ }
+
+ /* Validate header and decode PN. */
+ if (!qrl_validate_hdr(qrl, rxe))
+ goto malformed;
+
+ /*
+ * We automatically discard INITIAL keys when successfully decrypting a
+ * HANDSHAKE packet.
+ */
+ if (enc_level == QUIC_ENC_LEVEL_HANDSHAKE)
+ qrl_el_discard(qrl, QUIC_ENC_LEVEL_INITIAL, 0, 1);
+
+ /*
+ * 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 = qrl_reserve_rxe(&qrl->rx_free, rxe, rxe->hdr.len + i)) == NULL) {
+ /*
+ * Allocation failure, treat as malformed and do not bother processing
+ * any further packets in the datagram as they are likely to also
+ * encounter allocation failures.
+ */
+ eop = 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 (!qrl_decrypt_pkt_body(qrl, dst, rxe->hdr.data, rxe->hdr.len,
+ &dec_len, sop, aad_len, rxe->pn, enc_level))
+ goto malformed;
+
+ /*
+ * We have now successfully decrypted the packet payload. If there are
+ * additional packets in the datagram, it is possible we will fail to
+ * decrypt them and need to defer them until we have some key material we
+ * don't currently possess. If this happens, the URXE will be moved to the
+ * deferred queue. Since a URXE corresponds to one datagram, which may
+ * contain multiple packets, we must ensure any packets we have already
+ * processed in the URXE are not processed again (this is an RFC
+ * requirement). We do this by marking the nth packet in the datagram as
+ * processed.
+ *
+ * We are now committed to returning this decrypted packet to the user,
+ * meaning we now consider the packet processed and must mark it
+ * accordingly.
+ */
+ pkt_mark(&urxe->processed, pkt_idx);
+
+ /*
+ * 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;
+
+ /* We processed the PN successfully, so update largest processed PN. */
+ pn_space = rxe_determine_pn_space(rxe);
+ if (rxe->pn > qrl->rx_largest_pn[pn_space])
+ qrl->rx_largest_pn[pn_space] = rxe->pn;
+
+ /* Copy across network addresses from URXE to RXE. */
+ rxe->peer = urxe->peer;
+ rxe->local = urxe->local;
+
+ /* Move RXE to pending. */
+ rxe_remove(&qrl->rx_free, rxe);
+ rxe_insert_tail(&qrl->rx_pending, rxe);
+ return 0; /* success, did not defer; not distinguished from failure */
+
+cannot_decrypt:
+ /*
+ * We cannot process this packet right now (but might be able to later). We
+ * MUST attempt to process any other packets in the datagram, so defer it
+ * and skip over it.
+ */
+ assert(eop != NULL && eop >= PACKET_data(pkt));
+ /*
+ * We don't care if this fails as it will just result in the packet being at
+ * the end of the datagram buffer.
+ */
+ ignore_res(PACKET_forward(pkt, eop - PACKET_data(pkt)));
+ return 1; /* deferred */
+
+malformed:
+ if (eop != NULL) {
+ /*
+ * This packet cannot be processed and will never be processable. We
+ * were at least able to decode its header and determine its length, so
+ * we can skip over it and try to process any subsequent packets in the
+ * datagram.
+ *
+ * Mark as processed as an optimization.
+ */
+ assert(eop >= PACKET_data(pkt));
+ pkt_mark(&urxe->processed, pkt_idx);
+ /* We don't care if this fails (see above) */
+ ignore_res(PACKET_forward(pkt, eop - PACKET_data(pkt)));
+ } else {
+ /*
+ * This packet cannot be processed and will never be processable.
+ * Because even its header is not intelligible, we cannot examine any
+ * further packets in the datagram because its length cannot be
+ * discerned.
+ *
+ * Advance over the entire remainder of the datagram, and mark it as
+ * processed gap as an optimization.
+ */
+ pkt_mark(&urxe->processed, pkt_idx);
+ /* We don't care if this fails (see above) */
+ ignore_res(PACKET_forward(pkt, PACKET_remaining(pkt)));
+ }
+ return 0; /* failure, did not defer; not distinguished from success */
+}
+
+/* Process a datagram which was received. */
+static int qrl_process_datagram(OSSL_QRL *qrl, QUIC_URXE *e,
+ const unsigned char *data,
+ size_t data_len)
+{
+ int have_deferred = 0;
+ PACKET pkt;
+ size_t pkt_idx = 0;
+ RXE *first_rxe = NULL;
+
+ qrl->bytes_received += data_len;
+
+ if (!PACKET_buf_init(&pkt, data, data_len))
+ return 0;
+
+ for (; PACKET_remaining(&pkt) > 0; ++pkt_idx) {
+ /*
+ * A packet smallest than the minimum possible QUIC packet size is not
+ * considered valid. We also ignore more than a certain number of
+ * packets within the same datagram.
+ */
+ if (PACKET_remaining(&pkt) < QUIC_MIN_VALID_PKT_LEN
+ || pkt_idx >= QUIC_MAX_PKT_PER_URXE)
+ break;
+
+ /*
+ * We note whether packet processing resulted in a deferral since
+ * this means we need to move the URXE to the deferred list rather
+ * than the free list after we're finished dealing with it for now.
+ *
+ * However, we don't otherwise care here whether processing succeeded or
+ * failed, as the RFC says even if a packet in a datagram is malformed,
+ * we should still try to process any packets following it.
+ *
+ * In the case where the packet is so malformed we can't determine its
+ * lenngth, qrl_process_pkt will take care of advancing to the end of
+ * the packet, so we will exit the loop automatically in this case.
+ */
+ if (qrl_process_pkt(qrl, e, &pkt, pkt_idx, &first_rxe, data_len))
+ have_deferred = 1;
+ }
+
+ /* Only report whether there were any deferrals. */
+ return have_deferred;
+}
+
+/* Process a single pending URXE. */
+static int qrl_process_one_urxl(OSSL_QRL *qrl, QUIC_URXE *e)
+{
+ int was_deferred;
+
+ /* The next URXE we process should be at the head of the pending list. */
+ if (!ossl_assert(e == qrl->urx_pending.head))
+ return 0;
+
+ /*
+ * Attempt to process the datagram. The return value indicates only if
+ * processing of the datagram was deferred. If we failed to process the
+ * datagram, we do not attempt to process it again and silently eat the
+ * error.
+ */
+ was_deferred = qrl_process_datagram(qrl, e, ossl_quic_urxe_data(e),
+ e->data_len);
+
+ /*
+ * Remove the URXE from the pending list and return it to
+ * either the free or deferred list.
+ */
+ ossl_quic_urxe_remove(&qrl->urx_pending, e);
+ if (was_deferred > 0)
+ ossl_quic_urxe_insert_tail(&qrl->urx_deferred, e);
+ else
+ ossl_quic_demux_release_urxe(qrl->rx_demux, e);
+
+ return 1;
+}
+
+/* Process any pending URXEs to generate pending RXEs. */
+static int qrl_process_urxl(OSSL_QRL *qrl)
+{
+ QUIC_URXE *e;
+
+ while ((e = qrl->urx_pending.head) != NULL)
+ if (!qrl_process_one_urxl(qrl, e))
+ return 0;
+
+ return 1;
+}
+
+int ossl_qrl_read_pkt(OSSL_QRL *qrl, OSSL_QRL_RX_PKT *pkt)
+{
+ RXE *rxe;
+
+ if (!ossl_qrl_processed_read_pending(qrl)) {
+ if (!qrl_process_urxl(qrl))
+ return 0;
+
+ if (!ossl_qrl_processed_read_pending(qrl))
+ return 0;
+ }
+
+ rxe = qrl_pop_pending_rxe(qrl);
+ if (!ossl_assert(rxe != NULL))
+ return 0;
+
+ pkt->handle = rxe;
+ pkt->hdr = &rxe->hdr;
+ pkt->peer
+ = BIO_ADDR_family(&rxe->peer) != AF_UNSPEC ? &rxe->peer : NULL;
+ pkt->local
+ = BIO_ADDR_family(&rxe->local) != AF_UNSPEC ? &rxe->local : NULL;
+ return 1;
+}
+
+void ossl_qrl_release_pkt(OSSL_QRL *qrl, void *handle)
+{
+ RXE *rxe = handle;
+
+ qrl_recycle_rxe(qrl, rxe);
+}
+
+uint64_t ossl_qrl_get_bytes_received(OSSL_QRL *qrl, int clear)
+{
+ uint64_t v = qrl->bytes_received;
+
+ if (clear)
+ qrl->bytes_received = 0;
+
+ return v;
+}
+
+int ossl_qrl_set_early_rx_validation_cb(OSSL_QRL *qrl,
+ ossl_qrl_early_rx_validation_cb *cb,
+ void *cb_arg)
+{
+ qrl->rx_validation_cb = cb;
+ qrl->rx_validation_cb_arg = cb_arg;
+ return 1;
+}
diff --git a/ssl/quic/quic_record_util.c b/ssl/quic/quic_record_util.c
new file mode 100644
index 0000000000..6d0eeb5759
--- /dev/null
+++ b/ssl/quic/quic_record_util.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2022 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
+ */
+
+#include "internal/quic_record_util.h"
+#include "internal/quic_wire_pkt.h"
+#include <openssl/kdf.h>
+#include <openssl/core_names.h>
+
+/*
+ * QUIC Key Derivation Utilities
+ * =============================
+ */
+int ossl_quic_hkdf_extract(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const EVP_MD *md,
+ const unsigned char *salt, size_t salt_len,
+ const unsigned char *ikm, size_t ikm_len,
+ unsigned char *out, size_t out_len)
+{
+ int ret = 0;
+ EVP_KDF *kdf = NULL;
+ EVP_KDF_CTX *kctx = NULL;
+ OSSL_PARAM params[7], *p = params;
+ int mode = EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY;
+ const char *md_name;
+
+ if ((md_name = EVP_MD_get0_name(md)) == NULL
+ || (kdf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_HKDF, propq)) == NULL
+ || (kctx = EVP_KDF_CTX_new(kdf)) == NULL)
+ goto err;
+
+ *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+ (char *)md_name, 0);
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
+ (unsigned char *)salt, salt_len);
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
+ (unsigned char *)ikm, ikm_len);
+ *p++ = OSSL_PARAM_construct_end();
+
+ ret = EVP_KDF_derive(kctx, out, out_len, params);
+
+err:
+ EVP_KDF_CTX_free(kctx);
+ EVP_KDF_free(kdf);
+ return ret;
+}
+
+/*
+ * QUIC Record Layer Ciphersuite Info
+ * ==================================
+ */
+
+struct suite_info {
+ const char *cipher_name, *md_name;
+ uint32_t secret_len, cipher_key_len, cipher_iv_len, cipher_tag_len;
+ uint32_t hdr_prot_key_len, hdr_prot_cipher_id;
+};
+
+static const struct suite_info suite_aes128gcm = {
+ "AES-128-GCM", "SHA256", 32, 16, 12, 16, 16,
+ QUIC_HDR_PROT_CIPHER_AES_128
+};
+
+static const struct suite_info suite_aes256gcm = {
+ "AES-256-GCM", "SHA384", 48, 32, 12, 16, 32,
+ QUIC_HDR_PROT_CIPHER_AES_256
+};
+
+static const struct suite_info suite_chacha20poly1305 = {
+ "ChaCha20-Poly1305", "SHA256", 32, 32, 12, 16, 32,
+ QUIC_HDR_PROT_CIPHER_CHACHA
+};
+
+static const struct suite_info *get_suite(uint32_t suite_id)
+{
+ switch (suite_id) {
+ case QRL_SUITE_AES128GCM:
+ return &suite_aes128gcm;
+ case QRL_SUITE_AES256GCM:
+ return &suite_aes256gcm;
+ case QRL_SUITE_CHACHA20POLY1305:
+ return &suite_chacha20poly1305;
+ default:
+ return NULL;
+ }
+}
+
+const char *ossl_qrl_get_suite_cipher_name(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->cipher_name : NULL;
+}
+
+const char *ossl_qrl_get_suite_md_name(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->md_name : NULL;
+}
+
+uint32_t ossl_qrl_get_suite_secret_len(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->secret_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_key_len(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->cipher_key_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_iv_len(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->cipher_iv_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->cipher_tag_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_hdr_prot_cipher_id(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->hdr_prot_cipher_id : 0;
+}
+
+uint32_t ossl_qrl_get_suite_hdr_prot_key_len(uint32_t suite_id)
+{
+ const struct suite_info *c = get_suite(suite_id);
+ return c != NULL ? c->hdr_prot_key_len : 0;
+}
diff --git a/ssl/quic/quic_wire.c b/ssl/quic/quic_wire.c
index a3a9b252fa..4d19ad6013 100644
--- a/ssl/quic/quic_wire.c
+++ b/ssl/quic/quic_wire.c
@@ -263,7 +263,7 @@ int ossl_quic_wire_encode_frame_streams_blocked(WPACKET *pkt,
int ossl_quic_wire_encode_frame_new_conn_id(WPACKET *pkt,
const OSSL_QUIC_FRAME_NEW_CONN_ID *f)
{
- if (f->conn_id.id_len > OSSL_QUIC_MAX_CONN_ID_LEN)
+ if (f->conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
return 0;
if (!encode_frame_hdr(pkt, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID)
@@ -680,7 +680,7 @@ int ossl_quic_wire_decode_frame_new_conn_id(PACKET *pkt,
|| !PACKET_get_quic_vlint(pkt, &f->seq_num)
|| !PACKET_get_quic_vlint(pkt, &f->retire_prior_to)
|| !PACKET_get_1(pkt, &len)
- || len > OSSL_QUIC_MAX_CONN_ID_LEN)
+ || len > QUIC_MAX_CONN_ID_LEN)
return 0;
f->conn_id.id_len = (unsigned char)len;
@@ -688,8 +688,8 @@ int ossl_quic_wire_decode_frame_new_conn_id(PACKET *pkt,
return 0;
/* Clear unused bytes to allow consistent memcmp. */
- if (len < OSSL_QUIC_MAX_CONN_ID_LEN)
- memset(f->conn_id.id + len, 0, OSSL_QUIC_MAX_CONN_ID_LEN - len);
+ if (len < QUIC_MAX_CONN_ID_LEN)
+ memset(f->conn_id.id + len, 0, QUIC_MAX_CONN_ID_LEN - len);
if (!PACKET_copy_bytes(pkt, f->stateless_reset_token,
sizeof(f->stateless_reset_token)))
diff --git a/ssl/quic/quic_wire_pkt.c b/ssl/quic/quic_wire_pkt.c
new file mode 100644
index 0000000000..5d90d70c15
--- /dev/null
+++ b/ssl/quic/quic_wire_pkt.c
@@ -0,0 +1,678 @@
+/*
+ * Copyright 2022 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
+ */
+
+#include "internal/quic_wire_pkt.h"
+
+int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
+ OSSL_LIB_CTX *libctx,
+ const char *propq,
+ uint32_t cipher_id,
+ const unsigned char *quic_hp_key,
+ size_t quic_hp_key_len)
+{
+ const char *cipher_name = NULL;
+
+ switch (cipher_id) {
+ case QUIC_HDR_PROT_CIPHER_AES_128:
+ cipher_name = "AES-128-ECB";
+ break;
+ case QUIC_HDR_PROT_CIPHER_AES_256:
+ cipher_name = "AES-256-ECB";
+ break;
+ case QUIC_HDR_PROT_CIPHER_CHACHA:
+ cipher_name = "ChaCha20";
+ break;
+ default:
+ return 0;
+ }
+
+ hpr->cipher_ctx = EVP_CIPHER_CTX_new();
+ if (hpr->cipher_ctx == NULL)
+ return 0;
+
+ hpr->cipher = EVP_CIPHER_fetch(libctx, cipher_name, propq);
+ if (hpr->cipher == NULL
+ || quic_hp_key_len != (size_t)EVP_CIPHER_get_key_length(hpr->cipher))
+ goto err;
+
+ if (!EVP_CipherInit_ex(hpr->cipher_ctx, hpr->cipher, NULL,
+ quic_hp_key, NULL, 1))
+ goto err;
+
+ hpr->libctx = libctx;
+ hpr->propq = propq;
+ hpr->cipher_id = cipher_id;
+ return 1;
+
+err:
+ ossl_quic_hdr_protector_destroy(hpr);
+ return 0;
+}
+
+void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr)
+{
+ EVP_CIPHER_CTX_free(hpr->cipher_ctx);
+ hpr->cipher_ctx = NULL;
+
+ EVP_CIPHER_free(hpr->cipher);
+ hpr->cipher = NULL;
+}
+
+static int hdr_generate_mask(QUIC_HDR_PROTECTOR *hpr,
+ const unsigned char *sample, size_t sample_len,
+ unsigned char *mask)
+{
+ int l = 0;
+ unsigned char dst[16];
+ static const unsigned char zeroes[5] = {0};
+ size_t i;
+
+ if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_128
+ || hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_256) {
+ if (sample_len < 16)
+ return 0;
+
+ if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, NULL, 1)
+ || !EVP_CipherUpdate(hpr->cipher_ctx, dst, &l, sample, 16))
+ return 0;
+
+ for (i = 0; i < 5; ++i)
+ mask[i] = dst[i];
+ } else if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_CHACHA) {
+ if (sample_len < 16)
+ return 0;
+
+ if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, sample, 1)
+ || !EVP_CipherUpdate(hpr->cipher_ctx, mask, &l,
+ zeroes, sizeof(zeroes)))
+ return 0;
+ } else {
+ assert(0);
+ return 0;
+ }
+
+ return 1;
+}
+
+int ossl_quic_hdr_protector_decrypt(QUIC_HDR_PROTECTOR *hpr,
+ QUIC_PKT_HDR_PTRS *ptrs)
+{
+ return ossl_quic_hdr_protector_decrypt_fields(hpr,
+ ptrs->raw_sample,
+ ptrs->raw_sample_len,
+ ptrs->raw_start,
+ ptrs->raw_pn);
+}
+
+int ossl_quic_hdr_protector_decrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+ const unsigned char *sample,
+ size_t sample_len,
+ unsigned char *first_byte,
+ unsigned char *pn_bytes)
+{
+ unsigned char mask[5], pn_len, i;
+
+ if (!hdr_generate_mask(hpr, sample, sample_len, mask))
+ return 0;
+
+ *first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
+ pn_len = (*first_byte & 0x3) + 1;
+
+ for (i = 0; i < pn_len; ++i)
+ pn_bytes[i] ^= mask[i + 1];
+
+ return 1;
+}
+
+int ossl_quic_hdr_protector_encrypt(QUIC_HDR_PROTECTOR *hpr,
+ QUIC_PKT_HDR_PTRS *ptrs)
+{
+ return ossl_quic_hdr_protector_encrypt_fields(hpr,
+ ptrs->raw_sample,
+ ptrs->raw_sample_len,
+ ptrs->raw_start,
+ ptrs->raw_pn);
+}
+
+int ossl_quic_hdr_protector_encrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+ const unsigned char *sample,
+ size_t sample_len,
+ unsigned char *first_byte,
+ unsigned char *pn_bytes)
+{
+ unsigned char mask[5], pn_len, i;
+
+ if (!hdr_generate_mask(hpr, sample, sample_len, mask))
+ return 0;
+
+ pn_len = (*first_byte & 0x3) + 1;
+ for (i = 0; i < pn_len; ++i)
+ pn_bytes[i] ^= mask[i + 1];
+
+ *first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
+ return 1;
+}
+
+int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
+ size_t short_conn_id_len,
+ int partial,
+ QUIC_PKT_HDR *hdr,
+ QUIC_PKT_HDR_PTRS *ptrs)
+{
+ unsigned int b0;
+ unsigned char *pn = NULL;
+ size_t l = PACKET_remaining(pkt);
+
+ if (ptrs != NULL) {
+ ptrs->raw_start = (unsigned char *)PACKET_data(pkt);
+ ptrs->raw_sample = NULL;
+ ptrs->raw_sample_len = 0;
+ ptrs->raw_pn = NULL;
+ }
+
+ if (l < QUIC_MIN_VALID_PKT_LEN
+ || !PACKET_get_1(pkt, &b0))
+ return 0;
+
+ hdr->partial = partial;
+
+ if ((b0 & 0x80) == 0) {
+ /* Short header. */
+ if (short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+
+ if ((b0 & 0x40) == 0 /* fixed bit not set? */
+ || l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
+ return 0;
+
+ hdr->type = QUIC_PKT_TYPE_1RTT;
+ hdr->fixed = 1;
+ hdr->spin_bit = (b0 & 0x20) != 0;
+ if (partial) {
+ hdr->key_phase = 0; /* protected, zero for now */
+ hdr->pn_len = 0; /* protected, zero for now */
+ } else {
+ hdr->key_phase = (b0 & 0x4) != 0;
+ hdr->pn_len = (b0 & 0x3) + 1;
+ }
+
+ /* Copy destination connection ID field to header structure. */
+ if (!PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, short_conn_id_len))
+ return 0;
+
+ hdr->dst_conn_id.id_len = short_conn_id_len;
+
+ /*
+ * Skip over the PN. If this is a partial decode, the PN length field
+ * currently has header protection applied. Thus we do not know the
+ * length of the PN but we are allowed to assume it is 4 bytes long at
+ * this stage.
+ */
+ memset(hdr->pn, 0, sizeof(hdr->pn));
+ pn = (unsigned char *)PACKET_data(pkt);
+ if (partial) {
+ if (!PACKET_forward(pkt, sizeof(hdr->pn)))
+ return 0;
+ } else {
+ if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
+ return 0;
+ }
+
+ /* Fields not used in short-header packets. */
+ hdr->version = 0;
+ hdr->src_conn_id.id_len = 0;
+ hdr->token = NULL;
+ hdr->token_len = 0;
+
+ /*
+ * Short-header packets always come last in a datagram, the length
+ * is the remainder of the buffer.
+ */
+ hdr->len = PACKET_remaining(pkt);
+ hdr->data = PACKET_data(pkt);
+
+ /*
+ * Skip over payload so we are pointing at the start of the next packet,
+ * if any.
+ */
+ if (!PACKET_forward(pkt, hdr->len))
+ return 0;
+ } else {
+ /* Long header. */
+ unsigned long version;
+ unsigned int dst_conn_id_len, src_conn_id_len, raw_type;
+
+ if (!PACKET_get_net_4(pkt, &version))
+ return 0;
+
+ /*
+ * All QUIC packets must have the fixed bit set, except exceptionally
+ * for Version Negotiation packets.
+ */
+ if (version != 0 && (b0 & 0x40) == 0)
+ return 0;
+
+ if (!PACKET_get_1(pkt, &dst_conn_id_len)
+ || dst_conn_id_len > QUIC_MAX_CONN_ID_LEN
+ || !PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, dst_conn_id_len)
+ || !PACKET_get_1(pkt, &src_conn_id_len)
+ || src_conn_id_len > QUIC_MAX_CONN_ID_LEN
+ || !PACKET_copy_bytes(pkt, hdr->src_conn_id.id, src_conn_id_len))
+ return 0;
+
+ hdr->version = (uint32_t)version;
+ hdr->dst_conn_id.id_len = (unsigned char)dst_conn_id_len;
+ hdr->src_conn_id.id_len = (unsigned char)src_conn_id_len;
+
+ if (version == 0) {
+ /*
+ * Version negotiation packet. Version negotiation packets are
+ * identified by a version field of 0 and the type bits in the first
+ * byte are ignored (they may take any value, and we ignore them).
+ */
+ hdr->type = QUIC_PKT_TYPE_VERSION_NEG;
+ hdr->fixed = (b0 & 0x40) != 0;
+
+ hdr->data = PACKET_data(pkt);
+ hdr->len = PACKET_remaining(pkt);
+
+ /* Version negotiation packets are always fully decoded. */
+ hdr->partial = 0;
+
+ /* Fields not used in version negotiation packets. */
+ hdr->pn_len = 0;
+ hdr->spin_bit = 0;
+ hdr->key_phase = 0;
+ hdr->token = NULL;
+ hdr->token_len = 0;
+ memset(hdr->pn, 0, sizeof(hdr->pn));
+
+ if (!PACKET_forward(pkt, hdr->len))
+ return 0;
+ } else if (version != QUIC_VERSION_1) {
+ /* Unknown version, do not decode. */
+ return 0;
+ } else {
+ if (l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
+ return 0;
+
+ /* Get long packet type and decode to QUIC_PKT_TYPE_*. */
+ raw_type = ((b0 >> 4) & 0x3);
+
+ switch (raw_type) {
+ case 0: hdr->type = QUIC_PKT_TYPE_INITIAL; break;
+ case 1: hdr->type = QUIC_PKT_TYPE_0RTT; break;
+ case 2: hdr->type = QUIC_PKT_TYPE_HANDSHAKE; break;
+ case 3: hdr->type = QUIC_PKT_TYPE_RETRY; break;
+ }
+
+ hdr->pn_len = 0;
+ hdr->fixed = 1;
+
+ /* Fields not used in long-header packets. */
+ hdr->spin_bit = 0;
+ hdr->key_phase = 0;
+
+ if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
+ /* Initial packet. */
+ uint64_t token_len;
+
+ if (!PACKET_get_quic_vlint(pkt, &token_len)
+ || token_len > SIZE_MAX
+ || !PACKET_get_bytes(pkt, &hdr->token, token_len))
+ return 0;
+
+ hdr->token_len = (size_t)token_len;
+ if (token_len == 0)
+ hdr->token = NULL;
+ } else {
+ hdr->token = NULL;
+ hdr->token_len = 0;
+ }
+
+ if (hdr->type == QUIC_PKT_TYPE_RETRY) {
+ /* Retry packet. */
+ hdr->data = PACKET_data(pkt);
+ hdr->len = PACKET_remaining(pkt);
+
+ /* Retry packets are always fully decoded. */
+ hdr->partial = 0;
+
+ /* Fields not used in Retry packets. */
+ memset(hdr->pn, 0, sizeof(hdr->pn));
+
+ if (!PACKET_forward(pkt, hdr->len))
+ return 0;
+ } else {
+ /* Initial, 0-RTT or Handshake packet. */
+ uint64_t len;
+
+ hdr->pn_len = partial ? 0 : (b0 & 3) + 1;
+
+ if (!PACKET_get_quic_vlint(pkt, &len)
+ || len < sizeof(hdr->pn)
+ || len > PACKET_remaining(pkt))
+ return 0;
+
+ /*
+ * Skip over the PN. If this is a partial decode, the PN length
+ * field currently has header protection applied. Thus we do not
+ * know the length of the PN but we are allowed to assume it is
+ * 4 bytes long at this stage.
+ */
+ pn = (unsigned char *)PACKET_data(pkt);
+ memset(hdr->pn, 0, sizeof(hdr->pn));
+ if (partial) {
+ if (!PACKET_forward(pkt, sizeof(hdr->pn)))
+ return 0;
+
+ hdr->len = (size_t)(len - sizeof(hdr->pn));
+ } else {
+ if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
+ return 0;
+
+ hdr->len = (size_t)(len - hdr->pn_len);
+ }
+
+ hdr->data = PACKET_data(pkt);
+
+ /* Skip over packet body. */
+ if (!PACKET_forward(pkt, hdr->len))
+ return 0;
+ }
+ }
+ }
+
+ if (ptrs != NULL) {
+ ptrs->raw_pn = pn;
+ if (pn != NULL) {
+ ptrs->raw_sample = pn + 4;
+ ptrs->raw_sample_len = PACKET_end(pkt) - ptrs->raw_sample;
+ }
+ }
+
+ return 1;
+}
+
+int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
+ size_t short_conn_id_len,
+ const QUIC_PKT_HDR *hdr,
+ QUIC_PKT_HDR_PTRS *ptrs)
+{
+ unsigned char b0;
+ size_t off_start, off_sample, off_sample_end, off_pn;
+
+ if (!WPACKET_get_total_written(pkt, &off_start))
+ return 0;
+
+ if (ptrs != NULL) {
+ ptrs->raw_start = NULL;
+ ptrs->raw_sample = NULL;
+ ptrs->raw_sample_len = 0;
+ ptrs->raw_pn = 0;
+ }
+
+ /* Cannot serialize a partial header, or one whose DCID length is wrong. */
+ if (hdr->partial
+ || (hdr->type == QUIC_PKT_TYPE_1RTT
+ && hdr->dst_conn_id.id_len != short_conn_id_len))
+ return 0;
+
+ if (hdr->type == QUIC_PKT_TYPE_1RTT) {
+ /* Short header. */
+
+ /*
+ * Cannot serialize a header whose DCID length is wrong, or with an
+ * invalid PN length.
+ */
+ if (hdr->dst_conn_id.id_len != short_conn_id_len
+ || short_conn_id_len > QUIC_MAX_CONN_ID_LEN
+ || hdr->pn_len < 1 || hdr->pn_len > 4)
+ return 0;
+
+ b0 = (hdr->spin_bit << 5)
+ | (hdr->key_phase << 2)
+ | (hdr->pn_len - 1)
+ | 0x40; /* fixed bit */
+
+ if (!WPACKET_put_bytes_u8(pkt, b0)
+ || !WPACKET_memcpy(pkt, hdr->dst_conn_id.id, short_conn_id_len)
+ || !WPACKET_get_total_written(pkt, &off_pn)
+ || !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
+ return 0;
+ } else {
+ /* Long header. */
+ unsigned int raw_type;
+
+ if (hdr->dst_conn_id.id_len > QUIC_MAX_CONN_ID_LEN
+ || hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+
+ if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG
+ && hdr->type != QUIC_PKT_TYPE_RETRY
+ && (hdr->pn_len < 1 || hdr->pn_len > 4))
+ return 0;
+
+ switch (hdr->type) {
+ case QUIC_PKT_TYPE_VERSION_NEG:
+ if (hdr->version != 0)
+ return 0;
+
+ /* Version negotiation packets use zero for the type bits */
+ raw_type = 0;
+ break;
+
+ case QUIC_PKT_TYPE_INITIAL: raw_type = 0; break;
+ case QUIC_PKT_TYPE_0RTT: raw_type = 1; break;
+ case QUIC_PKT_TYPE_HANDSHAKE: raw_type = 2; break;
+ case QUIC_PKT_TYPE_RETRY: raw_type = 3; break;
+ default:
+ return 0;
+ }
+
+ b0 = (raw_type << 4) | 0x80; /* long */
+ if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG || hdr->fixed)
+ b0 |= 0x40; /* fixed */
+ if (hdr->type != QUIC_PKT_TYPE_RETRY
+ && hdr->type != QUIC_PKT_TYPE_VERSION_NEG)
+ b0 |= hdr->pn_len - 1;
+
+ if (!WPACKET_put_bytes_u8(pkt, b0)
+ || !WPACKET_put_bytes_u32(pkt, hdr->version)
+ || !WPACKET_put_bytes_u8(pkt, hdr->dst_conn_id.id_len)
+ || !WPACKET_memcpy(pkt, hdr->dst_conn_id.id,
+ hdr->dst_conn_id.id_len)
+ || !WPACKET_put_bytes_u8(pkt, hdr->src_conn_id.id_len)
+ || !WPACKET_memcpy(pkt, hdr->src_conn_id.id,
+ hdr->src_conn_id.id_len))
+ return 0;
+
+ if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG
+ || hdr->type == QUIC_PKT_TYPE_RETRY) {
+ if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+ return 0;
+
+ return 1;
+ }
+
+ if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
+ if (!WPACKET_quic_write_vlint(pkt, hdr->token_len)
+ || !WPACKET_memcpy(pkt, hdr->token, hdr->token_len))
+ return 0;
+ }
+
+ if (!WPACKET_quic_write_vlint(pkt, hdr->len + hdr->pn_len)
+ || !WPACKET_get_total_written(pkt, &off_pn)
+ || !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
+ return 0;
+ }
+
+ if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+ return 0;
+
+ off_sample = off_pn + 4;
+ if (!WPACKET_get_total_written(pkt, &off_sample_end))
+ return 0;
+
+ if (ptrs != NULL) {
+ ptrs->raw_start = (unsigned char *)pkt->buf->data + off_start;
+ ptrs->raw_sample = (unsigned char *)pkt->buf->data + off_sample;
+ ptrs->raw_sample_len = off_sample_end - off_sample;
+ ptrs->raw_pn = (unsigned char *)pkt->buf->data + off_pn;
+ }
+
+ return 1;
+}
+
+int ossl_quic_wire_get_pkt_hdr_dst_conn_id(const unsigned char *buf,
+ size_t buf_len,
+ size_t short_conn_id_len,
+ QUIC_CONN_ID *dst_conn_id)
+{
+ unsigned char b0;
+ size_t blen;
+
+ if (buf_len < QUIC_MIN_VALID_PKT_LEN
+ || short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+
+ b0 = buf[0];
+ if ((b0 & 0x80) != 0) {
+ /*
+ * Long header. We need 6 bytes (initial byte, 4 version bytes, DCID
+ * length byte to begin with). This is covered by the buf_len test
+ * above.
+ */
+
+ /*
+ * If the version field is non-zero (meaning that this is not a Version
+ * Negotiation packet), the fixed bit must be set.
+ */
+ if ((buf[1] || buf[2] || buf[3] || buf[4]) && (b0 & 0x40) == 0)
+ return 0;
+
+ blen = (size_t)buf[5]; /* DCID Length */
+ if (blen > QUIC_MAX_CONN_ID_LEN
+ || buf_len < QUIC_MIN_VALID_PKT_LEN + blen)
+ return 0;
+
+ dst_conn_id->id_len = (unsigned char)blen;
+ memcpy(dst_conn_id->id, buf + 6, blen);
+ return 1;
+ } else {
+ /* Short header. */
+ if ((b0 & 0x40) == 0)
+ /* Fixed bit not set, not a valid QUIC packet header. */
+ return 0;
+
+ if (buf_len < QUIC_MIN_VALID_PKT_LEN_CRYPTO + short_conn_id_len)
+ return 0;
+
+ dst_conn_id->id_len = short_conn_id_len;
+ memcpy(dst_conn_id->id, buf + 1, short_conn_id_len);
+ return 1;
+ }
+}
+
+int ossl_quic_wire_decode_pkt_hdr_pn(const unsigned char *enc_pn,
+ size_t enc_pn_len,
+ QUIC_PN largest_pn,
+ QUIC_PN *res_pn)
+{
+ int64_t expected_pn, truncated_pn, candidate_pn, pn_win, pn_hwin, pn_mask;
+
+ switch (enc_pn_len) {
+ case 1:
+ truncated_pn = enc_pn[0];
+ break;
+ case 2:
+ truncated_pn = ((QUIC_PN)enc_pn[0] << 8)
+ | (QUIC_PN)enc_pn[1];
+ break;
+ case 3:
+ truncated_pn = ((QUIC_PN)enc_pn[0] << 16)
+ | ((QUIC_PN)enc_pn[1] << 8)
+ | (QUIC_PN)enc_pn[2];
+ break;
+ case 4:
+ truncated_pn = ((QUIC_PN)enc_pn[0] << 24)
+ | ((QUIC_PN)enc_pn[1] << 16)
+ | ((QUIC_PN)enc_pn[2] << 8)
+ | (QUIC_PN)enc_pn[3];
+ break;
+ default:
+ return 0;
+ }
+
+ /* Implemented as per RFC 9000 Section A.3. */
+ expected_pn = largest_pn + 1;
+ pn_win = ((int64_t)1) << (enc_pn_len * 8);
+ pn_hwin = pn_win / 2;
+ pn_mask = pn_win - 1;
+ candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
+ if (candidate_pn <= expected_pn - pn_hwin
+ && candidate_pn < (((int64_t)1) << 62) - pn_win)
+ *res_pn = candidate_pn + pn_win;
+ else if (candidate_pn > expected_pn + pn_hwin
+ && candidate_pn >= pn_win)
+ *res_pn = candidate_pn - pn_win;
+ else
+ *res_pn = candidate_pn;
+ return 1;
+}
+
+/* From RFC 9000 Section A.2. Simplified implementation. */
+int ossl_quic_wire_determine_pn_len(QUIC_PN pn,
+ QUIC_PN largest_acked)
+{
+ uint64_t num_unacked
+ = (largest_acked == QUIC_PN_INVALID) ? pn + 1 : pn - largest_acked;
+
+ /*
+ * num_unacked \in [ 0, 2** 7] -> 1 byte
+ * num_unacked \in (2** 7, 2**15] -> 2 bytes
+ * num_unacked \in (2**15, 2**23] -> 3 bytes
+ * num_unacked \in (2**23, ] -> 4 bytes
+ */
+
+ if (num_unacked <= (1U<<7)) return 1;
+ if (num_unacked <= (1U<<15)) return 2;
+ if (num_unacked <= (1U<<23)) return 3;
+ return 4;
+}
+
+int ossl_quic_wire_encode_pkt_hdr_pn(QUIC_PN pn,
+ unsigned char *enc_pn,
+ size_t enc_pn_len)
+{
+ switch (enc_pn_len) {
+ case 1:
+ enc_pn[0] = (unsigned char)pn;
+ break;
+ case 2:
+ enc_pn[1] = (unsigned char)pn;
+ enc_pn[0] = (unsigned char)(pn >> 8);
+ break;
+ case 3:
+ enc_pn[2] = (unsigned char)pn;
+ enc_pn[1] = (unsigned char)(pn >> 8);
+ enc_pn[0] = (unsigned char)(pn >> 16);
+ break;
+ case 4:
+ enc_pn[3] = (unsigned char)pn;
+ enc_pn[2] = (unsigned char)(pn >> 8);
+ enc_pn[1] = (unsigned char)(pn >> 16);
+ enc_pn[0] = (unsigned char)(pn >> 24);
+ break;
+ default:
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 38b2c7e970..fec587ee8a 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2688,11 +2688,19 @@ __owur size_t tls13_final_finish_mac(SSL_CONNECTION *s, const char *str, size_t
unsigned char *p);
__owur int tls13_change_cipher_state(SSL_CONNECTION *s, int which);
__owur int tls13_update_key(SSL_CONNECTION *s, int send);
-__owur int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
+__owur int tls13_hkdf_expand(SSL_CONNECTION *s,
+ const EVP_MD *md,
const unsigned char *secret,
const unsigned char *label, size_t labellen,
const unsigned char *data, size_t datalen,
unsigned char *out, size_t outlen, int fatal);
+__owur int tls13_hkdf_expand_ex(OSSL_LIB_CTX *libctx, const char *propq,
+ const EVP_MD *md,
+ const unsigned char *secret,
+ const unsigned char *label, size_t labellen,
+ const unsigned char *data, size_t datalen,
+ unsigned char *out, size_t outlen,
+ int raise_error);
__owur int tls13_derive_key(SSL_CONNECTION *s, const EVP_MD *md,
const unsigned char *secret, unsigned char *key,
size_t keylen);
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c
index 0af8ad2918..0d0c0a14e5 100644
--- a/ssl/tls13_enc.c
+++ b/ssl/tls13_enc.c
@@ -30,16 +30,16 @@ static const unsigned char label_prefix[] = "tls13 ";
* secret |outlen| bytes long and store it in the location pointed to be |out|.
* The |data| value may be zero length. Any errors will be treated as fatal if
* |fatal| is set. Returns 1 on success 0 on failure.
+ * If |raise_error| is set, ERR_raise is called on failure.
*/
-int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
- const unsigned char *secret,
- const unsigned char *label, size_t labellen,
- const unsigned char *data, size_t datalen,
- unsigned char *out, size_t outlen, int fatal)
+int tls13_hkdf_expand_ex(OSSL_LIB_CTX *libctx, const char *propq,
+ const EVP_MD *md,
+ const unsigned char *secret,
+ const unsigned char *label, size_t labellen,
+ const unsigned char *data, size_t datalen,
+ unsigned char *out, size_t outlen, int raise_error)
{
- SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
- EVP_KDF *kdf = EVP_KDF_fetch(sctx->libctx, OSSL_KDF_NAME_TLS1_3_KDF,
- sctx->propq);
+ EVP_KDF *kdf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_TLS1_3_KDF, propq);
EVP_KDF_CTX *kctx;
OSSL_PARAM params[7], *p = params;
int mode = EVP_PKEY_HKDEF_MODE_EXPAND_ONLY;
@@ -53,24 +53,20 @@ int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
return 0;
if (labellen > TLS13_MAX_LABEL_LEN) {
- if (fatal) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- } else {
+ if (raise_error)
/*
* Probably we have been called from SSL_export_keying_material(),
* or SSL_export_keying_material_early().
*/
ERR_raise(ERR_LIB_SSL, SSL_R_TLS_ILLEGAL_EXPORTER_LABEL);
- }
+
EVP_KDF_CTX_free(kctx);
return 0;
}
if ((ret = EVP_MD_get_size(md)) <= 0) {
EVP_KDF_CTX_free(kctx);
- if (fatal)
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- else
+ if (raise_error)
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return 0;
}
@@ -96,15 +92,31 @@ int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
EVP_KDF_CTX_free(kctx);
if (ret != 0) {
- if (fatal)
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- else
+ if (raise_error)
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
}
return ret == 0;
}
+int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
+ const unsigned char *secret,
+ const unsigned char *label, size_t labellen,
+ const unsigned char *data, size_t datalen,
+ unsigned char *out, size_t outlen, int fatal)
+{
+ int ret;
+ SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
+
+ ret = tls13_hkdf_expand_ex(sctx->libctx, sctx->propq, md,
+ secret, label, labellen, data, datalen,
+ out, outlen, !fatal);
+ if (ret == 0 && fatal)
+ SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+
+ return ret;
+}
+
/*
* Given a |secret| generate a |key| of length |keylen| bytes. Returns 1 on
* success 0 on failure.