]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
TX key update support, RX time and PN reporting, general refactoring
authorHugo Landau <hlandau@openssl.org>
Mon, 15 Aug 2022 15:13:28 +0000 (16:13 +0100)
committerTomas Mraz <tomas@openssl.org>
Fri, 2 Sep 2022 08:03:55 +0000 (10:03 +0200)
- 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)

12 files changed:
include/internal/quic_demux.h
include/internal/quic_record_rx.h
include/internal/quic_record_tx.h
include/internal/quic_types.h
include/internal/quic_wire_pkt.h
ssl/quic/quic_demux.c
ssl/quic/quic_record_rx.c
ssl/quic/quic_record_shared.c
ssl/quic/quic_record_shared.h
ssl/quic/quic_record_tx.c
ssl/quic/quic_wire_pkt.c
test/quic_record_test.c

index 06f1afab5b1f2068c934a5024c87d8307165b63d..9912de7a0033a6d53a1bb70e7c95b0451899bc1e 100644 (file)
@@ -13,6 +13,7 @@
 # include <openssl/ssl.h>
 # include "internal/quic_types.h"
 # include "internal/bio_addr.h"
+# include "internal/time.h"
 
 /*
  * QUIC Demuxer
@@ -110,6 +111,12 @@ struct quic_urxe_st {
      * 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. */
@@ -163,10 +170,16 @@ typedef void (ossl_quic_demux_cb_fn)(QUIC_URXE *e, void *arg);
  * 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
