diff options
author | Hugo Landau <hlandau@openssl.org> | 2022-07-22 13:08:38 +0100 |
---|---|---|
committer | Tomas Mraz <tomas@openssl.org> | 2022-09-02 10:03:55 +0200 |
commit | ec279ac21105a85d9f11eed984eb64405811425d (patch) | |
tree | f793d4635eece923228d2a9d91aaaa91d134f612 /include | |
parent | fc2be2d07acc0cfe954320c2491b8c5461cbef09 (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.h | 29 | ||||
-rw-r--r-- | include/internal/quic_demux.h | 251 | ||||
-rw-r--r-- | include/internal/quic_record.h | 311 | ||||
-rw-r--r-- | include/internal/quic_record_util.h | 62 | ||||
-rw-r--r-- | include/internal/quic_types.h | 47 | ||||
-rw-r--r-- | include/internal/quic_wire.h | 8 | ||||
-rw-r--r-- | include/internal/quic_wire_pkt.h | 435 |
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 |