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 <pauli@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18949)
This commit is contained in:
Hugo Landau 2022-08-15 16:13:28 +01:00 committed by Tomas Mraz
parent 1957148384
commit 948c656c66
12 changed files with 1490 additions and 336 deletions

View File

@ -13,6 +13,7 @@
# include <openssl/ssl.h>
# include "internal/quic_types.h"
# include "internal/bio_addr.h"
# include "internal/time.h"
/*
* QUIC Demuxer
@ -110,6 +111,12 @@ struct quic_urxe_st {
* is zeroed.
*/
BIO_ADDR peer, local;
/*
* Time at which datagram was received (or ossl_time_zero()) if a now
* function was not provided).
*/
OSSL_TIME time;
};
/* Accessors for URXE buffer. */
@ -163,10 +170,16 @@ typedef void (ossl_quic_demux_cb_fn)(QUIC_URXE *e, void *arg);
* connections used on a socket. default_urxe_alloc_len is the buffer size to
* receive datagrams into; it should be a value large enough to contain any
* received datagram according to local MTUs, etc.
*
* now is an optional function used to determine the time a datagram was
* received. now_arg is an opaque argument passed to the function. If now is
* NULL, ossl_time_zero() is used as the datagram reception time.
*/
QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
size_t short_conn_id_len,
size_t default_urxe_alloc_len);
size_t default_urxe_alloc_len,
OSSL_TIME (*now)(void *arg),
void *now_arg);
/*
* Destroy a demuxer. All URXEs must have been released back to the demuxer

View File

@ -34,6 +34,9 @@ typedef struct ossl_qrx_args_st {
/* Initial reference PN used for RX. */
QUIC_PN init_largest_pn[QUIC_PN_SPACE_NUM];
/* Initial key phase. For debugging use only; always 0 in real use. */
unsigned char init_key_phase_bit;
} OSSL_QRX_ARGS;
/* Instantiates a new QRX. */
@ -92,7 +95,7 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *qrx,
*
* To transition the RX side of an EL from WAITING_FOR_KEYS to HAVE_KEYS, call
* ossl_qrx_provide_secret (for the INITIAL EL, use of
* ossl_qrl_provide_initial_secret is recommended).
* ossl_quic_provide_initial_secret is recommended).
*
* Once keys have been provisioned for an EL, you call
* ossl_qrx_discard_enc_level to transition the EL to the DISCARDED state. You
@ -129,14 +132,14 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *qrx,
* the QRX if it is not needed, for example if the QRX is being instantiated to
* take over handling of an existing connection which has already passed the
* INITIAL phase. This avoids the unnecessary derivation of INITIAL keys where
* they are not needed. In the ordinary case, ossl_qrx_provide_secret_initial
* they are not needed. In the ordinary case, ossl_quic_provide_initial_secret
* should be called immediately after instantiation.
*/
/*
* Provides a secret to the QRX, which arises due to an encryption level change.
* enc_level is a QUIC_ENC_LEVEL_* value. To initialise the INITIAL encryption
* level, it is recommended to use ossl_qrl_provide_initial_secret instead.
* level, it is recommended to use ossl_quic_provide_initial_secret instead.
*
* You should seek to call this function for a given EL before packets of that
* EL arrive and are processed by the QRX. However, if packets have already
@ -144,7 +147,7 @@ int ossl_qrx_remove_dst_conn_id(OSSL_QRX *qrx,
* processing of them when this function is eventually called for the EL in
* question.
*
* suite_id is a QRX_SUITE_* value which determines the AEAD function used for
* suite_id is a QRL_SUITE_* value which determines the AEAD function used for
* the QRX.
*
* The secret passed is used directly to derive the "quic key", "quic iv" and
@ -218,6 +221,15 @@ typedef struct ossl_qrx_pkt_st {
* datagrams containing INITIAL packets), as required by RFC 9000.
*/
size_t datagram_len;
/* The PN which was decoded for the packet, if the packet has a PN field. */
QUIC_PN pn;
/*
* Time the packet was received, or ossl_time_zero() if the demuxer is not
* using a now() function.
*/
OSSL_TIME time;
} OSSL_QRX_PKT;
/*
@ -226,9 +238,9 @@ typedef struct ossl_qrx_pkt_st {
* On success, all fields of *pkt are filled and 1 is returned.
* Else, returns 0.
*
* The resources referenced by pkt->hdr, pkt->data and pkt->peer will remain
* allocated at least until the user frees them by calling ossl_qrx_release_pkt,
* which must be called once you are done with the packet.
* The resources referenced by pkt->hdr, pkt->hdr->data and pkt->peer will
* remain allocated at least until the user frees them by calling
* ossl_qrx_release_pkt, which must be called once you are done with the packet.
*/
int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt);
@ -326,7 +338,7 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
* Two keys and a timer
*
* "Alternatively, endpoints can retain only two sets of packet protection
* neys, swapping previous keys for next after enough time has passed to
* keys, swapping previous keys for next after enough time has passed to
* allow for reordering in the network. In this case, the KP bit alone can
* be used to select keys."
*
@ -342,11 +354,11 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
* PROVISIONED
* _______________________________
* | |
* UNPROVISIONED --|----> NORMAL <----------\ |------> DROPPED
* UNPROVISIONED --|----> NORMAL <----------\ |------> DISCARDED
* | | | |
* | | | |
* | v | |
* | UPDATE_CONFIRMED | |
* | UPDATING | |
* | | | |
* | | | |
* | v | |
@ -362,16 +374,16 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
* recorded. When a flipped Key Phase bit is detected, the RX attempts to
* decrypt and authenticate the received packet with the 'next' keys rather than
* the 'current' keys. If (and only if) this authentication is successful, we
* move to the UPDATE_CONFIRMED state. (An attacker in the network could flip
* move to the UPDATING state. (An attacker in the network could flip
* the Key Phase bit randomly, so it is essential we do nothing until AEAD
* authentication is complete.)
*
* In the UPDATE_CONFIRMED state, we know a key update is occurring and record
* In the UPDATING state, we know a key update is occurring and record
* the new Key Phase bit value as the newly current value, but we still keep the
* old keys around so that we can still process any packets which were still in
* flight when the key update was initiated. In the UPDATE_CONFIRMED state, a
* flight when the key update was initiated. In the UPDATING state, a
* Key Phase bit value different to the current expected value is treated not as
* the initiation of another key update, but a reference to our old keys.
* the initiation of another key update, but a reference to our old keys.
*
* Eventually we will be reasonably sure we are not going to receive any more
* packets with the old keys. At this point, we can transition to the COOLDOWN
@ -386,21 +398,25 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
* as a request for a Key Update, but this request is ignored and the packet is
* treated as malformed. We do this to allow mitigation against malicious peers
* trying to initiate an excessive number of Key Updates. The timeout for the
* transition from UPDATE_CONFIRMED to COOLDOWN is recommended as adequate for
* transition from UPDATING to COOLDOWN is recommended as adequate for
* this purpose in itself by the RFC, so the normal additional timeout value for
* the transition from COOLDOWN to normal is zero (immediate transition).
*
* A summary of each state:
*
* Exp KP Uses Keys KS0 KS1 If Non-Expected KP Bit
* ------ --------- ------ ----- ----------------------
* NORMAL 0 Keyset 0 Gen 0 Gen 1 UPDATE_CONFIRMED
* UPDATE_CONFIRMED 1 Keyset 1 Gen 0 Gen 1 Use Keyset 0
* COOLDOWN 1 Keyset 1 Erased Gen 1 Ignore Packet
* Epoch Exp KP Uses Keys KS0 KS1 If Non-Expected KP Bit
* ----- ------ --------- ------ ----- ----------------------
* NORMAL 0 0 Keyset 0 Gen 0 Gen 1 UPDATING
* UPDATING 1 1 Keyset 1 Gen 0 Gen 1 Use Keyset 0
* COOLDOWN 1 1 Keyset 1 Erased Gen 1 Ignore Packet (*)
*
* NORMAL 1 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
* 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 (*)
*
* (*) 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.
*
* 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
* 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 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

View File

@ -163,7 +163,18 @@ typedef struct ossl_qtx_pkt_st {
* encoded packet should be by setting pkt->hdr->pn_len. This function takes
* care of the PN encoding. Set pkt->pn to the desired PN.
*
* Note that 1-RTT packets do not have a DCID Length field, therefore the DCID
* length must be understood contextually. This function assumes the caller
* knows what it is doing and will serialize a DCID of whatever length is given.
* It is the caller's responsibility to ensure it uses a consistent DCID length
* for communication with any given set of remote peers.
*
* The packet is queued regardless of whether it is able to be sent immediately.
* This enables packets to be batched and sent at once on systems which support
* system calls to send multiple datagrams in a single system call (see
* BIO_sendmmsg). To flush queued datagrams to the network, see
* ossl_qtx_flush_net().
*
* Returns 1 on success or 0 on failure.
*/
int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt);

View File

@ -31,16 +31,16 @@ static ossl_unused ossl_inline uint32_t
ossl_quic_enc_level_to_pn_space(uint32_t enc_level)
{
switch (enc_level) {
case QUIC_ENC_LEVEL_INITIAL:
return QUIC_PN_SPACE_INITIAL;
case QUIC_ENC_LEVEL_HANDSHAKE:
return QUIC_PN_SPACE_HANDSHAKE;
case QUIC_ENC_LEVEL_0RTT:
case QUIC_ENC_LEVEL_1RTT:
return QUIC_PN_SPACE_APP;
default:
assert(0);
return QUIC_PN_SPACE_APP;
case QUIC_ENC_LEVEL_INITIAL:
return QUIC_PN_SPACE_INITIAL;
case QUIC_ENC_LEVEL_HANDSHAKE:
return QUIC_PN_SPACE_HANDSHAKE;
case QUIC_ENC_LEVEL_0RTT:
case QUIC_ENC_LEVEL_1RTT:
return QUIC_PN_SPACE_APP;
default:
assert(0);
return QUIC_PN_SPACE_APP;
}
}

View File

@ -46,6 +46,59 @@ ossl_quic_pkt_type_to_enc_level(uint32_t pkt_type)
}
}
/* Determine if a packet type contains an encrypted payload. */
static ossl_inline ossl_unused int
ossl_quic_pkt_type_is_encrypted(uint32_t pkt_type)
{
switch (pkt_type) {
case QUIC_PKT_TYPE_RETRY:
case QUIC_PKT_TYPE_VERSION_NEG:
return 0;
default:
return 1;
}
}
/* Determine if a packet type contains a PN field. */
static ossl_inline ossl_unused int
ossl_quic_pkt_type_has_pn(uint32_t pkt_type)
{
/*
* Currently a packet has a PN iff it is encrypted. This could change
* someday.
*/
return ossl_quic_pkt_type_is_encrypted(pkt_type);
}
/*
* Determine if a packet type can appear with other packets in a datagram. Some
* packet types must be the sole packet in a datagram.
*/
static ossl_inline ossl_unused int
ossl_quic_pkt_type_can_share_dgram(uint32_t pkt_type)
{
/*
* Currently only the encrypted packet types can share a datagram. This
* could change someday.
*/
return ossl_quic_pkt_type_is_encrypted(pkt_type);
}
/*
* Determine if the packet type must come at the end of the datagram (due to the
* lack of a length field).
*/
static ossl_inline ossl_unused int
ossl_quic_pkt_type_must_be_last(uint32_t pkt_type)
{
/*
* Any packet type which cannot share a datagram obviously must come last.
* 1-RTT also must come last as it lacks a length field.
*/
return !ossl_quic_pkt_type_can_share_dgram(pkt_type)
|| pkt_type == QUIC_PKT_TYPE_1RTT;
}
/*
* Smallest possible QUIC packet size as per RFC (aside from version negotiation
* packets).
@ -62,7 +115,7 @@ typedef struct quic_pkt_hdr_ptrs_st QUIC_PKT_HDR_PTRS;
*
* Functions to apply and remove QUIC packet header protection. A header
* protector is initialised using ossl_quic_hdr_protector_init and must be
* destroyed using ossl_quic_hdr_protector_destroy when no longer needed.
* destroyed using ossl_quic_hdr_protector_cleanup when no longer needed.
*/
typedef struct quic_hdr_protector_st {
OSSL_LIB_CTX *libctx;
@ -107,7 +160,7 @@ int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
* OSSL_QUIC_HDR_PROTECTOR structure which has not been initialized, or which
* has already been destroyed.
*/
void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr);
void ossl_quic_hdr_protector_cleanup(QUIC_HDR_PROTECTOR *hpr);
/*
* Removes header protection from a packet. The packet payload must currently be
@ -255,11 +308,11 @@ typedef struct quic_pkt_hdr_st {
/* [L] Version field. Valid if (type != 1RTT). */
uint32_t version;
/* [ALL] Number of bytes in the connection ID (max 20). Always valid. */
/* [ALL] The destination connection ID. Always valid. */
QUIC_CONN_ID dst_conn_id;
/*
* [L] Number of bytes in the connection ID (max 20).
* [L] The source connection ID.
* Valid if (type != 1RTT).
*/
QUIC_CONN_ID src_conn_id;
@ -285,9 +338,13 @@ typedef struct quic_pkt_hdr_st {
size_t token_len;
/*
* [i0h] Payload length in bytes.
* [ALL] Payload length in bytes.
*
* Valid if (type != 1RTT && type != RETRY && version).
* Though 1-RTT, Retry and Version Negotiation packets do not contain an
* explicit length field, this field is always valid and is used by the
* packet header encoding and decoding routines to describe the payload
* length, regardless of whether the packet type encoded or decoded uses an
* explicit length indication.
*/
size_t len;

View File

@ -3,13 +3,13 @@
#include "internal/common.h"
#include <openssl/lhash.h>
#define OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL 32
#define DEMUX_MAX_MSGS_PER_CALL 32
void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e)
{
/* Must be in list currently. */
OPENSSL_assert((e->prev != NULL || l->head == e)
&& (e->next != NULL || l->tail == e));
assert((e->prev != NULL || l->head == e)
&& (e->next != NULL || l->tail == e));
if (e->prev != NULL)
e->prev->next = e->next;
@ -27,7 +27,7 @@ void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e)
void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e)
{
/* Must not be in list. */
OPENSSL_assert(e->prev == NULL && e->next == NULL);
assert(e->prev == NULL && e->next == NULL);
if (l->head == NULL) {
l->head = l->tail = e;
@ -44,7 +44,7 @@ void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e)
void ossl_quic_urxe_insert_tail(QUIC_URXE_LIST *l, QUIC_URXE *e)
{
/* Must not be in list. */
OPENSSL_assert(e->prev == NULL && e->next == NULL);
assert(e->prev == NULL && e->next == NULL);
if (l->tail == NULL) {
l->head = l->tail = e;
@ -103,6 +103,10 @@ struct quic_demux_st {
/* Default URXE buffer size in bytes. */
size_t default_urxe_alloc_len;
/* Time retrieval callback. */
OSSL_TIME (*now)(void *arg);
void *now_arg;
/* Hashtable mapping connection IDs to QUIC_DEMUX_CONN structures. */
LHASH_OF(QUIC_DEMUX_CONN) *conns_by_id;
@ -128,7 +132,9 @@ struct quic_demux_st {
QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
size_t short_conn_id_len,
size_t default_urxe_alloc_len)
size_t default_urxe_alloc_len,
OSSL_TIME (*now)(void *arg),
void *now_arg)
{
QUIC_DEMUX *demux;
@ -139,6 +145,8 @@ QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
demux->net_bio = net_bio;
demux->short_conn_id_len = short_conn_id_len;
demux->default_urxe_alloc_len = default_urxe_alloc_len;
demux->now = now;
demux->now_arg = now_arg;
demux->conns_by_id
= lh_QUIC_DEMUX_CONN_new(demux_conn_hash, demux_conn_cmp);
@ -194,7 +202,7 @@ static QUIC_DEMUX_CONN *demux_get_by_conn_id(QUIC_DEMUX *demux,
QUIC_DEMUX_CONN key;
if (dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
return 0;
return NULL;
key.dst_conn_id = *dst_conn_id;
return lh_QUIC_DEMUX_CONN_retrieve(demux->conns_by_id, &key);
@ -329,9 +337,10 @@ static int demux_ensure_free_urxe(QUIC_DEMUX *demux, size_t min_num_free)
*/
static int demux_recv(QUIC_DEMUX *demux)
{
BIO_MSG msg[OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL];
ossl_ssize_t rd, i;
BIO_MSG msg[DEMUX_MAX_MSGS_PER_CALL];
size_t rd, i;
QUIC_URXE *urxe = demux->urx_free.head, *unext;
OSSL_TIME now;
/* This should never be called when we have any pending URXE. */
assert(demux->urx_pending.head == NULL);
@ -363,15 +372,18 @@ static int demux_recv(QUIC_DEMUX *demux)
BIO_ADDR_clear(&urxe->local);
}
rd = BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0);
if (rd <= 0)
if (!BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0, &rd))
return 0;
now = demux->now != NULL ? demux->now(demux->now_arg) : ossl_time_zero();
urxe = demux->urx_free.head;
for (i = 0; i < rd; ++i, urxe = unext) {
unext = urxe->next;
/* Set URXE with actual length of received datagram. */
urxe->data_len = msg[i].data_len;
/* Time we received datagram. */
urxe->time = now;
/* Move from free list to pending list. */
ossl_quic_urxe_remove(&demux->urx_free, urxe);
--demux->num_urx_free;
@ -413,7 +425,8 @@ static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
QUIC_DEMUX_CONN *conn;
/* The next URXE we process should be at the head of the pending list. */
OPENSSL_assert(e == demux->urx_pending.head);
if (!ossl_assert(e == demux->urx_pending.head))
return 0;
conn = demux_identify_conn(demux, e);
if (conn == NULL) {
@ -457,7 +470,7 @@ int ossl_quic_demux_pump(QUIC_DEMUX *demux)
int ret;
if (demux->urx_pending.head == NULL) {
ret = demux_ensure_free_urxe(demux, OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL);
ret = demux_ensure_free_urxe(demux, DEMUX_MAX_MSGS_PER_CALL);
if (ret != 1)
return 0;
@ -517,7 +530,7 @@ int ossl_quic_demux_inject(QUIC_DEMUX *demux,
void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
QUIC_URXE *e)
{
OPENSSL_assert(e->prev == NULL && e->next == NULL);
assert(e->prev == NULL && e->next == NULL);
ossl_quic_urxe_insert_tail(&demux->urx_free, e);
++demux->num_urx_free;
}

View File

@ -52,6 +52,9 @@ struct rxe_st {
/* Addresses copied from URXE. */
BIO_ADDR peer, local;
/* Time we received the packet (not when we processed it). */
OSSL_TIME time;
/* Total length of the datagram which contained this packet. */
size_t datagram_len;
@ -147,9 +150,23 @@ struct ossl_qrx_st {
/* Bytes we have received since this counter was last cleared. */
uint64_t bytes_received;
/*
* Number of forged packets we have received since the QRX was instantiated.
* Note that as per RFC 9001, this is connection-level state; it is not per
* EL and is not reset by a key update.
*/
uint64_t forged_pkt_count;
/* Validation callback. */
ossl_qrx_early_validation_cb *validation_cb;
void *validation_cb_arg;
ossl_qrx_early_validation_cb *validation_cb;
void *validation_cb_arg;
/* Key update callback. */
ossl_qrx_key_update_cb *key_update_cb;
void *key_update_cb_arg;
/* Initial key phase. For debugging use only; always 0 in real use. */
unsigned char init_key_phase_bit;
};
static void qrx_on_rx(QUIC_URXE *urxe, void *arg);
@ -173,26 +190,31 @@ OSSL_QRX *ossl_qrx_new(const OSSL_QRX_ARGS *args)
qrx->propq = args->propq;
qrx->demux = args->demux;
qrx->short_conn_id_len = args->short_conn_id_len;
qrx->init_key_phase_bit = args->init_key_phase_bit;
return qrx;
}
static void qrx_cleanup_rxl(RXE_LIST *l)
{
RXE *e, *enext;
for (e = l->head; e != NULL; e = enext) {
enext = e->next;
OPENSSL_free(e);
}
l->head = l->tail = NULL;
}
static void qrx_cleanup_urxl(OSSL_QRX *qrx, QUIC_URXE_LIST *l)
{
QUIC_URXE *e, *enext;
for (e = l->head; e != NULL; e = enext) {
enext = e->next;
ossl_quic_demux_release_urxe(qrx->demux, e);
}
l->head = l->tail = NULL;
}
@ -211,7 +233,7 @@ void ossl_qrx_free(OSSL_QRX *qrx)
/* Drop keying material and crypto resources. */
for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i)
ossl_qrl_enc_level_set_discard(&qrx->el_set, i, 1);
ossl_qrl_enc_level_set_discard(&qrx->el_set, i);
OPENSSL_free(qrx);
}
@ -265,7 +287,9 @@ int ossl_qrx_provide_secret(OSSL_QRX *qrx, uint32_t enc_level,
suite_id,
md,
secret,
secret_len))
secret_len,
qrx->init_key_phase_bit,
/*is_tx=*/0))
return 0;
/*
@ -282,7 +306,7 @@ int ossl_qrx_discard_enc_level(OSSL_QRX *qrx, uint32_t enc_level)
if (enc_level >= QUIC_ENC_LEVEL_NUM)
return 0;
ossl_qrl_enc_level_set_discard(&qrx->el_set, enc_level, 1);
ossl_qrl_enc_level_set_discard(&qrx->el_set, enc_level);
return 1;
}
@ -372,7 +396,7 @@ static RXE *qrx_resize_rxe(RXE_LIST *rxl, RXE *rxe, size_t n)
*/
rxe2 = OPENSSL_realloc(rxe, sizeof(RXE) + n);
if (rxe2 == NULL)
/* original RXE is still in tact unchanged */
/* original RXE is still intact unchanged */
return NULL;
if (rxe != rxe2) {
@ -480,8 +504,7 @@ static int qrx_validate_hdr_early(OSSL_QRX *qrx, RXE *rxe,
return 0;
/* Version negotiation and retry packets must be the first packet. */
if (first_rxe != NULL && (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
|| rxe->hdr.type == QUIC_PKT_TYPE_RETRY))
if (first_rxe != NULL && !ossl_quic_pkt_type_can_share_dgram(rxe->hdr.type))
return 0;
/*
@ -517,6 +540,44 @@ static int qrx_validate_hdr(OSSL_QRX *qrx, RXE *rxe)
return 1;
}
/* Retrieves the correct cipher context for an EL and key phase. */
static size_t qrx_get_cipher_ctx_idx(OSSL_QRX *qrx, OSSL_QRL_ENC_LEVEL *el,
uint32_t enc_level,
unsigned char key_phase_bit)
{
if (enc_level != QUIC_ENC_LEVEL_1RTT)
return 0;
if (!ossl_assert(key_phase_bit <= 1))
return SIZE_MAX;
/*
* RFC 9001 requires that we not create timing channels which could reveal
* the decrypted value of the Key Phase bit. We usually handle this by
* keeping the cipher contexts for both the current and next key epochs
* around, so that we just select a cipher context blindly using the key
* phase bit, which is time-invariant.
*
* In the COOLDOWN state, we only have one keyslot/cipher context. RFC 9001
* suggests an implementation strategy to avoid creating a timing channel in
* this case:
*
* Endpoints can use randomized packet protection keys in place of
* discarded keys when key updates are not yet permitted.
*
* Rather than use a randomised key, we simply use our existing key as it
* will fail AEAD verification anyway. This avoids the need to keep around a
* dedicated garbage key.
*
* Note: Accessing different cipher contexts is technically not
* timing-channel safe due to microarchitectural side channels, but this is
* the best we can reasonably do and appears to be directly suggested by the
* RFC.
*/
return el->state == QRL_EL_STATE_PROV_COOLDOWN ? el->key_epoch & 1
: key_phase_bit;
}
/*
* Tries to decrypt a packet payload.
*
@ -530,13 +591,15 @@ static int qrx_decrypt_pkt_body(OSSL_QRX *qrx, unsigned char *dst,
const unsigned char *src,
size_t src_len, size_t *dec_len,
const unsigned char *aad, size_t aad_len,
QUIC_PN pn, uint32_t enc_level)
QUIC_PN pn, uint32_t enc_level,
unsigned char key_phase_bit)
{
int l = 0, l2 = 0;
unsigned char nonce[EVP_MAX_IV_LENGTH];
size_t nonce_len, i;
size_t nonce_len, i, cctx_idx;
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
enc_level, 1);
EVP_CIPHER_CTX *cctx;
if (src_len > INT_MAX || aad_len > INT_MAX)
return 0;
@ -548,45 +611,51 @@ static int qrx_decrypt_pkt_body(OSSL_QRX *qrx, unsigned char *dst,
if (el->tag_len >= src_len)
return 0;
/*
/*
* If we have failed to authenticate a certain number of ciphertexts, refuse
* to decrypt any more ciphertexts.
*/
if (el->op_count >= ossl_qrl_get_suite_max_forged_pkt(el->suite_id))
if (qrx->forged_pkt_count >= ossl_qrl_get_suite_max_forged_pkt(el->suite_id))
return 0;
cctx_idx = qrx_get_cipher_ctx_idx(qrx, el, enc_level, key_phase_bit);
if (!ossl_assert(cctx_idx < OSSL_NELEM(el->cctx)))
return 0;
cctx = el->cctx[cctx_idx];
/* Construct nonce (nonce=IV ^ PN). */
nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
nonce_len = EVP_CIPHER_CTX_get_iv_length(cctx);
if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
return 0;
memcpy(nonce, el->iv, nonce_len);
memcpy(nonce, el->iv[cctx_idx], nonce_len);
for (i = 0; i < sizeof(QUIC_PN); ++i)
nonce[nonce_len - i - 1] ^= (unsigned char)(pn >> (i * 8));
/* type and key will already have been setup; feed the IV. */
if (EVP_CipherInit_ex(el->cctx, NULL,
if (EVP_CipherInit_ex(cctx, NULL,
NULL, NULL, nonce, /*enc=*/0) != 1)
return 0;
/* Feed the AEAD tag we got so the cipher can validate it. */
if (EVP_CIPHER_CTX_ctrl(el->cctx, EVP_CTRL_AEAD_SET_TAG,
if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_SET_TAG,
el->tag_len,
(unsigned char *)src + src_len - el->tag_len) != 1)
return 0;
/* Feed AAD data. */
if (EVP_CipherUpdate(el->cctx, NULL, &l, aad, aad_len) != 1)
if (EVP_CipherUpdate(cctx, NULL, &l, aad, aad_len) != 1)
return 0;
/* Feed encrypted packet body. */
if (EVP_CipherUpdate(el->cctx, dst, &l, src, src_len - el->tag_len) != 1)
if (EVP_CipherUpdate(cctx, dst, &l, src, src_len - el->tag_len) != 1)
return 0;
/* Ensure authentication succeeded. */
if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1) {
if (EVP_CipherFinal_ex(cctx, NULL, &l2) != 1) {
/* Authentication failed, increment failed auth counter. */
++el->op_count;
++qrx->forged_pkt_count;
return 0;
}
@ -599,6 +668,15 @@ static ossl_inline void ignore_res(int x)
/* No-op. */
}
static void qrx_key_update_initiated(OSSL_QRX *qrx)
{
if (!ossl_qrl_enc_level_set_key_update(&qrx->el_set, QUIC_ENC_LEVEL_1RTT))
return;
if (qrx->key_update_cb != NULL)
qrx->key_update_cb(qrx->key_update_cb_arg);
}
/* Process a single packet in a datagram. */
static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
PACKET *pkt, size_t pkt_idx,
@ -614,6 +692,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
char need_second_decode = 0, already_processed = 0;
QUIC_PKT_HDR_PTRS ptrs;
uint32_t pn_space, enc_level;
OSSL_QRL_ENC_LEVEL *el = NULL;
/*
* Get a free RXE. If we need to allocate a new one, use the packet length
@ -634,8 +713,8 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
*/
need_second_decode = !pkt_is_marked(&urxe->hpr_removed, pkt_idx);
if (!ossl_quic_wire_decode_pkt_hdr(pkt,
qrx->short_conn_id_len,
need_second_decode, &rxe->hdr, &ptrs))
qrx->short_conn_id_len,
need_second_decode, &rxe->hdr, &ptrs))
goto malformed;
/*
@ -646,7 +725,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
/*
* Make a note of the first RXE so we can later ensure the destination
* connection IDs of all packets in a datagram mater.
* connection IDs of all packets in a datagram match.
*/
if (pkt_idx == 0)
*first_rxe = rxe;
@ -657,10 +736,13 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
*/
if (already_processed
|| !qrx_validate_hdr_early(qrx, rxe, pkt_idx == 0 ? NULL : *first_rxe))
/*
* Already processed packets are handled identically to malformed
* packets; i.e., they are ignored.
*/
goto malformed;
if (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
|| rxe->hdr.type == QUIC_PKT_TYPE_RETRY) {
if (!ossl_quic_pkt_type_is_encrypted(rxe->hdr.type)) {
/*
* Version negotiation and retry packets are a special case. They do not
* contain a payload which needs decrypting and have no header
@ -679,7 +761,8 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
memcpy(rxe_data(rxe), rxe->hdr.data, rxe->hdr.len);
pkt_mark(&urxe->processed, pkt_idx);
rxe->hdr.data = rxe_data(rxe);
rxe->hdr.data = rxe_data(rxe);
rxe->pn = QUIC_PN_INVALID;
/* Move RXE to pending. */
rxe_remove(&qrx->rx_free, rxe);
@ -728,11 +811,10 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
/* Now remove header protection. */
*pkt = orig_pkt;
if (need_second_decode) {
OSSL_QRL_ENC_LEVEL *el
= ossl_qrl_enc_level_set_get(&qrx->el_set, enc_level, 1);
el = ossl_qrl_enc_level_set_get(&qrx->el_set, enc_level, 1);
assert(el != NULL); /* Already checked above */
assert(el != NULL); /* Already checked above */
if (need_second_decode) {
if (!ossl_quic_hdr_protector_decrypt(&el->hpr, &ptrs))
goto malformed;
@ -757,7 +839,7 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
* HANDSHAKE packet.
*/
if (enc_level == QUIC_ENC_LEVEL_HANDSHAKE)
ossl_qrl_enc_level_set_discard(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL, 1);
ossl_qrl_enc_level_set_discard(&qrx->el_set, QUIC_ENC_LEVEL_INITIAL);
/*
* The AAD data is the entire (unprotected) packet header including the PN.
@ -792,9 +874,19 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
*/
dst = (unsigned char *)rxe_data(rxe) + i;
if (!qrx_decrypt_pkt_body(qrx, dst, rxe->hdr.data, rxe->hdr.len,
&dec_len, sop, aad_len, rxe->pn, enc_level))
&dec_len, sop, aad_len, rxe->pn, enc_level,
rxe->hdr.key_phase))
goto malformed;
/*
* At this point, we have successfully authenticated the AEAD tag and no
* longer need to worry about exposing the Key Phase bit in timing channels.
* Check for a Key Phase bit differing from our expectation.
*/
if (rxe->hdr.type == QUIC_PKT_TYPE_1RTT
&& rxe->hdr.key_phase != (el->key_epoch & 1))
qrx_key_update_initiated(qrx);
/*
* We have now successfully decrypted the packet payload. If there are
* additional packets in the datagram, it is possible we will fail to
@ -826,9 +918,10 @@ static int qrx_process_pkt(OSSL_QRX *qrx, QUIC_URXE *urxe,
if (rxe->pn > qrx->largest_pn[pn_space])
qrx->largest_pn[pn_space] = rxe->pn;
/* Copy across network addresses from URXE to RXE. */
/* Copy across network addresses and RX time from URXE to RXE. */
rxe->peer = urxe->peer;
rxe->local = urxe->local;
rxe->time = urxe->time;
/* Move RXE to pending. */
rxe_remove(&qrx->rx_free, rxe);
@ -915,7 +1008,7 @@ static int qrx_process_datagram(OSSL_QRX *qrx, QUIC_URXE *e,
* we should still try to process any packets following it.
*
* In the case where the packet is so malformed we can't determine its
* lenngth, qrx_process_pkt will take care of advancing to the end of
* length, qrx_process_pkt will take care of advancing to the end of
* the packet, so we will exit the loop automatically in this case.
*/
if (qrx_process_pkt(qrx, e, &pkt, pkt_idx, &first_rxe, data_len))
@ -927,7 +1020,7 @@ static int qrx_process_datagram(OSSL_QRX *qrx, QUIC_URXE *e,
}
/* Process a single pending URXE. */
static int qrx_process_one_urxl(OSSL_QRX *qrx, QUIC_URXE *e)
static int qrx_process_one_urxe(OSSL_QRX *qrx, QUIC_URXE *e)
{
int was_deferred;
@ -958,12 +1051,12 @@ static int qrx_process_one_urxl(OSSL_QRX *qrx, QUIC_URXE *e)
}
/* Process any pending URXEs to generate pending RXEs. */
static int qrx_process_urxl(OSSL_QRX *qrx)
static int qrx_process_pending_urxl(OSSL_QRX *qrx)
{
QUIC_URXE *e;
while ((e = qrx->urx_pending.head) != NULL)
if (!qrx_process_one_urxl(qrx, e))
if (!qrx_process_one_urxe(qrx, e))
return 0;
return 1;
@ -974,7 +1067,7 @@ int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt)
RXE *rxe;
if (!ossl_qrx_processed_read_pending(qrx)) {
if (!qrx_process_urxl(qrx))
if (!qrx_process_pending_urxl(qrx))
return 0;
if (!ossl_qrx_processed_read_pending(qrx))
@ -987,6 +1080,8 @@ int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt)
pkt->handle = rxe;
pkt->hdr = &rxe->hdr;
pkt->pn = rxe->pn;
pkt->time = rxe->time;
pkt->peer
= BIO_ADDR_family(&rxe->peer) != AF_UNSPEC ? &rxe->peer : NULL;
pkt->local
@ -1020,17 +1115,51 @@ int ossl_qrx_set_early_validation_cb(OSSL_QRX *qrx,
return 1;
}
uint64_t ossl_qrx_get_cur_epoch_forged_pkt_count(OSSL_QRX *qrx,
uint32_t enc_level)
int ossl_qrx_set_key_update_cb(OSSL_QRX *qrx,
ossl_qrx_key_update_cb *cb,
void *cb_arg)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
enc_level, 1);
return el == NULL ? UINT64_MAX : el->op_count;
qrx->key_update_cb = cb;
qrx->key_update_cb_arg = cb_arg;
return 1;
}
uint64_t ossl_qrx_get_max_epoch_forged_pkt_count(OSSL_QRX *qrx,
uint32_t enc_level)
uint64_t ossl_qrx_get_key_epoch(OSSL_QRX *qrx)
{
OSSL_QRL_ENC_LEVEL *el = ossl_qrl_enc_level_set_get(&qrx->el_set,
QUIC_ENC_LEVEL_1RTT, 1);
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_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);

View File

@ -13,10 +13,13 @@ static const unsigned char quic_v1_key_label[] = {
static const unsigned char quic_v1_hp_label[] = {
0x71, 0x75, 0x69, 0x63, 0x20, 0x68, 0x70 /* "quic hp" */
};
static const unsigned char quic_v1_ku_label[] = {
0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x75 /* "quic ku" */
};
OSSL_QRL_ENC_LEVEL *ossl_qrl_enc_level_set_get(OSSL_QRL_ENC_LEVEL_SET *els,
uint32_t enc_level,
int require_valid)
int require_prov)
{
OSSL_QRL_ENC_LEVEL *el;
@ -25,8 +28,15 @@ OSSL_QRL_ENC_LEVEL *ossl_qrl_enc_level_set_get(OSSL_QRL_ENC_LEVEL_SET *els,
el = &els->el[enc_level];
if (require_valid && (el->cctx == NULL || el->discarded))
return NULL;
if (require_prov)
switch (el->state) {
case QRL_EL_STATE_PROV_NORMAL:
case QRL_EL_STATE_PROV_UPDATING:
case QRL_EL_STATE_PROV_COOLDOWN:
break;
default:
return NULL;
}
return el;
}
@ -36,27 +46,139 @@ 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;
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;
}
}
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);
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;
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(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(el->suite_id)
|| secret_len > EVP_MAX_KEY_LENGTH)
return 0;
assert(el->cctx[keyslot] == NULL);
/* Derive "quic iv" key. */
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[keyslot], iv_len, 0))
goto err;
/* Derive "quic key" key. */
if (!tls13_hkdf_expand_ex(el->libctx, el->propq,
el->md,
secret,
quic_v1_key_label,
sizeof(quic_v1_key_label),
NULL, 0,
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;
}
/*
* 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,
@ -64,58 +186,41 @@ 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)
{
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;
EVP_CIPHER *cipher = NULL;
EVP_CIPHER_CTX *cctx = NULL;
int own_md = 0, have_hpr = 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->discarded)
/* Should not be trying to reinitialise an EL which was discarded. */
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;
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);
init_keyslot = is_tx ? 0 : init_key_phase_bit;
hpr_key_len = ossl_qrl_get_suite_hdr_prot_key_len(suite_id);
if (cipher_name == NULL)
return 0;
if (secret_len != ossl_qrl_get_suite_secret_len(suite_id))
if (hpr_key_len == 0)
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)
md = EVP_MD_fetch(libctx, md_name, propq);
if (md == NULL)
return 0;
own_md = 1;
}
/* Derive "quic iv" key. */
if (!tls13_hkdf_expand_ex(libctx, propq,
md,
secret,
quic_v1_iv_label,
sizeof(quic_v1_iv_label),
NULL, 0,
el->iv, iv_len, 0))
goto err;
/* Derive "quic key" key. */
if (!tls13_hkdf_expand_ex(libctx, propq,
md,
secret,
quic_v1_key_label,
sizeof(quic_v1_key_label),
NULL, 0,
key, key_len, 0))
goto err;
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,
@ -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;
/* Create and initialise cipher context. */
if ((cipher = EVP_CIPHER_fetch(libctx, cipher_name, propq)) == 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;
if ((cctx = EVP_CIPHER_CTX_new()) == NULL)
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, 0))
goto err;
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;
/* Zeroize intermediate keys. */
OPENSSL_cleanse(key, sizeof(key));
/*
* 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));
EVP_CIPHER_free(cipher);
OPENSSL_cleanse(ku_key, sizeof(ku_key));
el->state = QRL_EL_STATE_PROV_NORMAL;
return 1;
err:
if (have_hpr)
ossl_quic_hdr_protector_destroy(&el->hpr);
EVP_CIPHER_CTX_free(cctx);
EVP_CIPHER_free(cipher);
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;
}
/* 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)
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 (el == NULL || !ossl_assert(enc_level == QUIC_ENC_LEVEL_1RTT))
return 0;
if (el->state != QRL_EL_STATE_PROV_NORMAL)
return 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;
}
/*
* 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;
}
/* 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 || el->discarded)
return;
if (el == NULL || !ossl_assert(enc_level == QUIC_ENC_LEVEL_1RTT))
return 0;
if (el->cctx != NULL) {
ossl_quic_hdr_protector_destroy(&el->hpr);
/* 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;
}
EVP_CIPHER_CTX_free(el->cctx);
el->cctx = NULL;
/*
* 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];
EVP_MD_free(el->md);
el->md = NULL;
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->state != QRL_EL_STATE_PROV_COOLDOWN)
return 0;
secret_len = ossl_qrl_get_suite_secret_len(el->suite_id);
if (!el_setup_keyslot(els, enc_level, QRL_EL_STATE_PROV_NORMAL,
~el->key_epoch & 1, el->ku, secret_len))
return 0;
/* 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));
if (is_final)
el->discarded = 1;
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);
}
EVP_MD_free(el->md);
el->md = NULL;
el->state = QRL_EL_STATE_DISCARDED;
}

View File

@ -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 {
/* 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;
/* QRL_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;
/* Usage counter. The caller maintains this. */
/* Hash function used for key derivation. */
EVP_MD *md;
/* 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];
/*
* Secret for next key epoch.
*/
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);
/*
* Discard an EL. If is_final is non-zero, no secret can be provided for the EL
* ever again.
* 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. 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

View File

@ -79,28 +79,6 @@ static void txe_insert_tail(TXE_LIST *l, TXE *e)
* QTX
* ===
*/
/* (Encryption level, direction)-specific state. */
typedef struct ossl_qtx_enc_level_st {
/* Hash function used for key derivation. */
EVP_MD *md;
/* Context used for packet body ciphering. */
EVP_CIPHER_CTX *cctx;
/* IV used to construct nonces used for AEAD packet body ciphering. */
unsigned char iv[EVP_MAX_IV_LENGTH];
/* Have we permanently discarded this encryption level? */
unsigned char discarded;
/* QTX_SUITE_* value. */
uint32_t suite_id;
/* Length of authentication tag. */
uint32_t tag_len;
/*
* Cryptographic context used to apply and remove header protection from
* packet headers.
*/
QUIC_HDR_PROTECTOR hpr;
} OSSL_QTX_ENC_LEVEL;
struct ossl_qtx_st {
OSSL_LIB_CTX *libctx;
const char *propq;
@ -135,6 +113,12 @@ struct ossl_qtx_st {
*/
TXE *cons;
size_t cons_count; /* num packets */
/*
* Number of packets transmitted in this key epoch. Used to enforce AEAD
* confidentiality limit.
*/
uint64_t epoch_pkt_count;
};
/* Instantiates a new QTX. */
@ -176,10 +160,11 @@ void ossl_qtx_free(OSSL_QTX *qtx)
/* Free TXE queue data. */
qtx_cleanup_txl(&qtx->pending);
qtx_cleanup_txl(&qtx->free);
OPENSSL_free(qtx->cons);
/* Drop keying material and crypto resources. */
for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i)
ossl_qrl_enc_level_set_discard(&qtx->el_set, i, 1);
ossl_qrl_enc_level_set_discard(&qtx->el_set, i);
OPENSSL_free(qtx);
}
@ -201,7 +186,9 @@ int ossl_qtx_provide_secret(OSSL_QTX *qtx,
suite_id,
md,
secret,
secret_len);
secret_len,
0,
/*is_tx=*/1);
}
int ossl_qtx_discard_enc_level(OSSL_QTX *qtx, uint32_t enc_level)
@ -209,7 +196,7 @@ int ossl_qtx_discard_enc_level(OSSL_QTX *qtx, uint32_t enc_level)
if (enc_level >= QUIC_ENC_LEVEL_NUM)
return 0;
ossl_qrl_enc_level_set_discard(&qtx->el_set, enc_level, 1);
ossl_qrl_enc_level_set_discard(&qtx->el_set, enc_level);
return 1;
}
@ -432,7 +419,7 @@ static int qtx_write_hdr(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
txe->alloc_len - txe->data_len, 0))
return 0;
if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, pkt->hdr->src_conn_id.id_len,
if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, pkt->hdr->dst_conn_id.id_len,
pkt->hdr, ptrs)
|| !WPACKET_get_total_written(&wpkt, &l)) {
WPACKET_finish(&wpkt);
@ -454,6 +441,7 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
= ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1);
unsigned char nonce[EVP_MAX_IV_LENGTH];
size_t nonce_len, i;
EVP_CIPHER_CTX *cctx = NULL;
/* We should not have been called if we do not have key material. */
if (!ossl_assert(el != NULL))
@ -466,21 +454,30 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
if (el->op_count >= ossl_qrl_get_suite_max_pkt(el->suite_id))
return 0;
/*
* TX key update is simpler than for RX; once we initiate a key update, we
* never need the old keys, as we never deliberately send a packet with old
* keys. Thus the EL always uses keyslot 0 for the TX side.
*/
cctx = el->cctx[0];
if (!ossl_assert(cctx != NULL))
return 0;
/* Construct nonce (nonce=IV ^ PN). */
nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
nonce_len = EVP_CIPHER_CTX_get_iv_length(cctx);
if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
return 0;
memcpy(nonce, el->iv, nonce_len);
memcpy(nonce, el->iv[0], nonce_len);
for (i = 0; i < sizeof(QUIC_PN); ++i)
nonce[nonce_len - i - 1] ^= (unsigned char)(pn >> (i * 8));
/* type and key will already have been setup; feed the IV. */
if (EVP_CipherInit_ex(el->cctx, NULL, NULL, NULL, nonce, /*enc=*/1) != 1)
if (EVP_CipherInit_ex(cctx, NULL, NULL, NULL, nonce, /*enc=*/1) != 1)
return 0;
/* Feed AAD data. */
if (EVP_CipherUpdate(el->cctx, NULL, &l, hdr, hdr_len) != 1)
if (EVP_CipherUpdate(cctx, NULL, &l, hdr, hdr_len) != 1)
return 0;
/* Encrypt plaintext directly into TXE. */
@ -492,7 +489,7 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
if (src_len == 0)
break;
if (EVP_CipherUpdate(el->cctx, txe_data(txe) + txe->data_len,
if (EVP_CipherUpdate(cctx, txe_data(txe) + txe->data_len,
&l, src, src_len) != 1)
return 0;
@ -501,10 +498,10 @@ static int qtx_encrypt_into_txe(OSSL_QTX *qtx, struct iovec_cur *cur, TXE *txe,
}
/* Finalise and get tag. */
if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1)
if (EVP_CipherFinal_ex(cctx, NULL, &l2) != 1)
return 0;
if (EVP_CIPHER_CTX_ctrl(el->cctx, EVP_CTRL_AEAD_GET_TAG,
if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_GET_TAG,
el->tag_len, txe_data(txe) + txe->data_len) != 1)
return 0;
@ -531,18 +528,21 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
struct iovec_cur cur;
QUIC_PKT_HDR_PTRS ptrs;
unsigned char *hdr_start;
OSSL_QRL_ENC_LEVEL *el = NULL;
/*
* Determine if the packet needs encryption and the minimum conceivable
* serialization length.
*/
if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
|| pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG) {
if (!ossl_quic_pkt_type_is_encrypted(pkt->hdr->type)) {
needs_encrypt = 0;
min_len = QUIC_MIN_VALID_PKT_LEN;
} else {
needs_encrypt = 1;
min_len = QUIC_MIN_VALID_PKT_LEN_CRYPTO;
el = ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1);
if (!ossl_assert(el != NULL)) /* should already have been checked */
return 0;
}
orig_data_len = txe->data_len;
@ -564,7 +564,7 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
/* Determine header length. */
pkt->hdr->data = NULL;
pkt->hdr->len = payload_len;
pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(pkt->hdr->src_conn_id.id_len,
pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(pkt->hdr->dst_conn_id.id_len,
pkt->hdr);
if (pred_hdr_len == 0) {
ret = QTX_FAIL_GENERIC;
@ -580,12 +580,16 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
}
/* Set some fields in the header we are responsible for. */
pkt->hdr->key_phase = 0; /* TODO */
if (!ossl_quic_wire_encode_pkt_hdr_pn(pkt->pn,
pkt->hdr->pn,
pkt->hdr->pn_len)) {
ret = QTX_FAIL_GENERIC;
goto err;
if (pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
pkt->hdr->key_phase = (unsigned char)(el->key_epoch & 1);
if (ossl_quic_pkt_type_has_pn(pkt->hdr->type)) {
if (!ossl_quic_wire_encode_pkt_hdr_pn(pkt->pn,
pkt->hdr->pn,
pkt->hdr->pn_len)) {
ret = QTX_FAIL_GENERIC;
goto err;
}
}
/* Append the header to the TXE. */
@ -674,13 +678,13 @@ int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt)
enc_level = ossl_quic_pkt_type_to_enc_level(pkt->hdr->type);
/* Some packet types must be in a packet all by themselves. */
if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
|| pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG)
if (!ossl_quic_pkt_type_can_share_dgram(pkt->hdr->type))
ossl_qtx_finish_dgram(qtx);
else if (enc_level >= QUIC_ENC_LEVEL_NUM
|| ossl_qrl_enc_level_set_have_el(&qtx->el_set, enc_level) != 1)
|| ossl_qrl_enc_level_set_have_el(&qtx->el_set, enc_level) != 1) {
/* All other packet types are encrypted. */
return 0;
}
was_coalescing = (qtx->cons != NULL && qtx->cons->data_len > 0);
if (was_coalescing)
@ -751,9 +755,7 @@ int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt)
/*
* Some packet types cannot have another packet come after them.
*/
if (pkt->hdr->type == QUIC_PKT_TYPE_RETRY
|| pkt->hdr->type == QUIC_PKT_TYPE_VERSION_NEG
|| pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
if (ossl_quic_pkt_type_must_be_last(pkt->hdr->type))
coalescing = 0;
if (!coalescing)
@ -802,9 +804,8 @@ static void txe_to_msg(TXE *txe, BIO_MSG *msg)
void ossl_qtx_flush_net(OSSL_QTX *qtx)
{
BIO_MSG msg[MAX_MSGS_PER_SEND];
size_t i;
size_t wr, i;
TXE *txe;
ossl_ssize_t wr;
if (qtx->bio == NULL)
return;
@ -819,8 +820,7 @@ void ossl_qtx_flush_net(OSSL_QTX *qtx)
/* Nothing to send. */
return;
wr = BIO_sendmmsg(qtx->bio, msg, sizeof(BIO_MSG), i, 0);
if (wr <= 0)
if (!BIO_sendmmsg(qtx->bio, msg, sizeof(BIO_MSG), i, 0, &wr) || wr == 0)
/*
* We did not get anything, so further calls will probably not
* succeed either.
@ -830,7 +830,7 @@ void ossl_qtx_flush_net(OSSL_QTX *qtx)
/*
* Remove everything which was successfully sent from the pending queue.
*/
for (i = 0; i < (size_t)wr; ++i)
for (i = 0; i < wr; ++i)
qtx_pending_to_free(qtx);
}
}
@ -883,6 +883,12 @@ size_t ossl_qtx_get_unflushed_pkt_count(OSSL_QTX *qtx)
return qtx->cons_count;
}
int ossl_qtx_trigger_key_update(OSSL_QTX *qtx)
{
return ossl_qrl_enc_level_set_key_update(&qtx->el_set,
QUIC_ENC_LEVEL_1RTT);
}
uint64_t ossl_qtx_get_cur_epoch_pkt_count(OSSL_QTX *qtx, uint32_t enc_level)
{
OSSL_QRL_ENC_LEVEL *el;

View File

@ -51,11 +51,11 @@ int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
return 1;
err:
ossl_quic_hdr_protector_destroy(hpr);
ossl_quic_hdr_protector_cleanup(hpr);
return 0;
}
void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr)
void ossl_quic_hdr_protector_cleanup(QUIC_HDR_PROTECTOR *hpr)
{
EVP_CIPHER_CTX_free(hpr->cipher_ctx);
hpr->cipher_ctx = NULL;
@ -238,8 +238,9 @@ int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
hdr->data = PACKET_data(pkt);
/*
* Skip over payload so we are pointing at the start of the next packet,
* if any.
* Skip over payload. Since this is a short header packet, which cannot
* be followed by any other kind of packet, this advances us to the end
* of the datagram.
*/
if (!PACKET_forward(pkt, hdr->len))
return 0;
@ -306,10 +307,18 @@ int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
raw_type = ((b0 >> 4) & 0x3);
switch (raw_type) {
case 0: hdr->type = QUIC_PKT_TYPE_INITIAL; break;
case 1: hdr->type = QUIC_PKT_TYPE_0RTT; break;
case 2: hdr->type = QUIC_PKT_TYPE_HANDSHAKE; break;
case 3: hdr->type = QUIC_PKT_TYPE_RETRY; break;
case 0:
hdr->type = QUIC_PKT_TYPE_INITIAL;
break;
case 1:
hdr->type = QUIC_PKT_TYPE_0RTT;
break;
case 2:
hdr->type = QUIC_PKT_TYPE_HANDSHAKE;
break;
case 3:
hdr->type = QUIC_PKT_TYPE_RETRY;
break;
}
hdr->pn_len = 0;
@ -455,8 +464,7 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
|| hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
return 0;
if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG
&& hdr->type != QUIC_PKT_TYPE_RETRY
if (ossl_quic_pkt_type_has_pn(hdr->type)
&& (hdr->pn_len < 1 || hdr->pn_len > 4))
return 0;
@ -480,8 +488,7 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
b0 = (raw_type << 4) | 0x80; /* long */
if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG || hdr->fixed)
b0 |= 0x40; /* fixed */
if (hdr->type != QUIC_PKT_TYPE_RETRY
&& hdr->type != QUIC_PKT_TYPE_VERSION_NEG)
if (ossl_quic_pkt_type_has_pn(hdr->type))
b0 |= hdr->pn_len - 1;
if (!WPACKET_put_bytes_u8(pkt, b0)
@ -560,15 +567,17 @@ int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_len,
|| hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
return 0;
if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG
&& hdr->type != QUIC_PKT_TYPE_RETRY
&& (hdr->pn_len < 1 || hdr->pn_len > 4))
return 0;
len += 1 /* Initial byte */ + 4 /* Version */
+ 1 + hdr->dst_conn_id.id_len /* DCID Len, DCID */
+ 1 + hdr->src_conn_id.id_len /* SCID Len, SCID */
+ hdr->pn_len; /* PN */
;
if (ossl_quic_pkt_type_has_pn(hdr->type)) {
if (hdr->pn_len < 1 || hdr->pn_len > 4)
return 0;
len += hdr->pn_len;
}
if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
enclen = ossl_quic_vlint_encode_len(hdr->token_len);
@ -577,11 +586,14 @@ int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_len,
len += enclen;
}
enclen = ossl_quic_vlint_encode_len(hdr->len);
if (!enclen)
return 0;
if (!ossl_quic_pkt_type_must_be_last(hdr->type)) {
enclen = ossl_quic_vlint_encode_len(hdr->len);
if (!enclen)
return 0;
len += enclen;
}
len += enclen;
return len;
}
}