index 8f9ffab36591ed3c18a0706da03eca8e3e2a36b2..c22fc089c71c2b10e37d8506e1ac3d67f017b1f6 100644 (file)
@@ -34,6 +34,9 @@ typedef struct ossl_qrx_args_st {
 
     /* 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. */
@@ -92,7 +95,7 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *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
@@ -129,14 +132,14 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *qrx,
  * 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
@@ -144,7 +147,7 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *qrx,
  * 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
@@ -218,6 +221,15 @@ typedef struct ossl_qrx_pkt_st {
      * 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;
 
 /*
@@ -226,9 +238,9 @@ typedef struct ossl_qrx_pkt_st {
  * 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);
 
@@ -326,7 +338,7 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  *      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."
  *
@@ -342,11 +354,11 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  *                               PROVISIONED
  *                     _______________________________
  *                    |                               |
- *   UNPROVISIONED  --|---->  NORMAL  <----------\    |------>  DROPPED
+ *   UNPROVISIONED  --|---->  NORMAL  <----------\    |------>  DISCARDED
  *                    |          |               |    |
  *                    |          |               |    |
  *                    |          v               |    |
- *                    |   UPDATE_CONFIRMED       |    |
+ *                    |      UPDATING            |    |
  *                    |          |               |    |
  *                    |          |               |    |
  *                    |          v               |    |
@@ -362,16 +374,16 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  * 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
@@ -386,21 +398,25 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  * 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).
@@ -411,11 +427,11 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  * 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
@@ -423,8 +439,10 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  */
 
 /*
- * 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
@@ -438,21 +456,33 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
  * 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
@@ -470,19 +500,22 @@ int ossl_qrx_key_update_timeout(OSSL_QRX *qrx, int normal);
 /*
  * 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
index 13405ad5782b0763801727120e93774f41f5ec3a..71949ae05c22761bbd13b5aacc4adc6e70caebc1 100644 (file)
@@ -163,7 +163,18 @@ typedef struct ossl_qtx_pkt_st {
  * 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);
index b8b60c5cafa8130d48d516666c3b8b001c6e5ab2..22de5f2d42de07ebe0d785ba17c6ca5d35d954eb 100644 (file)
@@ -31,16 +31,16 @@ 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;
+    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;
     }
 }
 
index 0c3cbbf673020ac8a675e52e3ceeeb3669692711..60528811ad0aaa4b6554b5e2fb04b87914774566 100644 (file)
@@ -46,6 +46,59 @@ ossl_quic_pkt_type_to_enc_level(uint32_t pkt_type)
     }
 }
 
+/* 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).
@@ -62,7 +115,7 @@ typedef struct quic_pkt_hdr_ptrs_st QUIC_PKT_HDR_PTRS;
  *
  * 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;
@@ -107,7 +160,7 @@ int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
  * 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
@@ -255,11 +308,11 @@ typedef struct quic_pkt_hdr_st {
     /* [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;
@@ -285,9 +338,13 @@ typedef struct quic_pkt_hdr_st {
     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;
 
index 3eb4f6dfb4e1946d1008a3a392abd4cc4cedaf52..67a61c691a1a4adf71911eac163498fe2b972998 100644 (file)
@@ -3,13 +3,13 @@
 #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;
@@ -27,7 +27,7 @@ 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)
 {
     /* 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;
@@ -44,7 +44,7 @@ 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)
 {
     /* 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;
@@ -103,6 +103,10 @@ struct quic_demux_st {
     /* 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;
 
@@ -128,7 +132,9 @@ struct quic_demux_st {
 
 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;
 
@@ -139,6 +145,8 @@ QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
     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);
@@ -194,7 +202,7 @@ static QUIC_DEMUX_CONN *demux_get_by_conn_id(QUIC_DEMUX *demux,
     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);
@@ -329,9 +337,10 @@ static int demux_ensure_free_urxe(QUIC_DEMUX *demux, size_t min_num_free)
  */
 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);
@@ -363,15 +372,18 @@ static int demux_recv(QUIC_DEMUX *demux)
             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;
@@ -413,7 +425,8 @@ static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
     QUIC_DEMUX_CONN *conn;
 
     /* The next URXE we process should be at the head of the pending list. */
-    OPENSSL_assert(e == demux->urx_pending.head);
+    if (!ossl_assert(e == demux->urx_pending.head))
+        return 0;
 
     conn = demux_identify_conn(demux, e);
     if (conn == NULL) {
@@ -457,7 +470,7 @@ int ossl_quic_demux_pump(QUIC_DEMUX *demux)
     int ret;
 
     if (demux->urx_pending.head == NULL) {
-        ret = demux_ensure_free_urxe(demux, OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL);
+        ret = demux_ensure_free_urxe(demux, DEMUX_MAX_MSGS_PER_CALL);
         if (ret != 1)
             return 0;
 
@@ -517,7 +530,7 @@ int ossl_quic_demux_inject(QUIC_DEMUX *demux,
 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;
 }
index e1093f791bd95f93fb9bd50c16a17b16a5ecf9fc..08c8e7d992c0270c3b39ac59649fd82d911da688 100644 (file)
@@ -52,6 +52,9 @@ struct rxe_st {
     /* 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;
 
@@ -147,9 +150,23 @@ struct ossl_qrx_st {
     /* 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);
@@ -173,26 +190,31 @@ OSSL_QRX *ossl_qrx_new(const OSSL_QRX_ARGS *args)
     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;
 }
 
@@ -211,7 +233,7 @@ void ossl_qrx_free(OSSL_QRX *qrx)
 
     /* 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);
 }
@@ -265,7 +287,9 @@ int ossl_qrx_provide_secret(OSSL_QRX *qrx, uint32_t enc_level,
                                                suite_id,
                                                md,
                                                secret,
-                                               secret_len))
+                                               secret_len,
+                                               qrx->init_key_phase_bit,
+                                               /*is_tx=*/0))
         return 0;
 
     /*
@@ -282,7 +306,7 @@ int ossl_qrx_discard_enc_level(OSSL_QRX *qrx, uint32_t enc_level)
     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;
 }
 
@@ -372,7 +396,7 @@ static RXE *qrx_resize_rxe(RXE_LIST *rxl, RXE *rxe, size_t n)
      */
     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) {
@@ -480,8 +504,7 @@ static int qrx_validate_hdr_early(OSSL_QRX *qrx, RXE *rxe,
         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;
 
     /*
@@ -517,6 +540,44 @@ static int qrx_validate_hdr(OSSL_QRX *qrx, RXE *rxe)
     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.
  *
@@ -530,13 +591,15 @@ static int qrx_decrypt_pkt_body(OSSL_QRX *qrx, unsigned char *dst,
                                 const unsigned char *src,
                                 size_t src_len, size_t *dec_len,
                                 const unsigned char *aad, size_t aad_len,
-                                QUIC_PN pn, uint32_t enc_level)
+                                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;
@@ -548,45 +611,51 @@ static int qrx_decrypt_pkt_body(OSSL_QRX *qrx, unsigned char *dst,
     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;
     }
 
@@ -599,6 +668,15 @@ static ossl_inline void ignore_res(int x)
     /* 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,
@@ -614,6 +692,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
     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
@@ -634,8 +713,8 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
      */
     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;
 
     /*
@@ -646,7 +725,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
 
     /*
      * 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;
@@ -657,10 +736,13 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
      */
     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
@@ -679,7 +761,8 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
         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);
@@ -728,11 +811,10 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
     /* 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;
 
@@ -757,7 +839,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
      * 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.
@@ -792,9 +874,19 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
      */
     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
@@ -826,9 +918,10 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
     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);
@@ -915,7 +1008,7 @@ static int qrx_process_datagram(OSSL_QRX *qrx, QUIC_URXE *e,
          * 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))
