- Adds an RX time field to the OSSL_QRX_PKT structure.
- Adds a timekeeping argument to ossl_demux_new which is used to determine
packet reception time.
- Adds a decoded PN field to the OSSL_QRX_PKT structure.
This has to be decoded by the QRX anyway, and its omission was an oversight.
- Key update support for the TX side.
- Minor refactoring.
Reviewed-by: Paul Dale <pauli@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18949)
# include <openssl/ssl.h>
# include "internal/quic_types.h"
# include "internal/bio_addr.h"
+# include "internal/time.h"
/*
* QUIC Demuxer
* is zeroed.
*/
BIO_ADDR peer, local;
+
+ /*
+ * Time at which datagram was received (or ossl_time_zero()) if a now
+ * function was not provided).
+ */
+ OSSL_TIME time;
};
/* Accessors for URXE buffer. */
* 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.
+ *
+ * now is an optional function used to determine the time a datagram was
+ * received. now_arg is an opaque argument passed to the function. If now is
+ * NULL, ossl_time_zero() is used as the datagram reception time.
*/
QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
size_t short_conn_id_len,
- size_t default_urxe_alloc_len);
+ size_t default_urxe_alloc_len,
+ OSSL_TIME (*now)(void *arg),
+ void *now_arg);
/*
* Destroy a demuxer. All URXEs must have been released back to the demuxer
/* Initial reference PN used for RX. */
QUIC_PN init_largest_pn[QUIC_PN_SPACE_NUM];
+
+ /* Initial key phase. For debugging use only; always 0 in real use. */
+ unsigned char init_key_phase_bit;
} OSSL_QRX_ARGS;
/* Instantiates a new QRX. */
*
* To transition the RX side of an EL from WAITING_FOR_KEYS to HAVE_KEYS, call
* ossl_qrx_provide_secret (for the INITIAL EL, use of
- * ossl_qrl_provide_initial_secret is recommended).
+ * ossl_quic_provide_initial_secret is recommended).
*
* Once keys have been provisioned for an EL, you call
* ossl_qrx_discard_enc_level to transition the EL to the DISCARDED state. You
* the QRX if it is not needed, for example if the QRX 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_qrx_provide_secret_initial
+ * they are not needed. In the ordinary case, ossl_quic_provide_initial_secret
* should be called immediately after instantiation.
*/
/*
* Provides a secret to the QRX, which arises due to an encryption level change.
* enc_level is a QUIC_ENC_LEVEL_* value. To initialise the INITIAL encryption
- * level, it is recommended to use ossl_qrl_provide_initial_secret instead.
+ * level, it is recommended to use ossl_quic_provide_initial_secret instead.
*
* You should seek to call this function for a given EL before packets of that
* EL arrive and are processed by the QRX. However, if packets have already
* processing of them when this function is eventually called for the EL in
* question.
*
- * suite_id is a QRX_SUITE_* value which determines the AEAD function used for
+ * suite_id is a QRL_SUITE_* value which determines the AEAD function used for
* the QRX.
*
* The secret passed is used directly to derive the "quic key", "quic iv" and
* datagrams containing INITIAL packets), as required by RFC 9000.
*/
size_t datagram_len;
+
+ /* The PN which was decoded for the packet, if the packet has a PN field. */
+ QUIC_PN pn;
+
+ /*
+ * Time the packet was received, or ossl_time_zero() if the demuxer is not
+ * using a now() function.
+ */
+ OSSL_TIME time;
} OSSL_QRX_PKT;
/*
* 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_qrx_release_pkt,
- * which must be called once you are done with the packet.
+ * The resources referenced by pkt->hdr, pkt->hdr->data and pkt->peer will
+ * remain allocated at least until the user frees them by calling
+ * ossl_qrx_release_pkt, which must be called once you are done with the packet.
*/
int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt);
* Two keys and a timer
*
* "Alternatively, endpoints can retain only two sets of packet protection
- * neys, swapping previous keys for next after enough time has passed to
+ * keys, swapping previous keys for next after enough time has passed to
* allow for reordering in the network. In this case, the KP bit alone can
* be used to select keys."
*
* PROVISIONED
* _______________________________
* | |
- * UNPROVISIONED --|----> NORMAL <----------\ |------> DROPPED
+ * UNPROVISIONED --|----> NORMAL <----------\ |------> DISCARDED
* | | | |
* | | | |
* | v | |
- * | UPDATE_CONFIRMED | |
+ * | UPDATING | |
* | | | |
* | | | |
* | v | |
* recorded. When a flipped Key Phase bit is detected, the RX attempts to
* decrypt and authenticate the received packet with the 'next' keys rather than
* the 'current' keys. If (and only if) this authentication is successful, we
- * move to the UPDATE_CONFIRMED state. (An attacker in the network could flip
+ * move to the UPDATING state. (An attacker in the network could flip
* the Key Phase bit randomly, so it is essential we do nothing until AEAD
* authentication is complete.)
*
- * In the UPDATE_CONFIRMED state, we know a key update is occurring and record
+ * In the UPDATING state, we know a key update is occurring and record
* the new Key Phase bit value as the newly current value, but we still keep the
* old keys around so that we can still process any packets which were still in
- * flight when the key update was initiated. In the UPDATE_CONFIRMED state, a
+ * flight when the key update was initiated. In the UPDATING state, a
* Key Phase bit value different to the current expected value is treated not as
-* the initiation of another key update, but a reference to our old keys.
+ * the initiation of another key update, but a reference to our old keys.
*
* Eventually we will be reasonably sure we are not going to receive any more
* packets with the old keys. At this point, we can transition to the COOLDOWN
* as a request for a Key Update, but this request is ignored and the packet is
* treated as malformed. We do this to allow mitigation against malicious peers
* trying to initiate an excessive number of Key Updates. The timeout for the
- * transition from UPDATE_CONFIRMED to COOLDOWN is recommended as adequate for
+ * transition from UPDATING to COOLDOWN is recommended as adequate for
* this purpose in itself by the RFC, so the normal additional timeout value for
* the transition from COOLDOWN to normal is zero (immediate transition).
*
* A summary of each state:
*
- * Exp KP Uses Keys KS0 KS1 If Non-Expected KP Bit
- * ------ --------- ------ ----- ----------------------
- * NORMAL 0 Keyset 0 Gen 0 Gen 1 → UPDATE_CONFIRMED
- * UPDATE_CONFIRMED 1 Keyset 1 Gen 0 Gen 1 Use Keyset 0
- * COOLDOWN 1 Keyset 1 Erased Gen 1 Ignore Packet
+ * Epoch Exp KP Uses Keys KS0 KS1 If Non-Expected KP Bit
+ * ----- ------ --------- ------ ----- ----------------------
+ * NORMAL 0 0 Keyset 0 Gen 0 Gen 1 → UPDATING
+ * UPDATING 1 1 Keyset 1 Gen 0 Gen 1 Use Keyset 0
+ * COOLDOWN 1 1 Keyset 1 Erased Gen 1 Ignore Packet (*)
+ *
+ * NORMAL 1 1 Keyset 1 Gen 2 Gen 1 → UPDATING
+ * UPDATING 2 0 Keyset 0 Gen 2 Gen 1 Use Keyset 1
+ * COOLDOWN 2 0 Keyset 0 Gen 2 Erased Ignore Packet (*)
*
- * NORMAL 1 Keyset 1 Gen 2 Gen 1 → UPDATE_CONFIRMED
- * UPDATE_CONFIRMED 0 Keyset 0 Gen 2 Gen 1 Use Keyset 1
- * COOLDOWN 0 Keyset 0 Gen 2 Erased Ignore Packet
+ * (*) Actually implemented by attempting to decrypt the packet with the
+ * wrong keys (which ultimately has the same outcome), as recommended
+ * by RFC 9001 to avoid creating timing channels.
*
* Note that the key material for the next key generation ("key epoch") is
* always kept in the NORMAL state (necessary to avoid side-channel attacks).
* and making the necessary calls to the TX side by detecting changes to the
* return value of ossl_qrx_get_key_epoch().
*
- * The above states (NORMAL, UPDATE_CONFIRMED, COOLDOWN) can themselves be
+ * The above states (NORMAL, UPDATING, COOLDOWN) can themselves be
* considered substates of the PROVISIONED state. Providing a secret to the QRX
* for an EL transitions from UNPROVISIONED, the initial state, to PROVISIONED
* (NORMAL). Dropping key material for an EL transitions from whatever the
- * current substate of the PROVISIONED state is to the DROPPED state, which is
+ * current substate of the PROVISIONED state is to the DISCARDED state, which is
* the terminal state.
*
* Note that non-1RTT ELs cannot undergo key update, therefore a non-1RT EL is
*/
/*
- * Return the current RX key epoch. This is initially zero and is incremented by
- * one for every Key Update successfully signalled by the peer.
+ * Return the current RX key epoch for the 1-RTT encryption level. This is
+ * initially zero and is incremented by one for every Key Update successfully
+ * signalled by the peer. If the 1-RTT EL has not yet been provisioned or has
+ * been discarded, returns UINT64_MAX.
*
* A necessary implication of this API is that the least significant bit of the
* returned value corresponds to the currently expected Key Phase bit, though
* and use it to initiate a key update on the TX side.
*
* The value returned by this function increments specifically at the transition
- * from the NORMAL to the UPDATE_CONFIRMED state discussed above.
+ * from the NORMAL to the UPDATING state discussed above.
*/
uint64_t ossl_qrx_get_key_epoch(OSSL_QRX *qrx);
/*
- * The caller should call this after the UPDATE_CONFIRMED state is reached,
- * after a timeout to be determined by the caller.
+ * Sets an optional callback which will be called when the key epoch changes.
+ *
+ * The callback is optional and can be unset by passing NULL for cb.
+ * cb_arg is an opaque value passed to cb.
+*/
+typedef void (ossl_qrx_key_update_cb)(void *arg);
+
+int ossl_qrx_set_key_update_cb(OSSL_QRX *qrx,
+ ossl_qrx_key_update_cb *cb, void *cb_arg);
+
+/*
+ * Relates to the 1-RTT encryption level. The caller should call this after the
+ * UPDATING state is reached, after a timeout to be determined by the caller.
*
- * This transitions from the UPDATE_CONFIRMED state to the COOLDOWN state (if
- * still in the UPDATE_CONFIRMED state). If normal is 1, then transitions from
+ * This transitions from the UPDATING state to the COOLDOWN state (if
+ * still in the UPDATING state). If normal is 1, then transitions from
* the COOLDOWN state to the NORMAL state. Both transitions can be performed at
* once if desired.
*
* If in the normal state, or if in the COOLDOWN state and normal is 0, this is
- * a no-op and returns 1.
+ * a no-op and returns 1. Returns 0 if the 1-RTT EL has not been provisioned or
+ * has been dropped.
*
* It is essential that the caller call this within a few PTO intervals of a key
* update occurring (as detected by the caller in a call to
/*
* Returns the number of seemingly forged packets which have been received by
* the QRX. If this value reaches the value returned by
- * ossl_qrx_get_max_epoch_forged_pkt_count(), all further received encrypted
- * packets will be discarded without processing; thus, callers should trigger a
- * key update on the TX side (which will cause the peer to trigger a key update
- * on our RX side) well before this occurs.
+ * ossl_qrx_get_max_epoch_forged_pkt_count() for a given EL, all further
+ * received encrypted packets for that EL will be discarded without processing.
+ *
+ * Note that the forged packet limit is for the connection lifetime, thus it is
+ * not reset by a key update. It is suggested that the caller terminate the
+ * connection a reasonable margin before the limit is reached. However, the
+ * exact limit imposed does vary by EL due to the possibility that different ELs
+ * use different AEADs.
*/
-uint64_t ossl_qrx_get_cur_epoch_forged_pkt_count(OSSL_QRX *qrx,
- uint32_t enc_level);
+uint64_t ossl_qrx_get_cur_forged_pkt_count(OSSL_QRX *qrx);
/*
- * Returns the maximum number of forged packets which the record layer
- * will permit to be verified using the current set of RX keys.
+ * Returns the maximum number of forged packets which the record layer will
+ * permit to be verified using this QRX instance.
*/
-uint64_t ossl_qrx_get_max_epoch_forged_pkt_count(OSSL_QRX *qrx,
- uint32_t enc_level);
+uint64_t ossl_qrx_get_max_forged_pkt_count(OSSL_QRX *qrx,
+ uint32_t enc_level);
#endif
* encoded packet should be by setting pkt->hdr->pn_len. This function takes
* care of the PN encoding. Set pkt->pn to the desired PN.
*
+ * Note that 1-RTT packets do not have a DCID Length field, therefore the DCID
+ * length must be understood contextually. This function assumes the caller
+ * knows what it is doing and will serialize a DCID of whatever length is given.
+ * It is the caller's responsibility to ensure it uses a consistent DCID length
+ * for communication with any given set of remote peers.
+ *
* The packet is queued regardless of whether it is able to be sent immediately.
+ * This enables packets to be batched and sent at once on systems which support
+ * system calls to send multiple datagrams in a single system call (see
+ * BIO_sendmmsg). To flush queued datagrams to the network, see
+ * ossl_qtx_flush_net().
+ *
* Returns 1 on success or 0 on failure.
*/
int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt);
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;
+ 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;
}
}
}
}
+/* Determine if a packet type contains an encrypted payload. */
+static ossl_inline ossl_unused int
+ossl_quic_pkt_type_is_encrypted(uint32_t pkt_type)
+{
+ switch (pkt_type) {
+ case QUIC_PKT_TYPE_RETRY:
+ case QUIC_PKT_TYPE_VERSION_NEG:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+/* Determine if a packet type contains a PN field. */
+static ossl_inline ossl_unused int
+ossl_quic_pkt_type_has_pn(uint32_t pkt_type)
+{
+ /*
+ * Currently a packet has a PN iff it is encrypted. This could change
+ * someday.
+ */
+ return ossl_quic_pkt_type_is_encrypted(pkt_type);
+}
+
+/*
+ * Determine if a packet type can appear with other packets in a datagram. Some
+ * packet types must be the sole packet in a datagram.
+ */
+static ossl_inline ossl_unused int
+ossl_quic_pkt_type_can_share_dgram(uint32_t pkt_type)
+{
+ /*
+ * Currently only the encrypted packet types can share a datagram. This
+ * could change someday.
+ */
+ return ossl_quic_pkt_type_is_encrypted(pkt_type);
+}
+
+/*
+ * Determine if the packet type must come at the end of the datagram (due to the
+ * lack of a length field).
+ */
+static ossl_inline ossl_unused int
+ossl_quic_pkt_type_must_be_last(uint32_t pkt_type)
+{
+ /*
+ * Any packet type which cannot share a datagram obviously must come last.
+ * 1-RTT also must come last as it lacks a length field.
+ */
+ return !ossl_quic_pkt_type_can_share_dgram(pkt_type)
+ || pkt_type == QUIC_PKT_TYPE_1RTT;
+}
+
/*
* Smallest possible QUIC packet size as per RFC (aside from version negotiation
* packets).
*
* 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.
+ * destroyed using ossl_quic_hdr_protector_cleanup when no longer needed.
*/
typedef struct quic_hdr_protector_st {
OSSL_LIB_CTX *libctx;
* 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);
+void ossl_quic_hdr_protector_cleanup(QUIC_HDR_PROTECTOR *hpr);
/*
* Removes header protection from a packet. The packet payload must currently be
/* [L] Version field. Valid if (type != 1RTT). */
uint32_t version;
- /* [ALL] Number of bytes in the connection ID (max 20). Always valid. */
+ /* [ALL] The destination connection ID. Always valid. */
QUIC_CONN_ID dst_conn_id;
/*
- * [L] Number of bytes in the connection ID (max 20).
+ * [L] The source connection ID.
* Valid if (type != 1RTT).
*/
QUIC_CONN_ID src_conn_id;
size_t token_len;
/*
- * [i0h] Payload length in bytes.
+ * [ALL] Payload length in bytes.
*
- * Valid if (type != 1RTT && type != RETRY && version).
+ * Though 1-RTT, Retry and Version Negotiation packets do not contain an
+ * explicit length field, this field is always valid and is used by the
+ * packet header encoding and decoding routines to describe the payload
+ * length, regardless of whether the packet type encoded or decoded uses an
+ * explicit length indication.
*/
size_t len;
#include "internal/common.h"
#include <openssl/lhash.h>
-#define OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL 32
+#define 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));
+ assert((e->prev != NULL || l->head == e)
+ && (e->next != NULL || l->tail == e));
if (e->prev != NULL)
e->prev->next = e->next;
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);
+ assert(e->prev == NULL && e->next == NULL);
if (l->head == NULL) {
l->head = l->tail = 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);
+ assert(e->prev == NULL && e->next == NULL);
if (l->tail == NULL) {
l->head = l->tail = e;
/* Default URXE buffer size in bytes. */
size_t default_urxe_alloc_len;
+ /* Time retrieval callback. */
+ OSSL_TIME (*now)(void *arg);
+ void *now_arg;
+
/* Hashtable mapping connection IDs to QUIC_DEMUX_CONN structures. */
LHASH_OF(QUIC_DEMUX_CONN) *conns_by_id;
QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
size_t short_conn_id_len,
- size_t default_urxe_alloc_len)
+ size_t default_urxe_alloc_len,
+ OSSL_TIME (*now)(void *arg),
+ void *now_arg)
{
QUIC_DEMUX *demux;
demux->net_bio = net_bio;
demux->short_conn_id_len = short_conn_id_len;
demux->default_urxe_alloc_len = default_urxe_alloc_len;
+ demux->now = now;
+ demux->now_arg = now_arg;
demux->conns_by_id
= lh_QUIC_DEMUX_CONN_new(demux_conn_hash, demux_conn_cmp);
QUIC_DEMUX_CONN key;
if (dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
- return 0;
+ return NULL;
key.dst_conn_id = *dst_conn_id;
return lh_QUIC_DEMUX_CONN_retrieve(demux->conns_by_id, &key);
*/
static int demux_recv(QUIC_DEMUX *demux)
{
- BIO_MSG msg[OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL];
- ossl_ssize_t rd, i;
+ BIO_MSG msg[DEMUX_MAX_MSGS_PER_CALL];
+ size_t rd, i;
QUIC_URXE *urxe = demux->urx_free.head, *unext;
+ OSSL_TIME now;
/* This should never be called when we have any pending URXE. */
assert(demux->urx_pending.head == NULL);
BIO_ADDR_clear(&urxe->local);
}
- rd = BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0);
- if (rd <= 0)
+ if (!BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0, &rd))
return 0;
+ now = demux->now != NULL ? demux->now(demux->now_arg) : ossl_time_zero();
+
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;
+ /* Time we received datagram. */
+ urxe->time = now;
/* Move from free list to pending list. */
ossl_quic_urxe_remove(&demux->urx_free, urxe);
--demux->num_urx_free;
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);
+ if (!ossl_assert(e == demux->urx_pending.head))
+ return 0;
conn = demux_identify_conn(demux, e);
if (conn == NULL) {
int ret;
if (demux->urx_pending.head == NULL) {
- ret = demux_ensure_free_urxe(demux, OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL);
+ ret = demux_ensure_free_urxe(demux, DEMUX_MAX_MSGS_PER_CALL);
if (ret != 1)
return 0;
void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
QUIC_URXE *e)
{
- OPENSSL_assert(e->prev == NULL && e->next == NULL);
+ assert(e->prev == NULL && e->next == NULL);
ossl_quic_urxe_insert_tail(&demux->urx_free, e);
++demux->num_urx_free;
}
/* Addresses copied from URXE. */
BIO_ADDR peer, local;
+ /* Time we received the packet (not when we processed it). */
+ OSSL_TIME time;
+
/* Total length of the datagram which contained this packet. */
size_t datagram_len;
/* Bytes we have received since this counter was last cleared. */
uint64_t bytes_received;
+ /*
+ * Number of forged packets we have received since the QRX was instantiated.
+ * Note that as per RFC 9001, this is connection-level state; it is not per
+ * EL and is not reset by a key update.
+ */
+ uint64_t forged_pkt_count;
+
/* Validation callback. */
- ossl_qrx_early_validation_cb *validation_cb;
- void *validation_cb_arg;
+ ossl_qrx_early_validation_cb *validation_cb;
+ void *validation_cb_arg;
+
+ /* Key update callback. */
+ ossl_qrx_key_update_cb *key_update_cb;
+ void *key_update_cb_arg;
+
+ /* Initial key phase. For debugging use only; always 0 in real use. */
+ unsigned char init_key_phase_bit;
};
static void qrx_on_rx(QUIC_URXE *urxe, void *arg);
qrx->propq = args->propq;
qrx->demux = args->demux;
qrx->short_conn_id_len = args->short_conn_id_len;
+ qrx->init_key_phase_bit = args->init_key_phase_bit;
return qrx;
}
static void qrx_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 qrx_cleanup_urxl(OSSL_QRX *qrx, QUIC_URXE_LIST *l)
{
QUIC_URXE *e, *enext;
+
for (e = l->head; e != NULL; e = enext) {
enext = e->next;
ossl_quic_demux_release_urxe(qrx->demux, e);
}
+
l->head = l->tail = NULL;
}
/* Drop keying material and crypto resources. */
for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i)
- ossl_qrl_enc_level_set_discard(&qrx->el_set, i, 1);
+ ossl_qrl_enc_level_set_discard(&qrx->el_set, i);
OPENSSL_free(qrx);
}
suite_id,
md,
secret,
- secret_len))
+ secret_len,
+ qrx->init_key_phase_bit,
+ /*is_tx=*/0))
return 0;
/*
if (enc_level >= QUIC_ENC_LEVEL_NUM)
return 0;
- ossl_qrl_enc_level_set_discard(&qrx->el_set, enc_level, 1);
+ ossl_qrl_enc_level_set_discard(&qrx->el_set, enc_level);
return 1;
}
*/
rxe2 = OPENSSL_realloc(rxe, sizeof(RXE) + n);
if (rxe2 == NULL)
- /* original RXE is still in tact unchanged */
+ /* original RXE is still intact unchanged */
return NULL;
if (rxe != rxe2) {
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))
+ if (first_rxe != NULL && !ossl_quic_pkt_type_can_share_dgram(rxe->hdr.type))
return 0;
/*
return 1;
}
+/* Retrieves the correct cipher context for an EL and key phase. */
+static size_t qrx_get_cipher_ctx_idx(OSSL_QRX *qrx, OSSL_QRL_ENC_LEVEL *el,
+ uint32_t enc_level,
+ unsigned char key_phase_bit)
+{
+ if (enc_level != QUIC_ENC_LEVEL_1RTT)
+ return 0;
+
+ if (!ossl_assert(key_phase_bit <= 1))
+ return SIZE_MAX;
+
+ /*
+ * RFC 9001 requires that we not create timing channels which could reveal
+ * the decrypted value of the Key Phase bit. We usually handle this by
+ * keeping the cipher contexts for both the current and next key epochs
+ * around, so that we just select a cipher context blindly using the key
+ * phase bit, which is time-invariant.
+ *
+ * In the COOLDOWN state, we only have one keyslot/cipher context. RFC 9001
+ * suggests an implementation strategy to avoid creating a timing channel in
+ * this case:
+ *
+ * Endpoints can use randomized packet protection keys in place of
+ * discarded keys when key updates are not yet permitted.
+ *
+ * Rather than use a randomised key, we simply use our existing key as it
+ * will fail AEAD verification anyway. This avoids the need to keep around a
+ * dedicated garbage key.
+ *
+ * Note: Accessing different cipher contexts is technically not
+ * timing-channel safe due to microarchitectural side channels, but this is
+ * the best we can reasonably do and appears to be directly suggested by the
+ * RFC.
+ */
+ return el->state == QRL_EL_STATE_PROV_COOLDOWN ? el->key_epoch & 1
+ : key_phase_bit;
+}
+
/*
* Tries to decrypt a packet payload.
*
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)
+ QUIC_PN pn, uint32_t enc_level,
+ unsigned char key_phase_bit)
{
int l = 0, l2 = 0;
unsigned char nonce[EVP_MAX_IV_LENGTH];
- size_t nonce_len, i;
+ size_t nonce_len, i, cctx_idx;
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
enc_level, 1);
+ EVP_CIPHER_CTX *cctx;
if (src_len > INT_MAX || aad_len > INT_MAX)
return 0;
if (el->tag_len >= src_len)
return 0;
- /*
+ /*
* If we have failed to authenticate a certain number of ciphertexts, refuse
* to decrypt any more ciphertexts.
*/
- if (el->op_count >= ossl_qrl_get_suite_max_forged_pkt(el->suite_id))
+ if (qrx->forged_pkt_count >= ossl_qrl_get_suite_max_forged_pkt(el->suite_id))
+ return 0;
+
+ cctx_idx = qrx_get_cipher_ctx_idx(qrx, el, enc_level, key_phase_bit);
+ if (!ossl_assert(cctx_idx < OSSL_NELEM(el->cctx)))
return 0;
+ cctx = el->cctx[cctx_idx];
+
/* Construct nonce (nonce=IV ^ PN). */
- nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
+ nonce_len = EVP_CIPHER_CTX_get_iv_length(cctx);
if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
return 0;
- memcpy(nonce, el->iv, nonce_len);
+ memcpy(nonce, el->iv[cctx_idx], 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,
+ if (EVP_CipherInit_ex(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,
+ if (EVP_CIPHER_CTX_ctrl(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)
+ if (EVP_CipherUpdate(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)
+ if (EVP_CipherUpdate(cctx, dst, &l, src, src_len - el->tag_len) != 1)
return 0;
/* Ensure authentication succeeded. */
- if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1) {
+ if (EVP_CipherFinal_ex(cctx, NULL, &l2) != 1) {
/* Authentication failed, increment failed auth counter. */
- ++el->op_count;
+ ++qrx->forged_pkt_count;
return 0;
}
/* No-op. */
}
+static void qrx_key_update_initiated(OSSL_QRX *qrx)
+{
+ if (!ossl_qrl_enc_level_set_key_update(&qrx->el_set, QUIC_ENC_LEVEL_1RTT))
+ return;
+
+ if (qrx->key_update_cb != NULL)
+ qrx->key_update_cb(qrx->key_update_cb_arg);
+}
+
/* Process a single packet in a datagram. */
static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
PACKET *pkt, size_t pkt_idx,
char need_second_decode = 0, already_processed = 0;
QUIC_PKT_HDR_PTRS ptrs;
uint32_t pn_space, enc_level;
+ OSSL_QRL_ENC_LEVEL *el = NULL;
/*
* Get a free RXE. If we need to allocate a new one, use the packet length
*/
need_second_decode = !pkt_is_marked(&urxe->hpr_removed, pkt_idx);
if (!ossl_quic_wire_decode_pkt_hdr(pkt,
- qrx->short_conn_id_len,
- need_second_decode, &rxe->hdr, &ptrs))
+ qrx->short_conn_id_len,
+ need_second_decode, &rxe->hdr, &ptrs))
goto malformed;
/*
/*
* Make a note of the first RXE so we can later ensure the destination
- * connection IDs of all packets in a datagram mater.
+ * connection IDs of all packets in a datagram match.
*/
if (pkt_idx == 0)
*first_rxe = rxe;
*/
if (already_processed
|| !qrx_validate_hdr_early(qrx, rxe, pkt_idx == 0 ? NULL : *first_rxe))
+ /*
+ * Already processed packets are handled identically to malformed
+ * packets; i.e., they are ignored.
+ */
goto malformed;
- if (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
- || rxe->hdr.type == QUIC_PKT_TYPE_RETRY) {
+ if (!ossl_quic_pkt_type_is_encrypted(rxe->hdr.type)) {
/*
* Version negotiation and retry packets are a special case. They do not
* contain a payload which needs decrypting and have no header
memcpy(rxe_data(rxe), rxe->hdr.data, rxe->hdr.len);
pkt_mark(&urxe->processed, pkt_idx);
- rxe->hdr.data = rxe_data(rxe);
+ rxe->hdr.data = rxe_data(rxe);
+ rxe->pn = QUIC_PN_INVALID;
/* Move RXE to pending. */
rxe_remove(&qrx->rx_free, rxe);
/* Now remove header protection. */
*pkt = orig_pkt;
- if (need_second_decode) {
- OSSL_QRL_ENC_LEVEL *el
- = ossl_qrl_enc_level_set_get(&qrx->el_set, enc_level, 1);
+ el = ossl_qrl_enc_level_set_get(&qrx->el_set, enc_level, 1);
+ assert(el != NULL); /* Already checked above */
- assert(el != NULL); /* Already checked above */
+ if (need_second_decode) {
if (!ossl_quic_hdr_protector_decrypt(&el->hpr, &ptrs))
goto malformed;
* HANDSHAKE packet.
*/
if (enc_level == QUIC_ENC_LEVEL_HANDSHAKE)
- ossl_qrl_enc_level_set_discard(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL, 1);
+ ossl_qrl_enc_level_set_discard(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL);
/*
* The AAD data is the entire (unprotected) packet header including the PN.
*/
dst = (unsigned char *)rxe_data(rxe) + i;
if (!qrx_decrypt_pkt_body(qrx, dst, rxe->hdr.data, rxe->hdr.len,
- &dec_len, sop, aad_len, rxe->pn, enc_level))
+ &dec_len, sop, aad_len, rxe->pn, enc_level,
+ rxe->hdr.key_phase))
goto malformed;
+ /*
+ * At this point, we have successfully authenticated the AEAD tag and no
+ * longer need to worry about exposing the Key Phase bit in timing channels.
+ * Check for a Key Phase bit differing from our expectation.
+ */
+ if (rxe->hdr.type == QUIC_PKT_TYPE_1RTT
+ && rxe->hdr.key_phase != (el->key_epoch & 1))
+ qrx_key_update_initiated(qrx);
+
/*
* We have now successfully decrypted the packet payload. If there are
* additional packets in the datagram, it is possible we will fail to
if (rxe->pn > qrx->largest_pn[pn_space])
qrx->largest_pn[pn_space] = rxe->pn;
- /* Copy across network addresses from URXE to RXE. */
+ /* Copy across network addresses and RX time from URXE to RXE. */
rxe->peer = urxe->peer;
rxe->local = urxe->local;
+ rxe->time = urxe->time;
/* Move RXE to pending. */
rxe_remove(&qrx->rx_free, rxe);
* 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, qrx_process_pkt will take care of advancing to the end of
+ * length, qrx_process_pkt will take care of advancing to the end of
* the packet, so we will exit the loop automatically in this case.
*/
if (qrx_process_pkt(qrx, e, &pkt, pkt_idx, &first_rxe, data_len))
}
/* Process a single pending URXE. */
-static int qrx_process_one_urxl(OSSL_QRX *qrx, QUIC_URXE *e)
+static int qrx_process_one_urxe(OSSL_QRX *qrx, QUIC_URXE *e)
{
int was_deferred;
}
/* Process any pending URXEs to generate pending RXEs. */
-static int qrx_process_urxl(OSSL_QRX *qrx)
+static int qrx_process_pending_urxl(OSSL_QRX *qrx)
{
QUIC_URXE *e;
while ((e = qrx->urx_pending.head) != NULL)
- if (!qrx_process_one_urxl(qrx, e))
+ if (!qrx_process_one_urxe(qrx, e))
return 0;
return 1;
RXE *rxe;
if (!ossl_qrx_processed_read_pending(qrx)) {
- if (!qrx_process_urxl(qrx))
+ if (!qrx_process_pending_urxl(qrx))
return 0;
if (!ossl_qrx_processed_read_pending(qrx))
pkt->handle = rxe;
pkt->hdr = &rxe->hdr;
+ pkt->pn = rxe->pn;
+ pkt->time = rxe->time;
pkt->peer
= BIO_ADDR_family(&rxe->peer) != AF_UNSPEC ? &rxe->peer : NULL;
pkt->local
return 1;
}
-uint64_t ossl_qrx_get_cur_epoch_forged_pkt_count(OSSL_QRX *qrx,
- uint32_t enc_level)
+int ossl_qrx_set_key_update_cb(OSSL_QRX *qrx,
+ ossl_qrx_key_update_cb *cb,
+ void *cb_arg)
+{
+ qrx->key_update_cb = cb;
+ qrx->key_update_cb_arg = cb_arg;
+ return 1;
+}
+
+uint64_t ossl_qrx_get_key_epoch(OSSL_QRX *qrx)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
- enc_level, 1);
+ QUIC_ENC_LEVEL_1RTT, 1);
- return el == NULL ? UINT64_MAX : el->op_count;
+ return el == NULL ? UINT64_MAX : el->key_epoch;
+}
+
+int ossl_qrx_key_update_timeout(OSSL_QRX *qrx, int normal)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
+ QUIC_ENC_LEVEL_1RTT, 1);
+
+ if (el == NULL)
+ return 0;
+
+ if (el->state == QRL_EL_STATE_PROV_UPDATING
+ && !ossl_qrl_enc_level_set_key_update_done(&qrx->el_set,
+ QUIC_ENC_LEVEL_1RTT))
+ return 0;
+
+ if (normal && el->state == QRL_EL_STATE_PROV_COOLDOWN
+ && !ossl_qrl_enc_level_set_key_cooldown_done(&qrx->el_set,
+ QUIC_ENC_LEVEL_1RTT))
+ return 0;
+
+ return 1;
+}
+
+uint64_t ossl_qrx_get_cur_forged_pkt_count(OSSL_QRX *qrx)
+{
+ return qrx->forged_pkt_count;
}
-uint64_t ossl_qrx_get_max_epoch_forged_pkt_count(OSSL_QRX *qrx,
- uint32_t enc_level)
+uint64_t ossl_qrx_get_max_forged_pkt_count(OSSL_QRX *qrx,
+ uint32_t enc_level)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
enc_level, 1);
static const unsigned char quic_v1_hp_label[] = {
0x71, 0x75, 0x69, 0x63, 0x20, 0x68, 0x70 /* "quic hp" */
};
+static const unsigned char quic_v1_ku_label[] = {
+ 0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x75 /* "quic ku" */
+};
OSSL_QRL_ENC_LEVEL *ossl_qrl_enc_level_set_get(OSSL_QRL_ENC_LEVEL_SET *els,
uint32_t enc_level,
- int require_valid)
+ int require_prov)
{
OSSL_QRL_ENC_LEVEL *el;
el = &els->el[enc_level];
- if (require_valid && (el->cctx == NULL || el->discarded))
- return NULL;
+ if (require_prov)
+ switch (el->state) {
+ case QRL_EL_STATE_PROV_NORMAL:
+ case QRL_EL_STATE_PROV_UPDATING:
+ case QRL_EL_STATE_PROV_COOLDOWN:
+ break;
+ default:
+ return NULL;
+ }
return el;
}
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
- if (el == NULL)
+ switch (el->state) {
+ case QRL_EL_STATE_UNPROV:
+ return 0;
+ case QRL_EL_STATE_PROV_NORMAL:
+ case QRL_EL_STATE_PROV_UPDATING:
+ case QRL_EL_STATE_PROV_COOLDOWN:
+ return 1;
+ default:
+ case QRL_EL_STATE_DISCARDED:
+ return -1;
+ }
+}
+
+int ossl_qrl_enc_level_set_has_keyslot(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level,
+ unsigned char tgt_state,
+ size_t keyslot)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+
+ if (!ossl_assert(el != NULL && keyslot < 2))
return 0;
- if (el->cctx != NULL)
- return 1;
- if (el->discarded)
- return -1;
- return 0;
+
+ switch (tgt_state) {
+ case QRL_EL_STATE_PROV_NORMAL:
+ case QRL_EL_STATE_PROV_UPDATING:
+ return enc_level == QUIC_ENC_LEVEL_1RTT || keyslot == 0;
+ case QRL_EL_STATE_PROV_COOLDOWN:
+ assert(enc_level == QUIC_ENC_LEVEL_1RTT);
+ return keyslot == (el->key_epoch & 1);
+ default:
+ return 0;
+ }
}
-/*
- * 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.
- */
-int ossl_qrl_enc_level_set_provide_secret(OSSL_QRL_ENC_LEVEL_SET *els,
- OSSL_LIB_CTX *libctx,
- const char *propq,
- uint32_t enc_level,
- uint32_t suite_id,
- EVP_MD *md,
- const unsigned char *secret,
- size_t secret_len)
+static void el_teardown_keyslot(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level,
+ size_t keyslot)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
- 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;
+
+ if (!ossl_qrl_enc_level_set_has_keyslot(els, enc_level, el->state, keyslot))
+ return;
+
+ if (el->cctx[keyslot] != NULL) {
+ EVP_CIPHER_CTX_free(el->cctx[keyslot]);
+ el->cctx[keyslot] = NULL;
+ }
+
+ OPENSSL_cleanse(el->iv[keyslot], sizeof(el->iv[keyslot]));
+}
+
+static int el_setup_keyslot(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level,
+ unsigned char tgt_state,
+ size_t keyslot,
+ const unsigned char *secret,
+ size_t secret_len)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+ unsigned char key[EVP_MAX_KEY_LENGTH];
+ size_t key_len = 0, iv_len = 0;
+ const char *cipher_name = NULL;
EVP_CIPHER *cipher = NULL;
EVP_CIPHER_CTX *cctx = NULL;
- int own_md = 0, have_hpr = 0;
- if (el == NULL || el->discarded)
- /* Should not be trying to reinitialise an EL which was discarded. */
+ if (!ossl_assert(el != NULL
+ && ossl_qrl_enc_level_set_has_keyslot(els, enc_level,
+ tgt_state, keyslot)))
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);
+ cipher_name = ossl_qrl_get_suite_cipher_name(el->suite_id);
+ iv_len = ossl_qrl_get_suite_cipher_iv_len(el->suite_id);
+ key_len = ossl_qrl_get_suite_cipher_key_len(el->suite_id);
if (cipher_name == NULL)
return 0;
- if (secret_len != ossl_qrl_get_suite_secret_len(suite_id))
+ if (secret_len != ossl_qrl_get_suite_secret_len(el->suite_id)
+ || secret_len > EVP_MAX_KEY_LENGTH)
return 0;
- if (md == NULL) {
- md_name = ossl_qrl_get_suite_md_name(suite_id);
-
- if ((md = EVP_MD_fetch(libctx, md_name, propq)) == NULL)
- return 0;
-
- own_md = 1;
- }
+ assert(el->cctx[keyslot] == NULL);
/* Derive "quic iv" key. */
- if (!tls13_hkdf_expand_ex(libctx, propq,
- md,
+ if (!tls13_hkdf_expand_ex(el->libctx, el->propq,
+ el->md,
secret,
quic_v1_iv_label,
sizeof(quic_v1_iv_label),
NULL, 0,
- el->iv, iv_len, 0))
+ el->iv[keyslot], iv_len, 0))
goto err;
/* Derive "quic key" key. */
- if (!tls13_hkdf_expand_ex(libctx, propq,
- md,
+ if (!tls13_hkdf_expand_ex(el->libctx, el->propq,
+ el->md,
secret,
quic_v1_key_label,
sizeof(quic_v1_key_label),
key, key_len, 0))
goto err;
+ /* Create and initialise cipher context. */
+ if ((cipher = EVP_CIPHER_fetch(el->libctx, cipher_name, el->propq)) == NULL)
+ goto err;
+
+ if ((cctx = EVP_CIPHER_CTX_new()) == 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;
+
+ /* IV will be changed on RX/TX so we don't need to use a real value here. */
+ if (!EVP_CipherInit_ex(cctx, cipher, NULL, key, el->iv[keyslot], 0))
+ goto err;
+
+ el->cctx[keyslot] = cctx;
+
+ /* Zeroize intermediate keys. */
+ OPENSSL_cleanse(key, sizeof(key));
+ EVP_CIPHER_free(cipher);
+ return 1;
+
+err:
+ EVP_CIPHER_CTX_free(cctx);
+ EVP_CIPHER_free(cipher);
+ OPENSSL_cleanse(el->iv[keyslot], sizeof(el->iv[keyslot]));
+ OPENSSL_cleanse(key, sizeof(key));
+ return 0;
+}
+
+int ossl_qrl_enc_level_set_provide_secret(OSSL_QRL_ENC_LEVEL_SET *els,
+ OSSL_LIB_CTX *libctx,
+ const char *propq,
+ uint32_t enc_level,
+ uint32_t suite_id,
+ EVP_MD *md,
+ const unsigned char *secret,
+ size_t secret_len,
+ unsigned char init_key_phase_bit,
+ int is_tx)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+ unsigned char ku_key[EVP_MAX_KEY_LENGTH], hpr_key[EVP_MAX_KEY_LENGTH];
+ int have_ks0 = 0, have_ks1 = 0, own_md = 0;
+ const char *md_name = ossl_qrl_get_suite_md_name(suite_id);
+ size_t hpr_key_len, init_keyslot;
+
+ if (el == NULL || el->state != QRL_EL_STATE_UNPROV || md_name == NULL
+ || init_key_phase_bit > 1 || is_tx < 0 || is_tx > 1)
+ return 0;
+
+ init_keyslot = is_tx ? 0 : init_key_phase_bit;
+ hpr_key_len = ossl_qrl_get_suite_hdr_prot_key_len(suite_id);
+ if (hpr_key_len == 0)
+ return 0;
+
+ if (md == NULL) {
+ md = EVP_MD_fetch(libctx, md_name, propq);
+ if (md == NULL)
+ return 0;
+
+ own_md = 1;
+ }
+
+ el->libctx = libctx;
+ el->propq = propq;
+ el->md = md;
+ el->suite_id = suite_id;
+ el->tag_len = ossl_qrl_get_suite_cipher_tag_len(suite_id);
+ el->op_count = 0;
+ el->key_epoch = (uint64_t)init_key_phase_bit;
+ el->is_tx = (unsigned char)is_tx;
+
/* Derive "quic hp" key. */
if (!tls13_hkdf_expand_ex(libctx, propq,
md,
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 KS0 (or KS1 if init_key_phase_bit), our initial keyslot. */
+ if (!el_setup_keyslot(els, enc_level, QRL_EL_STATE_PROV_NORMAL,
+ init_keyslot, secret, secret_len))
+ goto err;
+
+ have_ks0 = 1;
+
+ if (enc_level == QUIC_ENC_LEVEL_1RTT) {
+ /* Derive "quic ku" key (the epoch 1 secret). */
+ if (!tls13_hkdf_expand_ex(libctx, propq,
+ md,
+ secret,
+ quic_v1_ku_label,
+ sizeof(quic_v1_ku_label),
+ NULL, 0,
+ is_tx ? el->ku : ku_key, secret_len, 0))
+ goto err;
+
+ if (!is_tx) {
+ /* Setup KS1 (or KS0 if init_key_phase_bit), our next keyslot. */
+ if (!el_setup_keyslot(els, enc_level, QRL_EL_STATE_PROV_NORMAL,
+ !init_keyslot, ku_key, secret_len))
+ goto err;
+
+ have_ks1 = 1;
+
+ /* Derive NEXT "quic ku" key (the epoch 2 secret). */
+ if (!tls13_hkdf_expand_ex(libctx, propq,
+ md,
+ ku_key,
+ quic_v1_ku_label,
+ sizeof(quic_v1_ku_label),
+ NULL, 0,
+ el->ku, secret_len, 0))
+ goto err;
+ }
}
/* Setup header protection context. */
if (!ossl_quic_hdr_protector_init(&el->hpr,
- libctx,
- propq,
+ libctx, propq,
ossl_qrl_get_suite_hdr_prot_cipher_id(suite_id),
- hpr_key,
- hpr_key_len))
+ hpr_key, hpr_key_len))
goto err;
- have_hpr = 1;
+ /*
+ * We are now provisioned: KS0 has our current key (for key epoch 0), KS1
+ * has our next key (for key epoch 1, in the case of the 1-RTT EL only), and
+ * el->ku has the secret which will be used to generate keys for key epoch
+ * 2.
+ */
+ OPENSSL_cleanse(hpr_key, sizeof(hpr_key));
+ OPENSSL_cleanse(ku_key, sizeof(ku_key));
+ el->state = QRL_EL_STATE_PROV_NORMAL;
+ return 1;
- /* Create and initialise cipher context. */
- if ((cipher = EVP_CIPHER_fetch(libctx, cipher_name, propq)) == NULL)
- goto err;
+err:
+ el->suite_id = 0;
+ OPENSSL_cleanse(hpr_key, sizeof(hpr_key));
+ OPENSSL_cleanse(ku_key, sizeof(ku_key));
+ OPENSSL_cleanse(el->ku, sizeof(el->ku));
+ if (have_ks0)
+ el_teardown_keyslot(els, enc_level, 0);
+ if (have_ks1)
+ el_teardown_keyslot(els, enc_level, 1);
+ if (own_md)
+ EVP_MD_free(md);
+ return 0;
+}
- 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;
+int ossl_qrl_enc_level_set_key_update(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+ size_t secret_len;
+ unsigned char new_ku[EVP_MAX_KEY_LENGTH];
- if ((cctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
+ if (el == NULL || !ossl_assert(enc_level == QUIC_ENC_LEVEL_1RTT))
+ return 0;
- /* IV will be changed on RX/TX so we don't need to use a real value here. */
- if (!EVP_CipherInit_ex(cctx, cipher, NULL, key, el->iv, 0))
- goto err;
+ if (el->state != QRL_EL_STATE_PROV_NORMAL)
+ return 0;
- el->suite_id = suite_id;
- el->cctx = cctx;
- el->md = md;
- el->tag_len = ossl_qrl_get_suite_cipher_tag_len(suite_id);
- el->op_count = 0;
+ if (!el->is_tx) {
+ /*
+ * We already have the key for the next epoch, so just move to using it.
+ */
+ ++el->key_epoch;
+ el->state = QRL_EL_STATE_PROV_UPDATING;
+ return 1;
+ }
- /* Zeroize intermediate keys. */
- OPENSSL_cleanse(key, sizeof(key));
- OPENSSL_cleanse(hpr_key, sizeof(hpr_key));
- EVP_CIPHER_free(cipher);
+ /*
+ * TX case. For the TX side we use only keyslot 0; it replaces the old key
+ * immediately.
+ */
+ secret_len = ossl_qrl_get_suite_secret_len(el->suite_id);
+
+ /* Derive NEXT "quic ku" key (the epoch n+1 secret). */
+ if (!tls13_hkdf_expand_ex(el->libctx, el->propq,
+ el->md, el->ku,
+ quic_v1_ku_label,
+ sizeof(quic_v1_ku_label),
+ NULL, 0,
+ new_ku, secret_len, 0))
+ return 0;
+
+ el_teardown_keyslot(els, enc_level, 0);
+
+ /* Setup keyslot for CURRENT "quic ku" key. */
+ if (!el_setup_keyslot(els, enc_level, QRL_EL_STATE_PROV_NORMAL,
+ 0, el->ku, secret_len))
+ return 0;
+
+ ++el->key_epoch;
+ el->op_count = 0;
+ memcpy(el->ku, new_ku, secret_len);
+ /* Remain in PROV_NORMAL state */
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;
+/* Transitions from PROV_UPDATING to PROV_COOLDOWN. */
+int ossl_qrl_enc_level_set_key_update_done(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+
+ if (el == NULL || !ossl_assert(enc_level == QUIC_ENC_LEVEL_1RTT))
+ return 0;
+
+ /* No new key yet, but erase key material to aid PFS. */
+ el_teardown_keyslot(els, enc_level, ~el->key_epoch & 1);
+ el->state = QRL_EL_STATE_PROV_COOLDOWN;
+ return 1;
}
-/* Drops keying material for a given encryption level. */
-void ossl_qrl_enc_level_set_discard(OSSL_QRL_ENC_LEVEL_SET *els,
- uint32_t enc_level, int is_final)
+/*
+ * Transitions from PROV_COOLDOWN to PROV_NORMAL. (If in PROV_UPDATING,
+ * auto-transitions to PROV_COOLDOWN first.)
+ */
+int ossl_qrl_enc_level_set_key_cooldown_done(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+ size_t secret_len;
+ unsigned char new_ku[EVP_MAX_KEY_LENGTH];
- if (el == NULL || el->discarded)
- return;
+ if (el == NULL || !ossl_assert(enc_level == QUIC_ENC_LEVEL_1RTT))
+ return 0;
+
+ if (el->state == QRL_EL_STATE_PROV_UPDATING
+ && !ossl_qrl_enc_level_set_key_update_done(els, enc_level))
+ return 0;
- if (el->cctx != NULL) {
- ossl_quic_hdr_protector_destroy(&el->hpr);
+ if (el->state != QRL_EL_STATE_PROV_COOLDOWN)
+ return 0;
+
+ secret_len = ossl_qrl_get_suite_secret_len(el->suite_id);
- EVP_CIPHER_CTX_free(el->cctx);
- el->cctx = NULL;
+ if (!el_setup_keyslot(els, enc_level, QRL_EL_STATE_PROV_NORMAL,
+ ~el->key_epoch & 1, el->ku, secret_len))
+ return 0;
- EVP_MD_free(el->md);
- el->md = NULL;
+ /* Derive NEXT "quic ku" key (the epoch n+1 secret). */
+ if (!tls13_hkdf_expand_ex(el->libctx, el->propq,
+ el->md,
+ el->ku,
+ quic_v1_ku_label,
+ sizeof(quic_v1_ku_label),
+ NULL, 0,
+ new_ku, secret_len, 0)) {
+ el_teardown_keyslot(els, enc_level, ~el->key_epoch & 1);
+ return 0;
}
- /* Zeroise IV. */
- OPENSSL_cleanse(el->iv, sizeof(el->iv));
+ memcpy(el->ku, new_ku, secret_len);
+ el->state = QRL_EL_STATE_PROV_NORMAL;
+ return 1;
+}
+
+/*
+ * Discards keying material for a given encryption level. Transitions from any
+ * state to DISCARDED.
+ */
+void ossl_qrl_enc_level_set_discard(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level)
+{
+ OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(els, enc_level, 0);
+
+ if (el == NULL || el->state == QRL_EL_STATE_DISCARDED)
+ return;
+
+ if (ossl_qrl_enc_level_set_have_el(els, enc_level) == 1) {
+ ossl_quic_hdr_protector_cleanup(&el->hpr);
+
+ el_teardown_keyslot(els, enc_level, 0);
+ el_teardown_keyslot(els, enc_level, 1);
+ }
- if (is_final)
- el->discarded = 1;
+ EVP_MD_free(el->md);
+ el->md = NULL;
+ el->state = QRL_EL_STATE_DISCARDED;
}
* encryption level, as this functionality is shared between QRX and QTX. For
* QRL use only.
*/
+
+/*
+ * States an EL can be in. The Updating and Cooldown states are used by RX only;
+ * a TX EL in the Provisioned state is always in the Normal substate.
+ *
+ * Key material is available if in the Provisioned state.
+ */
+#define QRL_EL_STATE_UNPROV 0 /* Unprovisioned (initial state) */
+#define QRL_EL_STATE_PROV_NORMAL 1 /* Provisioned - Normal */
+#define QRL_EL_STATE_PROV_UPDATING 2 /* Provisioned - Updating */
+#define QRL_EL_STATE_PROV_COOLDOWN 3 /* Provisioned - Cooldown */
+#define QRL_EL_STATE_DISCARDED 4 /* Discarded (terminal state) */
+
typedef struct ossl_qrl_enc_level_st {
+ /*
+ * Cryptographic context used to apply and remove header protection from
+ * packet headers.
+ */
+ QUIC_HDR_PROTECTOR hpr;
+
/* 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;
+
+ /* Context used for packet body ciphering. One for each keyslot. */
+ EVP_CIPHER_CTX *cctx[2];
+
+ OSSL_LIB_CTX *libctx;
+ const char *propq;
+
+ /*
+ * Key epoch, essentially the number of times we have done a key update.
+ *
+ * The least significant bit of this is therefore by definition the current
+ * Key Phase bit value.
+ */
+ uint64_t key_epoch;
+
+ /* Usage counter. The caller maintains this. Used by TX side only. */
+ uint64_t op_count;
+
/* QRL_SUITE_* value. */
uint32_t suite_id;
+
/* Length of authentication tag. */
uint32_t tag_len;
+
+ /* Current EL state. */
+ unsigned char state; /* QRL_EL_STATE_* */
+
+ /* 1 if for TX, else RX. Initialised when secret provided. */
+ unsigned char is_tx;
+
+ /* IV used to construct nonces used for AEAD packet body ciphering. */
+ unsigned char iv[2][EVP_MAX_IV_LENGTH];
+
/*
- * Cryptographic context used to apply and remove header protection from
- * packet headers.
+ * Secret for next key epoch.
*/
- QUIC_HDR_PROTECTOR hpr;
- /* Usage counter. The caller maintains this. */
- uint64_t op_count;
+ unsigned char ku[EVP_MAX_KEY_LENGTH];
} OSSL_QRL_ENC_LEVEL;
typedef struct ossl_qrl_enc_level_set_st {
} OSSL_QRL_ENC_LEVEL_SET;
/*
- * 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.
+ * Returns 1 if we have key material for a given encryption level (that is, if
+ * we are in the PROVISIONED state), 0 if we do not yet have material (we are in
+ * the UNPROVISIONED state) and -1 if the EL is discarded (we are in the
+ * DISCARDED state).
*/
int ossl_qrl_enc_level_set_have_el(OSSL_QRL_ENC_LEVEL_SET *els,
uint32_t enc_level);
/*
* Returns EL in a set. If enc_level is not a valid QUIC_ENC_LEVEL_* value,
- * returns NULL. If require_valid is 1, returns NULL if the EL is not
- * provisioned or has been discarded; otherwise, the returned EL may be
- * unprovisioned or discarded.
+ * returns NULL. If require_prov is 1, returns NULL if the EL is not in
+ * the PROVISIONED state; otherwise, the returned EL may be in any state.
*/
OSSL_QRL_ENC_LEVEL *ossl_qrl_enc_level_set_get(OSSL_QRL_ENC_LEVEL_SET *els,
uint32_t enc_level,
- int require_valid);
+ int require_prov);
/* Provide secret to an EL. md may be NULL. */
int ossl_qrl_enc_level_set_provide_secret(OSSL_QRL_ENC_LEVEL_SET *els,
uint32_t suite_id,
EVP_MD *md,
const unsigned char *secret,
- size_t secret_len);
+ size_t secret_len,
+ unsigned char init_key_phase_bit,
+ int is_tx);
+
+/*
+ * Returns 1 if the given keyslot index is currently valid for a given EL and EL
+ * state.
+ */
+int ossl_qrl_enc_level_set_has_keyslot(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level,
+ unsigned char tgt_state,
+ size_t keyslot);
+
+/* Perform a key update. Transitions from PROV_NORMAL to PROV_UPDATING. */
+int ossl_qrl_enc_level_set_key_update(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level);
+
+/* Transitions from PROV_UPDATING to PROV_COOLDOWN. */
+int ossl_qrl_enc_level_set_key_update_done(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level);
+
+/*
+ * Transitions from PROV_COOLDOWN to PROV_NORMAL. (If in PROV_UPDATING,
+ * auto-transitions to PROV_COOLDOWN first.)
+ */
+int ossl_qrl_enc_level_set_key_cooldown_done(OSSL_QRL_ENC_LEVEL_SET *els,
+ uint32_t enc_level);
/*
- * Discard an EL. If is_final is non-zero, no secret can be provided for the EL
- * ever again.
+ * Discard an EL. No secret can be provided for the EL ever again.
*/
void ossl_qrl_enc_level_set_discard(OSSL_QRL_ENC_LEVEL_SET *els,
- uint32_t enc_level,
- int is_final);
+ uint32_t enc_level);
#endif
* QTX
* ===
*/
-
-/* (Encryption level, direction)-specific state. */
-typedef struct ossl_qtx_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;
- /* QTX_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_QTX_ENC_LEVEL;
-
struct ossl_qtx_st {
OSSL_LIB_CTX *libctx;
const char *propq;
*/
TXE *cons;
size_t cons_count; /* num packets */
+
+ /*
+ * Number of packets transmitted in this key epoch. Used to enforce AEAD
+ * confidentiality limit.
+ */
+ uint64_t epoch_pkt_count;
};
/* Instantiates a new QTX. */
/* Free TXE queue data. */
qtx_cleanup_txl(&qtx->pending);
qtx_cleanup_txl(&qtx->free);
+ OPENSSL_free(qtx->cons);
/* Drop keying material and crypto resources. */
for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i)
- ossl_qrl_enc_level_set_discard(&qtx->el_set, i, 1);
+ ossl_qrl_enc_level_set_discard(&qtx->el_set, i);
OPENSSL_free(qtx);
}
suite_id,
md,
secret,
- secret_len);
+ secret_len,
+ 0,
+ /*is_tx=*/1);
}
int ossl_qtx_discard_enc_level(OSSL_QTX *qtx, uint32_t enc_level)
if (enc_level >= QUIC_ENC_LEVEL_NUM)
return 0;
- ossl_qrl_enc_level_set_discard(&qtx->el_set, enc_level, 1);
+ ossl_qrl_enc_level_set_discard(&qtx->el_set, enc_level);
return 1;
}
txe->alloc_len - txe->data_len, 0))
return 0;
- if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, pkt->hdr->src_conn_id.id_len,
+ if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, pkt->hdr->dst_conn_id.id_len,
pkt->hdr, ptrs)
|| !WPACKET_get_total_written(&wpkt, &l)) {
WPACKET_finish(&wpkt);
= ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1);
unsigned char nonce[EVP_MAX_IV_LENGTH];
size_t nonce_len, i;
+ EVP_CIPHER_CTX *cctx = NULL;
/* We should not have been called if we do not have key material. */
if (!ossl_assert(el != NULL))
if (el->op_count >= ossl_qrl_get_suite_max_pkt(el->suite_id))
return 0;
+ /*
+ * TX key update is simpler than for RX; once we initiate a key update, we
+ * never need the old keys, as we never deliberately send a packet with old
+ * keys. Thus the EL always uses keyslot 0 for the TX side.
+ */
+ cctx = el->cctx[0];
+ if (!ossl_assert(cctx != NULL))
+ return 0;
+
/* Construct nonce (nonce=IV ^ PN). */
- nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
+ nonce_len = EVP_CIPHER_CTX_get_iv_length(cctx);
if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
return 0;
- memcpy(nonce, el->iv, nonce_len);
+ memcpy(nonce, el->iv[0], 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=*/1) != 1)
+ if (EVP_CipherInit_ex(cctx, NULL, NULL, NULL, nonce, /*enc=*/1) != 1)
return 0;
/* Feed AAD data. */
- if (EVP_CipherUpdate(el->cctx, NULL, &l, hdr, hdr_len) != 1)
+ if (EVP_CipherUpdate(cctx, NULL, &l, hdr, hdr_len) != 1)
return 0;
/* Encrypt plaintext directly into TXE. */
if (src_len == 0)
break;
- if (EVP_CipherUpdate(el->cctx, txe_data(txe) + txe->data_len,
+ if (EVP_CipherUpdate(cctx, txe_data(txe) + txe->data_len,
&l, src, src_len) != 1)
return 0;
}
/* Finalise and get tag. */
- if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1)
+ if (EVP_CipherFinal_ex(cctx, NULL, &l2) != 1)
return 0;
- if (EVP_CIPHER_CTX_ctrl(el->cctx, EVP_CTRL_AEAD_GET_TAG,
+ if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_GET_TAG,
el->tag_len, txe_data(txe) + txe->data_len) != 1)
return 0;
struct iovec_cur cur;
QUIC_PKT_HDR_PTRS ptrs;
unsigned char *hdr_start;
+ OSSL_QRL_ENC_LEVEL *el = NULL;
/*
* Determine if the packet needs encryption and the minimum conceivable
* serialization length.
*/
- if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
- || pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG) {
+ if (!ossl_quic_pkt_type_is_encrypted(pkt->hdr->type)) {
needs_encrypt = 0;
min_len = QUIC_MIN_VALID_PKT_LEN;
} else {
needs_encrypt = 1;
min_len = QUIC_MIN_VALID_PKT_LEN_CRYPTO;
+ el = ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1);
+ if (!ossl_assert(el != NULL)) /* should already have been checked */
+ return 0;
}
orig_data_len = txe->data_len;
/* Determine header length. */
pkt->hdr->data = NULL;
pkt->hdr->len = payload_len;
- pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(pkt->hdr->src_conn_id.id_len,
+ pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(pkt->hdr->dst_conn_id.id_len,
pkt->hdr);
if (pred_hdr_len == 0) {
ret = QTX_FAIL_GENERIC;
}
/* Set some fields in the header we are responsible for. */
- pkt->hdr->key_phase = 0; /* TODO */
- if (!ossl_quic_wire_encode_pkt_hdr_pn(pkt->pn,
- pkt->hdr->pn,
- pkt->hdr->pn_len)) {
- ret = QTX_FAIL_GENERIC;
- goto err;
+ if (pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
+ pkt->hdr->key_phase = (unsigned char)(el->key_epoch & 1);
+
+ if (ossl_quic_pkt_type_has_pn(pkt->hdr->type)) {
+ if (!ossl_quic_wire_encode_pkt_hdr_pn(pkt->pn,
+ pkt->hdr->pn,
+ pkt->hdr->pn_len)) {
+ ret = QTX_FAIL_GENERIC;
+ goto err;
+ }
}
/* Append the header to the TXE. */
enc_level = ossl_quic_pkt_type_to_enc_level(pkt->hdr->type);
/* Some packet types must be in a packet all by themselves. */
- if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
- || pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG)
+ if (!ossl_quic_pkt_type_can_share_dgram(pkt->hdr->type))
ossl_qtx_finish_dgram(qtx);
else if (enc_level >= QUIC_ENC_LEVEL_NUM
- || ossl_qrl_enc_level_set_have_el(&qtx->el_set, enc_level) != 1)
+ || ossl_qrl_enc_level_set_have_el(&qtx->el_set, enc_level) != 1) {
/* All other packet types are encrypted. */
return 0;
+ }
was_coalescing = (qtx->cons != NULL && qtx->cons->data_len > 0);
if (was_coalescing)
/*
* Some packet types cannot have another packet come after them.
*/
- if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
- || pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG
- || pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
+ if (ossl_quic_pkt_type_must_be_last(pkt->hdr->type))
coalescing = 0;
if (!coalescing)
void ossl_qtx_flush_net(OSSL_QTX *qtx)
{
BIO_MSG msg[MAX_MSGS_PER_SEND];
- size_t i;
+ size_t wr, i;
TXE *txe;
- ossl_ssize_t wr;
if (qtx->bio == NULL)
return;
/* Nothing to send. */
return;
- wr = BIO_sendmmsg(qtx->bio, msg, sizeof(BIO_MSG), i, 0);
- if (wr <= 0)
+ if (!BIO_sendmmsg(qtx->bio, msg, sizeof(BIO_MSG), i, 0, &wr) || wr == 0)
/*
* We did not get anything, so further calls will probably not
* succeed either.
/*
* Remove everything which was successfully sent from the pending queue.
*/
- for (i = 0; i < (size_t)wr; ++i)
+ for (i = 0; i < wr; ++i)
qtx_pending_to_free(qtx);
}
}
return qtx->cons_count;
}
+int ossl_qtx_trigger_key_update(OSSL_QTX *qtx)
+{
+ return ossl_qrl_enc_level_set_key_update(&qtx->el_set,
+ QUIC_ENC_LEVEL_1RTT);
+}
+
uint64_t ossl_qtx_get_cur_epoch_pkt_count(OSSL_QTX *qtx, uint32_t enc_level)
{
OSSL_QRL_ENC_LEVEL *el;
return 1;
err:
- ossl_quic_hdr_protector_destroy(hpr);
+ ossl_quic_hdr_protector_cleanup(hpr);
return 0;
}
-void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr)
+void ossl_quic_hdr_protector_cleanup(QUIC_HDR_PROTECTOR *hpr)
{
EVP_CIPHER_CTX_free(hpr->cipher_ctx);
hpr->cipher_ctx = NULL;
hdr->data = PACKET_data(pkt);
/*
- * Skip over payload so we are pointing at the start of the next packet,
- * if any.
+ * Skip over payload. Since this is a short header packet, which cannot
+ * be followed by any other kind of packet, this advances us to the end
+ * of the datagram.
*/
if (!PACKET_forward(pkt, hdr->len))
return 0;
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;
+ 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->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
+ if (ossl_quic_pkt_type_has_pn(hdr->type)
&& (hdr->pn_len < 1 || hdr->pn_len > 4))
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)
+ if (ossl_quic_pkt_type_has_pn(hdr->type))
b0 |= hdr->pn_len - 1;
if (!WPACKET_put_bytes_u8(pkt, b0)
|| 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;
-
len += 1 /* Initial byte */ + 4 /* Version */
+ 1 + hdr->dst_conn_id.id_len /* DCID Len, DCID */
+ 1 + hdr->src_conn_id.id_len /* SCID Len, SCID */
- + hdr->pn_len; /* PN */
+ ;
+
+ if (ossl_quic_pkt_type_has_pn(hdr->type)) {
+ if (hdr->pn_len < 1 || hdr->pn_len > 4)
+ return 0;
+
+ len += hdr->pn_len;
+ }
if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
enclen = ossl_quic_vlint_encode_len(hdr->token_len);
len += enclen;
}
- enclen = ossl_quic_vlint_encode_len(hdr->len);
- if (!enclen)
- return 0;
+ if (!ossl_quic_pkt_type_must_be_last(hdr->type)) {
+ enclen = ossl_quic_vlint_encode_len(hdr->len);
+ if (!enclen)
+ return 0;
+
+ len += enclen;
+ }
- len += enclen;
return len;
}
}
#define RX_TEST_OP_DISCARD_EL 7 /* discard an encryption level */
#define RX_TEST_OP_CHECK_PKT 8 /* read packet, compare to expected */
#define RX_TEST_OP_CHECK_NO_PKT 9 /* check no packet is available to read */
+#define RX_TEST_OP_CHECK_KEY_EPOCH 10 /* check key epoch value matches */
+#define RX_TEST_OP_KEY_UPDATE_TIMEOUT 11 /* complete key update process */
+#define RX_TEST_OP_SET_INIT_KEY_PHASE 12 /* initial Key Phase bit value */
struct rx_test_op {
unsigned char op;
},
#define RX_OP_CHECK_NO_PKT() \
{ RX_TEST_OP_CHECK_NO_PKT, NULL, 0, NULL, 0, 0, 0, NULL, NULL },
+#define RX_OP_CHECK_KEY_EPOCH(expected) \
+ { RX_TEST_OP_CHECK_KEY_EPOCH, NULL, 0, NULL, 0, 0, (expected), NULL },
+#define RX_OP_KEY_UPDATE_TIMEOUT(normal) \
+ { RX_TEST_OP_KEY_UPDATE_TIMEOUT, NULL, 0, NULL, (normal), 0, 0, NULL },
+#define RX_OP_SET_INIT_KEY_PHASE(kp_bit) \
+ { RX_TEST_OP_SET_INIT_KEY_PHASE, NULL, 0, NULL, (kp_bit), 0, 0, NULL },
#define RX_OP_INJECT_N(n) \
RX_OP_INJECT(rx_script_##n##_in)
RX_OP_END
};
+/*
+ * 8. Real World - S2C Multiple Packets with Peer Initiated Key Phase Update
+ */
+static const unsigned char rx_script_8_1rtt_secret[32] = {
+ 0x5f, 0x1f, 0x47, 0xea, 0xc3, 0xb2, 0xce, 0x73, 0xfb, 0xa2, 0x9f, 0xac,
+ 0xc3, 0xa0, 0xfe, 0x9b, 0xf3, 0xc0, 0xde, 0x5d, 0x33, 0x11, 0x1c, 0x70,
+ 0xdd, 0xb4, 0x06, 0xcc, 0xdf, 0x7d, 0xe9, 0x9a
+};
+
+static const unsigned char rx_script_8a_in[] = {
+ 0x51, /* Short, 1-RTT, PN Length=2 bytes, KP=0 */
+ 0xcb, 0xf4, /* PN (4) */
+ 0x3f, 0x68, 0x7b, 0xa8, 0x2b, 0xb9, 0xfa, 0x7d, 0xe4, 0x6b, 0x20, 0x48,
+ 0xd1, 0x3c, 0xcb, 0x4b, 0xef, 0xb1, 0xfd, 0x5e, 0x1b, 0x19, 0x83, 0xa9,
+ 0x47, 0x62, 0xc1, 0x6e, 0xef, 0x27, 0xc3, 0x9b, 0x8f, 0x3f, 0xce, 0x11,
+ 0x68, 0xf5, 0x73, 0x0d, 0xf2, 0xdc, 0xe0, 0x28, 0x28, 0x79, 0xa6, 0x39,
+ 0xc3, 0xb9, 0xd3,
+};
+
+static const QUIC_PKT_HDR rx_script_8a_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 0, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 4}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 35, NULL
+};
+
+static const unsigned char rx_script_8a_body[] = {
+ 0x02, 0x03, 0x06, 0x00, 0x03, 0x0c, 0x00, 0x1b, 0x49, 0x27, 0x6d, 0x20,
+ 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
+ 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65
+};
+
+static const unsigned char rx_script_8b_in[] = {
+ 0x52, /* Short, 1-RTT, PN Length=2 bytes, KP=1 */
+ 0x21, 0x8e, /* PN (5) */
+ 0xa2, 0x6a, 0x9c, 0x83, 0x24, 0x48, 0xae, 0x60, 0x1e, 0xc2, 0xa5, 0x91,
+ 0xfa, 0xe5, 0xf2, 0x05, 0x14, 0x37, 0x04, 0x6a, 0xa8, 0xae, 0x06, 0x58,
+ 0xd7, 0x85, 0x48, 0xd7, 0x3b, 0x85, 0x9e, 0x5a, 0xb3, 0x46, 0x89, 0x1b,
+ 0x4b, 0x6e, 0x1d, 0xd1, 0xfc, 0xb7, 0x47, 0xda, 0x6a, 0x64, 0x4b, 0x8e,
+ 0xf2, 0x69, 0x16,
+};
+
+static const QUIC_PKT_HDR rx_script_8b_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 1, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 5}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 35, NULL
+};
+
+static const unsigned char rx_script_8b_body[] = {
+ 0x02, 0x04, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x36, 0x49, 0x27, 0x6d, 0x20,
+ 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
+ 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char rx_script_8c_in[] = {
+ 0x5b, /* Short, 1-RTT, PN Length=2 bytes, KP=0 */
+ 0x98, 0xd6, /* PN (3) */
+ 0x3c, 0x6f, 0x94, 0x20, 0x5e, 0xfc, 0x5b, 0x3a, 0x4a, 0x65, 0x1a, 0x9a,
+ 0x6c, 0x00, 0x52, 0xb6, 0x0c, 0x9b, 0x07, 0xf9, 0x6f, 0xbc, 0x3d, 0xb4,
+ 0x57, 0xe0, 0x15, 0x74, 0xfe, 0x76, 0xea, 0x1f, 0x23, 0xae, 0x22, 0x62,
+ 0xb7, 0x90, 0x94, 0x89, 0x38, 0x9b, 0x5b, 0x47, 0xed,
+};
+
+static const QUIC_PKT_HDR rx_script_8c_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 0, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 3}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 29, NULL
+};
+
+static const unsigned char rx_script_8c_body[] = {
+ 0x08, 0x00, 0x49, 0x27, 0x6d, 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67,
+ 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c,
+ 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char rx_script_8d_in[] = {
+ 0x55, /* Short, 1-RTT, PN Length=2 bytes, KP=1 */
+ 0x98, 0x20, /* PN (6) */
+ 0x45, 0x53, 0x05, 0x29, 0x30, 0x42, 0x29, 0x02, 0xf2, 0xa7, 0x27, 0xd6,
+ 0xb0, 0xb7, 0x30, 0xad, 0x45, 0xd8, 0x73, 0xd7, 0xe3, 0x65, 0xee, 0xd9,
+ 0x35, 0x33, 0x03, 0x3a, 0x35, 0x0b, 0x59, 0xa7, 0xbc, 0x23, 0x37, 0xc2,
+ 0x5e, 0x13, 0x88, 0x18, 0x79, 0x94, 0x6c, 0x15, 0xe3, 0x1f, 0x0d, 0xd1,
+ 0xc3, 0xfa, 0x40, 0xff,
+};
+
+static const QUIC_PKT_HDR rx_script_8d_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 1, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 6}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 36, NULL
+};
+
+static const unsigned char rx_script_8d_body[] = {
+ 0x02, 0x05, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x40, 0x51, 0x49, 0x27, 0x6d,
+ 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
+ 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char rx_script_8e_in[] = {
+ 0x55, /* Short, 1-RTTT, PN Length=2 bytes, KP=0 */
+ 0x76, 0x25, /* PN (10) */
+ 0x1c, 0x0d, 0x70, 0x4c, 0x2b, 0xc5, 0x7d, 0x7b, 0x77, 0x64, 0x03, 0x27,
+ 0xb3, 0x5d, 0x83, 0x9e, 0x35, 0x05, 0x10, 0xd2, 0xa4, 0x5c, 0x83, 0xd6,
+ 0x94, 0x12, 0x18, 0xc5, 0xb3, 0x0f, 0x0a, 0xb1, 0x8a, 0x82, 0x9f, 0xd6,
+ 0xa9, 0xab, 0x40, 0xc1, 0x05, 0xe8, 0x1b, 0x74, 0xaa, 0x8e, 0xd6, 0x8b,
+ 0xa5, 0xa3, 0x77, 0x79,
+};
+
+static const QUIC_PKT_HDR rx_script_8e_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 0, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 10}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 36, NULL
+};
+
+static const unsigned char rx_script_8e_body[] = {
+ 0x02, 0x09, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x40, 0xbd, 0x49, 0x27, 0x6d,
+ 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
+ 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char rx_script_8f_in[] = {
+ 0x48, /* Short, 1-RTT, PN Length=2 Bytes, KP=1 */
+ 0x4d, 0xf6, /* PN (15) */
+ 0x42, 0x86, 0xa1, 0xfa, 0x69, 0x6b, 0x1a, 0x45, 0xf2, 0xcd, 0xf6, 0x92,
+ 0xe1, 0xe6, 0x1a, 0x49, 0x37, 0xd7, 0x10, 0xae, 0x09, 0xbd
+};
+
+static const QUIC_PKT_HDR rx_script_8f_expect_hdr = {
+ QUIC_PKT_TYPE_1RTT,
+ 0, /* Spin Bit */
+ 1, /* Key Phase */
+ 2, /* PN Length */
+ 0, /* Partial */
+ 1, /* Fixed */
+ 0, /* Version */
+ {0, {0}}, /* DCID */
+ {0, {0}}, /* SCID */
+ {0, 15}, /* PN */
+ NULL, 0, /* Token/Token Len */
+ 6, NULL
+};
+
+static const unsigned char rx_script_8f_body[] = {
+ 0x02, 0x0e, 0x4c, 0x54, 0x00, 0x02
+};
+
+static const struct rx_test_op rx_script_8[] = {
+ RX_OP_ADD_RX_DCID(empty_conn_id)
+ /* Inject before we get the keys */
+ RX_OP_INJECT_N(8a)
+ /* Nothing yet */
+ RX_OP_CHECK_NO_PKT()
+ /* Provide keys */
+ RX_OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+ QRL_SUITE_AES128GCM, rx_script_8_1rtt_secret)
+ /* Now the injected packet is successfully returned */
+ RX_OP_CHECK_PKT_N(8a)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(0)
+
+ /* Packet with new key phase */
+ RX_OP_INJECT_N(8b)
+ /* Packet is successfully decrypted and returned */
+ RX_OP_CHECK_PKT_N(8b)
+ RX_OP_CHECK_NO_PKT()
+ /* Key epoch has increased */
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /*
+ * Now inject an old packet with the old keys (perhaps reordered in
+ * network).
+ */
+ RX_OP_INJECT_N(8c)
+ /* Should still be decrypted OK */
+ RX_OP_CHECK_PKT_N(8c)
+ RX_OP_CHECK_NO_PKT()
+ /* Epoch has not changed */
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /* Another packet with the new keys. */
+ RX_OP_INJECT_N(8d)
+ RX_OP_CHECK_PKT_N(8d)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /* We can inject the old packet multiple times and it still works */
+ RX_OP_INJECT_N(8c)
+ RX_OP_CHECK_PKT_N(8c)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /* Until we move from UPDATING to COOLDOWN */
+ RX_OP_KEY_UPDATE_TIMEOUT(0)
+ RX_OP_INJECT_N(8c)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /*
+ * Injecting a packet from the next epoch (epoch 2) while in COOLDOWN
+ * doesn't work
+ */
+ RX_OP_INJECT_N(8e)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(1)
+
+ /* Move from COOLDOWN to NORMAL and try again */
+ RX_OP_KEY_UPDATE_TIMEOUT(1)
+ RX_OP_INJECT_N(8e)
+ RX_OP_CHECK_PKT_N(8e)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(2)
+
+ /* Can still receive old packet */
+ RX_OP_INJECT_N(8d)
+ RX_OP_CHECK_PKT_N(8d)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(2)
+
+ /* Move straight from UPDATING to NORMAL */
+ RX_OP_KEY_UPDATE_TIMEOUT(1)
+
+ /* Try a packet from epoch 3 */
+ RX_OP_INJECT_N(8f)
+ RX_OP_CHECK_PKT_N(8f)
+ RX_OP_CHECK_NO_PKT()
+ RX_OP_CHECK_KEY_EPOCH(3)
+
+ RX_OP_END
+};
+
static const struct rx_test_op *rx_scripts[] = {
rx_script_1,
rx_script_2,
rx_script_4,
rx_script_5,
rx_script_6,
- rx_script_7
+ rx_script_7,
+ rx_script_8
};
static int cmp_pkt_hdr(const QUIC_PKT_HDR *a, const QUIC_PKT_HDR *b,
}
}
+static uint64_t time_counter = 0;
+
+static OSSL_TIME expected_time(uint64_t counter)
+{
+ return ossl_time_multiply(ossl_ticks2time(OSSL_TIME_MS), counter);
+}
+
+static OSSL_TIME fake_time(void *arg)
+{
+ return expected_time(++time_counter);
+}
+
static int rx_state_ensure(struct rx_state *s)
{
if (s->demux == NULL
&& !TEST_ptr(s->demux = ossl_quic_demux_new(NULL,
s->args.short_conn_id_len,
- 1500)))
+ 1500,
+ fake_time,
+ NULL)))
return 0;
s->args.demux = s->demux;
if (!TEST_false(ossl_qrx_read_pkt(s.qrx, &pkt)))
goto err;
+ break;
+ case RX_TEST_OP_CHECK_KEY_EPOCH:
+ if (!TEST_true(rx_state_ensure(&s)))
+ goto err;
+
+ if (!TEST_uint64_t_eq(ossl_qrx_get_key_epoch(s.qrx),
+ op->largest_pn))
+ goto err;
+
+ break;
+ case RX_TEST_OP_KEY_UPDATE_TIMEOUT:
+ if (!TEST_true(rx_state_ensure(&s)))
+ goto err;
+
+ if (!TEST_true(ossl_qrx_key_update_timeout(s.qrx,
+ op->enc_level)))
+ goto err;
+
+ break;
+ case RX_TEST_OP_SET_INIT_KEY_PHASE:
+ rx_state_teardown(&s);
+ s.args.init_key_phase_bit = (unsigned char)op->enc_level;
break;
default:
OPENSSL_assert(0);
testresult = 1;
err:
if (have_hpr)
- ossl_quic_hdr_protector_destroy(&hpr);
+ ossl_quic_hdr_protector_cleanup(&hpr);
WPACKET_finish(&wpkt);
BUF_MEM_free(buf);
OPENSSL_free(hbuf);
#define TX_TEST_OP_DISCARD_EL 4 /* discard an encryption level */
#define TX_TEST_OP_CHECK_DGRAM 5 /* read datagram, compare to expected */
#define TX_TEST_OP_CHECK_NO_DGRAM 6 /* check no datagram is in queue */
+#define TX_TEST_OP_KEY_UPDATE 7 /* perform key update for 1-RTT */
struct tx_test_op {
unsigned char op;
TX_OP_WRITE_N(n) \
TX_OP_CHECK_DGRAM_N(n)
+#define TX_OP_KEY_UPDATE() \
+ { TX_TEST_OP_KEY_UPDATE, NULL, 0, NULL, 0, 0, NULL },
+
/* 1. RFC 9001 - A.2 Client Initial */
static const unsigned char tx_script_1_body[1162] = {
0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb, 0xf8,
TX_OP_END
};
+/* 4. Real World - AES-128-GCM Key Update */
+static const unsigned char tx_script_4_secret[] = {
+ 0x70, 0x82, 0xc0, 0x45, 0x61, 0x4d, 0xfe, 0x04, 0x76, 0xa6, 0x4e, 0xf0,
+ 0x38, 0xe6, 0x63, 0xd9, 0xdd, 0x4a, 0x75, 0x16, 0xa8, 0xa0, 0x06, 0x5a,
+ 0xf2, 0x56, 0xfd, 0x84, 0x78, 0xfd, 0xf6, 0x5e
+};
+
+static const unsigned char tx_script_4a_body[] = {
+ 0x02, 0x03, 0x09, 0x00, 0x03, 0x0c, 0x00, 0x36, 0x49, 0x27, 0x6d, 0x20,
+ 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
+ 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char tx_script_4a_dgram[] = {
+ 0x47, 0x6e, 0x4e, 0xbd, 0x49, 0x7e, 0xbd, 0x15, 0x1c, 0xd1, 0x3e, 0xc8,
+ 0xcd, 0x43, 0x87, 0x6b, 0x84, 0xdb, 0xeb, 0x06, 0x8b, 0x8a, 0xae, 0x37,
+ 0xed, 0x9c, 0xeb, 0xbc, 0xcf, 0x0d, 0x3c, 0xf0, 0xa1, 0x6f, 0xee, 0xd2,
+ 0x7c, 0x07, 0x6e, 0xd1, 0xbe, 0x40, 0x6a, 0xd4, 0x53, 0x38, 0x9e, 0x63,
+ 0xb5, 0xde, 0x35, 0x09, 0xb2, 0x78, 0x94, 0xe4, 0x2b, 0x37
+};
+
+static QUIC_PKT_HDR tx_script_4a_hdr = {
+ QUIC_PKT_TYPE_1RTT, /* type */
+ 0, /* spin bit */
+ 0, /* key phase */
+ 2, /* PN length */
+ 0, /* partial */
+ 0, /* fixed */
+ 0, /* version */
+ { 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
+ { 0, {0} }, /* SCID */
+ { 0 }, /* PN */
+ NULL, 0, /* Token */
+ 5555, NULL /* Len/Data */
+};
+
+static const OSSL_QTX_IOVEC tx_script_4a_iovec[] = {
+ { tx_script_4a_body, sizeof(tx_script_4a_body) }
+};
+
+static const OSSL_QTX_PKT tx_script_4a_pkt = {
+ &tx_script_4a_hdr,
+ tx_script_4a_iovec,
+ OSSL_NELEM(tx_script_4a_iovec),
+ NULL, NULL,
+ 4,
+ 0
+};
+
+static const unsigned char tx_script_4b_body[] = {
+ 0x02, 0x04, 0x07, 0x00, 0x00, 0x0c, 0x00, 0x40, 0x51, 0x49, 0x27, 0x6d,
+ 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
+ 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char tx_script_4b_dgram[] = {
+ 0x58, 0x6e, 0x4e, 0xbd, 0x49, 0xa4, 0x43, 0x33, 0xea, 0x11, 0x3a, 0x6c,
+ 0xf5, 0x20, 0xef, 0x55, 0x8d, 0x25, 0xe2, 0x3b, 0x0e, 0x8c, 0xea, 0x17,
+ 0xfc, 0x2b, 0x7a, 0xab, 0xfa, 0x3d, 0x07, 0xda, 0xa7, 0x7c, 0xc7, 0x47,
+ 0x82, 0x02, 0x46, 0x40, 0x4f, 0x01, 0xad, 0xb2, 0x9d, 0x97, 0xdb, 0xfc,
+ 0x9c, 0x4b, 0x46, 0xb1, 0x5a, 0x7f, 0x0b, 0x12, 0xaf, 0x49, 0xdf,
+};
+
+static QUIC_PKT_HDR tx_script_4b_hdr = {
+ QUIC_PKT_TYPE_1RTT, /* type */
+ 0, /* spin bit */
+ 1, /* key phase */
+ 2, /* PN length */
+ 0, /* partial */
+ 0, /* fixed */
+ 0, /* version */
+ { 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
+ { 0, {0} }, /* SCID */
+ { 0 }, /* PN */
+ NULL, 0, /* Token */
+ 5555, NULL /* Len/Data */
+};
+
+static const OSSL_QTX_IOVEC tx_script_4b_iovec[] = {
+ { tx_script_4b_body, sizeof(tx_script_4b_body) }
+};
+
+static const OSSL_QTX_PKT tx_script_4b_pkt = {
+ &tx_script_4b_hdr,
+ tx_script_4b_iovec,
+ OSSL_NELEM(tx_script_4b_iovec),
+ NULL, NULL,
+ 5,
+ 0
+};
+
+static const unsigned char tx_script_4c_body[] = {
+ 0x02, 0x09, 0x0e, 0x00, 0x00, 0x0c, 0x00, 0x40, 0xd8, 0x49, 0x27, 0x6d,
+ 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
+ 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
+};
+
+static const unsigned char tx_script_4c_dgram[] = {
+ 0x49, 0x6e, 0x4e, 0xbd, 0x49, 0x4d, 0xd9, 0x85, 0xba, 0x26, 0xfb, 0x68,
+ 0x83, 0x9b, 0x94, 0x34, 0x7d, 0xc1, 0x7a, 0x05, 0xb7, 0x38, 0x43, 0x21,
+ 0xe2, 0xec, 0x2b, 0xc1, 0x81, 0x74, 0x2d, 0xda, 0x24, 0xba, 0xbd, 0x99,
+ 0x69, 0xd2, 0x56, 0xfa, 0xae, 0x29, 0x24, 0xb2, 0xaa, 0xda, 0xbd, 0x82,
+ 0x80, 0xf1, 0xbb, 0x6a, 0xfd, 0xae, 0xda, 0x0e, 0x09, 0xcf, 0x09,
+};
+
+static QUIC_PKT_HDR tx_script_4c_hdr = {
+ QUIC_PKT_TYPE_1RTT, /* type */
+ 0, /* spin bit */
+ 0, /* key phase */
+ 2, /* PN length */
+ 0, /* partial */
+ 0, /* fixed */
+ 0, /* version */
+ { 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
+ { 0, {0} }, /* SCID */
+ { 0 }, /* PN */
+ NULL, 0, /* Token */
+ 5555, NULL /* Len/Data */
+};
+
+static const OSSL_QTX_IOVEC tx_script_4c_iovec[] = {
+ { tx_script_4c_body, sizeof(tx_script_4c_body) }
+};
+
+static const OSSL_QTX_PKT tx_script_4c_pkt = {
+ &tx_script_4c_hdr,
+ tx_script_4c_iovec,
+ OSSL_NELEM(tx_script_4c_iovec),
+ NULL, NULL,
+ 10,
+ 0
+};
+
+static const struct tx_test_op tx_script_4[] = {
+ TX_OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, tx_script_4_secret)
+ TX_OP_WRITE_CHECK(4a)
+ TX_OP_KEY_UPDATE()
+ TX_OP_WRITE_CHECK(4b)
+ TX_OP_KEY_UPDATE()
+ TX_OP_WRITE_CHECK(4c)
+ TX_OP_END
+};
+
+/* 5. Real World - Retry Packet */
+static const unsigned char tx_script_5_body[] = {
+ /* Retry Token */
+ 0x92, 0xe7, 0xc6, 0xd8, 0x09, 0x65, 0x72, 0x55, 0xe5, 0xe2, 0x73, 0x04,
+ 0xf3, 0x07, 0x5b, 0x21, 0x9f, 0x50, 0xcb, 0xbc, 0x79, 0xc5, 0x77, 0x5a,
+ 0x29, 0x43, 0x65, 0x49, 0xf0, 0x6e, 0xc1, 0xc0, 0x3a, 0xe8, 0xca, 0xd2,
+ 0x44, 0x69, 0xdd, 0x23, 0x31, 0x93, 0x52, 0x02, 0xf7, 0x42, 0x07, 0x78,
+ 0xa1, 0x81, 0x61, 0x9c, 0x39, 0x07, 0x18, 0x69, 0x6e, 0x4f, 0xdc, 0xa0,
+ 0xbe, 0x4b, 0xe5, 0xf2, 0xe9, 0xd2, 0xa4, 0xa7, 0x34, 0x55, 0x5e, 0xf3,
+ 0xf8, 0x9c, 0x49, 0x8f, 0x0c, 0xc8, 0xb2, 0x75, 0x4b, 0x4d, 0x2f, 0xfe,
+ 0x05, 0x5a, 0xdd, 0x4b, 0xe6, 0x14, 0xb4, 0xd2, 0xc0, 0x93, 0x6e, 0x0e,
+ 0x84, 0x41, 0x4d, 0x31,
+ /* Retry Integrity Tag */
+ 0x43, 0x8e, 0xab, 0xcd, 0xce, 0x24, 0x44, 0xc2, 0x20, 0xe1, 0xe2, 0xc8,
+ 0xae, 0xa3, 0x8d, 0x4e,
+};
+
+static const unsigned char tx_script_5_dgram[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0xa9, 0x20, 0xcc, 0xc2, 0x92,
+ 0xe7, 0xc6, 0xd8, 0x09, 0x65, 0x72, 0x55, 0xe5, 0xe2, 0x73, 0x04, 0xf3,
+ 0x07, 0x5b, 0x21, 0x9f, 0x50, 0xcb, 0xbc, 0x79, 0xc5, 0x77, 0x5a, 0x29,
+ 0x43, 0x65, 0x49, 0xf0, 0x6e, 0xc1, 0xc0, 0x3a, 0xe8, 0xca, 0xd2, 0x44,
+ 0x69, 0xdd, 0x23, 0x31, 0x93, 0x52, 0x02, 0xf7, 0x42, 0x07, 0x78, 0xa1,
+ 0x81, 0x61, 0x9c, 0x39, 0x07, 0x18, 0x69, 0x6e, 0x4f, 0xdc, 0xa0, 0xbe,
+ 0x4b, 0xe5, 0xf2, 0xe9, 0xd2, 0xa4, 0xa7, 0x34, 0x55, 0x5e, 0xf3, 0xf8,
+ 0x9c, 0x49, 0x8f, 0x0c, 0xc8, 0xb2, 0x75, 0x4b, 0x4d, 0x2f, 0xfe, 0x05,
+ 0x5a, 0xdd, 0x4b, 0xe6, 0x14, 0xb4, 0xd2, 0xc0, 0x93, 0x6e, 0x0e, 0x84,
+ 0x41, 0x4d, 0x31, 0x43, 0x8e, 0xab, 0xcd, 0xce, 0x24, 0x44, 0xc2, 0x20,
+ 0xe1, 0xe2, 0xc8, 0xae, 0xa3, 0x8d, 0x4e,
+};
+
+static QUIC_PKT_HDR tx_script_5_hdr = {
+ QUIC_PKT_TYPE_RETRY, /* type */
+ 0, /* spin bit */
+ 0, /* key phase */
+ 0, /* PN length */
+ 0, /* partial */
+ 0, /* fixed */
+ 1, /* version */
+ { 0, {0} }, /* DCID */
+ { 4, {0xa9, 0x20, 0xcc, 0xc2} }, /* SCID */
+ { 0 }, /* PN */
+ NULL, 0, /* Token */
+ 5555, NULL /* Len/Data */
+};
+
+static const OSSL_QTX_IOVEC tx_script_5_iovec[] = {
+ { tx_script_5_body, sizeof(tx_script_5_body) }
+};
+
+static const OSSL_QTX_PKT tx_script_5_pkt = {
+ &tx_script_5_hdr,
+ tx_script_5_iovec,
+ OSSL_NELEM(tx_script_5_iovec),
+ NULL, NULL,
+ 0,
+ 0
+};
+
+static const struct tx_test_op tx_script_5[] = {
+ TX_OP_WRITE_CHECK(5)
+ TX_OP_END
+};
+
+/* 6. Real World - Version Negotiation Packet */
+static const unsigned char tx_script_6_body[] = {
+ 0x00, 0x00, 0x00, 0x01, /* Supported Version: 1 */
+ 0xaa, 0x9a, 0x3a, 0x9a /* Supported Version: Random (GREASE) */
+};
+
+static const unsigned char tx_script_6_dgram[] = {
+ 0x80, /* Long */
+ 0x00, 0x00, 0x00, 0x00, /* Version 0 (Version Negotiation) */
+ 0x00, /* DCID */
+ 0x0c, 0x35, 0x3c, 0x1b, 0x97, 0xca, /* SCID */
+ 0xf8, 0x99, 0x11, 0x39, 0xad, 0x79,
+ 0x1f,
+ 0x00, 0x00, 0x00, 0x01, /* Supported Version: 1 */
+ 0xaa, 0x9a, 0x3a, 0x9a /* Supported Version: Random (GREASE) */
+};
+
+static QUIC_PKT_HDR tx_script_6_hdr = {
+ QUIC_PKT_TYPE_VERSION_NEG, /* type */
+ 0, /* spin bit */
+ 0, /* key phase */
+ 0, /* PN length */
+ 0, /* partial */
+ 0, /* fixed */
+ 0, /* version */
+ { 0, {0} }, /* DCID */
+ { 12, {0x35, 0x3c, 0x1b, 0x97, 0xca, 0xf8, 0x99,
+ 0x11, 0x39, 0xad, 0x79, 0x1f} }, /* SCID */
+ { 0 }, /* PN */
+ NULL, 0, /* Token */
+ 5555, NULL /* Len/Data */
+};
+
+static const OSSL_QTX_IOVEC tx_script_6_iovec[] = {
+ { tx_script_6_body, sizeof(tx_script_6_body) }
+};
+
+static const OSSL_QTX_PKT tx_script_6_pkt = {
+ &tx_script_6_hdr,
+ tx_script_6_iovec,
+ OSSL_NELEM(tx_script_6_iovec),
+ NULL, NULL,
+ 0,
+ 0
+};
+
+static const struct tx_test_op tx_script_6[] = {
+ TX_OP_WRITE_CHECK(6)
+ TX_OP_END
+};
+
static const struct tx_test_op *const tx_scripts[] = {
tx_script_1,
tx_script_2,
- tx_script_3
+ tx_script_3,
+ tx_script_4,
+ tx_script_5,
+ tx_script_6
};
static int tx_run_script(const struct tx_test_op *script)
if (!TEST_false(ossl_qtx_pop_net(qtx, &msg)))
goto err;
break;
+ case TX_TEST_OP_KEY_UPDATE:
+ if (!TEST_true(ossl_qtx_trigger_key_update(qtx)))
+ goto err;
+ break;
default:
OPENSSL_assert(0);
goto err;