From: Hugo Landau Date: Mon, 15 Aug 2022 15:13:28 +0000 (+0100) Subject: TX key update support, RX time and PN reporting, general refactoring X-Git-Tag: openssl-3.2.0-alpha1~2141 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=948c656c66a3846337a0262197766c80ec7c9e59;p=thirdparty%2Fopenssl.git TX key update support, RX time and PN reporting, general refactoring - 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 Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/18949) --- diff --git a/include/internal/quic_demux.h b/include/internal/quic_demux.h index 06f1afab5b1..9912de7a003 100644 --- a/include/internal/quic_demux.h +++ b/include/internal/quic_demux.h @@ -13,6 +13,7 @@ # include # 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 diff --git a/include/internal/quic_record_rx.h b/include/internal/quic_record_rx.h index 8f9ffab3659..c22fc089c71 100644 --- a/include/internal/quic_record_rx.h +++ b/include/internal/quic_record_rx.h @@ -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 diff --git a/include/internal/quic_record_tx.h b/include/internal/quic_record_tx.h index 13405ad5782..71949ae05c2 100644 --- a/include/internal/quic_record_tx.h +++ b/include/internal/quic_record_tx.h @@ -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); diff --git a/include/internal/quic_types.h b/include/internal/quic_types.h index b8b60c5cafa..22de5f2d42d 100644 --- a/include/internal/quic_types.h +++ b/include/internal/quic_types.h @@ -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; } } diff --git a/include/internal/quic_wire_pkt.h b/include/internal/quic_wire_pkt.h index 0c3cbbf6730..60528811ad0 100644 --- a/include/internal/quic_wire_pkt.h +++ b/include/internal/quic_wire_pkt.h @@ -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; diff --git a/ssl/quic/quic_demux.c b/ssl/quic/quic_demux.c index 3eb4f6dfb4e..67a61c691a1 100644 --- a/ssl/quic/quic_demux.c +++ b/ssl/quic/quic_demux.c @@ -3,13 +3,13 @@ #include "internal/common.h" #include -#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; } diff --git a/ssl/quic/quic_record_rx.c b/ssl/quic/quic_record_rx.c index e1093f791bd..08c8e7d992c 100644 --- a/ssl/quic/quic_record_rx.c +++ b/ssl/quic/quic_record_rx.c @@ -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); diff --git a/ssl/quic/quic_record_shared.c b/ssl/quic/quic_record_shared.c index f5f06e26dda..9832b0a3e75 100644 --- a/ssl/quic/quic_record_shared.c +++ b/ssl/quic/quic_record_shared.c @@ -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; } diff --git a/ssl/quic/quic_record_shared.h b/ssl/quic/quic_record_shared.h index 40f05997dfe..e8c9e28e92a 100644 --- a/ssl/quic/quic_record_shared.h +++ b/ssl/quic/quic_record_shared.h @@ -22,26 +22,65 @@ * 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 diff --git a/ssl/quic/quic_record_tx.c b/ssl/quic/quic_record_tx.c index 8bd5fffc531..20a3a763818 100644 --- a/ssl/quic/quic_record_tx.c +++ b/ssl/quic/quic_record_tx.c @@ -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; diff --git a/ssl/quic/quic_wire_pkt.c b/ssl/quic/quic_wire_pkt.c index 2d62cb4b7d2..484a5cc7663 100644 --- a/ssl/quic/quic_wire_pkt.c +++ b/ssl/quic/quic_wire_pkt.c @@ -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; } } diff --git a/test/quic_record_test.c b/test/quic_record_test.c index 436fe387ecb..523d8ec27b0 100644 --- a/test/quic_record_test.c +++ b/test/quic_record_test.c @@ -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;