@@ -927,7 +1020,7 @@ static int qrx_process_datagram(OSSL_QRX *qrx, QUIC_URXE *e,
 }
 
 /* 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;
 
@@ -958,12 +1051,12 @@ static int qrx_process_one_urxl(OSSL_QRX *qrx, QUIC_URXE *e)
 }
 
 /* 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;
@@ -974,7 +1067,7 @@ int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt)
     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))
@@ -987,6 +1080,8 @@ int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt)
 
     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
@@ -1020,17 +1115,51 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
     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);
index f5f06e26dda939860460f30d463965e02cba59b8..9832b0a3e7523f9ad9db85824d64a9cb0688f592 100644 (file)
@@ -13,10 +13,13 @@ static const unsigned char quic_v1_key_label[] = {
 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;
 
@@ -25,8 +28,15 @@ OSSL_QRL_ENC_LEVEL *ossl_qrl_enc_level_set_get(OSSL_QRL_ENC_LEVEL_SET *els,
 
     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;
 }
@@ -36,80 +46,102 @@ int ossl_qrl_enc_level_set_have_el(OSSL_QRL_ENC_LEVEL_SET *els,
 {
     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),
@@ -117,6 +149,79 @@ int ossl_qrl_enc_level_set_provide_secret(OSSL_QRL_ENC_LEVEL_SET *els,
                               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,
@@ -127,83 +232,206 @@ int ossl_qrl_enc_level_set_provide_secret(OSSL_QRL_ENC_LEVEL_SET *els,
                               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;
 }
index 40f05997dfe4ca179f8c970b1c61c5375340956f..e8c9e28e92a4445766ad94758b41c69810bf2fa7 100644 (file)
  * 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 {
@@ -49,21 +88,22 @@ 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,
@@ -73,14 +113,38 @@ 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
index 8bd5fffc531402b6f0a4cacd4838692dfb8c27cd..20a3a76381899537b1432e7fb3c0b5cfcf56dd80 100644 (file)
@@ -79,28 +79,6 @@ static void txe_insert_tail(TXE_LIST *l, TXE *e)
  * 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;
@@ -135,6 +113,12 @@ struct ossl_qtx_st {
      */
     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. */
@@ -176,10 +160,11 @@ void ossl_qtx_free(OSSL_QTX *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);
 }
@@ -201,7 +186,9 @@ int ossl_qtx_provide_secret(OSSL_QTX              *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)
@@ -209,7 +196,7 @@ 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;
 }
 
@@ -432,7 +419,7 @@ static int qtx_write_hdr(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
                                  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);
@@ -454,6 +441,7 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
         = 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))
@@ -466,21 +454,30 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
     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. */
@@ -492,7 +489,7 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *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;
 
@@ -501,10 +498,10 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
     }
 
     /* 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;
 
@@ -531,18 +528,21 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
     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;
@@ -564,7 +564,7 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
     /* 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;
@@ -580,12 +580,16 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
     }
 
     /* 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. */
@@ -674,13 +678,13 @@ int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt)
     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)
@@ -751,9 +755,7 @@ int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt)
     /*
      * 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)
@@ -802,9 +804,8 @@ static void txe_to_msg(TXE *txe, BIO_MSG *msg)
 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;
@@ -819,8 +820,7 @@ void ossl_qtx_flush_net(OSSL_QTX *qtx)
             /* 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.
@@ -830,7 +830,7 @@ void ossl_qtx_flush_net(OSSL_QTX *qtx)
         /*
          * 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);
     }
 }
@@ -883,6 +883,12 @@ size_t ossl_qtx_get_unflushed_pkt_count(OSSL_QTX *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;
index 2d62cb4b7d2f066aa09cfdeb752ee0bc018e81b0..484a5cc766379ce578ad9beeab5b3c3d7f8b28d0 100644 (file)
@@ -51,11 +51,11 @@ int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
     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;
@@ -238,8 +238,9 @@ int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
         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;
@@ -306,10 +307,18 @@ int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
             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;
@@ -455,8 +464,7 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
             || 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;
 
@@ -480,8 +488,7 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
         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)
@@ -560,15 +567,17 @@ int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_len,
             || hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
             return 0;
 
-        if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG
-            && hdr->type != QUIC_PKT_TYPE_RETRY
-            && (hdr->pn_len < 1 || hdr->pn_len > 4))
-            return 0;
-
         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);
@@ -577,11 +586,14 @@ int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_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;
     }
 }
index 436fe387ecb498cc34674fb59d3d72aca9a92efc..523d8ec27b04bef5b4bebbc48b94ee9e9f501618 100644 (file)
@@ -23,6 +23,9 @@ static const QUIC_CONN_ID empty_conn_id = {0, {0}};
 #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;
@@ -61,6 +64,12 @@ struct rx_test_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)
@@ -1327,6 +1336,279 @@ static const struct rx_test_op rx_script_7[] = {
     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,
@@ -1334,7 +1616,8 @@ static const struct rx_test_op *rx_scripts[] = {
     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,
@@ -1395,12 +1678,26 @@ static void rx_state_teardown(struct rx_state *s)
     }
 }
 
+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;
@@ -1497,6 +1794,28 @@ static int rx_run_script(const struct rx_test_op *script)
                 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);
@@ -2285,7 +2604,7 @@ static int test_wire_pkt_hdr_actual(int tidx, int repeat, int cipher,
     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);
@@ -2366,6 +2685,7 @@ static int test_wire_pkt_hdr(int idx)
 #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;
@@ -2407,6 +2727,9 @@ struct tx_test_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,
@@ -2681,10 +3004,271 @@ static const struct tx_test_op tx_script_3[] = {
     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)
@@ -2758,6 +3342,10 @@ 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;