View File

@ -23,6 +23,9 @@ static const QUIC_CONN_ID empty_conn_id = {0, {0}};
#define RX_TEST_OP_DISCARD_EL 7 /* discard an encryption level */
#define RX_TEST_OP_CHECK_PKT 8 /* read packet, compare to expected */
#define RX_TEST_OP_CHECK_NO_PKT 9 /* check no packet is available to read */
#define RX_TEST_OP_CHECK_KEY_EPOCH 10 /* check key epoch value matches */
#define RX_TEST_OP_KEY_UPDATE_TIMEOUT 11 /* complete key update process */
#define RX_TEST_OP_SET_INIT_KEY_PHASE 12 /* initial Key Phase bit value */
struct rx_test_op {
unsigned char op;
@ -61,6 +64,12 @@ struct rx_test_op {
},
#define RX_OP_CHECK_NO_PKT() \
{ RX_TEST_OP_CHECK_NO_PKT, NULL, 0, NULL, 0, 0, 0, NULL, NULL },
#define RX_OP_CHECK_KEY_EPOCH(expected) \
{ RX_TEST_OP_CHECK_KEY_EPOCH, NULL, 0, NULL, 0, 0, (expected), NULL },
#define RX_OP_KEY_UPDATE_TIMEOUT(normal) \
{ RX_TEST_OP_KEY_UPDATE_TIMEOUT, NULL, 0, NULL, (normal), 0, 0, NULL },
#define RX_OP_SET_INIT_KEY_PHASE(kp_bit) \
{ RX_TEST_OP_SET_INIT_KEY_PHASE, NULL, 0, NULL, (kp_bit), 0, 0, NULL },
#define RX_OP_INJECT_N(n) \
RX_OP_INJECT(rx_script_##n##_in)
@ -1327,6 +1336,279 @@ static const struct rx_test_op rx_script_7[] = {
RX_OP_END
};
/*
* 8. Real World - S2C Multiple Packets with Peer Initiated Key Phase Update
*/
static const unsigned char rx_script_8_1rtt_secret[32] = {
0x5f, 0x1f, 0x47, 0xea, 0xc3, 0xb2, 0xce, 0x73, 0xfb, 0xa2, 0x9f, 0xac,
0xc3, 0xa0, 0xfe, 0x9b, 0xf3, 0xc0, 0xde, 0x5d, 0x33, 0x11, 0x1c, 0x70,
0xdd, 0xb4, 0x06, 0xcc, 0xdf, 0x7d, 0xe9, 0x9a
};
static const unsigned char rx_script_8a_in[] = {
0x51, /* Short, 1-RTT, PN Length=2 bytes, KP=0 */
0xcb, 0xf4, /* PN (4) */
0x3f, 0x68, 0x7b, 0xa8, 0x2b, 0xb9, 0xfa, 0x7d, 0xe4, 0x6b, 0x20, 0x48,
0xd1, 0x3c, 0xcb, 0x4b, 0xef, 0xb1, 0xfd, 0x5e, 0x1b, 0x19, 0x83, 0xa9,
0x47, 0x62, 0xc1, 0x6e, 0xef, 0x27, 0xc3, 0x9b, 0x8f, 0x3f, 0xce, 0x11,
0x68, 0xf5, 0x73, 0x0d, 0xf2, 0xdc, 0xe0, 0x28, 0x28, 0x79, 0xa6, 0x39,
0xc3, 0xb9, 0xd3,
};
static const QUIC_PKT_HDR rx_script_8a_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
0, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 4}, /* PN */
NULL, 0, /* Token/Token Len */
35, NULL
};
static const unsigned char rx_script_8a_body[] = {
0x02, 0x03, 0x06, 0x00, 0x03, 0x0c, 0x00, 0x1b, 0x49, 0x27, 0x6d, 0x20,
0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65
};
static const unsigned char rx_script_8b_in[] = {
0x52, /* Short, 1-RTT, PN Length=2 bytes, KP=1 */
0x21, 0x8e, /* PN (5) */
0xa2, 0x6a, 0x9c, 0x83, 0x24, 0x48, 0xae, 0x60, 0x1e, 0xc2, 0xa5, 0x91,
0xfa, 0xe5, 0xf2, 0x05, 0x14, 0x37, 0x04, 0x6a, 0xa8, 0xae, 0x06, 0x58,
0xd7, 0x85, 0x48, 0xd7, 0x3b, 0x85, 0x9e, 0x5a, 0xb3, 0x46, 0x89, 0x1b,
0x4b, 0x6e, 0x1d, 0xd1, 0xfc, 0xb7, 0x47, 0xda, 0x6a, 0x64, 0x4b, 0x8e,
0xf2, 0x69, 0x16,
};
static const QUIC_PKT_HDR rx_script_8b_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
1, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 5}, /* PN */
NULL, 0, /* Token/Token Len */
35, NULL
};
static const unsigned char rx_script_8b_body[] = {
0x02, 0x04, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x36, 0x49, 0x27, 0x6d, 0x20,
0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char rx_script_8c_in[] = {
0x5b, /* Short, 1-RTT, PN Length=2 bytes, KP=0 */
0x98, 0xd6, /* PN (3) */
0x3c, 0x6f, 0x94, 0x20, 0x5e, 0xfc, 0x5b, 0x3a, 0x4a, 0x65, 0x1a, 0x9a,
0x6c, 0x00, 0x52, 0xb6, 0x0c, 0x9b, 0x07, 0xf9, 0x6f, 0xbc, 0x3d, 0xb4,
0x57, 0xe0, 0x15, 0x74, 0xfe, 0x76, 0xea, 0x1f, 0x23, 0xae, 0x22, 0x62,
0xb7, 0x90, 0x94, 0x89, 0x38, 0x9b, 0x5b, 0x47, 0xed,
};
static const QUIC_PKT_HDR rx_script_8c_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
0, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 3}, /* PN */
NULL, 0, /* Token/Token Len */
29, NULL
};
static const unsigned char rx_script_8c_body[] = {
0x08, 0x00, 0x49, 0x27, 0x6d, 0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67,
0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c,
0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char rx_script_8d_in[] = {
0x55, /* Short, 1-RTT, PN Length=2 bytes, KP=1 */
0x98, 0x20, /* PN (6) */
0x45, 0x53, 0x05, 0x29, 0x30, 0x42, 0x29, 0x02, 0xf2, 0xa7, 0x27, 0xd6,
0xb0, 0xb7, 0x30, 0xad, 0x45, 0xd8, 0x73, 0xd7, 0xe3, 0x65, 0xee, 0xd9,
0x35, 0x33, 0x03, 0x3a, 0x35, 0x0b, 0x59, 0xa7, 0xbc, 0x23, 0x37, 0xc2,
0x5e, 0x13, 0x88, 0x18, 0x79, 0x94, 0x6c, 0x15, 0xe3, 0x1f, 0x0d, 0xd1,
0xc3, 0xfa, 0x40, 0xff,
};
static const QUIC_PKT_HDR rx_script_8d_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
1, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 6}, /* PN */
NULL, 0, /* Token/Token Len */
36, NULL
};
static const unsigned char rx_script_8d_body[] = {
0x02, 0x05, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x40, 0x51, 0x49, 0x27, 0x6d,
0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char rx_script_8e_in[] = {
0x55, /* Short, 1-RTTT, PN Length=2 bytes, KP=0 */
0x76, 0x25, /* PN (10) */
0x1c, 0x0d, 0x70, 0x4c, 0x2b, 0xc5, 0x7d, 0x7b, 0x77, 0x64, 0x03, 0x27,
0xb3, 0x5d, 0x83, 0x9e, 0x35, 0x05, 0x10, 0xd2, 0xa4, 0x5c, 0x83, 0xd6,
0x94, 0x12, 0x18, 0xc5, 0xb3, 0x0f, 0x0a, 0xb1, 0x8a, 0x82, 0x9f, 0xd6,
0xa9, 0xab, 0x40, 0xc1, 0x05, 0xe8, 0x1b, 0x74, 0xaa, 0x8e, 0xd6, 0x8b,
0xa5, 0xa3, 0x77, 0x79,
};
static const QUIC_PKT_HDR rx_script_8e_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
0, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 10}, /* PN */
NULL, 0, /* Token/Token Len */
36, NULL
};
static const unsigned char rx_script_8e_body[] = {
0x02, 0x09, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x40, 0xbd, 0x49, 0x27, 0x6d,
0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char rx_script_8f_in[] = {
0x48, /* Short, 1-RTT, PN Length=2 Bytes, KP=1 */
0x4d, 0xf6, /* PN (15) */
0x42, 0x86, 0xa1, 0xfa, 0x69, 0x6b, 0x1a, 0x45, 0xf2, 0xcd, 0xf6, 0x92,
0xe1, 0xe6, 0x1a, 0x49, 0x37, 0xd7, 0x10, 0xae, 0x09, 0xbd
};
static const QUIC_PKT_HDR rx_script_8f_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
0, /* Spin Bit */
1, /* Key Phase */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
{0, 15}, /* PN */
NULL, 0, /* Token/Token Len */
6, NULL
};
static const unsigned char rx_script_8f_body[] = {
0x02, 0x0e, 0x4c, 0x54, 0x00, 0x02
};
static const struct rx_test_op rx_script_8[] = {
RX_OP_ADD_RX_DCID(empty_conn_id)
/* Inject before we get the keys */
RX_OP_INJECT_N(8a)
/* Nothing yet */
RX_OP_CHECK_NO_PKT()
/* Provide keys */
RX_OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
QRL_SUITE_AES128GCM, rx_script_8_1rtt_secret)
/* Now the injected packet is successfully returned */
RX_OP_CHECK_PKT_N(8a)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(0)
/* Packet with new key phase */
RX_OP_INJECT_N(8b)
/* Packet is successfully decrypted and returned */
RX_OP_CHECK_PKT_N(8b)
RX_OP_CHECK_NO_PKT()
/* Key epoch has increased */
RX_OP_CHECK_KEY_EPOCH(1)
/*
* Now inject an old packet with the old keys (perhaps reordered in
* network).
*/
RX_OP_INJECT_N(8c)
/* Should still be decrypted OK */
RX_OP_CHECK_PKT_N(8c)
RX_OP_CHECK_NO_PKT()
/* Epoch has not changed */
RX_OP_CHECK_KEY_EPOCH(1)
/* Another packet with the new keys. */
RX_OP_INJECT_N(8d)
RX_OP_CHECK_PKT_N(8d)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(1)
/* We can inject the old packet multiple times and it still works */
RX_OP_INJECT_N(8c)
RX_OP_CHECK_PKT_N(8c)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(1)
/* Until we move from UPDATING to COOLDOWN */
RX_OP_KEY_UPDATE_TIMEOUT(0)
RX_OP_INJECT_N(8c)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(1)
/*
* Injecting a packet from the next epoch (epoch 2) while in COOLDOWN
* doesn't work
*/
RX_OP_INJECT_N(8e)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(1)
/* Move from COOLDOWN to NORMAL and try again */
RX_OP_KEY_UPDATE_TIMEOUT(1)
RX_OP_INJECT_N(8e)
RX_OP_CHECK_PKT_N(8e)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(2)
/* Can still receive old packet */
RX_OP_INJECT_N(8d)
RX_OP_CHECK_PKT_N(8d)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(2)
/* Move straight from UPDATING to NORMAL */
RX_OP_KEY_UPDATE_TIMEOUT(1)
/* Try a packet from epoch 3 */
RX_OP_INJECT_N(8f)
RX_OP_CHECK_PKT_N(8f)
RX_OP_CHECK_NO_PKT()
RX_OP_CHECK_KEY_EPOCH(3)
RX_OP_END
};
static const struct rx_test_op *rx_scripts[] = {
rx_script_1,
rx_script_2,
@ -1334,7 +1616,8 @@ static const struct rx_test_op *rx_scripts[] = {
rx_script_4,
rx_script_5,
rx_script_6,
rx_script_7
rx_script_7,
rx_script_8
};
static int cmp_pkt_hdr(const QUIC_PKT_HDR *a, const QUIC_PKT_HDR *b,
@ -1395,12 +1678,26 @@ static void rx_state_teardown(struct rx_state *s)
}
}
static uint64_t time_counter = 0;
static OSSL_TIME expected_time(uint64_t counter)
{
return ossl_time_multiply(ossl_ticks2time(OSSL_TIME_MS), counter);
}
static OSSL_TIME fake_time(void *arg)
{
return expected_time(++time_counter);
}
static int rx_state_ensure(struct rx_state *s)
{
if (s->demux == NULL
&& !TEST_ptr(s->demux = ossl_quic_demux_new(NULL,
s->args.short_conn_id_len,
1500)))
1500,
fake_time,
NULL)))
return 0;
s->args.demux = s->demux;
@ -1497,6 +1794,28 @@ static int rx_run_script(const struct rx_test_op *script)
if (!TEST_false(ossl_qrx_read_pkt(s.qrx, &pkt)))
goto err;
break;
case RX_TEST_OP_CHECK_KEY_EPOCH:
if (!TEST_true(rx_state_ensure(&s)))
goto err;
if (!TEST_uint64_t_eq(ossl_qrx_get_key_epoch(s.qrx),
op->largest_pn))
goto err;
break;
case RX_TEST_OP_KEY_UPDATE_TIMEOUT:
if (!TEST_true(rx_state_ensure(&s)))
goto err;
if (!TEST_true(ossl_qrx_key_update_timeout(s.qrx,
op->enc_level)))
goto err;
break;
case RX_TEST_OP_SET_INIT_KEY_PHASE:
rx_state_teardown(&s);
s.args.init_key_phase_bit = (unsigned char)op->enc_level;
break;
default:
OPENSSL_assert(0);
@ -2285,7 +2604,7 @@ static int test_wire_pkt_hdr_actual(int tidx, int repeat, int cipher,
testresult = 1;
err:
if (have_hpr)
ossl_quic_hdr_protector_destroy(&hpr);
ossl_quic_hdr_protector_cleanup(&hpr);
WPACKET_finish(&wpkt);
BUF_MEM_free(buf);
OPENSSL_free(hbuf);
@ -2366,6 +2685,7 @@ static int test_wire_pkt_hdr(int idx)
#define TX_TEST_OP_DISCARD_EL 4 /* discard an encryption level */
#define TX_TEST_OP_CHECK_DGRAM 5 /* read datagram, compare to expected */
#define TX_TEST_OP_CHECK_NO_DGRAM 6 /* check no datagram is in queue */
#define TX_TEST_OP_KEY_UPDATE 7 /* perform key update for 1-RTT */
struct tx_test_op {
unsigned char op;
@ -2407,6 +2727,9 @@ struct tx_test_op {
TX_OP_WRITE_N(n) \
TX_OP_CHECK_DGRAM_N(n)
#define TX_OP_KEY_UPDATE() \
{ TX_TEST_OP_KEY_UPDATE, NULL, 0, NULL, 0, 0, NULL },
/* 1. RFC 9001 - A.2 Client Initial */
static const unsigned char tx_script_1_body[1162] = {
0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb, 0xf8,
@ -2681,10 +3004,271 @@ static const struct tx_test_op tx_script_3[] = {
TX_OP_END
};
/* 4. Real World - AES-128-GCM Key Update */
static const unsigned char tx_script_4_secret[] = {
0x70, 0x82, 0xc0, 0x45, 0x61, 0x4d, 0xfe, 0x04, 0x76, 0xa6, 0x4e, 0xf0,
0x38, 0xe6, 0x63, 0xd9, 0xdd, 0x4a, 0x75, 0x16, 0xa8, 0xa0, 0x06, 0x5a,
0xf2, 0x56, 0xfd, 0x84, 0x78, 0xfd, 0xf6, 0x5e
};
static const unsigned char tx_script_4a_body[] = {
0x02, 0x03, 0x09, 0x00, 0x03, 0x0c, 0x00, 0x36, 0x49, 0x27, 0x6d, 0x20,
0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f, 0x6e,
0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char tx_script_4a_dgram[] = {
0x47, 0x6e, 0x4e, 0xbd, 0x49, 0x7e, 0xbd, 0x15, 0x1c, 0xd1, 0x3e, 0xc8,
0xcd, 0x43, 0x87, 0x6b, 0x84, 0xdb, 0xeb, 0x06, 0x8b, 0x8a, 0xae, 0x37,
0xed, 0x9c, 0xeb, 0xbc, 0xcf, 0x0d, 0x3c, 0xf0, 0xa1, 0x6f, 0xee, 0xd2,
0x7c, 0x07, 0x6e, 0xd1, 0xbe, 0x40, 0x6a, 0xd4, 0x53, 0x38, 0x9e, 0x63,
0xb5, 0xde, 0x35, 0x09, 0xb2, 0x78, 0x94, 0xe4, 0x2b, 0x37
};
static QUIC_PKT_HDR tx_script_4a_hdr = {
QUIC_PKT_TYPE_1RTT, /* type */
0, /* spin bit */
0, /* key phase */
2, /* PN length */
0, /* partial */
0, /* fixed */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
{ 0 }, /* PN */
NULL, 0, /* Token */
5555, NULL /* Len/Data */
};
static const OSSL_QTX_IOVEC tx_script_4a_iovec[] = {
{ tx_script_4a_body, sizeof(tx_script_4a_body) }
};
static const OSSL_QTX_PKT tx_script_4a_pkt = {
&tx_script_4a_hdr,
tx_script_4a_iovec,
OSSL_NELEM(tx_script_4a_iovec),
NULL, NULL,
4,
0
};
static const unsigned char tx_script_4b_body[] = {
0x02, 0x04, 0x07, 0x00, 0x00, 0x0c, 0x00, 0x40, 0x51, 0x49, 0x27, 0x6d,
0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char tx_script_4b_dgram[] = {
0x58, 0x6e, 0x4e, 0xbd, 0x49, 0xa4, 0x43, 0x33, 0xea, 0x11, 0x3a, 0x6c,
0xf5, 0x20, 0xef, 0x55, 0x8d, 0x25, 0xe2, 0x3b, 0x0e, 0x8c, 0xea, 0x17,
0xfc, 0x2b, 0x7a, 0xab, 0xfa, 0x3d, 0x07, 0xda, 0xa7, 0x7c, 0xc7, 0x47,
0x82, 0x02, 0x46, 0x40, 0x4f, 0x01, 0xad, 0xb2, 0x9d, 0x97, 0xdb, 0xfc,
0x9c, 0x4b, 0x46, 0xb1, 0x5a, 0x7f, 0x0b, 0x12, 0xaf, 0x49, 0xdf,
};
static QUIC_PKT_HDR tx_script_4b_hdr = {
QUIC_PKT_TYPE_1RTT, /* type */
0, /* spin bit */
1, /* key phase */
2, /* PN length */
0, /* partial */
0, /* fixed */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
{ 0 }, /* PN */
NULL, 0, /* Token */
5555, NULL /* Len/Data */
};
static const OSSL_QTX_IOVEC tx_script_4b_iovec[] = {
{ tx_script_4b_body, sizeof(tx_script_4b_body) }
};
static const OSSL_QTX_PKT tx_script_4b_pkt = {
&tx_script_4b_hdr,
tx_script_4b_iovec,
OSSL_NELEM(tx_script_4b_iovec),
NULL, NULL,
5,
0
};
static const unsigned char tx_script_4c_body[] = {
0x02, 0x09, 0x0e, 0x00, 0x00, 0x0c, 0x00, 0x40, 0xd8, 0x49, 0x27, 0x6d,
0x20, 0x68, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x77, 0x6f,
0x6e, 0x64, 0x65, 0x72, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x69, 0x6d, 0x65,
};
static const unsigned char tx_script_4c_dgram[] = {
0x49, 0x6e, 0x4e, 0xbd, 0x49, 0x4d, 0xd9, 0x85, 0xba, 0x26, 0xfb, 0x68,
0x83, 0x9b, 0x94, 0x34, 0x7d, 0xc1, 0x7a, 0x05, 0xb7, 0x38, 0x43, 0x21,
0xe2, 0xec, 0x2b, 0xc1, 0x81, 0x74, 0x2d, 0xda, 0x24, 0xba, 0xbd, 0x99,
0x69, 0xd2, 0x56, 0xfa, 0xae, 0x29, 0x24, 0xb2, 0xaa, 0xda, 0xbd, 0x82,
0x80, 0xf1, 0xbb, 0x6a, 0xfd, 0xae, 0xda, 0x0e, 0x09, 0xcf, 0x09,
};
static QUIC_PKT_HDR tx_script_4c_hdr = {
QUIC_PKT_TYPE_1RTT, /* type */
0, /* spin bit */
0, /* key phase */
2, /* PN length */
0, /* partial */
0, /* fixed */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
{ 0 }, /* PN */
NULL, 0, /* Token */
5555, NULL /* Len/Data */
};
static const OSSL_QTX_IOVEC tx_script_4c_iovec[] = {
{ tx_script_4c_body, sizeof(tx_script_4c_body) }
};
static const OSSL_QTX_PKT tx_script_4c_pkt = {
&tx_script_4c_hdr,
tx_script_4c_iovec,
OSSL_NELEM(tx_script_4c_iovec),
NULL, NULL,
10,
0
};
static const struct tx_test_op tx_script_4[] = {
TX_OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, tx_script_4_secret)
TX_OP_WRITE_CHECK(4a)
TX_OP_KEY_UPDATE()
TX_OP_WRITE_CHECK(4b)
TX_OP_KEY_UPDATE()
TX_OP_WRITE_CHECK(4c)
TX_OP_END
};
/* 5. Real World - Retry Packet */
static const unsigned char tx_script_5_body[] = {
/* Retry Token */
0x92, 0xe7, 0xc6, 0xd8, 0x09, 0x65, 0x72, 0x55, 0xe5, 0xe2, 0x73, 0x04,
0xf3, 0x07, 0x5b, 0x21, 0x9f, 0x50, 0xcb, 0xbc, 0x79, 0xc5, 0x77, 0x5a,
0x29, 0x43, 0x65, 0x49, 0xf0, 0x6e, 0xc1, 0xc0, 0x3a, 0xe8, 0xca, 0xd2,
0x44, 0x69, 0xdd, 0x23, 0x31, 0x93, 0x52, 0x02, 0xf7, 0x42, 0x07, 0x78,
0xa1, 0x81, 0x61, 0x9c, 0x39, 0x07, 0x18, 0x69, 0x6e, 0x4f, 0xdc, 0xa0,
0xbe, 0x4b, 0xe5, 0xf2, 0xe9, 0xd2, 0xa4, 0xa7, 0x34, 0x55, 0x5e, 0xf3,
0xf8, 0x9c, 0x49, 0x8f, 0x0c, 0xc8, 0xb2, 0x75, 0x4b, 0x4d, 0x2f, 0xfe,
0x05, 0x5a, 0xdd, 0x4b, 0xe6, 0x14, 0xb4, 0xd2, 0xc0, 0x93, 0x6e, 0x0e,
0x84, 0x41, 0x4d, 0x31,
/* Retry Integrity Tag */
0x43, 0x8e, 0xab, 0xcd, 0xce, 0x24, 0x44, 0xc2, 0x20, 0xe1, 0xe2, 0xc8,
0xae, 0xa3, 0x8d, 0x4e,
};
static const unsigned char tx_script_5_dgram[] = {
0xf0, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0xa9, 0x20, 0xcc, 0xc2, 0x92,
0xe7, 0xc6, 0xd8, 0x09, 0x65, 0x72, 0x55, 0xe5, 0xe2, 0x73, 0x04, 0xf3,
0x07, 0x5b, 0x21, 0x9f, 0x50, 0xcb, 0xbc, 0x79, 0xc5, 0x77, 0x5a, 0x29,
0x43, 0x65, 0x49, 0xf0, 0x6e, 0xc1, 0xc0, 0x3a, 0xe8, 0xca, 0xd2, 0x44,
0x69, 0xdd, 0x23, 0x31, 0x93, 0x52, 0x02, 0xf7, 0x42, 0x07, 0x78, 0xa1,
0x81, 0x61, 0x9c, 0x39, 0x07, 0x18, 0x69, 0x6e, 0x4f, 0xdc, 0xa0, 0xbe,
0x4b, 0xe5, 0xf2, 0xe9, 0xd2, 0xa4, 0xa7, 0x34, 0x55, 0x5e, 0xf3, 0xf8,
0x9c, 0x49, 0x8f, 0x0c, 0xc8, 0xb2, 0x75, 0x4b, 0x4d, 0x2f, 0xfe, 0x05,
0x5a, 0xdd, 0x4b, 0xe6, 0x14, 0xb4, 0xd2, 0xc0, 0x93, 0x6e, 0x0e, 0x84,
0x41, 0x4d, 0x31, 0x43, 0x8e, 0xab, 0xcd, 0xce, 0x24, 0x44, 0xc2, 0x20,
0xe1, 0xe2, 0xc8, 0xae, 0xa3, 0x8d, 0x4e,
};
static QUIC_PKT_HDR tx_script_5_hdr = {
QUIC_PKT_TYPE_RETRY, /* type */
0, /* spin bit */
0, /* key phase */
0, /* PN length */
0, /* partial */
0, /* fixed */
1, /* version */
{ 0, {0} }, /* DCID */
{ 4, {0xa9, 0x20, 0xcc, 0xc2} }, /* SCID */
{ 0 }, /* PN */
NULL, 0, /* Token */
5555, NULL /* Len/Data */
};
static const OSSL_QTX_IOVEC tx_script_5_iovec[] = {
{ tx_script_5_body, sizeof(tx_script_5_body) }
};
static const OSSL_QTX_PKT tx_script_5_pkt = {
&tx_script_5_hdr,
tx_script_5_iovec,
OSSL_NELEM(tx_script_5_iovec),
NULL, NULL,
0,
0
};
static const struct tx_test_op tx_script_5[] = {
TX_OP_WRITE_CHECK(5)
TX_OP_END
};
/* 6. Real World - Version Negotiation Packet */
static const unsigned char tx_script_6_body[] = {
0x00, 0x00, 0x00, 0x01, /* Supported Version: 1 */
0xaa, 0x9a, 0x3a, 0x9a /* Supported Version: Random (GREASE) */
};
static const unsigned char tx_script_6_dgram[] = {
0x80, /* Long */
0x00, 0x00, 0x00, 0x00, /* Version 0 (Version Negotiation) */
0x00, /* DCID */
0x0c, 0x35, 0x3c, 0x1b, 0x97, 0xca, /* SCID */
0xf8, 0x99, 0x11, 0x39, 0xad, 0x79,
0x1f,
0x00, 0x00, 0x00, 0x01, /* Supported Version: 1 */
0xaa, 0x9a, 0x3a, 0x9a /* Supported Version: Random (GREASE) */
};
static QUIC_PKT_HDR tx_script_6_hdr = {
QUIC_PKT_TYPE_VERSION_NEG, /* type */
0, /* spin bit */
0, /* key phase */
0, /* PN length */
0, /* partial */
0, /* fixed */
0, /* version */
{ 0, {0} }, /* DCID */
{ 12, {0x35, 0x3c, 0x1b, 0x97, 0xca, 0xf8, 0x99,
0x11, 0x39, 0xad, 0x79, 0x1f} }, /* SCID */
{ 0 }, /* PN */
NULL, 0, /* Token */
5555, NULL /* Len/Data */
};
static const OSSL_QTX_IOVEC tx_script_6_iovec[] = {
{ tx_script_6_body, sizeof(tx_script_6_body) }
};
static const OSSL_QTX_PKT tx_script_6_pkt = {
&tx_script_6_hdr,
tx_script_6_iovec,
OSSL_NELEM(tx_script_6_iovec),
NULL, NULL,
0,
0
};
static const struct tx_test_op tx_script_6[] = {
TX_OP_WRITE_CHECK(6)
TX_OP_END
};
static const struct tx_test_op *const tx_scripts[] = {
tx_script_1,
tx_script_2,
tx_script_3
tx_script_3,
tx_script_4,
tx_script_5,
tx_script_6
};
static int tx_run_script(const struct tx_test_op *script)
@ -2758,6 +3342,10 @@ static int tx_run_script(const struct tx_test_op *script)
if (!TEST_false(ossl_qtx_pop_net(qtx, &msg)))
goto err;
break;
case TX_TEST_OP_KEY_UPDATE:
if (!TEST_true(ossl_qtx_trigger_key_update(qtx)))
goto err;
break;
default:
OPENSSL_assert(0);
goto err;