summaryrefslogtreecommitdiffstats
path: root/include
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 /include
parentfc2be2d07acc0cfe954320c2491b8c5461cbef09 (diff)
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 'include')
-rw-r--r--include/internal/bio_addr.h29
-rw-r--r--include/internal/quic_demux.h251
-rw-r--r--include/internal/quic_record.h311
-rw-r--r--include/internal/quic_record_util.h62
-rw-r--r--include/internal/quic_types.h47
-rw-r--r--include/internal/quic_wire.h8
-rw-r--r--include/internal/quic_wire_pkt.h435
7 files changed, 1136 insertions, 7 deletions
diff --git a/include/internal/bio_addr.h b/include/internal/bio_addr.h
new file mode 100644
index 0000000000..a6449b7eb0
--- /dev/null
+++ b/include/internal/bio_addr.h
@@ -0,0 +1,29 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_BIO_ADDR_H
+# define OSSL_BIO_ADDR_H
+
+# include "internal/e_os.h"
+# include "internal/sockets.h"
+
+# ifndef OPENSSL_NO_SOCK
+union bio_addr_st {
+ struct sockaddr sa;
+# if OPENSSL_USE_IPV6
+ struct sockaddr_in6 s_in6;
+# endif
+ struct sockaddr_in s_in;
+# ifndef OPENSSL_NO_UNIX_SOCK
+ struct sockaddr_un s_un;
+# endif
+};
+# endif
+
+#endif
diff --git a/include/internal/quic_demux.h b/include/internal/quic_demux.h
new file mode 100644
index 0000000000..7d4b0df67e
--- /dev/null
+++ b/include/internal/quic_demux.h
@@ -0,0 +1,251 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_QUIC_DEMUX_H
+# define OSSL_QUIC_DEMUX_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_types.h"
+# include "internal/bio_addr.h"
+
+/*
+ * QUIC Demuxer
+ * ============
+ *
+ * The QUIC connection demuxer is the entity responsible for receiving datagrams
+ * from the network via a datagram BIO. It parses packet headers to determine
+ * each packet's destination connection ID (DCID) and hands off processing of
+ * the packet to the correct QUIC Record Layer (QRL)'s RX side.
+ *
+ * A QRL is instantiated per QUIC connection and contains the cryptographic
+ * resources needed to decrypt QUIC packets for that connection. Received
+ * datagrams are passed from the demuxer to the QRL via a callback registered
+ * for a specific DCID by the QRL; thus the demuxer has no specific knowledge of
+ * the QRL and is not coupled to it.
+ *
+ * A connection may have multiple connection IDs associated with it; a QRL
+ * handles this simply by registering multiple connection IDs with the demuxer
+ * via multiple register calls.
+ *
+ * URX Queue
+ * ---------
+ *
+ * Since the demuxer must handle the initial reception of datagrams from the OS,
+ * RX queue management for new, unprocessed datagrams is also handled by the
+ * demuxer.
+ *
+ * The demuxer maintains a queue of Unprocessed RX Entries (URXEs), which store
+ * unprocessed (i.e., encrypted, unvalidated) data received from the network.
+ * The URXE queue is designed to allow multiple datagrams to be received in a
+ * single call to BIO_recvmmsg, where supported.
+ *
+ * One URXE is used per received datagram. Each datagram may contain multiple
+ * packets, however, this is not the demuxer's concern. QUIC prohibits different
+ * packets in the same datagram from containing different DCIDs; the demuxer
+ * only considers the DCID of the first packet in a datagram when deciding how
+ * to route a received datagram, and it is the responsibility of the QRL to
+ * enforce this rule. Packets other than the first packet in a datagram are not
+ * examined by the demuxer, and the demuxer does not perform validation of
+ * packet headers other than to the minimum extent necessary to extract the
+ * DCID; further parsing and validation of packet headers is the responsibility
+ * of the QRL.
+ *
+ * Rather than defining an opaque interface, the URXE structure internals
+ * are exposed. Since the demuxer is only exposed to other parts of the QUIC
+ * implementation internals, this poses no problem, and has a number of
+ * advantages:
+ *
+ * - Fields in the URXE can be allocated to support requirements in other
+ * components, like the QRL, which would otherwise have to allocate extra
+ * memory corresponding to each URXE.
+ *
+ * - Other components, like the QRL, can keep the URXE in queues of its own
+ * when it is not being managed by the demuxer.
+ *
+ * URX Queue Structure
+ * -------------------
+ *
+ * The URXE queue is maintained as a simple doubly-linked list. URXE entries are
+ * moved between different lists in their lifecycle (for example, from a free
+ * list to a pending list and vice versa). The buffer into which datagrams are
+ * received immediately follows this URXE header structure and is part of the
+ * same allocation.
+ */
+
+typedef struct quic_urxe_st QUIC_URXE;
+
+/* Maximum number of packets we allow to exist in one datagram. */
+#define QUIC_MAX_PKT_PER_URXE (sizeof(uint64_t) * 8)
+
+struct quic_urxe_st {
+ QUIC_URXE *prev, *next;
+
+ /*
+ * The URXE data starts after this structure so we don't need a pointer.
+ * data_len stores the current length (i.e., the length of the received
+ * datagram) and alloc_len stores the allocation length. The URXE will be
+ * reallocated if we need a larger allocation than is available, though this
+ * should not be common as we will have a good idea of worst-case MTUs up
+ * front.
+ */
+ size_t data_len, alloc_len;
+
+ /*
+ * Bitfields per packet. processed indicates the packet has been processed
+ * and must not be processed again, hpr_removed indicates header protection
+ * has already been removed. Used by QRL only; not used by the demuxer.
+ */
+ uint64_t processed, hpr_removed;
+
+ /*
+ * Address of peer we received the datagram from, and the local interface
+ * address we received it on. If local address support is not enabled, local
+ * is zeroed.
+ */
+ BIO_ADDR peer, local;
+};
+
+/* Accessors for URXE buffer. */
+static ossl_unused ossl_inline unsigned char *
+ossl_quic_urxe_data(const QUIC_URXE *e)
+{
+ return (unsigned char *)&e[1];
+}
+
+static ossl_unused ossl_inline unsigned char *
+ossl_quic_urxe_data_end(const QUIC_URXE *e)
+{
+ return ossl_quic_urxe_data(e) + e->data_len;
+}
+
+/* List structure tracking a queue of URXEs. */
+typedef struct quic_urxe_list_st {
+ QUIC_URXE *head, *tail;
+} QUIC_URXE_LIST;
+
+/*
+ * List management helpers. These are used by the demuxer but can also be used
+ * by users of the demuxer to manage URXEs.
+ */
+void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e);
+void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e);
+void ossl_quic_urxe_insert_tail(QUIC_URXE_LIST *l, QUIC_URXE *e);
+
+/* Opaque type representing a demuxer. */
+typedef struct quic_demux_st QUIC_DEMUX;
+
+/*
+ * Called when a datagram is received for a given connection ID.
+ *
+ * e is a URXE containing the datagram payload. It is permissible for the callee
+ * to mutate this buffer; once the demuxer calls this callback, it will never
+ * read the buffer again.
+ *
+ * The callee must arrange for ossl_quic_demux_release_urxe to be called on the URXE
+ * at some point in the future (this need not be before the callback returns).
+ *
+ * At the time the callback is made, the URXE will not be in any queue,
+ * therefore the callee can use the prev and next fields as it wishes.
+ */
+typedef void (ossl_quic_demux_cb_fn)(QUIC_URXE *e, void *arg);
+
+/*
+ * Creates a new demuxer. The given BIO is used to receive datagrams from the
+ * network using BIO_recvmmsg. short_conn_id_len is the length of destination
+ * connection IDs used in RX'd packets; it must have the same value for all
+ * connections used on a socket. default_urxe_alloc_len is the buffer size to
+ * receive datagrams into; it should be a value large enough to contain any
+ * received datagram according to local MTUs, etc.
+ */
+QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
+ size_t short_conn_id_len,
+ size_t default_urxe_alloc_len);
+
+/*
+ * Destroy a demuxer. All URXEs must have been released back to the demuxer
+ * before calling this. No-op if demux is NULL.
+ */
+void ossl_quic_demux_free(QUIC_DEMUX *demux);
+
+/*
+ * Register a datagram handler callback for a connection ID.
+ *
+ * ossl_quic_demux_pump will call the specified function if it receives a datagram
+ * the first packet of which has the specified destination connection ID.
+ *
+ * It is assumed all packets in a datagram have the same destination connection
+ * ID (as QUIC mandates this), but it is the user's responsibility to check for
+ * this and reject subsequent packets in a datagram that violate this rule.
+ *
+ * dst_conn_id is a destination connection ID; it is copied and need not remain
+ * valid after this function returns.
+ *
+ * cb_arg is passed to cb when it is called. For information on the callback,
+ * see its typedef above.
+ *
+ * Only one handler can be set for a given connection ID. If a handler is
+ * already set for the given connection ID, returns 0.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_register(QUIC_DEMUX *demux,
+ const QUIC_CONN_ID *dst_conn_id,
+ ossl_quic_demux_cb_fn *cb,
+ void *cb_arg);
+
+/*
+ * Unregisters any datagram handler callback set for the given connection ID.
+ * Fails if no handler is registered for the given connection ID.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_unregister(QUIC_DEMUX *demux,
+ const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Unregisters any datagram handler callback from all connection IDs it is used
+ * for. cb and cb_arg must both match the values passed to
+ * ossl_quic_demux_register.
+ */
+void ossl_quic_demux_unregister_by_cb(QUIC_DEMUX *demux,
+ ossl_quic_demux_cb_fn *cb,
+ void *cb_arg);
+
+/*
+ * Releases a URXE back to the demuxer. No reference must be made to the URXE or
+ * its buffer after calling this function. The URXE must not be in any queue;
+ * that is, its prev and next pointers must be NULL.
+ */
+void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
+ QUIC_URXE *e);
+
+/*
+ * Process any unprocessed RX'd datagrams, by calling registered callbacks by
+ * connection ID, reading more datagrams from the BIO if necessary.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_pump(QUIC_DEMUX *demux);
+
+/*
+ * Artificially inject a packet into the demuxer for testing purposes. The
+ * buffer must not exceed the URXE size being used by the demuxer.
+ *
+ * If peer or local are NULL, their respective fields are zeroed in the injected
+ * URXE.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_inject(QUIC_DEMUX *demux,
+ const unsigned char *buf,
+ size_t buf_len,
+ const BIO_ADDR *peer,
+ const BIO_ADDR *local);
+
+#endif
diff --git a/include/internal/quic_record.h b/include/internal/quic_record.h
new file mode 100644
index 0000000000..06284c251b
--- /dev/null
+++ b/include/internal/quic_record.h
@@ -0,0 +1,311 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_QUIC_RECORD_H
+# define OSSL_QUIC_RECORD_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_wire_pkt.h"
+# include "internal/quic_types.h"
+# include "internal/quic_record_util.h"
+# include "internal/quic_demux.h"
+
+/*
+ * QUIC Record Layer
+ * =================
+ */
+typedef struct ossl_qrl_st OSSL_QRL;
+
+typedef struct ossl_qrl_args_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;
+
+ /* Initial reference PN used for RX. */
+ QUIC_PN rx_init_largest_pn[QUIC_PN_SPACE_NUM];
+} OSSL_QRL_ARGS;
+
+/* Instantiates a new QRL. */
+OSSL_QRL *ossl_qrl_new(const OSSL_QRL_ARGS *args);
+
+/*
+ * Frees the QRL. All packets obtained using ossl_qrl_read_pkt must already
+ * have been released by calling ossl_qrl_release_pkt.
+ *
+ * You do not need to call ossl_qrl_remove_dst_conn_id first; this function will
+ * unregister the QRL from the demuxer for all registered destination connection
+ * IDs (DCIDs) automatically.
+ */
+void ossl_qrl_free(OSSL_QRL *qrl);
+
+/*
+ * DCID Management
+ * ===============
+ */
+
+/*
+ * Adds a given DCID to the QRL. The QRL will register the DCID with the demuxer
+ * so that incoming packets with that DCID are passed to the given QRL. Multiple
+ * DCIDs may be associated with a QRL at any one time. You will need to add at
+ * least one DCID after instantiating the QRL. A zero-length DCID is a valid
+ * input to this function. This function fails if the DCID is already
+ * registered.
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_add_dst_conn_id(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Remove a DCID previously registered with ossl_qrl_add_dst_conn_id. The DCID
+ * is unregistered from the demuxer. Fails if the DCID is not registered with
+ * the demuxer.
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_remove_dst_conn_id(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Secret Management
+ * =================
+ *
+ * A QRL has several encryption levels (Initial, Handshake, 0-RTT, 1-RTT) and
+ * two directions (RX, TX). At any given time, key material is managed for each
+ * (EL, RX/TX) combination.
+ *
+ * Broadly, for a given (EL, RX/TX), the following state machine is applicable:
+ *
+ * WAITING_FOR_KEYS --[Provide]--> HAVE_KEYS --[Discard]--> | DISCARDED |
+ * \-------------------------------------[Discard]--> | |
+ *
+ * To transition the RX side of an EL from WAITING_FOR_KEYS to HAVE_KEYS, call
+ * ossl_qrl_provide_rx_secret (or for the INITIAL EL,
+ * ossl_qrl_provide_rx_secret_initial).
+ *
+ * Once keys have been provisioned for an EL, you call
+ * ossl_qrl_discard_enc_level to transition the EL to the DISCARDED state. You
+ * can also call this function to transition directly to the DISCARDED state
+ * even before any keys have been provisioned for that EL.
+ *
+ * The DISCARDED state is terminal for a given EL; you cannot provide a secret
+ * again for that EL after reaching it.
+ *
+ * Incoming packets cannot be processed and decrypted if they target an EL
+ * not in the HAVE_KEYS state. However, there is a distinction between
+ * the WAITING_FOR_KEYS and DISCARDED states:
+ *
+ * - In the WAITING_FOR_KEYS state, the QRL assumes keys for the given
+ * EL will eventually arrive. Therefore, if it receives any packet
+ * for an EL in this state, it buffers it and tries to process it
+ * again once the EL reaches HAVE_KEYS.
+ *
+ * - In the DISCARDED state, the QRL assumes no keys for the given
+ * EL will ever arrive again. If it receives any packet for an EL
+ * in this state, it is simply discarded.
+ *
+ * If the user wishes to instantiate a new QRL to replace an old one for
+ * whatever reason, for example to take over for an already established QUIC
+ * connection, it is important that all ELs no longer being used (i.e., INITIAL,
+ * 0-RTT, 1-RTT) are transitioned to the DISCARDED state. Otherwise, the QRL
+ * will assume that keys for these ELs will arrive in future, and will buffer
+ * any received packets for those ELs perpetually. This can be done by calling
+ * ossl_qrl_discard_enc_level for all non-1-RTT ELs immediately after
+ * instantiating the QRL.
+ *
+ * The INITIAL EL is not setup automatically when the QRL is instantiated. This
+ * allows the caller to instead discard it immediately after instantiation of
+ * the QRL if it is not needed, for example if the QRL is being instantiated to
+ * take over handling of an existing connection which has already passed the
+ * INITIAL phase. This avoids the unnecessary derivation of INITIAL keys where
+ * they are not needed. In the ordinary case, ossl_qrl_provide_rx_secret_initial
+ * should be called immediately after instantiation.
+ */
+
+/*
+ * A QUIC client sends its first INITIAL packet with a random DCID, which is
+ * used to compute the secret used for INITIAL packet encryption. This function
+ * must be called to provide the DCID used for INITIAL packet secret computation
+ * before the QRL can process any INITIAL response packets.
+ *
+ * It is possible to use the QRL without ever calling this, for example if there
+ * is no desire to handle INITIAL packets (e.g. if the QRL is instantiated to
+ * succeed a previous QRL and handle a connection which is already established.)
+ * However, in this case you should make sure you call
+ * ossl_qrl_discard_enc_level (see above).
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_provide_rx_secret_initial(OSSL_QRL *qrl,
+ const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Provides a secret to the QRL, which arises due to an encryption level change.
+ * enc_level is a QUIC_ENC_LEVEL_* value. This function cannot be used to
+ * initialise the INITIAL encryption level; see
+ * ossl_qrl_provide_rx_secret_initial instead.
+ *
+ * You should seek to call this function for a given EL before packets of that
+ * EL arrive and are processed by the QRL. However, if packets have already
+ * arrived for a given EL, the QRL will defer processing of them and perform
+ * processing of them when this function is eventually called for the EL in
+ * question.
+ *
+ * suite_id is a QRL_SUITE_* value which determines the AEAD function used for
+ * the QRL.
+ *
+ * The secret passed is used directly to derive the "quic key", "quic iv" and
+ * "quic hp" values.
+ *
+ * secret_len is the length of the secret buffer in bytes. The buffer must be
+ * sized correctly to the chosen suite, else the function fails.
+ *
+ * This function can only be called once for a given EL. Subsequent calls fail,
+ * as do calls made after a corresponding call to ossl_qrl_discard_enc_level for
+ * that EL. The secret for a EL cannot be changed after it is set because QUIC
+ * has no facility for introducing additional key material after an EL is setup.
+ * QUIC key updates are managed automatically by the QRL and do not require user
+ * intervention.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+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);
+
+/*
+ * Informs the QRL that it can now discard key material for a given EL. The QRL
+ * will no longer be able to process incoming packets received at that
+ * encryption level. This function is idempotent and succeeds if the EL has
+ * already been discarded.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_qrl_discard_enc_level(OSSL_QRL *qrl, uint32_t enc_level);
+
+/*
+ * Packet Reception
+ * ================
+ */
+
+/* Information about a received packet. */
+typedef struct ossl_qrl_rx_pkt_st {
+ /* Opaque handle to be passed to ossl_qrl_release_pkt. */
+ void *handle;
+
+ /*
+ * Points to a logical representation of the decoded QUIC packet header. The
+ * data and len fields point to the decrypted QUIC payload (i.e., to a
+ * sequence of zero or more (potentially malformed) frames to be decoded).
+ */
+ QUIC_PKT_HDR *hdr;
+
+ /*
+ * Address the packet was received from. If this is not available for this
+ * packet, this field is NULL (but this can only occur for manually injected
+ * packets).
+ */
+ const BIO_ADDR *peer;
+
+ /*
+ * Local address the packet was sent to. If this is not available for this
+ * packet, this field is NULL.
+ */
+ const BIO_ADDR *local;
+
+ /*
+ * This is the length of the datagram which contained this packet. Note that
+ * the datagram may have contained other packets than this. The intended use
+ * for this is so that the user can enforce minimum datagram sizes (e.g. for
+ * datagrams containing INITIAL packets), as required by RFC 9000.
+ */
+ size_t datagram_len;
+} OSSL_QRL_RX_PKT;
+
+/*
+ * Tries to read a new decrypted packet from the QRL.
+ *
+ * On success, all fields of *pkt are filled and 1 is returned.
+ * Else, returns 0.
+ *
+ * The resources referenced by pkt->hdr, pkt->data and pkt->peer will remain
+ * allocated at least until the user frees them by calling ossl_qrl_release_pkt,
+ * which must be called once you are done with the packet.
+ */
+int ossl_qrl_read_pkt(OSSL_QRL *qrl, OSSL_QRL_RX_PKT *pkt);
+
+/*
+ * Release the resources pointed to by an OSSL_QRL_RX_PKT returned by
+ * ossl_qrl_read_pkt. Pass the opaque value pkt->handle returned in the
+ * structure.
+ */
+void ossl_qrl_release_pkt(OSSL_QRL *qrl, void *handle);
+
+/*
+ * Returns 1 if there are any already processed (i.e. decrypted) packets waiting
+ * to be read from the QRL.
+ */
+int ossl_qrl_processed_read_pending(OSSL_QRL *qrl);
+
+/*
+ * Returns 1 if there arre any unprocessed (i.e. not yet decrypted) packets
+ * waiting to be processed by the QRL. These may or may not result in
+ * successfully decrypted packets once processed. This indicates whether
+ * unprocessed data is buffered by the QRL, not whether any data is available in
+ * a kernel socket buffer.
+ */
+int ossl_qrl_unprocessed_read_pending(OSSL_QRL *qrl);
+
+/*
+ * Returns the number of UDP payload bytes received from the network so far
+ * since the last time this counter was cleared. If clear is 1, clears the
+ * counter and returns the old value.
+ *
+ * The intended use of this is to allow callers to determine how much credit to
+ * add to their anti-amplification budgets. This is reported separately instead
+ * of in the OSSL_QRL_RX_PKT structure so that a caller can apply
+ * anti-amplification credit as soon as a datagram is received, before it has
+ * necessarily read all processed packets contained within that datagram from
+ * the QRL.
+ */
+uint64_t ossl_qrl_get_bytes_received(OSSL_QRL *qrl, int clear);
+
+/*
+ * Sets a callback which is called when a packet is received and being
+ * validated before being queued in the read queue. This is called before packet
+ * body decryption. pn_space is a QUIC_PN_SPACE_* value denoting which PN space
+ * the PN belongs to.
+ *
+ * If this callback returns 1, processing continues normally.
+ * If this callback returns 0, the packet is discarded.
+ *
+ * Other packets in the same datagram will still be processed where possible.
+ *
+ * The intended use for this function is to allow early validation of whether
+ * a PN is a potential duplicate before spending CPU time decrypting the
+ * packet payload.
+ *
+ * The callback is optional and can be unset by passing NULL for cb.
+ * cb_arg is an opaque value passed to cb.
+ */
+typedef int (ossl_qrl_early_rx_validation_cb)(QUIC_PN pn, int pn_space,
+ void *arg);
+
+int ossl_qrl_set_early_rx_validation_cb(OSSL_QRL *qrl,
+ ossl_qrl_early_rx_validation_cb *cb,
+ void *cb_arg);
+
+#endif
diff --git a/include/internal/quic_record_util.h b/include/internal/quic_record_util.h
new file mode 100644
index 0000000000..cc103505c4
--- /dev/null
+++ b/include/internal/quic_record_util.h
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_QUIC_RECORD_UTIL_H
+# define OSSL_QUIC_RECORD_UTIL_H
+
+# include <openssl/ssl.h>
+
+/*
+ * QUIC Key Derivation Utilities
+ * =============================
+ */
+
+/* HKDF-Extract(salt, IKM) (RFC 5869) */
+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);
+
+/*
+ * QUIC Record Layer Ciphersuite Info
+ * ==================================
+ */
+
+/* Available QUIC Record Layer (QRL) ciphersuites. */
+# define QRL_SUITE_AES128GCM 1 /* SHA256 */
+# define QRL_SUITE_AES256GCM 2 /* SHA384 */
+# define QRL_SUITE_CHACHA20POLY1305 3 /* SHA256 */
+
+/* Returns cipher name in bytes or NULL if suite ID is invalid. */
+const char *ossl_qrl_get_suite_cipher_name(uint32_t suite_id);
+
+/* Returns hash function name in bytes or NULL if suite ID is invalid. */
+const char *ossl_qrl_get_suite_md_name(uint32_t suite_id);
+
+/* Returns secret length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_secret_len(uint32_t suite_id);
+
+/* Returns key length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_key_len(uint32_t suite_id);
+
+/* Returns IV length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_iv_len(uint32_t suite_id);
+
+/* Returns AEAD auth tag length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id);
+
+/* Returns a QUIC_HDR_PROT_CIPHER_* value or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_hdr_prot_cipher_id(uint32_t suite_id);
+
+/* Returns header protection key length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_hdr_prot_key_len(uint32_t suite_id);
+
+#endif
diff --git a/include/internal/quic_types.h b/include/internal/quic_types.h
index bd37019d21..b8b60c5caf 100644
--- a/include/internal/quic_types.h
+++ b/include/internal/quic_types.h
@@ -11,6 +11,38 @@
# define OSSL_QUIC_TYPES_H
# include <openssl/ssl.h>
+# include <assert.h>
+# include <string.h>
+
+/* QUIC encryption levels. */
+#define QUIC_ENC_LEVEL_INITIAL 0
+#define QUIC_ENC_LEVEL_HANDSHAKE 1
+#define QUIC_ENC_LEVEL_0RTT 2
+#define QUIC_ENC_LEVEL_1RTT 3
+#define QUIC_ENC_LEVEL_NUM 4
+
+/* QUIC packet number spaces. */
+#define QUIC_PN_SPACE_INITIAL 0
+#define QUIC_PN_SPACE_HANDSHAKE 1
+#define QUIC_PN_SPACE_APP 2
+#define QUIC_PN_SPACE_NUM 3
+
+static ossl_unused ossl_inline uint32_t
+ossl_quic_enc_level_to_pn_space(uint32_t enc_level)
+{
+ switch (enc_level) {
+ case QUIC_ENC_LEVEL_INITIAL:
+ return QUIC_PN_SPACE_INITIAL;
+ case QUIC_ENC_LEVEL_HANDSHAKE:
+ return QUIC_PN_SPACE_HANDSHAKE;
+ case QUIC_ENC_LEVEL_0RTT:
+ case QUIC_ENC_LEVEL_1RTT:
+ return QUIC_PN_SPACE_APP;
+ default:
+ assert(0);
+ return QUIC_PN_SPACE_APP;
+ }
+}
/* QUIC packet number spaces. */
#define QUIC_PN_SPACE_INITIAL 0
@@ -32,4 +64,19 @@ static ossl_unused ossl_inline QUIC_PN ossl_quic_pn_min(QUIC_PN a, QUIC_PN b)
return a < b ? a : b;
}
+/* QUIC connection ID representation. */
+#define QUIC_MAX_CONN_ID_LEN 20
+
+typedef struct quic_conn_id_st {
+ unsigned char id_len, id[QUIC_MAX_CONN_ID_LEN];
+} QUIC_CONN_ID;
+
+static ossl_unused ossl_inline int ossl_quic_conn_id_eq(const QUIC_CONN_ID *a,
+ const QUIC_CONN_ID *b)
+{
+ if (a->id_len != b->id_len || a->id_len > QUIC_MAX_CONN_ID_LEN)
+ return 0;
+ return memcmp(a->id, b->id, a->id_len) == 0;
+}
+
#endif
diff --git a/include/internal/quic_wire.h b/include/internal/quic_wire.h
index 1ead6fcf99..704684b0b4 100644
--- a/include/internal/quic_wire.h
+++ b/include/internal/quic_wire.h
@@ -166,16 +166,10 @@ typedef struct ossl_quic_frame_stop_sending_st {
} OSSL_QUIC_FRAME_STOP_SENDING;
/* QUIC Frame: NEW_CONNECTION_ID */
-#define OSSL_QUIC_MAX_CONN_ID_LEN 20
-typedef struct ossl_quic_conn_id_st {
- unsigned char id_len; /* length of id in bytes */
- unsigned char id[OSSL_QUIC_MAX_CONN_ID_LEN];
-} OSSL_QUIC_CONN_ID;
-
typedef struct ossl_quic_frame_new_conn_id_st {
uint64_t seq_num;
uint64_t retire_prior_to;
- OSSL_QUIC_CONN_ID conn_id;
+ QUIC_CONN_ID conn_id;
unsigned char stateless_reset_token[16];
} OSSL_QUIC_FRAME_NEW_CONN_ID;
diff --git a/include/internal/quic_wire_pkt.h b/include/internal/quic_wire_pkt.h
new file mode 100644
index 0000000000..614593b920
--- /dev/null
+++ b/include/internal/quic_wire_pkt.h
@@ -0,0 +1,435 @@
+/*
+ * 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
+ */
+
+#ifndef OSSL_QUIC_WIRE_PKT_H
+# define OSSL_QUIC_WIRE_PKT_H
+
+# include <openssl/ssl.h>
+# include "internal/packet.h"
+# include "internal/quic_types.h"
+
+# define QUIC_VERSION_NONE ((uint32_t)0) /* Used for version negotiation */
+# define QUIC_VERSION_1 ((uint32_t)1) /* QUIC v1 */
+
+/* QUIC logical packet type. These do not match wire values. */
+# define QUIC_PKT_TYPE_INITIAL 1
+# define QUIC_PKT_TYPE_0RTT 2
+# define QUIC_PKT_TYPE_HANDSHAKE 3
+# define QUIC_PKT_TYPE_RETRY 4
+# define QUIC_PKT_TYPE_1RTT 5
+# define QUIC_PKT_TYPE_VERSION_NEG 6
+
+/*
+ * Smallest possible QUIC packet size as per RFC (aside from version negotiation
+ * packets).
+ */
+#define QUIC_MIN_VALID_PKT_LEN_CRYPTO 21
+#define QUIC_MIN_VALID_PKT_LEN_VERSION_NEG 7
+#define QUIC_MIN_VALID_PKT_LEN QUIC_MIN_VALID_PKT_LEN_VERSION_NEG
+
+typedef struct quic_pkt_hdr_ptrs_st QUIC_PKT_HDR_PTRS;
+
+/*
+ * QUIC Packet Header Protection
+ * =============================
+ *
+ * Functions to apply and remove QUIC packet header protection. A header
+ * protector is initialised using ossl_quic_hdr_protector_init and must be
+ * destroyed using ossl_quic_hdr_protector_destroy when no longer needed.
+ */
+typedef struct quic_hdr_protector_st {
+ OSSL_LIB_CTX *libctx;
+ const char *propq;
+ EVP_CIPHER_CTX *cipher_ctx;
+ EVP_CIPHER *cipher;
+ uint32_t cipher_id;
+} QUIC_HDR_PROTECTOR;
+
+# define QUIC_HDR_PROT_CIPHER_AES_128 1
+# define QUIC_HDR_PROT_CIPHER_AES_256 2
+# define QUIC_HDR_PROT_CIPHER_CHACHA 3
+
+/*
+ * Initialises a header protector.
+ *
+ * cipher_id:
+ * The header protection cipher method to use. One of
+ * QUIC_HDR_PROT_CIPHER_*. Must be chosen based on negotiated TLS cipher
+ * suite.
+ *
+ * quic_hp_key:
+ * This must be the "quic hp" key derived from a traffic secret.
+ *
+ * The length of the quic_hp_key must correspond to that expected for the
+ * given cipher ID.
+ *
+ * The header protector performs amortisable initialisation in this function,
+ * therefore a header protector should be used for as long as possible.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+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);
+
+/*
+ * Destroys a header protector. This is also safe to call on a zero-initialized
+ * OSSL_QUIC_HDR_PROTECTOR structure which has not been initialized, or which
+ * has already been destroyed.
+ */
+void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr);
+
+/*
+ * Removes header protection from a packet. The packet payload must currently be
+ * encrypted (i.e., you must remove header protection before decrypting packets
+ * received). The function examines the header buffer to determine which bytes
+ * of the header need to be decrypted.
+ *
+ * If this function fails, no data is modified.
+ *
+ * This is implemented as a call to ossl_quic_hdr_protector_decrypt_fields().
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_decrypt(QUIC_HDR_PROTECTOR *hpr,
+ QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Applies header protection to a packet. The packet payload must already have
+ * been encrypted (i.e., you must apply header protection after encrypting
+ * a packet). The function examines the header buffer to determine which bytes
+ * of the header need to be encrypted.
+ *
+ * This is implemented as a call to ossl_quic_hdr_protector_encrypt_fields().
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_encrypt(QUIC_HDR_PROTECTOR *hpr,
+ QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Removes header protection from a packet. The packet payload must currently
+ * be encrypted. This is a low-level function which assumes you have already
+ * determined which parts of the packet header need to be decrypted