Commit 0cb912ae30 for openssl.org
commit 0cb912ae302a85caaf7e3f698a336b1ac11ecab1
Author: Ryan Hooper <ryanh@openssl.foundation>
Date: Thu Sep 24 13:17:20 2026 -0400
DTLS 1.3: Fix simultaneous KeyUpdate deadlock
When both peers independently send their own KeyUpdate around the
same time, each one deferred acknowledging the other's KeyUpdate
until its own was acknowledged first. Since each side's ACK was
exactly what the other side was waiting for, neither side could
ever send it: a real deadlock.
RFC 9147 section 8, as corrected by errata 8047, has two separate
restrictions: don't use the new epoch's keys until our own
KeyUpdate is acknowledged, and don't send new post-handshake
messages until it is acknowledged. A bare ACK for the peer's
KeyUpdate is neither -- it is not new post-handshake content, and
it can go out under our current, still-valid keys as long as we
have not yet switched to the new ones.
Defer installing a DTLS 1.3 KeyUpdate's new write keys until its
own ACK arrives (dtls_process_ack() in ssl/statem/statem_dtls.c),
instead of switching immediately on send. Drop the hold-back guard
specifically at the KeyUpdate-acknowledgment transitions
(TLS_ST_SR_KEY_UPDATE / TLS_ST_CR_KEY_UPDATE), while leaving it in
place for genuinely new content (tickets, PHA responses).
Fix the resulting state-machine bug where the shared ACK-send tail
(TLS_ST_SW_ACK / TLS_ST_CW_ACK) reset hand_state to TLS_ST_OK after
sending this kind of ACK, which made SSL_read()/SSL_do_handshake()
incorrectly report the connection idle while our own KeyUpdate was
still outstanding; park at TLS_ST_SW_KEY_UPDATE / TLS_ST_CW_KEY_UPDATE
instead so the caller correctly sees SSL_ERROR_WANT_READ.
Also remove a redundant !SSL_IS_QUIC_HANDSHAKE(s) check in
ossl_statem_server13_read_transition(): it sat inside a block
already gated on SSL_CONNECTION_IS_DTLS13(s), and DTLS 1.3 and
QUIC are mutually exclusive, so the check could never be false
there.
Fixes: #32878
Assisted-by: Claude:claude-sonnet-5
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Merge-date: Fri Nov 13 17:22:22 2026
Merged-from: https://github.com/openssl/openssl/pull/32934
diff --git a/include/internal/statem.h b/include/internal/statem.h
index 1a2f5c85dc..517ecb6fe0 100644
--- a/include/internal/statem.h
+++ b/include/internal/statem.h
@@ -121,7 +121,10 @@ struct ossl_statem_st {
OSSL_HANDSHAKE_STATE deferred_ack_state;
/* The handshake state before receiving an ACK */
OSSL_HANDSHAKE_STATE pre_ack_hand_state;
- /* The handshake state to resume once our own KeyUpdate is acknowledged */
+ /*
+ * The handshake state to continue to, once our own KeyUpdate is
+ * acknowledged and the ACK held back behind it has finally been sent
+ */
OSSL_HANDSHAKE_STATE deferred_key_update_state;
ERROR_STATE error_state;
int in_init;
diff --git a/ssl/d1_lib.c b/ssl/d1_lib.c
index a4c50d844a..c25023f42a 100644
--- a/ssl/d1_lib.c
+++ b/ssl/d1_lib.c
@@ -242,7 +242,6 @@ void dtls1_clear_sent_buffer(SSL_CONNECTION *s, int keep_unacked_msgs)
while ((item = pqueue_pop(sent_messages)) != NULL) {
dtls_sent_msg *sent_msg = (dtls_sent_msg *)item->data;
- unsigned char msg_type = sent_msg->msg_info.msg_type;
unsigned char record_type = sent_msg->msg_info.record_type;
if (SSL_CONNECTION_IS_DTLS13(s)
@@ -252,20 +251,15 @@ void dtls1_clear_sent_buffer(SSL_CONNECTION *s, int keep_unacked_msgs)
continue;
}
- if (((!SSL_CONNECTION_IS_DTLS13(s) && record_type == SSL3_RT_CHANGE_CIPHER_SPEC)
- || (SSL_CONNECTION_IS_DTLS13(s)
- && (msg_type == SSL3_MT_FINISHED
- || msg_type == SSL3_MT_SERVER_HELLO
- || msg_type == SSL3_MT_KEY_UPDATE))
- /*
- * A ClientHello followed by early data. SSL3_MT_CLIENT_HELLO
- * has the same value as SSL3_MT_CCS, which is recorded as the
- * message type of a CCS, so the record type has to be checked
- * as well to tell the two apart. The other message types
- * above do not clash with anything.
- */
- || (record_type == SSL3_RT_HANDSHAKE
- && msg_type == SSL3_MT_CLIENT_HELLO))
+ /*
+ * Free the captured write layer once this is its last reference,
+ * regardless of this message's own type: any queued message can
+ * end up the final owner of a retired layer, not just the ones
+ * that originally triggered the epoch change.
+ */
+ if ((SSL_CONNECTION_IS_DTLS13(s)
+ || record_type == SSL3_RT_CHANGE_CIPHER_SPEC
+ || record_type == SSL3_RT_HANDSHAKE)
&& sent_msg->saved_retransmit_state.wrlmethod != NULL
&& s->rlayer.wrl != sent_msg->saved_retransmit_state.wrl
&& !dtls1_wrl_has_other_owner(sent_msg->saved_retransmit_state.wrl,
@@ -290,16 +284,20 @@ void dtls1_clear_sent_buffer(SSL_CONNECTION *s, int keep_unacked_msgs)
/*
* Retire every queued CertificateRequest regardless of acknowledgment
- * status, and report whether any was found. A CertificateRequest never
- * receives an explicit ACK (rfc9147 5.8.1: receiving the next flight, not
- * just an ACK, ends WAITING) -- the client's Finished is that next flight,
- * and completes it implicitly. Entries of any other type are left
- * untouched.
+ * status, and report whether any was found. Finding none is not an error:
+ * rfc9147 section 7.1 lets the client ACK a CertificateRequest explicitly
+ * if it cannot produce its response right away, in which case ACK
+ * processing already removed it before Finished arrived. Either way, the
+ * client's Finished completes it implicitly (rfc9147 5.8.1: receiving the
+ * next flight, not just an ACK, ends WAITING), so it must not still be
+ * queued by the time Finished is processed. Entries of any other type are
+ * left untouched.
*/
static int dtls1_retire_sent_certificate_request_messages(SSL_CONNECTION *s)
{
pitem *item = NULL;
- pqueue *remaining_sent_messages = pqueue_new();
+ pqueue remaining_sent_messages_buf = { NULL, 0 };
+ pqueue *remaining_sent_messages = &remaining_sent_messages_buf;
pqueue *sent_messages = &s->d1->sent_messages;
int retired = 0;
@@ -311,6 +309,8 @@ static int dtls1_retire_sent_certificate_request_messages(SSL_CONNECTION *s)
continue;
}
+ /* Same ownership rule as dtls1_clear_sent_buffer(): free the
+ * captured write layer only once this is its last reference. */
if (sent_msg->saved_retransmit_state.wrlmethod != NULL
&& s->rlayer.wrl != sent_msg->saved_retransmit_state.wrl
&& !dtls1_wrl_has_other_owner(sent_msg->saved_retransmit_state.wrl,
@@ -325,7 +325,6 @@ static int dtls1_retire_sent_certificate_request_messages(SSL_CONNECTION *s)
while ((item = pqueue_pop(remaining_sent_messages)) != NULL)
pqueue_insert(sent_messages, item);
- pqueue_free(remaining_sent_messages);
return retired;
}
@@ -679,33 +678,63 @@ void dtls1_stop_timer(SSL_CONNECTION *s)
* Retire the timer and retransmit buffer now that a flight has finished
* reading. Post-handshake message categories acknowledge nothing of each
* other (rfc9147 5.8.4), so an unrelated message must not discard our own
- * still-unacknowledged flight. A CertificateRequest is retired here
- * explicitly, since it completes via the next flight rather than an ACK
- * (rfc9147 5.8.1); every other occurrence of TLS_ST_SR_FINISHED, including
- * ordinary handshake completion, still falls through to the unconditional
- * clear below.
+ * still-unacknowledged flight. A post-handshake-auth CertificateRequest is
+ * retired here explicitly, since it completes via the next flight rather
+ * than an ACK (rfc9147 5.8.1); that check is scoped to SSL_PHA_REQUESTED so
+ * it can't also fire for the CertificateRequest an ordinary mTLS initial
+ * handshake sends, which has no such special case and still falls through
+ * to the unconditional clear below.
+ *
+ * Whether a CertificateRequest was actually found there is not itself a
+ * precondition for preserving anything else that is still outstanding:
+ * rfc9147 section 7.1 lets the client ACK the CertificateRequest explicitly
+ * if it cannot produce its response right away, in which case ACK
+ * processing already removed it before Finished arrived. Either way, by
+ * the time Finished is processed it must not still be queued, so retiring
+ * it here is idempotent -- the decision to preserve or discard everything
+ * else is made separately, from whether anything else remains unacked.
+ *
+ * This can also be what finally unblocks a deferred write-key install, with
+ * no ACK record involved at all. dtls1_check_deferred_write_key() normally
+ * runs from dtls_process_ack() as ACK records arrive, but a
+ * CertificateRequest retired above -- or anything else cleared below -- can
+ * be the last thing our own KeyUpdate was waiting on. If so, nothing else
+ * is ever going to call it, so it has to be checked here instead, at every
+ * point this function can return, using the state left by whatever
+ * clearing just happened.
+ *
+ * Returns 0 only on a fatal error installing the deferred write key
+ * (SSLfatal() already called, via dtls1_check_deferred_write_key()); 1
+ * otherwise. The caller must check this and stop driving the connection
+ * forward rather than treat reaching the end of this function as success --
+ * SSLfatal() only records the failure in s->statem.state, it has no other
+ * way to halt execution on its own.
*/
-void dtls1_stop_timer_for_read_flight(SSL_CONNECTION *s)
+int dtls1_stop_timer_for_read_flight(SSL_CONNECTION *s)
{
if (SSL_CONNECTION_IS_DTLS13(s)
&& s->statem.hand_state == TLS_ST_SR_FINISHED
- && dtls1_retire_sent_certificate_request_messages(s)
- && dtls_any_sent_messages_are_missing_acknowledge(s)) {
- dtls1_clear_sent_buffer(s, 1);
- return;
+ && s->post_handshake_auth == SSL_PHA_REQUESTED) {
+ dtls1_retire_sent_certificate_request_messages(s);
+ if (dtls_any_sent_messages_are_missing_acknowledge(s)) {
+ dtls1_clear_sent_buffer(s, 1);
+ return dtls1_check_deferred_write_key(s);
+ }
}
if (SSL_CONNECTION_IS_DTLS13(s)
&& (s->statem.hand_state == TLS_ST_SR_KEY_UPDATE
|| s->statem.hand_state == TLS_ST_CR_KEY_UPDATE
|| s->statem.hand_state == TLS_ST_CR_SESSION_TICKET
- || s->statem.hand_state == TLS_ST_CR_CERT_REQ)
+ || (s->statem.hand_state == TLS_ST_CR_CERT_REQ
+ && s->post_handshake_auth == SSL_PHA_REQUESTED))
&& dtls_any_sent_messages_are_missing_acknowledge(s)) {
dtls1_clear_sent_buffer(s, 1);
- return;
+ return dtls1_check_deferred_write_key(s);
}
dtls1_stop_timer(s);
+ return dtls1_check_deferred_write_key(s);
}
int dtls1_check_timeout_num(SSL_CONNECTION *s)
diff --git a/ssl/d1_msg.c b/ssl/d1_msg.c
index 5d49e3cd1b..fc9c08ad55 100644
--- a/ssl/d1_msg.c
+++ b/ssl/d1_msg.c
@@ -28,6 +28,21 @@ int dtls1_write_app_data_bytes(SSL *s, uint8_t type, const void *buf_,
if (SSL_in_init(s) && !ossl_statem_get_in_handshake(sc)) {
i = sc->handshake_func(s);
+
+ /*
+ * DTLS 1.3: finishing a pending ACK ends a call at TLS_ST_OK, so the
+ * KeyUpdate or ticket still owed is only built by the next call. A
+ * call can also finish with something new owed, such as a response to
+ * a KeyUpdate that arrived while it waited for an ACK. Keep calling
+ * until nothing is owed, before any application data is sent. A call
+ * that needs I/O or fails ends the loop.
+ */
+ while (i > 0 && SSL_CONNECTION_IS_DTLS13(sc)
+ && (sc->key_update != SSL_KEY_UPDATE_NONE
+ || sc->ext.extra_tickets_expected > 0)) {
+ ossl_statem_set_in_init(sc, 1);
+ i = sc->handshake_func(s);
+ }
if (i < 0)
return i;
if (i == 0) {
diff --git a/ssl/record/rec_layer_d1.c b/ssl/record/rec_layer_d1.c
index c99269c702..18fa2222e5 100644
--- a/ssl/record/rec_layer_d1.c
+++ b/ssl/record/rec_layer_d1.c
@@ -106,7 +106,14 @@ static int dtls_buffer_record(SSL_CONNECTION *s, TLS_RECORD *rec)
return -1;
rdata = OPENSSL_malloc(sizeof(*rdata));
- item = pitem_new_u64(rec->seq_num, rdata);
+ /*
+ * DTLS 1.3 sequence numbers restart in every epoch, so records of
+ * different epochs can share one. Key on the epoch as well: the 48-bit
+ * sequence number leaves the top 16 bits for the low bits of the epoch.
+ */
+ item = pitem_new_u64(((rec->epoch & 0xFFFF) << 48)
+ | (rec->seq_num & 0xFFFFFFFFFFFFULL),
+ rdata);
if (rdata == NULL || item == NULL) {
OPENSSL_free(rdata);
pitem_free(item);
@@ -400,6 +407,50 @@ start:
return -1;
if (is_dtls13 && current_state == TLS_ST_OK && SSL_in_init(s)) {
+ if (!ossl_statem_get_in_handshake(sc)) {
+ /*
+ * Post-handshake work is still owed (e.g. a reciprocal
+ * KeyUpdate) and nothing is driving the state machine. Run it
+ * now, as the top of this function does, so the message is
+ * sent and its retransmit timer armed. Otherwise nothing
+ * would be in flight and no event would ever end the wait for
+ * the buffered data. It is released by dtls_unbuffer_record()
+ * once the exchange completes.
+ */
+ i = sc->handshake_func(s);
+ /* SSLfatal() already called if appropriate */
+ if (i < 0)
+ return i;
+ if (i == 0)
+ return -1;
+ goto start;
+ }
+
+ /*
+ * The record was buffered, not dropped or failed: it will be
+ * delivered once the in-progress post-handshake exchange (e.g.
+ * a reciprocal KeyUpdate) completes. Without this, rwstate is
+ * left at whatever start: last set it to (SSL_NOTHING, since
+ * this returns instead of looping back there), and
+ * SSL_get_error() reports SSL_ERROR_SYSCALL on an empty error
+ * queue instead of the ordinary retry condition this actually
+ * is.
+ *
+ * A connection accepted by a DTLS listener has no read BIO of
+ * its own -- it receives through the listener's shared demux
+ * queue instead (see dtls_listener_create_conn_ssl()) -- so
+ * SSL_get_rbio() returns NULL here and these BIO flag updates
+ * must be skipped. SSL_get_error() already reports
+ * SSL_ERROR_WANT_READ for SSL_READING on such a connection
+ * without needing a BIO at all.
+ */
+ BIO *rbio = SSL_get_rbio(s);
+
+ sc->rwstate = SSL_READING;
+ if (rbio != NULL) {
+ BIO_clear_retry_flags(rbio);
+ BIO_set_retry_read(rbio);
+ }
return -1;
}
goto start;
@@ -801,10 +852,20 @@ start:
return -1;
} else if (sc->version == DTLS1_3_VERSION) {
/*
- * Let's let DTLS ACK and retransmits fix this problem.
+ * Only buffer data authenticated under application traffic keys
+ * (epoch 3 onwards), including delayed data interleaved with a
+ * post-handshake authentication response. Plaintext, early data,
+ * and handshake-epoch data must not be buffered here for delivery
+ * as ordinary application data.
*/
- ssl_release_record(sc, rr, 0);
- return -1;
+ if (rr->epoch >= 3 && dtls_buffer_record(sc, rr) < 0) {
+ /* SSLfatal() already called */
+ return -1;
+ }
+ if (!ssl_release_record(sc, rr, 0))
+ return -1;
+ /* Continue to the next record or obtain a genuine read retry. */
+ goto start;
} else {
SSLfatal(sc, SSL_AD_UNEXPECTED_MESSAGE, SSL_R_UNEXPECTED_RECORD);
return -1;
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 0572bd0630..a1330fa10a 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -3158,6 +3158,22 @@ int SSL_shutdown(SSL *s)
return -1;
}
+ /*
+ * An unsent reciprocal KeyUpdate does not prevent an orderly close.
+ * Cancel it only at a completed post-handshake boundary, with no write-key
+ * installation pending. A KeyUpdate already in flight must retain its
+ * state until the exchange completes.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(sc)
+ && !SSL_IS_FIRST_HANDSHAKE(sc)
+ && sc->statem.state == MSG_FLOW_FINISHED
+ && sc->statem.hand_state == TLS_ST_OK
+ && sc->key_update == SSL_KEY_UPDATE_NOT_REQUESTED
+ && !sc->d1->key_update_write_pending) {
+ sc->key_update = SSL_KEY_UPDATE_NONE;
+ ossl_statem_set_in_init(sc, 0);
+ }
+
if (!SSL_in_init(s)) {
if ((sc->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
struct ssl_async_args args;
@@ -3216,9 +3232,13 @@ int SSL_key_update(SSL *s, int updatetype)
/*
* RFC 9147 section 5.8.4: implementations MUST NOT send a KeyUpdate if
- * an earlier one has not yet been acknowledged.
+ * an earlier one has not yet been acknowledged. key_update_write_pending
+ * covers this and more: it also catches an earlier KeyUpdate that is
+ * acknowledged but whose write-key install is still withheld behind an
+ * earlier, unrelated message -- SSL_is_init_finished() above does not
+ * guarantee that has completed.
*/
- if (SSL_CONNECTION_IS_DTLS13(sc) && dtls_has_unacked_key_update(sc)) {
+ if (SSL_CONNECTION_IS_DTLS13(sc) && sc->d1->key_update_write_pending) {
ERR_raise(ERR_LIB_SSL, SSL_R_REQUEST_PENDING);
return 0;
}
@@ -3318,6 +3338,15 @@ int SSL_new_session_ticket(SSL *s)
|| SSL_IS_FIRST_HANDSHAKE(sc) || !sc->server
|| !SSL_CONNECTION_IS_VERSION13(sc))
return 0;
+
+ /*
+ * Our own KeyUpdate's new keys must not be used for anything else
+ * until its write-key install completes (RFC 9147 section 8) -- even
+ * though it may already be acknowledged, with nothing left for
+ * SSL_is_init_finished() to object to.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(sc) && sc->d1->key_update_write_pending)
+ return 0;
sc->ext.extra_tickets_expected++;
if (!RECORD_LAYER_write_pending(&sc->rlayer) && !SSL_in_init(s))
ossl_statem_set_in_init(sc, 1);
@@ -8005,6 +8034,17 @@ int SSL_verify_client_post_handshake(SSL *ssl)
return 0;
}
+ /*
+ * Our own KeyUpdate's new keys must not be used for anything else
+ * until its write-key install completes (RFC 9147 section 8) -- even
+ * though it may already be acknowledged, with nothing left for
+ * SSL_is_init_finished() to object to.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(sc) && sc->d1->key_update_write_pending) {
+ ERR_raise(ERR_LIB_SSL, SSL_R_REQUEST_PENDING);
+ return 0;
+ }
+
switch (sc->post_handshake_auth) {
case SSL_PHA_NONE:
ERR_raise(ERR_LIB_SSL, SSL_R_EXTENSION_NOT_RECEIVED);
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index f5f31e1384..4ccc6c3382 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2221,8 +2221,44 @@ typedef struct dtls_sent_msg_st {
} dtls_sent_msg;
int dtls_any_sent_messages_are_missing_acknowledge(SSL_CONNECTION *s);
+
+/**
+ * @brief Test whether our own KeyUpdate is still awaiting acknowledgement.
+ *
+ * @param s the DTLS connection
+ * @returns 1 if a KeyUpdate in the sent message queue still has unacknowledged
+ * record numbers, 0 otherwise.
+ */
int dtls_has_unacked_key_update(SSL_CONNECTION *s);
+/**
+ * @brief Install a deferred KeyUpdate write key once it is safe to do so.
+ *
+ * If our KeyUpdate is waiting to install its new write keys, check whether
+ * it and every message sent before it is now acknowledged (RFC 9147 erratum
+ * 8047), and install the key if so. Called when an ACK is processed, and when
+ * a flight is retired implicitly with no ACK record involved.
+ *
+ * @param s the DTLS connection
+ * @returns 0 only on a fatal error installing the key, in which case
+ * SSLfatal() has already been called; 1 otherwise, including when
+ * there was nothing to do.
+ */
+int dtls1_check_deferred_write_key(SSL_CONNECTION *s);
+
+/**
+ * @brief Test whether the next expected handshake message is already buffered.
+ *
+ * Reports a fully received message waiting in the reassembly buffer for the
+ * next expected sequence number, independently of any new record arriving, so
+ * that callers keep reading instead of going idle.
+ *
+ * @param s the DTLS connection
+ * @returns 1 if the next expected message is complete and buffered, 0
+ * otherwise.
+ */
+int dtls1_has_buffered_ready_message(SSL_CONNECTION *s);
+
static ossl_inline int dtls_msg_needs_ack(int sentbyserver, unsigned char msgtype)
{
switch (msgtype) {
@@ -2281,6 +2317,28 @@ typedef struct dtls1_state_st {
unsigned int retransmitting;
unsigned int has_change_cipher_spec;
+ /*
+ * Set when our own KeyUpdate has been sent but not yet acknowledged.
+ * The new write keys are not installed until the ACK arrives
+ * per RFC 9147 section 8: the restriction is on using the new
+ * epoch's keys, not on sending altogether, so we keep using the current
+ * keys in the meantime.
+ */
+ unsigned int key_update_write_pending;
+ /*
+ * Set true once our own pending KeyUpdate has been fully acknowledged.
+ * Tracked separately from its sent_messages entry because that entry
+ * can be freed as soon as it's acknowledged, even while
+ * key_update_write_pending is still waiting on an earlier message -- so
+ * the entry may already be gone by the time we need to know this.
+ */
+ unsigned int key_update_acked;
+ /*
+ * The handshake message sequence number of our own pending KeyUpdate,
+ * captured when it was sent. Lets us tell which other queued messages
+ * precede it, without needing its own entry to still be in the queue.
+ */
+ unsigned short key_update_msg_seq;
#ifndef OPENSSL_NO_SCTP
int shutdown_received;
#endif
@@ -3146,7 +3204,21 @@ __owur int dtls1_check_timeout_num(SSL_CONNECTION *s);
__owur int dtls1_handle_timeout(SSL_CONNECTION *s);
void dtls1_start_timer(SSL_CONNECTION *s);
void dtls1_stop_timer(SSL_CONNECTION *s);
-void dtls1_stop_timer_for_read_flight(SSL_CONNECTION *s);
+
+/**
+ * @brief Stop the retransmit timer on reading the peer's flight, keeping any
+ * of our own messages that are still unacknowledged.
+ *
+ * Unlike dtls1_stop_timer(), a DTLS 1.3 connection which still has other
+ * unacknowledged messages outstanding keeps them, retiring only what the
+ * peer's flight implicitly acknowledges (RFC 9147 section 7.2). A deferred
+ * KeyUpdate write key is installed if this unblocks it.
+ *
+ * @param s the DTLS connection
+ * @returns 0 only on a fatal error installing a deferred write key, in which
+ * case SSLfatal() has already been called; 1 otherwise.
+ */
+int dtls1_stop_timer_for_read_flight(SSL_CONNECTION *s);
__owur int dtls1_is_timer_expired(SSL_CONNECTION *s);
void dtls1_clear_current_wrl_from_sent_buffer(SSL_CONNECTION *s);
/**
diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c
index e517246d61..97b2971f2b 100644
--- a/ssl/statem/statem.c
+++ b/ssl/statem/statem.c
@@ -135,6 +135,7 @@ void ossl_statem_clear(SSL_CONNECTION *s)
ossl_statem_set_in_init(s, 1);
s->statem.no_cert_verify = 0;
s->statem.ack_for_retransmit = 0;
+ s->statem.deferred_key_update_state = TLS_ST_BEFORE;
}
/*
@@ -735,8 +736,10 @@ static SUB_STATE_RETURN read_state_machine(SSL_CONNECTION *s)
return SUB_STATE_ERROR;
case MSG_PROCESS_FINISHED_READING:
- if (SSL_CONNECTION_IS_DTLS(s)) {
- dtls1_stop_timer_for_read_flight(s);
+ if (SSL_CONNECTION_IS_DTLS(s)
+ && !dtls1_stop_timer_for_read_flight(s)) {
+ check_fatal(s);
+ return SUB_STATE_ERROR;
}
return SUB_STATE_FINISHED;
@@ -768,8 +771,10 @@ static SUB_STATE_RETURN read_state_machine(SSL_CONNECTION *s)
case WORK_FINISHED_SWAP:
case WORK_FINISHED_STOP:
- if (SSL_CONNECTION_IS_DTLS(s)) {
- dtls1_stop_timer_for_read_flight(s);
+ if (SSL_CONNECTION_IS_DTLS(s)
+ && !dtls1_stop_timer_for_read_flight(s)) {
+ check_fatal(s);
+ return SUB_STATE_ERROR;
}
return SUB_STATE_FINISHED;
}
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 3bdb4c3c2a..da3da65929 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -432,14 +432,18 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
}
/*
- * Resume a write we held back because our own KeyUpdate was still
- * unacknowledged (see the TLS_ST_CR_SESSION_TICKET/TLS_ST_CR_KEY_UPDATE/
- * TLS_ST_CR_CERT_REQ cases below). Once that ACK has arrived, fall
- * through into the switch below as if we were still at the deferred
- * read state, so it can now proceed to construct that response.
+ * Resume a write we held back because our own KeyUpdate's write-key
+ * install was still pending (see the TLS_ST_CR_CERT_REQ case below).
+ * Once that completes, fall through into the switch below as if we
+ * were still at the deferred read state, so it can now proceed to
+ * construct that response. Unlike the server's equivalent resume
+ * check, this one only ever resumes into TLS_ST_CR_CERT_REQ, which
+ * re-validates key_update_write_pending itself before sending
+ * anything -- so either check would behave the same here, but this
+ * one matches what it's actually resuming.
*/
if (st->deferred_key_update_state != TLS_ST_BEFORE
- && !dtls_has_unacked_key_update(s)) {
+ && !s->d1->key_update_write_pending) {
st->hand_state = st->deferred_key_update_state;
st->deferred_key_update_state = TLS_ST_BEFORE;
}
@@ -457,11 +461,17 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
case TLS_ST_CR_CERT_REQ:
if (s->post_handshake_auth == SSL_PHA_REQUESTED) {
- if (SSL_CONNECTION_IS_DTLS13(s) && dtls_has_unacked_key_update(s)) {
+ if (SSL_CONNECTION_IS_DTLS13(s) && s->d1->key_update_write_pending) {
/*
* RFC 9147 section 8: our own KeyUpdate's new keys must not
* be used for anything else until it is acknowledged. Hold
- * this response back rather than send it now.
+ * this response back rather than send it now. Checking
+ * key_update_write_pending rather than
+ * dtls_has_unacked_key_update() matters here: the KeyUpdate
+ * can already be acknowledged while its write-key install is
+ * still withheld behind an earlier, unrelated message, and
+ * sending genuinely new content in that window would still
+ * use the old keys.
*/
st->deferred_key_update_state = st->hand_state;
st->hand_state = TLS_ST_CW_KEY_UPDATE;
@@ -543,16 +553,18 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
case TLS_ST_CR_KEY_UPDATE:
case TLS_ST_CR_SESSION_TICKET:
if (SSL_CONNECTION_IS_DTLS13(s)) {
- if (dtls_has_unacked_key_update(s)) {
- /*
- * RFC 9147 section 8: our own KeyUpdate's new keys must not
- * be used for anything else until it is acknowledged. Hold
- * this ACK back rather than send it now.
- */
- st->deferred_key_update_state = st->hand_state;
- st->hand_state = TLS_ST_CW_KEY_UPDATE;
- return WRITE_TRAN_FINISHED;
- }
+ /*
+ * RFC 9147 section 8's restriction is on using the new
+ * epoch's keys, not on sending at all. Our own KeyUpdate's
+ * write keys are not installed until its ACK arrives, so
+ * this ACK -- for a received KeyUpdate or NewSessionTicket
+ * alike -- can safely go out under our current, still-valid
+ * keys even while our own KeyUpdate is outstanding. Unlike
+ * the TLS_ST_CR_CERT_REQ case above, this is only ever an
+ * ACK, never new post-handshake content, so it never needs
+ * to share deferred_key_update_state with a genuinely held
+ * back authentication response.
+ */
st->hand_state = TLS_ST_CW_ACK;
return WRITE_TRAN_CONTINUE;
}
@@ -581,6 +593,16 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
st->ack_for_retransmit = 0;
return WRITE_TRAN_FINISHED;
}
+ if (SSL_CONNECTION_IS_DTLS13(s) && s->d1->key_update_write_pending) {
+ /*
+ * This ACK was for something unrelated to our own KeyUpdate
+ * (see TLS_ST_CR_KEY_UPDATE above), whose write-key install is
+ * still withheld behind an earlier, unrelated message. Go back
+ * to waiting rather than reporting the connection idle.
+ */
+ st->hand_state = TLS_ST_CW_KEY_UPDATE;
+ return WRITE_TRAN_FINISHED;
+ }
st->hand_state = TLS_ST_OK;
return WRITE_TRAN_CONTINUE;
@@ -877,6 +899,31 @@ WORK_STATE ossl_statem_client_pre_work(SSL_CONNECTION *s, WORK_STATE wst)
return tls_finish_handshake(s, wst, 0, 1);
case TLS_ST_OK:
+ /*
+ * A message that arrived out of order may already be sitting fully
+ * received in the reassembly buffer, waiting on something earlier
+ * that we just finished processing in this same call. Loop back
+ * into reading instead of going idle, rather than stranding it
+ * until some unrelated new record happens to arrive later.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s) && dtls1_has_buffered_ready_message(s)) {
+ /*
+ * This skips tls_finish_handshake() below, so its
+ * post_handshake_auth reset must happen here instead.
+ * Calling the full function isn't an option: its
+ * initial-handshake cleanup clears the reassembly buffer,
+ * which would discard the very message we're trying to keep.
+ *
+ * Without this, a second CertificateRequest already sitting in
+ * that buffer gets rejected as unexpected --
+ * ossl_statem_client_read_transition() only accepts one while
+ * post_handshake_auth is SSL_PHA_EXT_SENT, and it would still
+ * be SSL_PHA_REQUESTED from the exchange that just finished.
+ */
+ if (s->post_handshake_auth == SSL_PHA_REQUESTED)
+ s->post_handshake_auth = SSL_PHA_EXT_SENT;
+ return WORK_FINISHED_SWAP;
+ }
/* Calls SSLfatal() as required */
return tls_finish_handshake(s, wst, 1, 1);
}
@@ -1024,7 +1071,17 @@ WORK_STATE ossl_statem_client_post_work(SSL_CONNECTION *s, WORK_STATE wst)
case TLS_ST_CW_KEY_UPDATE:
if (statem_flush(s) != 1)
return WORK_MORE_A;
- if (!tls13_update_key(s, 1)) {
+ if (SSL_CONNECTION_IS_DTLS13(s)) {
+ /*
+ * RFC 9147 section 8: don't switch to the new write keys
+ * until this KeyUpdate has been acknowledged; keep using
+ * the current, still-valid keys until then. Installed from
+ * dtls_process_ack() once the ACK arrives.
+ */
+ s->d1->key_update_write_pending = 1;
+ s->d1->key_update_acked = 0;
+ s->d1->key_update_msg_seq = s->d1->w_msg.msg_seq;
+ } else if (!tls13_update_key(s, 1)) {
/* SSLfatal() already called */
return WORK_ERROR;
}
diff --git a/ssl/statem/statem_dtls.c b/ssl/statem/statem_dtls.c
index 5f3b395efd..cfb2342421 100644
--- a/ssl/statem/statem_dtls.c
+++ b/ssl/statem/statem_dtls.c
@@ -505,8 +505,11 @@ again:
return 1;
/* reconstruct message header */
- dtls1_write_hm_header(rec_data, s->s3.tmp.message_type, s->s3.tmp.message_size,
- s->d1->r_msg_seq, 0, s->s3.tmp.message_size);
+ if (!dtls1_write_hm_header(rec_data, s->s3.tmp.message_type, s->s3.tmp.message_size,
+ s->d1->r_msg_seq, 0, s->s3.tmp.message_size)) {
+ SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
s->d1->r_msg_seq = 0;
@@ -764,6 +767,30 @@ static int dtls1_retrieve_buffered_fragment(SSL_CONNECTION *s, size_t *len)
}
}
+/*
+ * Whether a fully-received message is already sitting in the reassembly
+ * buffer for the next sequence number we expect, independently of any new
+ * record having just arrived on the wire. dtls1_retrieve_buffered_fragment()
+ * checks this unconditionally on every entry into the read state machine,
+ * but that entry only ever happens as a side effect of a *new*
+ * SSL3_RT_HANDSHAKE/SSL3_RT_ACK record arriving (see dtls1_read_bytes()'s
+ * trampoline) or of a message we just processed ourselves bringing us back
+ * here. Callers use this to decide whether to keep looping instead of
+ * going idle, so a message that arrived out of order and was buffered
+ * ahead of something still missing is not left stranded once that
+ * something finally arrives and is processed in the same call.
+ */
+int dtls1_has_buffered_ready_message(SSL_CONNECTION *s)
+{
+ pitem *item = pqueue_find_u64(&s->d1->rcvd_messages, s->d1->handshake_read_seq);
+ hm_fragment *frag;
+
+ if (item == NULL)
+ return 0;
+ frag = (hm_fragment *)item->data;
+ return frag->reassembly == NULL;
+}
+
static int dtls1_reassemble_fragment(SSL_CONNECTION *s,
const struct hm_header_st *msg_hdr)
{
@@ -1396,6 +1423,65 @@ CON_FUNC_RETURN dtls_construct_ack(SSL_CONNECTION *s, WPACKET *pkt)
return CON_FUNC_SUCCESS;
}
+/*
+ * If our own KeyUpdate is still awaiting installation of its new write
+ * keys, check whether it -- and everything sent before it -- is now
+ * acknowledged, and install the deferred write key if so. A peer can ACK a
+ * buffered message before it has actually processed the messages that
+ * precede it (rfc9147 section 7), so an ACK of the KeyUpdate itself does
+ * not establish that the peer has processed everything sent before it and
+ * installed the corresponding read keys. Erratum 8047 therefore requires
+ * waiting for every preceding message to be acknowledged too, not just the
+ * KeyUpdate -- a message sent *after* the KeyUpdate (e.g. a ticket) is
+ * unaffected and may remain outstanding.
+ *
+ * This deliberately does not search sent_messages for the KeyUpdate's own
+ * entry: key_update_acked, latched by dtls_process_ack() the instant
+ * coverage of that message completes, already captures whether it is
+ * acknowledged, and key_update_msg_seq -- the handshake message sequence
+ * number captured when it was sent -- identifies which other queued
+ * entries precede it, without depending on its own entry still being in
+ * the queue to do so. That matters because this is called from more than
+ * one place: not just from dtls_process_ack() when an ACK record arrives,
+ * but also from dtls1_stop_timer_for_read_flight(), since a preceding
+ * CertificateRequest can instead be retired implicitly (rfc9147 7.2) by
+ * the next flight, with no ACK record of its own ever involved.
+ *
+ * Returns 0 only on a fatal error installing the key (SSLfatal() already
+ * called); 1 otherwise, including when there was nothing to do.
+ */
+int dtls1_check_deferred_write_key(SSL_CONNECTION *s)
+{
+ pitem *item;
+ piterator iter;
+ int preceding_unacked = 0;
+
+ if (!s->d1->key_update_write_pending || !s->d1->key_update_acked)
+ return 1;
+
+ iter = pqueue_iterator(&s->d1->sent_messages);
+ while ((item = pqueue_next(&iter)) != NULL) {
+ dtls_sent_msg *msg = (dtls_sent_msg *)item->data;
+
+ if (msg->msg_info.msg_seq < s->d1->key_update_msg_seq
+ && !ossl_list_record_number_is_empty(&msg->rec_nums)) {
+ preceding_unacked = 1;
+ break;
+ }
+ }
+
+ if (!preceding_unacked) {
+ s->d1->key_update_write_pending = 0;
+ s->d1->key_update_acked = 0;
+ if (!tls13_update_key(s, 1)) {
+ /* SSLfatal() already called */
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
MSG_PROCESS_RETURN dtls_process_ack(SSL_CONNECTION *s, PACKET *pkt)
{
PACKET record_numbers;
@@ -1501,12 +1587,27 @@ MSG_PROCESS_RETURN dtls_process_ack(SSL_CONNECTION *s, PACKET *pkt)
RSMBLY_BITMASK_IS_COMPLETE(msg->covered,
(long)msg->msg_info.msg_body_len, is_complete);
- if (is_complete)
+ if (is_complete) {
ossl_list_record_number_elem_free(&msg->rec_nums);
+ /*
+ * Latch this immediately, rather than re-discovering it
+ * later by searching sent_messages for the entry: once
+ * fully acknowledged, this entry is eligible to be freed
+ * by dtls1_clear_sent_buffer() at any later, unrelated
+ * point (e.g. when the peer's own KeyUpdate is
+ * processed), so its continued presence in the queue
+ * cannot be relied on below.
+ */
+ if (msg->msg_info.msg_type == SSL3_MT_KEY_UPDATE)
+ s->d1->key_update_acked = 1;
+ }
}
}
}
+ if (!dtls1_check_deferred_write_key(s))
+ return MSG_PROCESS_ERROR;
+
/* Keep the retransmit timer running until the whole flight is ACKed. */
if (dtls_any_sent_messages_are_missing_acknowledge(s))
return MSG_PROCESS_CONTINUE_READING;
diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c
index 55ce0a4825..e951dc7901 100644
--- a/ssl/statem/statem_lib.c
+++ b/ssl/statem/statem_lib.c
@@ -728,9 +728,18 @@ MSG_PROCESS_RETURN tls_process_key_update(SSL_CONNECTION *s, PACKET *pkt)
* If we get a request for us to update our sending keys too then, we need
* to additionally send a KeyUpdate message. However that message should
* not also request an update (otherwise we get into an infinite loop).
+ * For DTLS1.3 specifically, this doesn't go through the public
+ * SSL_key_update() API, so it has to set in_init itself: without it,
+ * nothing tells a later SSL_write()/SSL_do_handshake() call to
+ * re-enter the state machine and notice key_update is no longer
+ * SSL_KEY_UPDATE_NONE, and the scheduled KeyUpdate would never
+ * actually get sent.
*/
- if (updatetype == SSL_KEY_UPDATE_REQUESTED)
+ if (updatetype == SSL_KEY_UPDATE_REQUESTED) {
s->key_update = SSL_KEY_UPDATE_NOT_REQUESTED;
+ if (SSL_CONNECTION_IS_DTLS13(s))
+ ossl_statem_set_in_init(s, 1);
+ }
if (!tls13_update_key(s, 0)) {
/* SSLfatal() already called */
@@ -1532,8 +1541,17 @@ WORK_STATE tls_finish_handshake(SSL_CONNECTION *s, ossl_unused WORK_STATE wst,
else if (sctx->info_callback != NULL)
cb = sctx->info_callback;
- /* The callback may expect us to not be in init at handshake done */
- ossl_statem_set_in_init(s, 0);
+ /*
+ * The callback may expect us to not be in init at handshake done --
+ * but not for DTLS1.3 if a KeyUpdate is still scheduled and hasn't
+ * been constructed yet (e.g. a reciprocal one scheduled by
+ * tls_process_key_update() while processing the very message whose
+ * completion brought us here): clearing in_init here would mean
+ * nothing ever tells a later SSL_write()/SSL_do_handshake() call to
+ * re-enter the state machine and actually send it.
+ */
+ if (!SSL_CONNECTION_IS_DTLS13(s) || s->key_update == SSL_KEY_UPDATE_NONE)
+ ossl_statem_set_in_init(s, 0);
if (cb != NULL) {
if (cleanuphand
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 682e816c25..f9561a2d23 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -153,6 +153,19 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
case TLS_ST_SR_COMP_CERT:
case TLS_ST_SR_CERT:
+ /*
+ * DTLS 1.3 only. A PHA response being read here can straddle our
+ * own still-unacknowledged KeyUpdate: datagram reordering can
+ * deliver that KeyUpdate's ACK in between the client's Certificate
+ * and its Finished. Accept it and resume this same read afterward,
+ * restored by the TLS_ST_SR_ACK handling at the top of this
+ * function.
+ */
+ if (mt == DTLS13_MT_ACK) {
+ st->pre_ack_hand_state = st->hand_state;
+ st->hand_state = TLS_ST_SR_ACK;
+ return 1;
+ }
if (st->no_cert_verify || !received_client_cert(s)) {
if (mt == SSL3_MT_FINISHED) {
st->hand_state = TLS_ST_SR_FINISHED;
@@ -167,6 +180,19 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
break;
case TLS_ST_SR_CERT_VRFY:
+ /*
+ * DTLS 1.3 only. A PHA response being read here can straddle our
+ * own still-unacknowledged KeyUpdate: datagram reordering can
+ * deliver that KeyUpdate's ACK in between the client's
+ * CertificateVerify and its Finished. Accept it and resume this
+ * same read afterward, restored by the TLS_ST_SR_ACK handling at
+ * the top of this function.
+ */
+ if (mt == DTLS13_MT_ACK) {
+ st->pre_ack_hand_state = st->hand_state;
+ st->hand_state = TLS_ST_SR_ACK;
+ return 1;
+ }
if (mt == SSL3_MT_FINISHED) {
st->hand_state = TLS_ST_SR_FINISHED;
return 1;
@@ -180,6 +206,32 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
st->hand_state = TLS_ST_SR_ACK;
return 1;
}
+ /*
+ * DTLS 1.3 only. Our own KeyUpdate being outstanding does not stop
+ * the peer from independently sending a KeyUpdate of its own, or
+ * answering a CertificateRequest we already sent: these are
+ * separate reliability state machines (rfc9147 5.8.4) and must be
+ * accepted the same as if we were at TLS_ST_OK.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s) && st->hand_state == TLS_ST_SW_KEY_UPDATE) {
+ if (s->post_handshake_auth == SSL_PHA_REQUESTED) {
+ if (mt == SSL3_MT_CERTIFICATE) {
+ st->hand_state = TLS_ST_SR_CERT;
+ return 1;
+ }
+#ifndef OPENSSL_NO_COMP_ALG
+ if (mt == SSL3_MT_COMPRESSED_CERTIFICATE
+ && s->ext.compress_certificate_sent) {
+ st->hand_state = TLS_ST_SR_COMP_CERT;
+ return 1;
+ }
+#endif
+ }
+ if (mt == SSL3_MT_KEY_UPDATE) {
+ st->hand_state = TLS_ST_SR_KEY_UPDATE;
+ return 1;
+ }
+ }
break;
case TLS_ST_OK:
@@ -633,6 +685,39 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
return WRITE_TRAN_CONTINUE;
}
+ /*
+ * Resume a write we held back waiting on our own KeyUpdate.
+ * deferred_key_update_state holds the real continuation (e.g.
+ * TLS_ST_SW_SESSION_TICKET or TLS_ST_OK), not TLS_ST_SW_ACK itself, and
+ * is copied into deferred_ack_state only here, right before the
+ * TLS_ST_SW_ACK case below consumes it -- deferred_ack_state is also
+ * used by the ack_for_retransmit handling above, which can fire any
+ * number of times while this wait is still in progress, so the
+ * continuation is kept in the field that handling never touches until
+ * the moment it's actually needed.
+ *
+ * What it waits on before resuming depends on which continuation that
+ * is, matching whichever of the two TLS_ST_SR_FINISHED branches below
+ * deferred it: a parked TLS_ST_SW_SESSION_TICKET continuation needs the
+ * real write-key install to have completed (key_update_write_pending),
+ * since issuing that ticket bypasses the public SSL_new_session_ticket()
+ * API's own guard; a parked plain TLS_ST_OK continuation only needs our
+ * KeyUpdate's own ACK (dtls_has_unacked_key_update()), since going idle
+ * sends nothing by itself and every public entry point that could
+ * schedule something new from idle already refuses to while
+ * key_update_write_pending is set -- waiting for the full install there
+ * too would just delay ordinary application data for no reason.
+ */
+ if (st->deferred_key_update_state != TLS_ST_BEFORE
+ && (st->deferred_key_update_state == TLS_ST_SW_SESSION_TICKET
+ ? !s->d1->key_update_write_pending
+ : !dtls_has_unacked_key_update(s))) {
+ st->deferred_ack_state = st->deferred_key_update_state;
+ st->deferred_key_update_state = TLS_ST_BEFORE;
+ st->hand_state = TLS_ST_SW_ACK;
+ return WRITE_TRAN_CONTINUE;
+ }
+
/*
* No case for TLS_ST_BEFORE, because at that stage we have not negotiated
* TLSv1.3 yet, so that is handled by ossl_statem_server_write_transition()
@@ -647,6 +732,15 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
case TLS_ST_OK:
if (s->key_update != SSL_KEY_UPDATE_NONE) {
st->hand_state = TLS_ST_SW_KEY_UPDATE;
+ /*
+ * A reply owed to the peer's KeyUpdate is built here without
+ * going through a public API, so nothing else has checked that
+ * our own KeyUpdate's write-key install is still pending.
+ * Building another KeyUpdate now would reset that install. Wait
+ * in TLS_ST_SW_KEY_UPDATE, which resumes here once it completes.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s) && s->d1->key_update_write_pending)
+ return WRITE_TRAN_FINISHED;
return WRITE_TRAN_CONTINUE;
}
if (s->post_handshake_auth == SSL_PHA_REQUEST_PENDING) {
@@ -737,6 +831,29 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
*/
if (SSL_CONNECTION_IS_DTLS13(s)) {
st->deferred_ack_state = TLS_ST_OK;
+ if (dtls_has_unacked_key_update(s)) {
+ /*
+ * Going idle sends nothing by itself, and
+ * SSL_new_session_ticket(), SSL_verify_client_post_handshake(),
+ * and SSL_key_update() each already refuse to schedule
+ * anything new while key_update_write_pending is set
+ * (test_dtls13_pha_completion_eviction_blocks_new_actions()).
+ * So this only needs our own KeyUpdate's ACK, not the
+ * full write-key install -- key_update_write_pending can
+ * stay set well after that ACK, behind some unrelated
+ * predecessor, and would otherwise delay ordinary
+ * application data for no reason.
+ * Hold this ACK back rather than send it now. The
+ * continuation (TLS_ST_OK) is saved in
+ * deferred_key_update_state, not this TLS_ST_SR_FINISHED
+ * state: re-entering TLS_ST_SR_FINISHED on resume would
+ * rerun this whole case against post_handshake_auth/
+ * ticket_expected state that may have since changed.
+ */
+ st->deferred_key_update_state = TLS_ST_OK;
+ st->hand_state = TLS_ST_SW_KEY_UPDATE;
+ return WRITE_TRAN_FINISHED;
+ }
st->hand_state = TLS_ST_SW_ACK;
} else {
st->hand_state = TLS_ST_OK;
@@ -785,6 +902,43 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
if (SSL_CONNECTION_IS_DTLS13(s)) {
st->deferred_ack_state = next_state;
+ /*
+ * next_state can be TLS_ST_SW_SESSION_TICKET or, if a ticket
+ * was expected but then suppressed above, plain TLS_ST_OK --
+ * and only the former actually needs to wait for our own
+ * KeyUpdate's write-key install. The automatic ticket reissue
+ * built here bypasses the public SSL_new_session_ticket() API
+ * guard entirely, so key_update_write_pending has to be
+ * enforced on this path directly: RFC 9147 section 8's new
+ * keys must not be used for anything else until the install
+ * completes, and key_update_write_pending -- not
+ * dtls_has_unacked_key_update() -- is what tracks that, since
+ * an earlier unrelated message (e.g. a prior ticket) can hold
+ * the install back even after the KeyUpdate itself is
+ * acknowledged. When next_state is plain TLS_ST_OK instead,
+ * this falls back to the same dtls_has_unacked_key_update()
+ * check as the no-ticket-expected branch above, for the same
+ * reason: going idle alone sends nothing, and every public API
+ * entry point that could schedule something new from idle
+ * already refuses to while key_update_write_pending is set.
+ */
+ if (next_state == TLS_ST_SW_SESSION_TICKET
+ ? s->d1->key_update_write_pending != 0
+ : dtls_has_unacked_key_update(s)) {
+ /*
+ * Hold this ACK back rather than send it now. The real
+ * continuation (next_state) is saved in
+ * deferred_key_update_state, not this TLS_ST_SR_FINISHED
+ * state: re-entering TLS_ST_SR_FINISHED on resume would
+ * rerun this whole case, and post_handshake_auth has by
+ * then moved past SSL_PHA_REQUESTED, so the
+ * ticket-suppressing branch above would compute the wrong
+ * continuation.
+ */
+ st->deferred_key_update_state = next_state;
+ st->hand_state = TLS_ST_SW_KEY_UPDATE;
+ return WRITE_TRAN_FINISHED;
+ }
st->hand_state = TLS_ST_SW_ACK;
} else {
st->hand_state = next_state;
@@ -793,17 +947,44 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
case TLS_ST_SR_KEY_UPDATE:
if (SSL_CONNECTION_IS_DTLS13(s)) {
- st->deferred_ack_state = TLS_ST_OK;
+ /*
+ * RFC 9147 section 8's restriction is on using the new
+ * epoch's keys, not on sending at all. Our own KeyUpdate's
+ * write keys are not installed until its ACK arrives (see
+ * dtls_process_ack()), so this ACK can safely go out under
+ * our current, still-valid keys even while our own
+ * KeyUpdate is outstanding. If its write-key install hasn't
+ * completed yet, go back to waiting rather than reporting
+ * the connection idle: key_update_write_pending, not
+ * dtls_has_unacked_key_update(), is what tracks that -- the
+ * KeyUpdate can already be acknowledged while its install is
+ * still withheld behind an earlier, unrelated message, and
+ * reporting idle in that window would admit new
+ * post-handshake content under the old keys.
+ */
+ st->deferred_ack_state = s->d1->key_update_write_pending
+ ? TLS_ST_SW_KEY_UPDATE
+ : TLS_ST_OK;
st->hand_state = TLS_ST_SW_ACK;
return WRITE_TRAN_CONTINUE;
}
/* Fall through */
case TLS_ST_SW_KEY_UPDATE:
- if (SSL_CONNECTION_IS_DTLS13(s))
+ /*
+ * Parked here waiting for our own KeyUpdate's write-key install,
+ * which can complete as a side effect of acknowledging some
+ * entirely unrelated message (e.g. a retransmitted ticket) --
+ * nothing re-enters this function for that on its own, so this
+ * case has to notice it the next time it's reached instead of
+ * assuming key_update_write_pending is still set just because
+ * hand_state hasn't moved. Otherwise anything scheduled behind
+ * it (TLS_ST_OK's own key_update check below) would stay stuck
+ * here indefinitely once the install genuinely finishes.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s) && s->d1->key_update_write_pending)
/* We wait for ACK */
return WRITE_TRAN_FINISHED;
- else
- st->hand_state = TLS_ST_OK;
+ st->hand_state = TLS_ST_OK;
return WRITE_TRAN_CONTINUE;
case TLS_ST_SW_SESSION_TICKET:
@@ -833,6 +1014,15 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
st->ack_for_retransmit = 0;
return WRITE_TRAN_FINISHED;
}
+ if (st->hand_state == TLS_ST_SW_KEY_UPDATE) {
+ /*
+ * We are only here to resume waiting for our own KeyUpdate's
+ * ACK (see the TLS_ST_SR_KEY_UPDATE and TLS_ST_SR_FINISHED
+ * cases above) -- it was already sent, so this must not
+ * trigger constructing another one.
+ */
+ return WRITE_TRAN_FINISHED;
+ }
return WRITE_TRAN_CONTINUE;
}
@@ -1124,6 +1314,15 @@ WORK_STATE ossl_statem_server_pre_work(SSL_CONNECTION *s, WORK_STATE wst)
/* Fall through */
case TLS_ST_OK:
+ /*
+ * A message that arrived out of order may already be sitting fully
+ * received in the reassembly buffer, waiting on something earlier
+ * that we just finished processing in this same call. Loop back
+ * into reading instead of going idle, rather than stranding it
+ * until some unrelated new record happens to arrive later.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s) && dtls1_has_buffered_ready_message(s))
+ return WORK_FINISHED_SWAP;
/* Calls SSLfatal() as required */
return tls_finish_handshake(s, wst, 1, 1);
}
@@ -1346,7 +1545,17 @@ WORK_STATE ossl_statem_server_post_work(SSL_CONNECTION *s, WORK_STATE wst)
case TLS_ST_SW_KEY_UPDATE:
if (statem_flush(s) != 1)
return WORK_MORE_A;
- if (!tls13_update_key(s, 1)) {
+ if (SSL_CONNECTION_IS_DTLS13(s)) {
+ /*
+ * RFC 9147 section 8: don't switch to the new write keys
+ * until this KeyUpdate has been acknowledged; keep using
+ * the current, still-valid keys until then. Installed from
+ * dtls_process_ack() once the ACK arrives.
+ */
+ s->d1->key_update_write_pending = 1;
+ s->d1->key_update_acked = 0;
+ s->d1->key_update_msg_seq = s->d1->w_msg.msg_seq;
+ } else if (!tls13_update_key(s, 1)) {
/* SSLfatal() already called */
return WORK_ERROR;
}
diff --git a/test/dtls13_internal_test.c b/test/dtls13_internal_test.c
index 7fe13c1fe9..459587e976 100644
--- a/test/dtls13_internal_test.c
+++ b/test/dtls13_internal_test.c
@@ -14,9 +14,12 @@
#include "internal/ssl_unwrap.h"
#include "helpers/ssltestlib.h"
#include "testutil.h"
+#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
+#include <stdlib.h>
+#include <string.h>
static char *cert = NULL;
static char *privkey = NULL;
@@ -251,6 +254,193 @@ static int test_seq_num_reconstruction(int idx)
}
#ifndef OPENSSL_NO_DTLS1_3
+/*
+ * Epoch-0 application data received before ClientHello must be discarded,
+ * allowing an ordinary direct DTLS connection to complete its handshake and
+ * deliver only protected application data.
+ */
+static int test_dtls13_epoch0_app_data(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ const unsigned char injected[] = {
+ SSL3_RT_APPLICATION_DATA,
+ 0xfe, 0xfd, /* legacy record version: DTLS 1.2 */
+ 0, 0, /* epoch 0: no authentication */
+ 0, 0, 0, 0, 0, 63, /* leave ClientHello sequence 0 in the replay window */
+ 0, 4, /* payload length */
+ 'E', 'V', 'I', 'L'
+ };
+ const unsigned char msg[] = { 'G', 'O', 'O', 'D' };
+ const unsigned char reply[] = { 'S', 'A', 'F', 'E' };
+ unsigned char buf[64];
+ int ret, err, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client,
+ NULL, NULL)))
+ goto end;
+
+ /*
+ * Write a raw datagram through the standard mempacket BIO. With no
+ * packet-injection or record-drop controls enabled, it preserves the
+ * wire sequence numbers of both this record and the real handshake.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_wbio(client), injected,
+ sizeof(injected)),
+ sizeof(injected)))
+ goto end;
+
+ /*
+ * No ClientHello has been sent. Require a legitimate
+ * retry before starting the client. If buffering the plaintext returns
+ * without setting BIO retry flags, SSL_get_error() reports SYSCALL here:
+ * that is an early API-error finding, not post-handshake data delivery.
+ */
+ ret = SSL_accept(server);
+ err = SSL_get_error(server, ret);
+ if (!TEST_int_eq(err, SSL_ERROR_WANT_READ)
+ || !TEST_int_lt(ret, 0)) {
+ TEST_info("Pre-ClientHello SSL_accept failed; "
+ "post-handshake delivery was not tested");
+ goto end;
+ }
+ if (!TEST_int_eq(SSL_get_state(server), TLS_ST_BEFORE)
+ || !TEST_false(SSL_is_init_finished(server)))
+ goto end;
+
+ /* Leave all application reads to the post-handshake delivery checks. */
+ if (!TEST_true(create_bare_ssl_connection(server, client,
+ SSL_ERROR_NONE, 0, 0))
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(SSL_version(server), DTLS1_3_VERSION)
+ || !TEST_int_eq(SSL_version(client), DTLS1_3_VERSION))
+ goto end;
+
+ /* Verify protected traffic in the other direction before the oracle. */
+ if (!TEST_int_eq(SSL_write(server, reply, sizeof(reply)), sizeof(reply))
+ || !TEST_int_eq(SSL_read(client, buf, sizeof(buf)), sizeof(reply))
+ || !TEST_mem_eq(buf, sizeof(reply), reply, sizeof(reply)))
+ goto end;
+
+ /* Equal payload lengths make EVIL fail the content check against GOOD. */
+ if (!TEST_int_eq(SSL_write(client, msg, sizeof(msg)), sizeof(msg))
+ || !TEST_int_eq(SSL_read(server, buf, sizeof(buf)), sizeof(msg))
+ || !TEST_mem_eq(buf, sizeof(msg), msg, sizeof(msg)))
+ goto end;
+
+ /* Also reject injected data queued behind the protected record. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * Application data protected with handshake keys must also be discarded while
+ * epoch 2 is active, before Finished installs the application read keys. Use
+ * the client's saved Finished writer so the injected record really authenticates.
+ */
+static int test_dtls13_active_epoch2_app_data(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ dtls_sent_msg *msg;
+ struct dtls1_retransmit_state *saved;
+ pitem *item;
+ OSSL_RECORD_TEMPLATE tmpl = { 0 };
+ const unsigned char injected[] = { 'E', 'V', 'I', 'L' };
+ const unsigned char good[] = { 'G', 'O', 'O', 'D' };
+ unsigned char finished[4096], buf[64];
+ int finishedlen, ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ ret = SSL_connect(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_accept(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_connect(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->sent_messages), 1)
+ || !TEST_ptr(item = pqueue_peek(&cc->d1->sent_messages)))
+ goto end;
+ msg = item->data;
+ saved = &msg->saved_retransmit_state;
+ if (!TEST_int_eq(msg->msg_info.msg_type, SSL3_MT_FINISHED)
+ || !TEST_uint64_t_eq(saved->wrl->epoch, 2))
+ goto end;
+
+ /* Withhold Finished so the injected data is read under active epoch 2. */
+ finishedlen = BIO_read(SSL_get_rbio(server), finished, sizeof(finished));
+ if (!TEST_int_gt(finishedlen, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+ tmpl.type = SSL3_RT_APPLICATION_DATA;
+ tmpl.version = DTLS1_2_VERSION;
+ tmpl.buf = injected;
+ tmpl.buflen = sizeof(injected);
+ if (!TEST_true(saved->wrlmethod->set1_bio(saved->wrl, cc->wbio))
+ || !TEST_int_eq(saved->wrlmethod->write_records(saved->wrl, &tmpl, 1),
+ OSSL_RECORD_RETURN_SUCCESS)
+ || !TEST_uint64_t_eq(tmpl.epoch, 2)
+ || !TEST_int_gt(BIO_flush(cc->wbio), 0))
+ goto end;
+
+ ret = SSL_accept(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_EARLY_DATA)
+ || !TEST_uint64_t_eq(sc->rlayer.rrl->epoch, 2)
+ /* The replay window advances only after successful deprotection. */
+ || !TEST_uint64_t_eq(sc->rlayer.rrl->bitmap.max_seq_num,
+ tmpl.sequence_number)
+ || !TEST_size_t_eq(pqueue_size(sc->rlayer.d->buffered_app_data), 0))
+ goto end;
+
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), finished, finishedlen),
+ finishedlen)
+ || !TEST_true(create_bare_ssl_connection(server, client,
+ SSL_ERROR_NONE, 0, 0))
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(SSL_write(client, good, sizeof(good)), sizeof(good))
+ || !TEST_int_eq(SSL_read(server, buf, sizeof(buf)), sizeof(good))
+ || !TEST_mem_eq(buf, sizeof(good), good, sizeof(good)))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
/* Empty and single-entry ACK vectors, with and without trailing data. */
static int test_dtls13_ack_length(int idx)
{
@@ -413,6 +603,19 @@ static int test_dtls13_ack_coverage(int server)
|| !TEST_false(ossl_time_is_zero(sc->d1->next_timeout)))
goto end;
+ /*
+ * When the sender is the client, mTLS is turned on above, so the
+ * peer (the server) sent its own CertificateRequest as part of the
+ * initial handshake, not PHA. Once the client's response -- the
+ * next flight -- arrives, that CertificateRequest and the rest of
+ * the server's flight must be fully retired, not preserved by the
+ * PHA-specific handling.
+ */
+ if (!server
+ && (!TEST_size_t_eq(pqueue_size(&psc->d1->sent_messages), 0)
+ || !TEST_true(ossl_time_is_zero(psc->d1->next_timeout))))
+ goto end;
+
/* ACK the last message first, keeping the earlier message outstanding. */
iter = pqueue_iterator(&sc->d1->sent_messages);
while ((item = pqueue_next(&iter)) != NULL)
@@ -1641,10 +1844,9 @@ static int test_dtls13_finished_ack_loss_recovers(void)
goto end;
/*
- * The server has already moved to the next read epoch and discarded the
- * old one, so it cannot authenticate this retransmission and never
- * produces a replacement ACK. This is the assertion that must flip once
- * the previous read epoch is retained: a fresh ACK should appear here.
+ * The server has already moved to the next read epoch, so this
+ * retransmission only authenticates via the server's retained previous
+ * read epoch: a fresh ACK must appear here.
*/
ret = SSL_read(server, &buf, sizeof(buf));
if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
@@ -1749,10 +1951,9 @@ static int test_dtls13_keyupdate_ack_loss_recovers(int idx)
goto end;
/*
- * The receiver has already moved to the next read epoch and discarded
- * the old one, so it cannot authenticate this retransmission and never
- * produces a replacement ACK. This is the assertion that must flip once
- * the previous read epoch is retained: a fresh ACK should appear here.
+ * The receiver has already moved to the next read epoch, so this
+ * retransmission only authenticates via the receiver's retained
+ * previous read epoch: a fresh ACK must appear here.
*/
ret = SSL_read(receiver, &buf, sizeof(buf));
if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)
@@ -2243,9 +2444,9 @@ end:
* above), but the ACK branch of dtls_get_reassembled_message() returns
* before the retained-epoch restriction is applied, dtls1_read_bytes()
* records the authenticating epoch for handshake records only, and
- * dtls_process_ack() removes matching entries from the retransmission queue
- * without checking which epoch authenticated the ACK. This test fails until
- * the ACK path enforces the handshake/application protection boundary.
+ * dtls_process_ack() removes matching entries from the retransmission queue,
+ * so the ACK path must itself enforce the handshake/application protection
+ * boundary rather than relying on which epoch authenticated the record.
*/
static int test_dtls13_retained_epoch_ack_authority(void)
{
@@ -3729,7 +3930,7 @@ static int test_dtls13_keyupdate_preserves_flight(int idx)
/* The preserved tickets must still be retransmittable, and be ACKed. */
sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(),
ossl_seconds2time(1));
- if (!TEST_int_gt(DTLSv1_handle_timeout(server), 0))
+ if (!TEST_true(SSL_handle_events(server)))
goto end;
ret = SSL_read(client, buf, 1);
if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
@@ -3743,7 +3944,7 @@ static int test_dtls13_keyupdate_preserves_flight(int idx)
if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
|| !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0)
|| !TEST_true(ossl_time_is_zero(sc->d1->next_timeout))
- || !TEST_int_eq(DTLSv1_handle_timeout(server), 0))
+ || !TEST_true(SSL_handle_events(server)))
goto end;
/* Application data must still flow in both directions. */
@@ -3819,8 +4020,24 @@ static int test_dtls13_keyupdate_preserves_flight(int idx)
if (!TEST_int_eq(SSL_get_state(client), TLS_ST_CW_KEY_UPDATE))
goto end;
- /* Nothing should have gone out at the new epoch yet. */
- if (!TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ /*
+ * The ticket's ACK isn't held back like new content would be, so
+ * it already reached the server. That's harmless: the server's
+ * own flight was already retired above, so the ACK matches
+ * nothing there.
+ */
+ if (!TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server's own read epoch already advanced when it processed
+ * the client's KeyUpdate above, so this ACK -- sent at the client's
+ * still-unbumped write epoch -- now only authenticates via the
+ * server's retained previous read epoch.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0))
goto end;
/* A second KeyUpdate must be refused while the first is unacked. */
@@ -3967,7 +4184,7 @@ static int test_dtls13_cert_req_preserves_flight(void)
* authenticates normally, producing a genuine, fresh ACK.
*/
cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
- if (!TEST_int_gt(DTLSv1_handle_timeout(client), 0))
+ if (!TEST_true(SSL_handle_events(client)))
goto end;
ret = SSL_read(server, buf, 1);
if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
@@ -4080,7 +4297,7 @@ static int test_dtls13_pha_keyupdate_shared_wrl(int idx)
* test -- it just needs to still be unread when the epoch moves on.
*/
cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
- if (!TEST_int_gt(DTLSv1_handle_timeout(client), 0))
+ if (!TEST_true(SSL_handle_events(client)))
goto end;
ret = SSL_read(server, buf, 1);
if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
@@ -4089,8 +4306,8 @@ static int test_dtls13_pha_keyupdate_shared_wrl(int idx)
/*
* Now the application requests a KeyUpdate. It is sent under the
- * current epoch's keys, before the connection moves on to the next
- * epoch.
+ * current epoch's keys; those write keys are not superseded until this
+ * KeyUpdate is itself acknowledged, so the epoch does not change yet.
*/
if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
goto end;
@@ -4100,9 +4317,10 @@ static int test_dtls13_pha_keyupdate_shared_wrl(int idx)
goto end;
/*
- * Confirm the premise: Finished and KeyUpdate were sent under the
- * identical epoch's keys, and the connection has since moved on to the
- * next epoch.
+ * Confirm the premise: Finished and KeyUpdate share the same saved
+ * write record layer. The KeyUpdate's own new write keys are not
+ * installed until it is acknowledged, so at this point both messages
+ * are still sitting under that one, current write layer.
*/
iter = pqueue_iterator(&cc->d1->sent_messages);
while ((item = pqueue_next(&iter)) != NULL) {
@@ -4174,7 +4392,7 @@ static int test_dtls13_pha_keyupdate_shared_wrl(int idx)
* correctly by continuing to use it afterward.
*/
cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
- if (!TEST_int_gt(DTLSv1_handle_timeout(client), 0))
+ if (!TEST_true(SSL_handle_events(client)))
goto end;
ret = SSL_read(server, buf, 1);
if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
@@ -4275,7 +4493,165 @@ static int test_dtls13_cert_req_finished_preserves_ticket(void)
* an uncollected leftover: force it and let the client process it.
*/
sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
- if (!TEST_int_gt(DTLSv1_handle_timeout(server), 0))
+ if (!TEST_true(SSL_handle_events(server)))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(ticket_count, 1))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * rfc9147 section 7.1 lets the client ACK a CertificateRequest explicitly
+ * if it cannot produce its response right away, rather than only ever
+ * completing it implicitly by eventually answering with Certificate and
+ * Finished. If that explicit ACK happens before Finished arrives,
+ * dtls1_retire_sent_certificate_request_messages() finds nothing left to
+ * retire when Finished is later processed -- that must not stop a
+ * different, still-unacknowledged ticket from being preserved.
+ */
+static int test_dtls13_cert_req_explicit_ack_preserves_ticket(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ dtls_sent_msg *msg;
+ DTLS1_RECORD_NUMBER *recnum;
+ piterator iter;
+ pitem *item;
+ unsigned char buf[2048], ack[18];
+ WPACKET pkt;
+ uint64_t epoch, seqnum;
+ size_t acklen, written;
+ int ret, dropped, found, testresult = 0;
+
+ ticket_count = 0;
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ SSL_CTX_set_session_cache_mode(cctx, SSL_SESS_CACHE_CLIENT);
+ SSL_CTX_sess_set_new_cb(cctx, count_ticket);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ /* Request PHA; this sends just the CertificateRequest. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * The client ACKs the CertificateRequest explicitly instead of
+ * answering it yet, modelling the rfc9147 7.1 case. Construct that
+ * ACK directly against the CertificateRequest's own record number,
+ * the same way test_dtls13_ack_coverage() constructs synthetic ACKs.
+ */
+ iter = pqueue_iterator(&sc->d1->sent_messages);
+ if (!TEST_ptr(item = pqueue_next(&iter)))
+ goto end;
+ msg = item->data;
+ if (!TEST_ptr(recnum = ossl_list_record_number_head(&msg->rec_nums)))
+ goto end;
+ epoch = recnum->epoch;
+ seqnum = recnum->seqnum;
+ if (!TEST_true(WPACKET_init_static_len(&pkt, ack, sizeof(ack), 2))
+ || !TEST_true(WPACKET_put_bytes_u64(&pkt, epoch))
+ || !TEST_true(WPACKET_put_bytes_u64(&pkt, seqnum))
+ || !TEST_true(WPACKET_finish(&pkt))
+ || !TEST_true(WPACKET_get_total_written(&pkt, &acklen))) {
+ WPACKET_cleanup(&pkt);
+ goto end;
+ }
+ WPACKET_cleanup(&pkt);
+ if (!TEST_int_eq(dtls1_write_bytes(cc, SSL3_RT_ACK, ack, acklen, &written), 1)
+ || !TEST_size_t_eq(written, acklen)
+ || !TEST_int_gt(BIO_flush(cc->wbio), 0))
+ goto end;
+
+ /*
+ * The server processes that explicit ACK. The CertificateRequest is
+ * now gone via ordinary ACK processing, not via the Finished-triggered
+ * retire exercised below.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ /*
+ * The client, which never saw the explicit ACK above (it was
+ * constructed directly against the server's view of what it sent),
+ * still answers the CertificateRequest itself with Certificate and
+ * Finished. This must happen before the ticket below is sent and
+ * dropped: the CertificateRequest datagram is otherwise still
+ * sitting unread in the client's rbio, and draining the rbio for the
+ * ticket would discard it too, along with this response.
+ */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Now send a ticket -- sequenced after the CertificateRequest -- and
+ * lose it before the client ever sees it. The client's response above
+ * is already sitting unprocessed in the server's rbio at this point.
+ */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(client), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /*
+ * The server processes the client's response, reaching
+ * TLS_ST_SR_FINISHED.
+ * dtls1_retire_sent_certificate_request_messages() finds nothing to
+ * retire here -- the CertificateRequest is already gone, from the
+ * explicit ACK above -- which must not stop the still-unacknowledged,
+ * unrelated ticket from being preserved.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1)
+ || !TEST_false(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+ found = 0;
+ iter = pqueue_iterator(&sc->d1->sent_messages);
+ while ((item = pqueue_next(&iter)) != NULL) {
+ msg = item->data;
+
+ if (msg->msg_info.msg_type == SSL3_MT_NEWSESSION_TICKET)
+ found++;
+ }
+ if (!TEST_int_eq(found, 1))
+ goto end;
+
+ /*
+ * Confirm the surviving entry is a genuinely live retransmit, not just
+ * an uncollected leftover: force it and let the client process it.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server)))
goto end;
ret = SSL_read(client, buf, 1);
if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
@@ -4290,6 +4666,4648 @@ end:
SSL_CTX_free(cctx);
return testresult;
}
+
+/*
+ * The same protections apply symmetrically when the server is the one with
+ * an outstanding KeyUpdate: its new write keys are not installed until its
+ * own KeyUpdate is acknowledged (see the deferred-install logic in
+ * dtls_process_ack() and the TLS_ST_SW_KEY_UPDATE/TLS_ST_CW_KEY_UPDATE
+ * post-work cases), so it must still refuse to start a second KeyUpdate of
+ * its own in that window.
+ *
+ * It must NOT, however, hold back acknowledging whatever the peer sends it
+ * meanwhile: since its own new keys are not installed yet, that
+ * acknowledgment goes out under its current, still-valid keys, which RFC
+ * 9147 section 8 never restricted.
+ *
+ * Here the server's own KeyUpdate is unacknowledged when the client
+ * independently sends its own, unrelated KeyUpdate.
+ */
+static int test_dtls13_server_keyupdate_preserves_ack(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t s_wepoch, c_wepoch;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ s_wepoch = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+ c_wepoch = dtls1_get_epoch(cc, SSL3_CC_WRITE);
+
+ /*
+ * The server sends a KeyUpdate and waits for its ACK. Its write epoch
+ * must not advance yet -- the new keys are held back until the ACK
+ * arrives.
+ */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), s_wepoch))
+ goto end;
+
+ /*
+ * The client independently sends its own, unrelated KeyUpdate to the
+ * server, without ever reading the server's KeyUpdate. This is not a
+ * response to anything the server sent -- it just needs to arrive
+ * while the server's own KeyUpdate is outstanding. Same deal: the
+ * client's write epoch must not advance until its own KeyUpdate is
+ * acknowledged.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->sent_messages), 1)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), c_wepoch))
+ goto end;
+
+ /*
+ * The server processes the client's KeyUpdate and immediately
+ * acknowledges it under its current, still-valid keys rather than
+ * holding it back. In the same call, it also receives the client's
+ * acknowledgment of the server's own KeyUpdate -- sent by the client
+ * back in the previous step, at the client's own still-current epoch,
+ * and only decryptable here because the server retains its previous
+ * read epoch's keys (which lets an old-epoch ACK that arrives after the
+ * read epoch has already moved on still authenticate, rather than being
+ * silently dropped). That completes the server's own KeyUpdate too:
+ * both sides are now fully resolved, with no deadlock, in just this
+ * one call.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), s_wepoch + 1))
+ goto end;
+
+ /*
+ * The client processes the server's KeyUpdate, acknowledging it
+ * immediately for the same reason, and in the same call also
+ * processes the server's acknowledgment of the client's own
+ * KeyUpdate (sent by the server just above). That completes the
+ * client's KeyUpdate too: its new write keys are installed and its
+ * retransmit entry is retired.
+ */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->sent_messages), 0)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), c_wepoch + 1))
+ goto end;
+
+ /* Confirm both newly installed write keys actually work. */
+ if (!TEST_int_eq(SSL_write(client, "c", 1), 1))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "c", 1))
+ goto end;
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "s", 1))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The server's own KeyUpdate can also become outstanding while a
+ * post-handshake authentication exchange it started is still in progress.
+ * Completing that exchange must not acknowledge the client's response, or
+ * issue a new ticket, under the KeyUpdate's new, unconfirmed keys -- both
+ * must wait until the KeyUpdate is itself acknowledged.
+ *
+ * This does not need to separately cover the server proactively issuing a
+ * new CertificateRequest or ticket of its own while its KeyUpdate is
+ * outstanding: SSL_verify_client_post_handshake() and
+ * SSL_new_session_ticket() both already refuse to start a new one while
+ * the connection is still mid-handshake-activity for any reason, which an
+ * unacknowledged KeyUpdate always is. That path never becomes reachable,
+ * so there is nothing to hold back there.
+ */
+static int test_dtls13_server_keyupdate_preserves_pha(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char buf[2048];
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /* Request PHA first, and let the client answer it. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Now send the server's own KeyUpdate. Processing it also reads the
+ * client's already-waiting response in the same call, so completing
+ * PHA and sending the KeyUpdate happen together here. The KeyUpdate
+ * itself is expected on the wire to the client -- what must NOT also
+ * go out is the completed PHA exchange's own acknowledgment, since
+ * the server's KeyUpdate is still unacknowledged at this point. The
+ * server's retransmit queue holding exactly the KeyUpdate (not also
+ * an ACK) confirms that.
+ */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /* The client reads the server's KeyUpdate and acknowledges it. */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose that acknowledgment, leaving the KeyUpdate outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * Recover the KeyUpdate by retransmission. The client already
+ * processed the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server)))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Once that acknowledgment is processed, the KeyUpdate is fully
+ * acknowledged, and the held-back acknowledgment of the PHA response
+ * resumes: it is finally sent.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ /* Confirm it is genuine, processable data, not just bytes. */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The server's own KeyUpdate being deferred-resumed after a post-handshake
+ * authentication exchange must resume by actually sending the held-back
+ * ACK, not by reprocessing TLS_ST_SR_FINISHED from scratch. The latter sees
+ * post_handshake_auth already advanced past SSL_PHA_REQUESTED on this
+ * second pass, takes the ordinary "no ticket" branch, and overwrites
+ * deferred_ack_state -- silently dropping a NewSessionTicket that the first,
+ * PHA pass had already decided to issue.
+ */
+static int test_dtls13_server_keyupdate_preserves_pha_ticket(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char buf[2048];
+ size_t sent_tickets, new_quota;
+ int i, ret, testresult = 0;
+
+ ticket_count = 0;
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ SSL_CTX_set_session_cache_mode(cctx, SSL_SESS_CACHE_CLIENT);
+ SSL_CTX_sess_set_new_cb(cctx, count_ticket);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /*
+ * create_ssl_connection() only has the client read (and thus ACK) the
+ * initial handshake's NewSessionTicket messages; the server never reads
+ * those ACKs. Drain them here so sent_messages starts out empty, rather
+ * than carrying two leftover, unacked ticket entries into the scenario
+ * below.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ /* The initial handshake already issued its configured ticket quota. */
+ sent_tickets = sc->sent_tickets;
+ if (!TEST_size_t_gt(sent_tickets, 0)
+ || !TEST_int_eq(ticket_count, (int)sent_tickets))
+ goto end;
+
+ /*
+ * Raise the ticket quota. tls_post_process_client_certificate() is
+ * going to reset sc->sent_tickets to 0 once it processes the PHA
+ * response's Certificate below ("resend session tickets" on
+ * re-authentication), so this is not "one extra ticket" -- it is the
+ * quota the server will fully reissue from scratch, since num_tickets
+ * > sent_tickets(==0) again once that reset happens.
+ */
+ new_quota = sent_tickets + 1;
+ if (!TEST_true(SSL_set_num_tickets(server, new_quota)))
+ goto end;
+
+ /*
+ * Request PHA first, and let the client answer it. The ticket-issuing
+ * branch under test (ossl_statem_server13_write_transition()'s
+ * TLS_ST_SR_FINISHED case) skips issuing a ticket outright whenever
+ * SSL_VERIFY_PEER is set with no sid_ctx configured -- unrelated to the
+ * KeyUpdate defer/resume logic under test, but SSL_VERIFY_PEER is
+ * required to make the server actually request a client certificate
+ * for PHA, so a sid_ctx must be set too.
+ */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_set_session_id_context(server,
+ (const unsigned char *)"pha_ticket_test", 15)))
+ goto end;
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Send the server's own KeyUpdate. Processing it also reads the
+ * client's already-waiting PHA response in the same call, deciding to
+ * reissue the ticket quota -- but holding both that and the PHA
+ * response's own ACK back behind the still-unacknowledged KeyUpdate.
+ */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /* The client reads the server's KeyUpdate and acknowledges it. */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes that ACK. Its own KeyUpdate is now fully
+ * acknowledged, so both the held-back PHA-response ACK and the full
+ * reissued ticket quota decided above must now actually be sent.
+ * TLS_ST_SW_SESSION_TICKET's write_transition case keeps constructing
+ * tickets, once resumed, until sent_tickets reaches num_tickets again
+ * (SSL_IS_FIRST_HANDSHAKE() stays true throughout this connection) --
+ * i.e. sc->sent_tickets ends at new_quota, not at 1.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_OK)
+ || !TEST_size_t_eq(sc->sent_tickets, new_quota)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ /*
+ * The pending bytes above are not just the tickets: the held-back ACK
+ * for the client's PHA Finished must itself have actually been
+ * constructed and sent, not silently skipped in favour of jumping
+ * straight to the tickets. dtls_construct_ack() drains
+ * sc->d1->ack_rec_num as it writes each entry, so a still-pending
+ * entry here means the resume reached TLS_ST_SW_ACK without ever
+ * dispatching to it for construction.
+ */
+ if (!TEST_true(ossl_list_record_number_is_empty(&sc->d1->ack_rec_num)))
+ goto end;
+
+ /*
+ * Confirm these are genuine, processable tickets, not just bytes.
+ * Mirror create_ssl_connection_ex()'s pattern for the initial
+ * handshake's own tickets: one SSL_read() per ticket, not a single call
+ * draining all of them.
+ */
+ for (i = 0; i < (int)new_quota; i++) {
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ }
+ if (!TEST_int_eq(ticket_count, (int)(sent_tickets + new_quota)))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A server's own KeyUpdate can be acknowledged while its write-key install
+ * is still withheld behind an earlier, unrelated message that is itself
+ * still unacknowledged (dtls1_check_deferred_write_key()'s
+ * preceding_unacked check) -- the KeyUpdate's own ACK alone does not mean
+ * the new keys are usable yet.
+ *
+ * This drives the same CertReq-then-KeyUpdate ordering as
+ * test_dtls13_keyupdate_install_on_pha_cert_req_retirement() above (the PHA
+ * response is held back, not delivered, until after the KeyUpdate is
+ * acknowledged), with two additions: the ticket quota is raised so PHA
+ * completion takes the ticket-reissuing branch, and the initial handshake's
+ * own ticket ACK is dropped first, so that ticket is still the oldest
+ * outstanding message by the time the held PHA response is finally
+ * delivered -- at which point the KeyUpdate is acknowledged but the ticket
+ * is not.
+ *
+ * The write-key install, and the PHA-response ACK and reissued ticket
+ * quota waiting on it, must all stay deferred as long as that ticket is
+ * outstanding: sending them under the still-current epoch would be content
+ * the client, already expecting the new one, cannot accept. Forcing the
+ * ticket's own retransmission and letting the client acknowledge it clears
+ * the last predecessor; only then does the install complete, the deferred
+ * ACK and tickets go out for real under the new epoch, and application
+ * data flows normally in both directions.
+ */
+static int test_dtls13_server_keyupdate_pha_ticket_waits_for_earlier_ack(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ size_t new_quota;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ if (!TEST_size_t_gt(sc->sent_tickets, 0)
+ || !TEST_size_t_gt(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ /*
+ * Drop the client's ACK for the initial handshake's ticket before the
+ * server ever sees it. The ticket stays the oldest outstanding
+ * predecessor for the rest of this test.
+ */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ new_quota = sc->sent_tickets + 1;
+ if (!TEST_true(SSL_set_num_tickets(server, new_quota)))
+ goto end;
+
+ /* Request PHA -- nothing else is outstanding except the dropped ticket. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_set_session_id_context(server,
+ (const unsigned char *)"pha_ticket_wait_test", 20)))
+ goto end;
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+
+ /*
+ * The client reads the CertificateRequest and immediately builds and
+ * sends its Certificate + Finished response.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Drop that response before the server ever sees it -- its eventual
+ * retirement is only ever implicit, via RFC 9147 7.2, once delivered
+ * below.
+ */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate is acknowledged. Both the initial ticket (dropped
+ * above) and the CertificateRequest (not yet delivered) are still
+ * outstanding -- the write-key install must stay withheld.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Force the client to retransmit its still-unacknowledged PHA response. */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server processes the recovered Certificate + Finished. This
+ * implicitly retires the CertificateRequest (RFC 9147 7.2), leaving
+ * only the still-dropped initial ticket outstanding. The KeyUpdate is
+ * already acknowledged at this point, so retiring the CertificateRequest
+ * must not be mistaken for "nothing left to wait for": issuing the
+ * reissued ticket quota now would send it under the old epoch. The
+ * write-key install must stay withheld while the initial ticket is
+ * outstanding, and nothing new may be sent to the client yet.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ /*
+ * The initial ticket's ACK was dropped, not merely delayed, so recovery
+ * depends on the server's own retransmit timer for it, not on anything
+ * the client does unprompted. Force that retransmission; once the
+ * client acknowledges it, nothing is left outstanding, the deferred
+ * write-key install can finally complete, and the parked ACK plus
+ * reissued ticket quota must be sent for real, under the new epoch.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_OK)
+ || !TEST_size_t_eq(sc->sent_tickets, new_quota))
+ goto end;
+
+ /* The client receives the ACK and each reissued ticket under the new epoch. */
+ for (ret = 0; ret < (int)new_quota; ret++) {
+ int r = SSL_read(client, buf, sizeof(buf));
+
+ if (!TEST_int_eq(SSL_get_error(client, r), SSL_ERROR_WANT_READ))
+ goto end;
+ }
+ if (!TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_READ), epoch_before + 1))
+ goto end;
+
+ /*
+ * Application data flows both ways under the new epoch -- explicitly,
+ * not merely inferred from the writes/reads succeeding: a receiver that
+ * still accepted the old, retired epoch would let this pass silently.
+ */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_READ), epoch_before + 1)
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ /*
+ * SSL_KEY_UPDATE_NOT_REQUESTED only updates the server's write/the
+ * client's read direction -- the client never sends a reciprocal
+ * KeyUpdate, so the server's own read epoch stays at epoch_before for
+ * the life of this connection; "c" above was decrypted under that
+ * unchanged epoch, not a new one.
+ */
+ if (!TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_READ), epoch_before))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * ossl_statem_server13_write_transition()'s top-of-function ack_for_retransmit
+ * handling and its deferred-KeyUpdate resume handling (see
+ * test_dtls13_server_keyupdate_preserves_pha_ticket() above) both use
+ * st->deferred_ack_state, for two different purposes: the former saves
+ * st->hand_state there to come back to after re-ACKing a stale
+ * retransmission without reprocessing it; the latter saves there what
+ * should happen once the held-back PHA-response ACK is finally sent (here,
+ * issuing the reissued ticket quota).
+ *
+ * While the server is parked at TLS_ST_SW_KEY_UPDATE waiting for its own
+ * KeyUpdate to be acknowledged, with deferred_ack_state holding that
+ * ticket continuation, an entirely ordinary DTLS retransmission -- the
+ * client's retransmit timer for its own still-unacknowledged PHA response
+ * firing, because the server is deliberately holding that ACK back --
+ * takes the ack_for_retransmit path and overwrites deferred_ack_state with
+ * TLS_ST_SW_KEY_UPDATE (st->hand_state at that moment). The ticket
+ * continuation is lost. Once the KeyUpdate is finally acknowledged, the
+ * resume sends the held-back ACK correctly, but then reads the clobbered
+ * deferred_ack_state back into hand_state, hits the
+ * "already sent, don't construct another" guard for TLS_ST_SW_KEY_UPDATE,
+ * and parks there permanently: deferred_key_update_state is already
+ * TLS_ST_BEFORE by this point, so nothing will ever resume it again. The
+ * ticket is never sent and the server's write side never reaches
+ * TLS_ST_OK.
+ */
+static int test_dtls13_server_keyupdate_pha_ticket_survives_retransmit(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ size_t sent_tickets, new_quota;
+ int ret, testresult = 0;
+
+ ticket_count = 0;
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ SSL_CTX_set_session_cache_mode(cctx, SSL_SESS_CACHE_CLIENT);
+ SSL_CTX_sess_set_new_cb(cctx, count_ticket);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ /*
+ * create_ssl_connection() only has the client read (and thus ACK) the
+ * initial handshake's NewSessionTicket messages; the server never
+ * reads those ACKs. Drain them here so sent_messages starts empty.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ /* The initial handshake already issued its configured ticket quota. */
+ sent_tickets = sc->sent_tickets;
+ if (!TEST_size_t_gt(sent_tickets, 0)
+ || !TEST_int_eq(ticket_count, (int)sent_tickets))
+ goto end;
+
+ /*
+ * Raise the ticket quota. tls_post_process_client_certificate() resets
+ * sc->sent_tickets to 0 once it processes the PHA response's
+ * Certificate below, so the server reissues this whole quota once
+ * Finished is processed and num_tickets > sent_tickets(==0) again.
+ */
+ new_quota = sent_tickets + 1;
+ if (!TEST_true(SSL_set_num_tickets(server, new_quota)))
+ goto end;
+
+ /* Request PHA first, and let the client answer it. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_set_session_id_context(server,
+ (const unsigned char *)"pha_ticket_test", 15)))
+ goto end;
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * The client answers the CertificateRequest with Certificate and
+ * Finished, sitting unprocessed in the server's rbio for now.
+ */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Send the server's own KeyUpdate. Processing it also reads the
+ * client's already-waiting PHA response in the same call, deciding to
+ * reissue the ticket quota -- but holding both that and the PHA
+ * response's own ACK back behind the still-unacknowledged KeyUpdate.
+ */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * Before the server's own KeyUpdate is acknowledged, the client's
+ * retransmit timer for its still-unacknowledged PHA response fires --
+ * an entirely ordinary DTLS retransmission, not an adversarial one --
+ * and it resends Certificate and Finished.
+ */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(),
+ ossl_seconds2time(1));
+ if (!TEST_int_gt(DTLSv1_handle_timeout(client), 0))
+ goto end;
+
+ /*
+ * The server recognizes this as a retransmission of an
+ * already-processed message and takes the ack_for_retransmit path to
+ * re-ACK it without reprocessing, while still parked at
+ * TLS_ST_SW_KEY_UPDATE waiting for its own KeyUpdate's ACK.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The client reads the server's KeyUpdate and acknowledges it. */
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes that ACK. Its own KeyUpdate is now fully
+ * acknowledged, so both the held-back PHA-response ACK and the full
+ * reissued ticket quota decided above must now actually be sent --
+ * this is exactly what the ack_for_retransmit interleaving above must
+ * not be able to prevent.
+ */
+ ret = SSL_read(server, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_OK)
+ || !TEST_size_t_eq(sc->sent_tickets, new_quota)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * dtls_process_ack() must not install a pending KeyUpdate's new write keys
+ * merely because the KeyUpdate's own record was acknowledged. RFC 9147
+ * section 7 lets a peer ACK a buffered message before it has processed the
+ * messages that precede it, so an ACK of the KeyUpdate alone does not
+ * establish that the peer has processed everything sent before it and
+ * installed the corresponding read keys. Erratum 8047 requires waiting for
+ * every preceding message to be acknowledged too.
+ *
+ * The server sends a ticket, then -- without waiting for it to be
+ * acknowledged -- a KeyUpdate, both still at the current write epoch. Only
+ * the KeyUpdate datagram is delivered, so the client buffers it ahead of
+ * the still-missing ticket and does not install new read keys; the record
+ * is merely queued on its ACK list. Forcing the client to flush that
+ * queued ACK immediately, before the ticket ever arrives, models a peer
+ * acknowledging a buffered record it has not actually processed (OpenSSL's
+ * own client would not otherwise flush it until the next in-order
+ * message). The server must still withhold its new write keys, because the
+ * ticket sent before the KeyUpdate remains unacknowledged.
+ *
+ * The ticket's retransmit timer is then forced, so the still-unacknowledged
+ * ticket (and only the ticket -- the already-acknowledged KeyUpdate is not
+ * retransmitted, per rfc9147) goes out again. The client processes it and
+ * acknowledges it. The KeyUpdate it had buffered ahead of the ticket is now
+ * unblocked -- dtls1_has_buffered_ready_message() is what lets the same
+ * read that just handled the ticket loop back and dispatch it too, instead
+ * of stranding it until some unrelated new record happens to arrive later.
+ * Once the ticket's ACK arrives, everything is finally acknowledged, and
+ * the server must install its new write keys; application data must then
+ * flow normally both ways, since the client's own read epoch was updated
+ * when it processed the KeyUpdate above.
+ */
+static int test_dtls13_keyupdate_ack_defers_write_keys(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char discard[2048], buf;
+ uint64_t epoch_before;
+ unsigned short seq_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* Send a ticket, then a KeyUpdate the client never gets to see yet. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Drop the ticket datagram; the KeyUpdate datagram is still queued behind it. */
+ if (!TEST_int_gt(BIO_read(SSL_get_rbio(client), discard, sizeof(discard)), 0))
+ goto end;
+
+ /* The client buffers the out-of-order KeyUpdate rather than processing it. */
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->rcvd_messages), 1)
+ || !TEST_ptr(ossl_list_record_number_head(&cc->d1->ack_rec_num)))
+ goto end;
+
+ /*
+ * Force the client to flush that queued ACK now, as though it were
+ * acknowledging the buffered KeyUpdate on receipt rather than once it
+ * is actually processed.
+ */
+ cc->statem.ack_for_retransmit = 1;
+ ossl_statem_set_in_init(cc, 1);
+ ret = SSL_do_handshake(client);
+ if (ret != 1 && !TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate's own record is now acknowledged, but the ticket sent
+ * before it is not. The new write keys must still be withheld.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * The ticket is still outstanding, so its retransmit timer is still
+ * armed. Force it: the ticket goes out again, but the already-acked
+ * KeyUpdate entry does not -- dtls1_retransmit_sent_messages() skips
+ * anything with an empty record-number list.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ /*
+ * The client processes the retransmitted ticket -- now, finally, in
+ * order -- and acknowledges it. The KeyUpdate it had buffered ahead of
+ * the ticket is dispatched in this same call too: handshake_read_seq
+ * advances by 2 (ticket, then KeyUpdate), and the reassembly buffer
+ * drains completely rather than leaving the KeyUpdate stranded.
+ */
+ seq_before = cc->d1->handshake_read_seq;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq((int)cc->d1->handshake_read_seq, (int)seq_before + 2)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->rcvd_messages), 0))
+ goto end;
+
+ /*
+ * The server processes the ticket's ACK: everything is now
+ * acknowledged, so the deferred write keys must finally be installed.
+ */
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_true(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ /*
+ * Application data now flows both ways: the client's own read epoch
+ * was updated when it processed the KeyUpdate above.
+ */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The same deferred-write-key guarantee as
+ * test_dtls13_keyupdate_ack_defers_write_keys() above, exercised end to end
+ * without ever buffering anything out of order: the ticket is delivered and
+ * processed normally, but its ACK specifically is what gets lost in
+ * transit. The KeyUpdate sent after it is delivered and acknowledged
+ * normally too, so the server ends up seeing exactly the preconditions
+ * comment 1 is about -- the KeyUpdate's record acknowledged, the earlier
+ * ticket's not -- through perfectly ordinary message delivery, nothing
+ * buffered out of order at all.
+ *
+ * Recovering the lost ACK via the ticket's own retransmit timer -- the same
+ * mechanism test_dtls13_cert_req_explicit_ack_preserves_ticket() already
+ * proves elsewhere in this file, where a retransmission is recognized and
+ * freshly acknowledged rather than reprocessed -- then lets both the
+ * deferred write-key install and bidirectional application data be
+ * verified completely: the client's own read epoch was already updated
+ * when it processed the KeyUpdate in order, so unlike the sibling test
+ * above there is nothing left needing a further trigger to catch up.
+ */
+static int test_dtls13_keyupdate_ack_defers_write_keys_ticket_ack_lost(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char discard[2048], buf;
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), discard, sizeof(discard)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /* The KeyUpdate is sent, delivered, and acknowledged normally too. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate is acknowledged, but the ticket sent before it is
+ * not -- its ACK never arrived. The new write keys must still be
+ * withheld.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Force the ticket's retransmit timer. The client already processed
+ * the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes the fresh ACK: everything is now acknowledged,
+ * so the deferred write keys must finally be installed.
+ */
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_true(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ /*
+ * Application data flows both ways: the client's own read epoch was
+ * already updated when it processed the KeyUpdate above, in order, so
+ * this needs no further trigger to catch up.
+ */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A ticket's ACK is lost, and the server's own KeyUpdate is then sent and
+ * acknowledged normally, so it is correctly withheld behind that
+ * still-unacknowledged ticket. The client then sends its own, independent
+ * KeyUpdate; the server processing that finishes a read flight, which
+ * clears the server's own, now fully-acknowledged KeyUpdate entry out of
+ * sent_messages, leaving only the still-unacknowledged ticket behind.
+ * Recovering the ticket's ACK after that must still install the deferred
+ * write key, even though the KeyUpdate's own entry no longer exists in
+ * sent_messages to be found.
+ */
+static int test_dtls13_keyupdate_ack_survives_entry_eviction(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char discard[2048], buf;
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), discard, sizeof(discard)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /* The server's KeyUpdate is sent, delivered, and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate is acknowledged, but the ticket sent before it is not.
+ * The new write keys must still be withheld -- same as the sibling
+ * test up to this point.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Now the client sends its own KeyUpdate. The server processing it
+ * finishes a read flight, which clears the server's own,
+ * already-acknowledged KeyUpdate entry out of sent_messages -- the
+ * queue drops from two entries to one, leaving only the still-
+ * unacknowledged ticket.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /* The server's own KeyUpdate is still logically pending. */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Let the client see the server's ACK of its KeyUpdate. */
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Force the ticket's retransmit timer. The client already processed
+ * the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes the fresh ACK: everything is now acknowledged,
+ * so the deferred write keys must finally be installed -- even though
+ * the KeyUpdate's own sent_messages entry was evicted before this
+ * point.
+ */
+ ret = SSL_read(server, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /* Application data now flows both ways under the new keys. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, &buf, 1), 1)
+ || !TEST_uchar_eq(buf, 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The same entry-eviction scenario as
+ * test_dtls13_keyupdate_ack_survives_entry_eviction() above, but with a
+ * completed post-handshake authentication exchange as the trigger instead
+ * of an inbound KeyUpdate from the peer: completing the client's PHA
+ * response (Certificate + Finished) also finishes a read flight, the same
+ * as processing that inbound KeyUpdate does in the sibling test.
+ *
+ * PHA must be requested before the server's own KeyUpdate even exists:
+ * SSL_verify_client_post_handshake() refuses to start while any KeyUpdate
+ * is still outstanding (see test_dtls13_server_keyupdate_preserves_pha()'s
+ * doc comment above), so the reverse order used by the sibling test isn't
+ * available here. The ticket being unacknowledged does not block it,
+ * though, so that ordering constraint is the only one in play.
+ *
+ * To still isolate the KeyUpdate's send-and-ack cycle from the PHA
+ * response -- rather than reading the response bundled into the same call
+ * that sends the KeyUpdate, as the existing PHA tests do -- the client's
+ * first PHA response is dropped before the server ever reads it. The
+ * KeyUpdate is then sent and acknowledged on its own. Only afterward is
+ * the client's still-unacknowledged response recovered, by forcing its own
+ * retransmit timer: that recovery is what finishes the read flight and
+ * evicts the server's already-acknowledged KeyUpdate entry, exactly as in
+ * the sibling test, just reached by a different route.
+ */
+static int test_dtls13_keyupdate_ack_survives_pha_completion_eviction(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * Request PHA -- allowed, since an outstanding ticket alone is not
+ * "mid-handshake-activity" the way an outstanding KeyUpdate is.
+ */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2))
+ goto end;
+
+ /*
+ * The client reads the CertificateRequest and immediately builds and
+ * sends its Certificate + Finished response.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Drop that response before the server ever sees it. This isolates the
+ * server's own KeyUpdate, sent next, from being bundled together with
+ * reading the PHA response in the same call, the way the existing PHA
+ * tests do it -- the response is only delivered later, by the client's
+ * own retransmit timer, once the KeyUpdate has already been fully
+ * acknowledged.
+ */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 3)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate is acknowledged, but the ticket sent before it is not.
+ * The new write keys must still be withheld. The PHA response's entry
+ * is still sitting in sent_messages too, unacknowledged -- the server
+ * never saw the client's first attempt.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Force the client to retransmit its still-unacknowledged PHA
+ * response -- the server never saw the original.
+ */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server processes the recovered Certificate + Finished. This
+ * finishes a read flight, which clears the server's own, already-
+ * acknowledged KeyUpdate entry out of sent_messages -- leaving only the
+ * still-unacknowledged ticket behind. PHA itself completes normally:
+ * nothing here was waiting on the KeyUpdate's own ACK, only on the
+ * ticket's, so the PHA response's ACK is not held back either.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * The server's own KeyUpdate is still logically pending -- even though
+ * PHA just completed successfully and its ACK is already on the wire.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Application data flows both ways right now, under the client's
+ * retained, still-current epoch -- demonstrating that this alone is
+ * not proof the deferred key change completed.
+ */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ /*
+ * Force the ticket's retransmit timer. The client already processed
+ * the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes the fresh ACK: everything is now acknowledged,
+ * so the deferred write keys must finally be installed -- even though
+ * the KeyUpdate's own sent_messages entry was evicted, by PHA
+ * completion, before this point.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /* Application data still flows both ways, now under the new epoch. */
+ if (!TEST_int_eq(SSL_write(server, "S", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'S')
+ || !TEST_int_eq(SSL_write(client, "C", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'C'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The same guarantee as
+ * test_dtls13_keyupdate_ack_survives_pha_completion_eviction() above --
+ * install succeeds once every message the KeyUpdate was withheld behind is
+ * resolved -- but reached a different way. Here the CertificateRequest
+ * itself is the only preceding message, and RFC 9147 section 7.2 lets it
+ * be retired purely implicitly, by the client's Certificate + Finished
+ * response, with no ACK record involved at all. No ticket is involved
+ * either, so by the time that response is processed, sent_messages goes
+ * completely empty in that same call -- nothing else is ever going to
+ * arrive afterward to prompt a recheck, so the install has to happen as a
+ * direct consequence of processing that response.
+ */
+static int test_dtls13_keyupdate_install_on_pha_cert_req_retirement(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* Request PHA -- nothing else is outstanding yet. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ /*
+ * The client reads the CertificateRequest and immediately builds and
+ * sends its Certificate + Finished response.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Drop that response before the server ever sees it -- it is never
+ * explicitly ACKed in this test at all. Its eventual retirement is
+ * only ever implicit, via RFC 9147 7.2.
+ */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The KeyUpdate is acknowledged by an ordinary ACK record. The
+ * CertificateRequest sent before it is the only thing still
+ * outstanding -- and it was never, and will never be, explicitly
+ * ACKed.
+ */
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Force the client to retransmit its still-unacknowledged PHA
+ * response -- the server never saw the original.
+ */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server processes the recovered Certificate + Finished. This
+ * implicitly retires the CertificateRequest (RFC 9147 7.2) -- no ACK
+ * record is involved at all. With nothing else outstanding, the
+ * sent_messages queue goes completely empty right here. The deferred
+ * write key must be installed as a direct consequence of this, since
+ * no ACK record will ever arrive to trigger it afterward.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ if (!TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /* Application data flows both ways under the new epoch. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+static int pha_install_failure_armed;
+static int pha_install_failure_hits;
+
+/* Match by allocation size, not file/line -- portable and OPENSSL_NO_FILENAMES-safe. */
+static void *pha_install_failure_malloc(size_t n, const char *file, int line)
+{
+ if (pha_install_failure_armed && n == sizeof(OSSL_RECORD_LAYER)) {
+ pha_install_failure_hits++;
+ pha_install_failure_armed = 0;
+ return NULL;
+ }
+ return malloc(n);
+}
+
+static void *pha_install_failure_realloc(void *p, size_t n, const char *file,
+ int line)
+{
+ return realloc(p, n);
+}
+
+static void pha_install_failure_free(void *p, const char *file, int line)
+{
+ free(p);
+}
+
+/*
+ * dtls1_stop_timer_for_read_flight() calls dtls1_check_deferred_write_key()
+ * and ignores the result. SSLfatal() records MSG_FLOW_ERROR, but
+ * read_state_machine() returns SUB_STATE_FINISHED and state_machine() then
+ * assigns MSG_FLOW_WRITING, silently discarding it. This drives the exact
+ * same scenario as test_dtls13_keyupdate_install_on_pha_cert_req_retirement()
+ * above, but forces the deferred write-key install's first allocation
+ * (ssl/record/methods/tls_common.c, the OPENSSL_zalloc in
+ * tls_int_new_record_layer()) to fail via CRYPTO_set_mem_functions().
+ *
+ * That call only succeeds as the very first thing a process does, before any
+ * OpenSSL allocation -- but mfail_install() (test/mfail/mfail.c) already took
+ * that slot, as the first statement of this binary's main(), before this
+ * test or any other ever ran. Once installed, every allocation routes
+ * through its replacement malloc_impl instead of CRYPTO_malloc()'s own
+ * fallback -- the only place that would otherwise block later customization
+ * -- so swapping it again here, this deep into the run, still succeeds.
+ */
+static int test_dtls13_pha_install_failure(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+ CRYPTO_malloc_fn save_malloc;
+ CRYPTO_realloc_fn save_realloc;
+ CRYPTO_free_fn save_free;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1))
+ goto end;
+
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Drop the client's PHA response; its retirement will be implicit. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ if (!TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Force the client to retransmit its still-unacknowledged PHA response. */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The call under test: processing the recovered Certificate + Finished
+ * implicitly retires the CertificateRequest and attempts the deferred
+ * write-key install. Force its first allocation to fail, restoring the
+ * previous allocator immediately afterward so nothing later in this
+ * binary is affected.
+ */
+ CRYPTO_get_mem_functions(&save_malloc, &save_realloc, &save_free);
+ if (!TEST_true(CRYPTO_set_mem_functions(pha_install_failure_malloc,
+ pha_install_failure_realloc, pha_install_failure_free)))
+ goto end;
+ pha_install_failure_armed = 1;
+ ret = SSL_read(server, buf, sizeof(buf));
+ CRYPTO_set_mem_functions(save_malloc, save_realloc, save_free);
+
+ if (!TEST_int_eq(pha_install_failure_hits, 1)
+ || !TEST_uint64_t_ne(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * The explicit ACK path (dtls_process_ack() checking
+ * dtls1_check_deferred_write_key()'s return) leaves MSG_FLOW_ERROR on
+ * this same allocation failure and rejects the next write. This
+ * implicit path must do the same: the traffic secret and epoch have
+ * already changed, so recovering silently here would mean using the new
+ * epoch's keys for content the peer never agreed to.
+ */
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_SSL)
+ || !TEST_true(ossl_statem_in_error(sc)))
+ goto end;
+
+ ret = SSL_write(server, "x", 1);
+ if (!TEST_int_le(ret, 0)
+ || !TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_SSL))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+#define PHA_ACK_STOPS_TIMER_MAX_RECS 4
+
+/*
+ * Split a buffer of concatenated DTLS 1.3 unified-header records -- as
+ * produced by this stack's own writer, which always sets both the
+ * sequence-number and length bits -- into its individual records' lengths,
+ * so each can be delivered to the peer as its own separate datagram.
+ */
+static int split_unified_header_records(const unsigned char *in, size_t inlen,
+ size_t reclen[PHA_ACK_STOPS_TIMER_MAX_RECS])
+{
+ size_t off = 0;
+ int n = 0;
+
+ while (off < inlen) {
+ size_t len;
+
+ if (n >= PHA_ACK_STOPS_TIMER_MAX_RECS
+ || inlen - off < DTLS13_UNI_HDR_FIXED_LENGTH
+ || !DTLS13_UNI_HDR_FIX_BITS_IS_SET(in[off])
+ || DTLS13_UNI_HDR_CID_BIT_IS_SET(in[off])
+ || !DTLS13_UNI_HDR_LEN_BIT_IS_SET(in[off])
+ || !DTLS13_UNI_HDR_SEQ_BIT_IS_SET(in[off]))
+ return -1;
+ len = DTLS13_UNI_HDR_FIXED_LENGTH
+ + (((size_t)in[off + 3] << 8) | in[off + 4]);
+ if (inlen - off < len)
+ return -1;
+ reclen[n++] = len;
+ off += len;
+ }
+ return n;
+}
+
+/*
+ * A datagram sent before PHA can arrive between Certificate and Finished.
+ * This reaches the generic application-data fallback in TLS_ST_SR_CERT,
+ * rather than the earlier TLS_ST_OK/ACK-wait buffering path.
+ *
+ * idx 0: Finished is already queued behind the delayed application data.
+ * idx 1: Finished arrives after a read retry with the data still buffered.
+ */
+static int test_dtls13_pha_interleaved_app_data(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char held[2048], flight[4096], buf[64];
+ const unsigned char msg[] = { 'G', 'O', 'O', 'D' };
+ size_t flightlen = 0, reclen[PHA_ACK_STOPS_TIMER_MAX_RECS];
+ int heldlen, nrec, ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /* Hold a genuine application record, retaining its original sequence. */
+ if (!TEST_int_eq(SSL_write(client, msg, sizeof(msg)), sizeof(msg)))
+ goto end;
+ heldlen = BIO_read(SSL_get_rbio(server), held, sizeof(held));
+ if (!TEST_int_gt(heldlen, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Split the real empty-Certificate/Finished response into two records. */
+ while (BIO_ctrl_pending(SSL_get_rbio(server)) > 0) {
+ if (!TEST_size_t_lt(flightlen, sizeof(flight)))
+ goto end;
+ ret = BIO_read(SSL_get_rbio(server), flight + flightlen,
+ (int)(sizeof(flight) - flightlen));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ flightlen += (size_t)ret;
+ }
+ nrec = split_unified_header_records(flight, flightlen, reclen);
+ if (!TEST_int_eq(nrec, 2)
+ || !TEST_int_eq(BIO_write(SSL_get_rbio(server), flight, (int)reclen[0]),
+ (int)reclen[0]))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_SR_CERT)
+ || !TEST_true(SSL_in_init(server))
+ || !TEST_uint64_t_eq(sc->rlayer.rrl->epoch, 3))
+ goto end;
+
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), held, heldlen), heldlen))
+ goto end;
+ if (idx == 1) {
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_SR_CERT)
+ || !TEST_false(SSL_is_init_finished(server))
+ || !TEST_size_t_eq(pqueue_size(sc->rlayer.d->buffered_app_data), 1))
+ goto end;
+ }
+
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), flight + reclen[0],
+ (int)reclen[1]),
+ (int)reclen[1]))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, sizeof(msg))
+ || !TEST_mem_eq(buf, sizeof(msg), msg, sizeof(msg))
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_size_t_eq(pqueue_size(sc->rlayer.d->buffered_app_data), 0))
+ goto end;
+
+ /* Complete the client's ACK wait and verify subsequent traffic both ways. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(SSL_write(client, "next", 4), 4)
+ || !TEST_int_eq(SSL_read(server, buf, sizeof(buf)), 4)
+ || !TEST_mem_eq(buf, 4, "next", 4)
+ || !TEST_int_eq(SSL_write(server, "reply", 5), 5)
+ || !TEST_int_eq(SSL_read(client, buf, sizeof(buf)), 5)
+ || !TEST_mem_eq(buf, 5, "reply", 5))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * dtls_process_ack() must stop the retransmission timer when an ACK
+ * completes the outstanding flight even while the read sub-state-machine
+ * is still mid-flight on an incomplete post-handshake authentication
+ * response: returning MSG_PROCESS_CONTINUE_READING there bypasses the
+ * dtls1_stop_timer_for_read_flight() call that ordinary flight completion
+ * makes, so a fully-acknowledged flight's timer was otherwise left
+ * running, eventually failing the connection with SSL_R_READ_TIMEOUT_EXPIRED
+ * even though nothing was left to retransmit.
+ *
+ * The client's PHA response (an empty Certificate, then Finished) is split
+ * into its individual records here and delivered one at a time -- OpenSSL
+ * would otherwise coalesce them into one datagram -- so an explicit ACK of
+ * the CertificateRequest (rfc9147 section 7.1) can be injected between
+ * them, leaving the server mid-read (TLS_ST_SR_CERT) at the moment its own
+ * flight becomes fully acknowledged.
+ */
+static int test_dtls13_pha_ack_stops_timer(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ dtls_sent_msg *msg;
+ DTLS1_RECORD_NUMBER *recnum;
+ piterator iter;
+ pitem *item;
+ unsigned char dgram[4096], ack[18], buf[256];
+ size_t dgramlen, reclen[PHA_ACK_STOPS_TIMER_MAX_RECS], off;
+ WPACKET pkt;
+ uint64_t epoch, seqnum;
+ size_t acklen, written;
+ int ret, nrec, i, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ /* Request PHA; this sends just the CertificateRequest. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 1)
+ || !TEST_false(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ /* The CertificateRequest's own record number, to forge its ACK below. */
+ iter = pqueue_iterator(&sc->d1->sent_messages);
+ if (!TEST_ptr(item = pqueue_next(&iter)))
+ goto end;
+ msg = item->data;
+ if (!TEST_ptr(recnum = ossl_list_record_number_head(&msg->rec_nums)))
+ goto end;
+ epoch = recnum->epoch;
+ seqnum = recnum->seqnum;
+
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Capture the client's PHA response without letting the server see any
+ * of it yet, and split it into its individual records.
+ */
+ dgramlen = 0;
+ while (BIO_ctrl_pending(SSL_get_rbio(server)) > 0) {
+ ret = BIO_read(SSL_get_rbio(server), dgram + dgramlen,
+ (int)(sizeof(dgram) - dgramlen));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ dgramlen += (size_t)ret;
+ }
+ nrec = split_unified_header_records(dgram, dgramlen, reclen);
+ if (!TEST_int_ge(nrec, 2))
+ goto end;
+
+ /* Deliver only the Certificate. Hold the rest back. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), dgram, (int)reclen[0]),
+ (int)reclen[0]))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->statem.hand_state, TLS_ST_SR_CERT)
+ || !TEST_false(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ /*
+ * The explicit ACK, as permitted by rfc9147 section 7.1, arrives after
+ * Certificate and before Finished -- while the server is still mid-read
+ * of the PHA response.
+ */
+ if (!TEST_true(WPACKET_init_static_len(&pkt, ack, sizeof(ack), 2))
+ || !TEST_true(WPACKET_put_bytes_u64(&pkt, epoch))
+ || !TEST_true(WPACKET_put_bytes_u64(&pkt, seqnum))
+ || !TEST_true(WPACKET_finish(&pkt))
+ || !TEST_true(WPACKET_get_total_written(&pkt, &acklen))) {
+ WPACKET_cleanup(&pkt);
+ goto end;
+ }
+ WPACKET_cleanup(&pkt);
+ if (!TEST_int_eq(dtls1_write_bytes(cc, SSL3_RT_ACK, ack, acklen, &written), 1)
+ || !TEST_size_t_eq(written, acklen)
+ || !TEST_int_gt(BIO_flush(cc->wbio), 0))
+ goto end;
+
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ /*
+ * A fully-ACKed entry isn't swept out of sent_messages until
+ * something calls dtls1_clear_sent_buffer(), so check
+ * acknowledgment directly rather than via pqueue_size().
+ */
+ || !TEST_false(dtls_any_sent_messages_are_missing_acknowledge(sc))
+ || !TEST_int_eq(sc->statem.hand_state, TLS_ST_SR_CERT))
+ goto end;
+
+ /*
+ * The flight is now fully acknowledged, but the server is still
+ * mid-read: the timer must be stopped here regardless, not left
+ * running until the rest of the PHA response completes it.
+ */
+ if (!TEST_true(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ /* The withheld records still complete the authentication exchange. */
+ off = reclen[0];
+ for (i = 1; i < nrec; i++) {
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), dgram + off,
+ (int)reclen[i]),
+ (int)reclen[i]))
+ goto end;
+ off += reclen[i];
+ }
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_int_eq(sc->statem.hand_state, TLS_ST_OK)
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_true(ossl_time_is_zero(sc->d1->next_timeout)))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * ossl_statem_client_post_work()'s TLS_ST_OK case can complete a finished
+ * post-handshake authentication exchange by looping straight back into
+ * reading an already-buffered message, instead of calling
+ * tls_finish_handshake(). post_handshake_auth must still be restored to
+ * SSL_PHA_EXT_SENT either way, so that a second CertificateRequest is
+ * recognized rather than rejected as unexpected.
+ *
+ * Completing round 1 from the client's own side normally happens by
+ * processing the server's ACK of its response -- but an ACK never advances
+ * handshake_read_seq, so it can never be the trigger for this early-return
+ * path (dtls1_has_buffered_ready_message() keys strictly off
+ * handshake_read_seq). The trigger instead has to be a genuine handshake
+ * message whose processing both advances handshake_read_seq to match an
+ * already-buffered one and independently drives hand_state back to
+ * TLS_ST_OK -- and round 1's own ACK is deliberately never delivered here
+ * at all, so post_handshake_auth is still sitting at SSL_PHA_REQUESTED,
+ * unreset, when that happens.
+ *
+ * Round 2's CertificateRequest is delivered first, on its own: its sequence
+ * number is one past a ticket that hasn't been sent to the client yet, so
+ * it buffers rather than dispatching. The withheld ticket is delivered
+ * next: processing it advances handshake_read_seq to match the buffered
+ * CertificateRequest, and acking it drives hand_state through
+ * TLS_ST_CW_ACK back to TLS_ST_OK -- the exact moment under test, with
+ * post_handshake_auth still unreset from round 1. post_handshake_auth must
+ * come out of that same call correctly transitioned for round 2, not
+ * stranded at whatever the skipped reset left it at.
+ */
+static int test_dtls13_pha_second_request_survives_buffered_dispatch(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048], discard[2048], ticket[2048], certreq2[2048];
+ size_t ticketlen, certreq2len;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ /* Round 1: request PHA, let the client answer, and the server finish it. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /*
+ * Discard the server's ACK of that response -- it is never delivered
+ * to the client at all in this test. post_handshake_auth is left
+ * sitting at SSL_PHA_REQUESTED, exactly as it was when round 1's
+ * CertificateRequest first arrived.
+ */
+ while (BIO_read(SSL_get_rbio(client), discard, sizeof(discard)) > 0)
+ continue;
+ if (!TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+
+ /*
+ * The server sends a ticket, captured and withheld -- it is the thing
+ * round 2's CertificateRequest will be buffered behind.
+ */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ticketlen = 0;
+ while (BIO_ctrl_pending(SSL_get_rbio(client)) > 0) {
+ ret = BIO_read(SSL_get_rbio(client), ticket + ticketlen,
+ (int)(sizeof(ticket) - ticketlen));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ ticketlen += (size_t)ret;
+ }
+ if (!TEST_size_t_gt(ticketlen, 0))
+ goto end;
+
+ /*
+ * The server, already idle again, immediately requests round 2. Its
+ * CertificateRequest is captured too.
+ */
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ certreq2len = 0;
+ while (BIO_ctrl_pending(SSL_get_rbio(client)) > 0) {
+ ret = BIO_read(SSL_get_rbio(client), certreq2 + certreq2len,
+ (int)(sizeof(certreq2) - certreq2len));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ certreq2len += (size_t)ret;
+ }
+ if (!TEST_size_t_gt(certreq2len, 0))
+ goto end;
+
+ /*
+ * Deliver round 2's CertificateRequest on its own, first. Its sequence
+ * number is one past the still-withheld ticket's, so it buffers rather
+ * than dispatching -- hand_state never moves, and post_handshake_auth
+ * is untouched.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), certreq2,
+ (int)certreq2len),
+ (int)certreq2len))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->rcvd_messages), 1)
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+
+ /*
+ * Deliver the withheld ticket. Processing it advances
+ * handshake_read_seq to match the buffered CertificateRequest, and
+ * acking it drives hand_state back to TLS_ST_OK with that
+ * CertificateRequest already sitting there ready -- the exact
+ * TLS_ST_OK early-return path under test, reached without
+ * tls_finish_handshake() ever running, and with post_handshake_auth
+ * still unreset from round 1. It must come out of this call correctly
+ * transitioned to SSL_PHA_REQUESTED for round 2, not stranded at
+ * whatever the skipped reset left it at.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), ticket, (int)ticketlen),
+ (int)ticketlen))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->rcvd_messages), 0)
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+
+ /* Round 2 completes normally: the server reads the client's response. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /*
+ * Let the client process the server's ACK of that second response too.
+ * This doesn't yet bring the client fully idle: round 1's own flight
+ * (Certificate + CertificateVerify + Finished) was never acknowledged
+ * -- its ACK was deliberately discarded above, to set up the scenario
+ * -- so dtls_any_sent_messages_are_missing_acknowledge() still sees it
+ * outstanding, and dtls_process_ack() keeps reverting hand_state to
+ * pre_ack_hand_state (TLS_ST_CW_FINISHED) instead of reaching
+ * TLS_ST_OK.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Force that still-outstanding round 1 flight to retransmit, and let
+ * the server acknowledge it for real this time, so the connection can
+ * finish settling to idle on both sides.
+ */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client)))
+ goto end;
+
+ /* The connection is now genuinely usable. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A client's own deferred write-key install must be respected not just by
+ * the install logic itself, but by anything else deciding whether it's
+ * safe to send new post-handshake content.
+ *
+ * A first PHA response is sent and left deliberately unacknowledged. A
+ * ticket returns the client to idle anyway -- post-handshake message
+ * categories don't acknowledge each other, so this doesn't retire that
+ * response. The client then sends its own KeyUpdate, withheld behind the
+ * still-unacknowledged first response; only the KeyUpdate's own ACK is
+ * delivered, so the KeyUpdate itself ends up acknowledged while the
+ * install it's waiting on stays pending.
+ *
+ * A second CertificateRequest arriving in that window must still be
+ * deferred, not answered immediately: answering it would send genuinely
+ * new content under the client's current write epoch, which hasn't
+ * actually switched over yet. Delivering the withheld first response's
+ * own ACK afterward must complete the install and release the deferred
+ * second response at the new epoch, which the server must then accept.
+ */
+static int test_dtls13_pha_defer_waits_for_write_key_install(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048], ack1[2048];
+ size_t ack1len;
+ uint64_t epoch_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(cc, SSL3_CC_WRITE);
+
+ /* Round 1: request PHA, let the client answer. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(client), TLS_ST_CW_FINISHED)
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /*
+ * Capture the server's ACK of that response instead of delivering it
+ * -- held back until the very end of this test.
+ */
+ ack1len = 0;
+ while (BIO_ctrl_pending(SSL_get_rbio(client)) > 0) {
+ ret = BIO_read(SSL_get_rbio(client), ack1 + ack1len,
+ (int)(sizeof(ack1) - ack1len));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ ack1len += (size_t)ret;
+ }
+ if (!TEST_size_t_gt(ack1len, 0))
+ goto end;
+
+ /*
+ * A ticket takes the client back to idle anyway, without acknowledging
+ * the still-outstanding first response.
+ */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_EXT_SENT))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The client's own KeyUpdate is sent and withheld behind the still-
+ * outstanding first response.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_READ), epoch_before + 1))
+ goto end;
+
+ /*
+ * Only the KeyUpdate's own ACK is delivered. The KeyUpdate is now
+ * acked, but its write-key install is still withheld behind the
+ * first response.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_false(dtls_has_unacked_key_update(cc))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * A second CertificateRequest arrives in exactly this window. It must
+ * be deferred, not answered now.
+ */
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq((int)cc->statem.deferred_key_update_state,
+ (int)TLS_ST_CR_CERT_REQ)
+ || !TEST_int_eq(SSL_get_state(client), TLS_ST_CW_KEY_UPDATE)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Delivering the withheld first response's ACK completes the install
+ * and releases the deferred second response at the new epoch.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), ack1, (int)ack1len),
+ (int)ack1len))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_int_eq((int)cc->statem.deferred_key_update_state,
+ (int)TLS_ST_BEFORE))
+ goto end;
+
+ /* The server accepts the now-released second response. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /* Let the client process the server's ACK of that second response too. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client)))
+ goto end;
+
+ /* The connection is genuinely usable afterward. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A server with its own deferred write-key install still outstanding must
+ * not report itself idle just because some unrelated KeyUpdate gets
+ * acknowledged along the way.
+ *
+ * The server's own KeyUpdate is sent and withheld behind an earlier,
+ * unacknowledged ticket. The peer then sends its own, independent
+ * KeyUpdate, and the server acknowledges it -- a wholly separate exchange
+ * from the server's own still-pending install. That must not be mistaken
+ * for the connection becoming idle: SSL_is_init_finished() must stay
+ * false, and SSL_new_session_ticket()/SSL_verify_client_post_handshake()
+ * must both still refuse to start. Admitting either would send it under
+ * the still-current write epoch, which the peer -- already on the new
+ * read epoch from installing that independent KeyUpdate -- would
+ * silently drop.
+ */
+static int test_dtls13_server_idle_waits_for_write_key_install(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char buf[2048], discard[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), discard, sizeof(discard)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_false(dtls_has_unacked_key_update(sc))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * The client independently sends its own KeyUpdate. Acknowledging it
+ * is what reaches the idle decision under test.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server must not report itself idle: its own deferred write-key
+ * install is still outstanding, even though the peer's KeyUpdate is
+ * now fully acknowledged.
+ */
+ if (!TEST_false(SSL_is_init_finished(server))
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Neither a new ticket nor a new authentication request may start yet. */
+ if (!TEST_false(SSL_new_session_ticket(server)))
+ goto end;
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_false(SSL_verify_client_post_handshake(server)))
+ goto end;
+
+ /*
+ * Force the ticket's retransmit timer. The client already processed
+ * the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes the fresh ACK: the deferred write key must
+ * finally be installed, and the connection genuinely becomes idle.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_true(SSL_is_init_finished(server)))
+ goto end;
+
+ /* Application data flows both ways under the new epoch. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A client with its own deferred write-key install still outstanding must
+ * not report itself idle just because some unrelated post-handshake
+ * message gets acknowledged along the way.
+ *
+ * The client's own KeyUpdate is sent and withheld behind an earlier,
+ * unacknowledged PHA response. A new ticket then arrives and the client
+ * acknowledges it -- a wholly separate exchange from the client's own
+ * still-pending install. That must not be mistaken for the connection
+ * becoming idle: SSL_is_init_finished() must stay false until the
+ * withheld response is finally acknowledged and the install completes.
+ */
+static int test_dtls13_client_idle_waits_for_write_key_install(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048], ack1[2048];
+ size_t ack1len;
+ uint64_t epoch_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(cc, SSL3_CC_WRITE);
+
+ /* Request PHA and let the client answer. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /*
+ * Capture the server's ACK of that response instead of delivering it
+ * -- held back until the very end of this test.
+ */
+ ack1len = 0;
+ while (BIO_ctrl_pending(SSL_get_rbio(client)) > 0) {
+ ret = BIO_read(SSL_get_rbio(client), ack1 + ack1len,
+ (int)(sizeof(ack1) - ack1len));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ ack1len += (size_t)ret;
+ }
+ if (!TEST_size_t_gt(ack1len, 0))
+ goto end;
+
+ /*
+ * A ticket takes the client back to idle anyway, without acknowledging
+ * the still-outstanding response.
+ */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_EXT_SENT))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The client's own KeyUpdate is sent and withheld behind the still-
+ * outstanding PHA response.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_READ), epoch_before + 1))
+ goto end;
+
+ /*
+ * Only the KeyUpdate's own ACK is delivered. The KeyUpdate is now
+ * acked, but its write-key install is still withheld behind the PHA
+ * response.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_false(dtls_has_unacked_key_update(cc))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * A second ticket arrives in exactly this window. Acknowledging it
+ * must not be mistaken for the connection becoming idle: the
+ * client's own deferred write-key install is still outstanding.
+ */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(SSL_is_init_finished(client))
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Delivering the withheld PHA response's ACK completes the install. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), ack1, (int)ack1len),
+ (int)ack1len))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), epoch_before + 1)
+ || !TEST_true(SSL_is_init_finished(client)))
+ goto end;
+
+ /* The connection is genuinely usable afterward. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * The same entry-eviction scenario as
+ * test_dtls13_keyupdate_ack_survives_pha_completion_eviction() above, but
+ * checking whether a completed PHA round, by itself, is mistaken for
+ * permission to start something new -- a ticket or a second PHA round --
+ * while the server's own deferred write-key install is still outstanding,
+ * rather than whether app data can still be sent on the existing
+ * connection, which that test covers.
+ */
+static int test_dtls13_pha_completion_eviction_blocks_new_actions(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* Request PHA. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Drop the client's response before the server ever sees it. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Force the client to retransmit its still-unacknowledged PHA response. */
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(client))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server processes the recovered Certificate + Finished. PHA
+ * completes, evicting the server's own, already-acknowledged KeyUpdate
+ * entry -- leaving only the still-unacknowledged ticket behind.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * The server must not admit a new ticket, a new authentication
+ * request, or a second KeyUpdate: its own deferred write-key install
+ * is still outstanding, even though PHA just completed and the
+ * connection is otherwise idle.
+ */
+ if (!TEST_false(SSL_new_session_ticket(server))
+ || !TEST_false(SSL_verify_client_post_handshake(server))
+ || !TEST_false(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+
+ /* Application data still flows normally in the meantime. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A reciprocal KeyUpdate requested by the peer is still subject to the
+ * same pending write-key install as anything else in this window -- even
+ * though nothing about requesting one goes through the public API that
+ * normally guards against sending a KeyUpdate too early.
+ *
+ * The server's own KeyUpdate is sent and withheld behind an earlier,
+ * unacknowledged ticket. The peer then asks for a reciprocal KeyUpdate
+ * instead of sending its own independent one. That request must not be
+ * allowed to jump the queue: it must not be treated as making the
+ * connection idle, and a later write must not flush it under the
+ * still-current write epoch either.
+ */
+static int test_dtls13_requested_key_update_waits_for_write_key_install(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf[2048], discard[2048];
+ uint64_t epoch_before;
+ int ret, dropped, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ epoch_before = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ /* The ticket is sent and delivered normally. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Lose the client's ACK for it; the server still sees it outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), discard, sizeof(discard)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* The server's own KeyUpdate is sent and acknowledged normally. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_READ), epoch_before + 1))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_false(dtls_has_unacked_key_update(sc))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * The public API already refuses to start a fresh KeyUpdate here,
+ * confirming the server is not considered idle by that check.
+ */
+ if (!TEST_false(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+
+ /*
+ * The client requests a reciprocal KeyUpdate. Processing it bypasses
+ * SSL_key_update() entirely: tls_process_key_update() schedules the
+ * response by assigning s->key_update directly.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_READ), epoch_before + 1))
+ goto end;
+
+ /*
+ * The reciprocal KeyUpdate must not jump ahead of the still-pending
+ * install: the server must not consider itself idle, and the write
+ * epoch must not have moved.
+ */
+ if (!TEST_false(SSL_is_init_finished(server))
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * A write attempt must not flush the scheduled KeyUpdate under the
+ * still-current epoch either.
+ */
+ ret = SSL_write(server, "s", 1);
+ if (!TEST_int_eq(sc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /*
+ * Force the ticket's retransmit timer. The client already processed
+ * the original, so this is recognized and acknowledged as a
+ * retransmission rather than reprocessed.
+ */
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes the fresh ACK: the deferred write key must
+ * finally be installed. Processing an ACK is bookkeeping, not a
+ * trigger to write something new on its own, so the scheduled
+ * reciprocal KeyUpdate isn't flushed by this read alone -- and with it
+ * still scheduled, the connection correctly isn't considered fully
+ * idle yet either.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(sc->d1->key_update_write_pending)
+ || !TEST_int_eq(sc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /*
+ * A write now drives the scheduled reciprocal KeyUpdate out, at the
+ * new epoch -- not before. Sending it leaves the connection waiting
+ * on its own fresh ACK, same as any other KeyUpdate send, so this
+ * returns WANT_READ rather than a clean success.
+ */
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->key_update, SSL_KEY_UPDATE_NONE)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /* The client receives the server's now-released reciprocal KeyUpdate. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_READ), epoch_before + 2))
+ goto end;
+
+ /* Application data flows both ways under the new epoch. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A received KeyUpdate requesting a reciprocal response leaves hand_state
+ * at TLS_ST_OK with in_init still set until that reciprocal KeyUpdate has
+ * been sent, and dtls1_read_bytes() can reach an application-data record,
+ * sent under the peer's own still-current epoch, before that happens.
+ * Receiving a record under those conditions (TLS_ST_OK, in_init set) is
+ * exactly what DTLS 1.3 buffers application data behind: genuine data, not
+ * an error, that will be delivered once the reciprocal exchange completes.
+ * dtls1_read_bytes() must report that as an ordinary retry condition
+ * (SSL_ERROR_WANT_READ), not a syscall-level failure, and must still
+ * deliver the data afterward rather than losing it.
+ *
+ * It must also not leave the receiver idle while it waits. Buffering the
+ * record has to drive the state machine so the reciprocal KeyUpdate is sent
+ * and its retransmit timer armed before WANT_READ is returned; otherwise
+ * nothing is in flight and no event would ever end the wait.
+ *
+ * The client requests the reciprocal KeyUpdate and holds its own
+ * application datagram back so it reaches the server right after, in the
+ * same read -- both records sit in the server's buffer before SSL_read()
+ * ever touches either one. Processing the KeyUpdate schedules the server's
+ * own response; the application record is then buffered rather than
+ * delivered, and the response is sent before SSL_read() returns. Only once
+ * that reciprocal KeyUpdate is acknowledged and its write-key install
+ * completes does the buffered data finally surface.
+ */
+static int test_dtls13_reciprocal_keyupdate_app_data_classification(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char buf[2048], held[2048];
+ int ret, held_len, is_infinite, testresult = 0;
+ struct timeval tv;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /*
+ * The client writes application data under the current epoch, then
+ * requests a reciprocal KeyUpdate. Hold the application datagram back
+ * so it can be delivered to the server right after the KeyUpdate --
+ * both records sit in the server's buffer before it reads either one.
+ */
+ if (!TEST_int_eq(SSL_write(client, "ABCD", 4), 4))
+ goto end;
+ held_len = BIO_read(SSL_get_rbio(server), held, sizeof(held));
+ if (!TEST_int_gt(held_len, 0))
+ goto end;
+
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The server's rbio already has the KeyUpdate; add ABCD back right behind it. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), held, held_len), held_len))
+ goto end;
+
+ /*
+ * One SSL_read() processes the KeyUpdate -- scheduling the server's own
+ * reciprocal response -- then buffers the application record that
+ * follows and sends the response before returning.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_false(SSL_is_init_finished(server)))
+ goto end;
+
+ /*
+ * Returning WANT_READ with the application data buffered must not leave
+ * the server idle: its reciprocal KeyUpdate has to be on the wire
+ * already, so an application waiting on SSL_poll() or the event timeout
+ * is woken by the peer's ACK, with no further SSL_read() needed to
+ * start the exchange.
+ */
+ if (!TEST_int_eq(SSL_get_event_timeout(server, &tv, &is_infinite), 1)
+ || !TEST_false(is_infinite)
+ || !TEST_int_gt((int)BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+
+ /*
+ * The client consumes the server's ACK of its KeyUpdate, then the
+ * reciprocal KeyUpdate sent in the same read, and acknowledges it.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server processes that ACK, completing its own write-key install.
+ * The application data buffered several reads ago finally surfaces --
+ * it was never lost, only deferred.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 4)
+ || !TEST_mem_eq(buf, 4, "ABCD", 4)
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_false(sc->d1->key_update_write_pending))
+ goto end;
+
+ /* Application data flows both ways under the fully-updated epoch. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * With no application data in flight, a reciprocal KeyUpdate can remain
+ * scheduled while both endpoints wait for input. SSL_in_init() describes
+ * handshake state, not readiness, so it is not a reason to retry a read.
+ * A later application write must send the response before its application
+ * data. idx selects the requester: 0 for the client, 1 for the server.
+ */
+static int test_dtls13_reciprocal_keyupdate_no_app_data_classification(int idx)
+{
+ static const char msg[] = "p";
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL *local, *peer;
+ SSL_CONNECTION *pc;
+ unsigned char buf[2048];
+ uint64_t epoch_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ local = (idx == 0) ? client : server;
+ peer = (idx == 0) ? server : client;
+ pc = SSL_CONNECTION_FROM_SSL(peer);
+ epoch_before = dtls1_get_epoch(pc, SSL3_CC_WRITE);
+
+ if (!TEST_true(SSL_key_update(local, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(local);
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The peer processes the KeyUpdate, scheduling its own reciprocal
+ * response and leaving it parked at TLS_ST_OK with in_init set. With no
+ * application data anywhere in flight, this is the only thing left
+ * outstanding.
+ */
+ ret = SSL_read(peer, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(pc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_true(SSL_in_init(peer))
+ || !TEST_false(SSL_is_init_finished(peer)))
+ goto end;
+
+ /* The requester consumes the ACK and has no further input to send. */
+ if (!TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(local)), 0))
+ goto end;
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(SSL_in_init(local)))
+ goto end;
+ if (!TEST_int_eq(SSL_pending(peer), 0)
+ || !TEST_false(SSL_has_pending(peer))
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(peer)), 0)
+ || !TEST_true(SSL_in_init(peer)))
+ goto end;
+
+ /*
+ * No input is available, so do not retry SSL_read(). A later application
+ * write sends the scheduled reciprocal KeyUpdate, then waits for its ACK.
+ */
+ ret = SSL_write(peer, msg, sizeof(msg) - 1);
+ if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The requester receives the reciprocal KeyUpdate and acknowledges it. */
+ if (!TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(local)), 0))
+ goto end;
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Input is available for the blocked write: retry with the same data to
+ * consume the ACK, install the write keys and send the application record.
+ */
+ if (!TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(peer)), 0)
+ || !TEST_int_eq(SSL_write(peer, msg, sizeof(msg) - 1), 1)
+ || !TEST_true(SSL_is_init_finished(peer))
+ || !TEST_false(pc->d1->key_update_write_pending)
+ || !TEST_uint64_t_ne(dtls1_get_epoch(pc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* Application data flows both ways under the fully-updated epoch. */
+ if (!TEST_int_eq(SSL_read(local, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'p')
+ || !TEST_int_eq(SSL_write(local, "l", 1), 1)
+ || !TEST_int_eq(SSL_read(peer, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'l'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * An unsent reciprocal KeyUpdate must not prevent an orderly close. Exercise
+ * both responder roles without application data or buffered input, and check
+ * that shutdown does not start another KeyUpdate exchange.
+ */
+static int test_dtls13_scheduled_keyupdate_shutdown(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL *local, *peer;
+ SSL_CONNECTION *pc;
+ unsigned char buf[1];
+ uint64_t epoch_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+
+ local = (idx == 0) ? client : server;
+ peer = (idx == 0) ? server : client;
+ pc = SSL_CONNECTION_FROM_SSL(peer);
+ epoch_before = dtls1_get_epoch(pc, SSL3_CC_WRITE);
+
+ if (!TEST_true(SSL_key_update(local, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(local);
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Receive the request and schedule, but do not send, the response. */
+ ret = SSL_read(peer, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Consume the request's ACK so that neither transport has pending input. */
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(local))
+ || !TEST_int_eq(SSL_get_state(peer), TLS_ST_OK)
+ || !TEST_int_eq(pc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_false(pc->d1->key_update_write_pending)
+ || !TEST_int_eq(SSL_pending(peer), 0)
+ || !TEST_false(SSL_has_pending(peer))
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(peer)), 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(local)), 0))
+ goto end;
+
+ if (!TEST_int_eq(SSL_shutdown(peer), 0)
+ || !TEST_int_eq(SSL_get_shutdown(peer), SSL_SENT_SHUTDOWN)
+ || !TEST_int_eq(pc->key_update, SSL_KEY_UPDATE_NONE)
+ || !TEST_false(pc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(pc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ /* The requester receives close_notify, not a reciprocal KeyUpdate. */
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 0)
+ || !TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_ZERO_RETURN)
+ || !TEST_int_eq(SSL_get_shutdown(local), SSL_RECEIVED_SHUTDOWN)
+ || !TEST_int_eq(SSL_shutdown(local), 1)
+ || !TEST_int_eq(SSL_shutdown(peer), 1)
+ || !TEST_int_eq(SSL_get_shutdown(peer),
+ SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN)
+ || !TEST_false(pc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(pc, SSL3_CC_WRITE), epoch_before))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * Combines two mechanisms that have only ever been tested separately: the
+ * simultaneous-bidirectional-KeyUpdate deadlock avoidance (see
+ * test_dtls13_server_keyupdate_preserves_ack) and application data racing a
+ * KeyUpdate's resolution (see
+ * test_dtls13_reciprocal_keyupdate_app_data_classification). Both sides
+ * write and hold application data, then both independently send their own
+ * KeyUpdate before either has read anything from the other, and the held
+ * application data is reinjected immediately behind each KeyUpdate.
+ *
+ * idx selects which side moves first (SSL_key_update()+SSL_do_handshake()):
+ * idx == 0 the server, idx == 1 the client. This matters because whichever
+ * side moves *second* finds the first side's KeyUpdate already sitting in
+ * its rbio, so its own SSL_do_handshake() call bundles its new KeyUpdate
+ * together with its ack of the first one -- held data injected after that
+ * lands behind both records. Whichever side moves *first* has nothing to
+ * process yet, so its KeyUpdate goes out alone -- held data injected right
+ * behind it lands ahead of the ack, which doesn't exist yet. Both orderings
+ * need to classify buffered application data correctly, so both sides take
+ * a turn at each role across the two idx values.
+ */
+static int test_dtls13_simultaneous_keyupdate_app_data_classification(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL *first, *second;
+ unsigned char buf[2048], held_first[2048], held_second[2048];
+ int held_first_len, held_second_len, ret;
+ int first_delivered = 0, second_delivered = 0, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ first = (idx == 0) ? server : client;
+ second = (idx == 0) ? client : server;
+
+ /* Each side writes and holds its own application data, before either KeyUpdate goes out. */
+ if (!TEST_int_eq(SSL_write(first, "1", 1), 1))
+ goto end;
+ held_first_len = BIO_read(SSL_get_rbio(second), held_first, sizeof(held_first));
+ if (!TEST_int_gt(held_first_len, 0))
+ goto end;
+ if (!TEST_int_eq(SSL_write(second, "2", 1), 1))
+ goto end;
+ held_second_len = BIO_read(SSL_get_rbio(first), held_second, sizeof(held_second));
+ if (!TEST_int_gt(held_second_len, 0))
+ goto end;
+
+ /* The first mover sends its own KeyUpdate -- nothing from the other side has arrived yet. */
+ if (!TEST_true(SSL_key_update(first, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(first);
+ if (!TEST_int_eq(SSL_get_error(first, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Squeeze the first mover's held application data right behind its lone
+ * KeyUpdate, in the second mover's rbio -- there is no ack to land
+ * behind yet, since the second mover hasn't read anything.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(second), held_first, held_first_len), held_first_len))
+ goto end;
+
+ /*
+ * The second mover independently sends its own, unrelated KeyUpdate --
+ * but SSL_do_handshake() here still has to process what's already
+ * sitting in its rbio (the first mover's KeyUpdate, with the first
+ * mover's held data squeezed right behind it) as a side effect of
+ * advancing the state machine, and in the same call also constructs and
+ * sends its own new KeyUpdate *and* its ack of the first mover's. That
+ * must not misreport as a syscall error just because application data
+ * was buffered along the way.
+ */
+ if (!TEST_true(SSL_key_update(second, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(second);
+ if (!TEST_int_eq(SSL_get_error(second, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Put the second mover's held application data behind both of what that
+ * call just sent -- its own KeyUpdate and its ack of the first mover's,
+ * bundled together -- in the first mover's rbio.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(first), held_second, held_second_len), held_second_len))
+ goto end;
+
+ /*
+ * The first mover processes the second mover's KeyUpdate, acknowledges
+ * it, and in the same call also receives the second mover's
+ * acknowledgment of its own KeyUpdate, completing it -- with the second
+ * mover's held application data sitting immediately behind all of that.
+ * Either the resolution finishes synchronously and the data comes back
+ * right now, or it's correctly deferred; either way this must never be
+ * SYSCALL.
+ */
+ ret = SSL_read(first, buf, sizeof(buf));
+ if (ret > 0) {
+ if (!TEST_mem_eq(buf, ret, "2", 1))
+ goto end;
+ second_delivered = 1;
+ } else if (!TEST_int_eq(SSL_get_error(first, ret), SSL_ERROR_WANT_READ)) {
+ goto end;
+ }
+
+ /*
+ * The second mover processes the first mover's KeyUpdate and the first
+ * mover's acknowledgment of its own KeyUpdate, completing it -- with
+ * the first mover's held application data (squeezed in ahead of that
+ * ack, before it existed) sitting somewhere in that same stream.
+ */
+ ret = SSL_read(second, buf, sizeof(buf));
+ if (ret > 0) {
+ if (!TEST_mem_eq(buf, ret, "1", 1))
+ goto end;
+ first_delivered = 1;
+ } else if (!TEST_int_eq(SSL_get_error(second, ret), SSL_ERROR_WANT_READ)) {
+ goto end;
+ }
+
+ /* Whichever side's application data was not already delivered above must still surface now. */
+ if (!first_delivered) {
+ ret = SSL_read(second, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "1", 1))
+ goto end;
+ }
+ if (!second_delivered) {
+ ret = SSL_read(first, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "2", 1))
+ goto end;
+ }
+
+ /* Both newly installed write keys must actually work. */
+ if (!TEST_int_eq(SSL_write(second, "3", 1), 1))
+ goto end;
+ ret = SSL_read(first, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "3", 1))
+ goto end;
+ if (!TEST_int_eq(SSL_write(first, "4", 1), 1))
+ goto end;
+ ret = SSL_read(second, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 1) || !TEST_mem_eq(buf, 1, "4", 1))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * Application data racing a post-handshake authentication exchange: the
+ * server is parked at TLS_ST_OK with SSL_in_init() set, waiting for the
+ * client's Certificate/Finished response, when application data the client
+ * sent earlier arrives first. This is the same dtls1_read_bytes() buffering
+ * path already proven for KeyUpdate (see
+ * test_dtls13_reciprocal_keyupdate_app_data_classification), exercised here
+ * for PHA instead.
+ */
+static int test_dtls13_pha_response_app_data_classification(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ unsigned char buf[2048], held[2048];
+ int held_len, ret, data_delivered = 0, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /* The client writes and holds application data before PHA is ever requested. */
+ if (!TEST_int_eq(SSL_write(client, "ABCD", 4), 4))
+ goto end;
+ held_len = BIO_read(SSL_get_rbio(server), held, sizeof(held));
+ if (!TEST_int_gt(held_len, 0))
+ goto end;
+
+ /* The server requests post-handshake authentication. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+
+ /*
+ * Put the held application data into the server's rbio now, before the
+ * client's response flight is even built -- it must be the first thing
+ * the server sees once it starts waiting for that response.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(server), held, held_len), held_len))
+ goto end;
+
+ /* The client processes the CertificateRequest and sends its (empty) response flight. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server, still waiting for that response, hits the held
+ * application data first -- nothing else is queued yet, so this may
+ * either buffer it (reporting WANT_READ) or, since finding nothing else
+ * to read while still marked in-init ends this post-handshake attempt
+ * and falls back to ordinary app-data reading, deliver it immediately
+ * within this same call. Either is fine; SYSCALL or losing the data
+ * would not be.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (ret > 0) {
+ if (!TEST_mem_eq(buf, ret, "ABCD", 4))
+ goto end;
+ data_delivered = 1;
+ } else if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)) {
+ goto end;
+ }
+
+ /* Now the server processes the actual PHA response, completing the exchange. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ if (!data_delivered) {
+ /* The application data was not lost -- it finally surfaces now. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 4) || !TEST_mem_eq(buf, 4, "ABCD", 4))
+ goto end;
+ }
+
+ /* Application data flows both ways afterward. */
+ if (!TEST_int_eq(SSL_write(server, "x", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'x')
+ || !TEST_int_eq(SSL_write(client, "y", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'y'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * Application data racing a NewSessionTicket delivery: the client is
+ * reading, having just received a ticket, with application data the server
+ * sent right behind it. Same dtls1_read_bytes() buffering path as
+ * test_dtls13_reciprocal_keyupdate_app_data_classification, exercised here
+ * for a ticket instead of a KeyUpdate.
+ */
+static int test_dtls13_ticket_app_data_classification(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ unsigned char buf[2048], held_ticket[2048], held_data[2048];
+ int held_ticket_len, held_data_len, ret, testresult = 0;
+
+ ticket_count = 0;
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 1)))
+ goto end;
+ SSL_CTX_set_session_cache_mode(cctx, SSL_SESS_CACHE_CLIENT);
+ SSL_CTX_sess_set_new_cb(cctx, count_ticket);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL)))
+ goto end;
+
+ /*
+ * Use the bare handshake primitive instead of create_ssl_connection():
+ * the latter forces an SSL_read_ex() call on the client purely to
+ * deliver the ticket, which would consume it before we get a chance to
+ * intercept it.
+ */
+ if (!TEST_true(create_bare_ssl_connection_ex(server, client,
+ SSL_ERROR_NONE, 1, 0, NULL, NULL)))
+ goto end;
+
+ /* Capture the ticket, then capture some application data sent right behind it. */
+ held_ticket_len = BIO_read(SSL_get_rbio(client), held_ticket, sizeof(held_ticket));
+ if (!TEST_int_gt(held_ticket_len, 0))
+ goto end;
+ if (!TEST_int_eq(SSL_write(server, "ABCD", 4), 4))
+ goto end;
+ held_data_len = BIO_read(SSL_get_rbio(client), held_data, sizeof(held_data));
+ if (!TEST_int_gt(held_data_len, 0))
+ goto end;
+
+ /* Redeliver the ticket, with the application data right behind it, in that order. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), held_ticket, held_ticket_len),
+ held_ticket_len)
+ || !TEST_int_eq(BIO_write(SSL_get_rbio(client), held_data, held_data_len),
+ held_data_len))
+ goto end;
+
+ /*
+ * One read processes the ticket -- this must never misreport as a
+ * syscall error just because application data was sitting right behind
+ * it, whether that data is buffered for later or (since reassembling
+ * and processing a single, unfragmented ticket can settle back to idle
+ * within this same call) delivered immediately.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (ret > 0) {
+ if (!TEST_mem_eq(buf, ret, "ABCD", 4))
+ goto end;
+ } else if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)) {
+ goto end;
+ } else {
+ /* Deferred -- it must still surface, and the ticket must have been processed. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 4) || !TEST_mem_eq(buf, 4, "ABCD", 4))
+ goto end;
+ }
+ if (!TEST_int_eq(ticket_count, 1))
+ goto end;
+
+ /* Application data flows both ways afterward. */
+ if (!TEST_int_eq(SSL_write(client, "y", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'y')
+ || !TEST_int_eq(SSL_write(server, "x", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'x'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * Mirror of test_dtls13_reciprocal_keyupdate_app_data_classification with
+ * roles reversed: that test only ever asserts the server's SSL_get_error()
+ * during the race. This asserts the client's own read path instead -- same
+ * shared dtls1_read_bytes() code, but never directly exercised from this
+ * side with application data in flight.
+ */
+static int test_dtls13_client_keyupdate_app_data_classification(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *cc;
+ unsigned char buf[2048], held[2048];
+ int ret, held_len, is_infinite, testresult = 0;
+ struct timeval tv;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ cc = SSL_CONNECTION_FROM_SSL(client);
+
+ /*
+ * The server writes application data under the current epoch, then
+ * requests a reciprocal KeyUpdate. Hold the application datagram back
+ * so it can be delivered to the client right after the KeyUpdate --
+ * both records sit in the client's buffer before it reads either one.
+ */
+ if (!TEST_int_eq(SSL_write(server, "ABCD", 4), 4))
+ goto end;
+ held_len = BIO_read(SSL_get_rbio(client), held, sizeof(held));
+ if (!TEST_int_gt(held_len, 0))
+ goto end;
+
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The client's rbio already has the KeyUpdate; add ABCD back right behind it. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), held, held_len), held_len))
+ goto end;
+
+ /*
+ * One SSL_read() processes the KeyUpdate -- scheduling the client's own
+ * reciprocal response -- then buffers the application record that
+ * follows and sends the response before returning.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_false(SSL_is_init_finished(client)))
+ goto end;
+
+ /*
+ * The reciprocal KeyUpdate must already be on the wire, with its
+ * retransmit timer armed, rather than waiting for another SSL_read().
+ */
+ if (!TEST_int_eq(SSL_get_event_timeout(client, &tv, &is_infinite), 1)
+ || !TEST_false(is_infinite)
+ || !TEST_int_gt((int)BIO_ctrl_pending(SSL_get_rbio(server)), 0))
+ goto end;
+
+ /*
+ * The server consumes the client's ACK of its KeyUpdate, then the
+ * reciprocal KeyUpdate sent in the same read, and acknowledges it.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The server receives the reciprocal KeyUpdate and acknowledges it. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The client processes that ACK, completing its own write-key install.
+ * The application data buffered several reads ago finally surfaces --
+ * it was never lost, only deferred.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, 4)
+ || !TEST_mem_eq(buf, 4, "ABCD", 4)
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_false(cc->d1->key_update_write_pending))
+ goto end;
+
+ /* Application data flows both ways under the fully-updated epoch. */
+ if (!TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c')
+ || !TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A write that is refused by the transport (WANT_WRITE) and later retried
+ * must not complete by sending the pending application data before an
+ * intervening reciprocal KeyUpdate has been sent, even though a single
+ * handshake_func() call inside the retry can report success while that
+ * KeyUpdate is still unconstructed.
+ *
+ * idx == 0: the server's write is refused right when it must also send a
+ * reciprocal KeyUpdate. idx == 1: same, with roles reversed.
+ */
+static int test_dtls13_pending_write_completes_before_reciprocal_keyupdate(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL *local, *peer;
+ SSL_CONNECTION *lc, *pc;
+ BIO *retry = NULL;
+ static const unsigned char payload[] = "pending-write-payload";
+ unsigned char buf[2048];
+ uint64_t epoch_before, peer_epoch_before;
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+
+ local = (idx == 0) ? server : client;
+ peer = (idx == 0) ? client : server;
+ lc = SSL_CONNECTION_FROM_SSL(local);
+ pc = SSL_CONNECTION_FROM_SSL(peer);
+ epoch_before = dtls1_get_epoch(lc, SSL3_CC_WRITE);
+ peer_epoch_before = dtls1_get_epoch(pc, SSL3_CC_WRITE);
+
+ /* Interpose a filter that can refuse local's writes on demand. */
+ if (!TEST_ptr(retry = BIO_new(bio_s_maybe_retry()))
+ || !TEST_true(BIO_up_ref(SSL_get_wbio(local))))
+ goto end;
+
+ SSL_set0_wbio(local, BIO_push(retry, SSL_get_wbio(local)));
+ retry = NULL;
+
+ /* Refuse exactly the next write: nothing reaches the peer. */
+ if (!TEST_long_eq(BIO_ctrl(SSL_get_wbio(local),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 0, NULL),
+ 1))
+ goto end;
+
+ ret = SSL_write(local, payload, sizeof(payload));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_WRITE))
+ goto end;
+
+ /* Reopen the filter: the refused record was dropped, not queued. */
+ if (!TEST_long_eq(BIO_ctrl(SSL_get_wbio(local),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 100, NULL),
+ 1))
+ goto end;
+
+ /* The peer requests a reciprocal KeyUpdate. */
+ if (!TEST_true(SSL_key_update(peer, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+
+ ret = SSL_do_handshake(peer);
+ if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Block the next write before local reads the peer's KeyUpdate.
+ * Processing it does two things: it schedules local's own reciprocal
+ * KeyUpdate, and it immediately tries to send the ACK for the KeyUpdate
+ * just received. That ACK send is what gets blocked here, leaving the
+ * reciprocal KeyUpdate still unsent when the pending write below
+ * retries.
+ */
+ if (!TEST_long_eq(BIO_ctrl(SSL_get_wbio(local),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 0, NULL),
+ 1))
+ goto end;
+
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_WRITE)
+ || !TEST_int_eq(lc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_true(SSL_in_init(local)))
+ goto end;
+
+ /* Reopen the filter and retry the original pending write. */
+ if (!TEST_long_eq(BIO_ctrl(SSL_get_wbio(local),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 100, NULL),
+ 1))
+ goto end;
+
+ /*
+ * The completing retry of the original pending write -- the call under
+ * test. It must send the reciprocal KeyUpdate and then, like any write
+ * that has just sent a KeyUpdate, wait for its ACK instead of sending
+ * the payload: key_update == SSL_KEY_UPDATE_NONE confirms the KeyUpdate
+ * was constructed (tls_construct_key_update() only resets key_update
+ * once that message has been built), and WANT_READ confirms the payload
+ * was not sent in this call.
+ */
+ ret = SSL_write(local, payload, sizeof(payload));
+ if (!TEST_int_eq(ret, -1)
+ || !TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(lc->key_update, SSL_KEY_UPDATE_NONE))
+ goto end;
+
+ /*
+ * Nothing ever blocks the peer's own writes in this test, so a single
+ * SSL_read() reads the delayed ACK, reads the KeyUpdate and sends its own
+ * ACK for it. No application data may have arrived ahead of the
+ * KeyUpdate, so there is nothing to return yet.
+ */
+ ret = SSL_read(peer, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, -1)
+ || !TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(pc, SSL3_CC_WRITE), peer_epoch_before + 1))
+ goto end;
+
+ /*
+ * The peer's ACK of the reciprocal KeyUpdate completes local's write-key
+ * install, bumping the write epoch past where this test started.
+ */
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)
+ || !TEST_false(lc->d1->key_update_write_pending)
+ || !TEST_true(SSL_is_init_finished(local))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(lc, SSL3_CC_WRITE), epoch_before + 1))
+ goto end;
+
+ /* The pending write now completes under the new epoch. */
+ if (!TEST_int_eq(SSL_write(local, payload, sizeof(payload)), (int)sizeof(payload)))
+ goto end;
+ ret = SSL_read(peer, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, (int)sizeof(payload))
+ || !TEST_mem_eq(buf, ret, payload, sizeof(payload)))
+ goto end;
+
+ /* Application data now flows both ways under the new epoch. */
+ if (!TEST_int_eq(SSL_write(local, "l", 1), 1)
+ || !TEST_int_eq(SSL_read(peer, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'l')
+ || !TEST_int_eq(SSL_write(peer, "p", 1), 1)
+ || !TEST_int_eq(SSL_read(local, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'p'))
+ goto end;
+
+ testresult = 1;
+end:
+ BIO_free(retry);
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A KeyUpdate(update_requested) from the peer must not make a side build its
+ * reply while its own earlier KeyUpdate still has its write-key install
+ * withheld.
+ *
+ * The replying side's KeyUpdate is acknowledged, but its install stays
+ * pending behind an earlier message that is still unacknowledged. The peer's
+ * own KeyUpdate arrives in that window. Building the reply right away would
+ * reset the pending install, send the reply under the old epoch that the peer
+ * has already left, and leave the replier retransmitting it forever. The
+ * reply must instead wait for the install, then go out under the new epoch
+ * and be accepted.
+ *
+ * idx 0: the server replies. It parks at TLS_ST_OK after completing PHA, then
+ * receives the client's KeyUpdate; driven with SSL_read().
+ * idx 1: the server replies. The client's PHA response reaches it only after
+ * the client's KeyUpdate, so it already owes a reply when it completes
+ * PHA; driven with SSL_read().
+ * idx 2: as idx 0, but the server is driven with SSL_write().
+ * idx 3: as idx 1, but the server is driven with SSL_write().
+ * idx 4: the client replies. Its KeyUpdate is pending behind its own
+ * unacknowledged PHA response when the server's KeyUpdate arrives;
+ * driven with SSL_read().
+ * idx 5: the client replies. The server's second CertificateRequest arrives
+ * ahead of its KeyUpdate, so the client is deferring a PHA response as
+ * well as owing the reply; driven with SSL_read().
+ * idx 6: as idx 4, but the client is driven with SSL_write().
+ * idx 7: as idx 5, but the client is driven with SSL_write().
+ */
+static int test_dtls13_reciprocal_keyupdate_waits_for_pending_install(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc, *repc, *reqc;
+ unsigned char buf[2048], ack1[2048];
+ size_t ack1len = 0;
+ uint64_t rep_epoch, req_epoch;
+ int client_replies = idx >= 4;
+ int extra_pha = idx & 1, via_write = (idx >> 1) & 1;
+ int i, ret, dropped, converged = 0, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey)))
+ goto end;
+ if (client_replies && !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+ SSL_CTX_set_post_handshake_auth(cctx, 1);
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+ cc = SSL_CONNECTION_FROM_SSL(client);
+ repc = client_replies ? cc : sc;
+ reqc = client_replies ? sc : cc;
+ rep_epoch = dtls1_get_epoch(repc, SSL3_CC_WRITE);
+ req_epoch = dtls1_get_epoch(reqc, SSL3_CC_WRITE);
+
+ if (!client_replies) {
+ if (!TEST_size_t_gt(sc->sent_tickets, 0)
+ || !TEST_size_t_gt(pqueue_size(&sc->d1->sent_messages), 0))
+ goto end;
+
+ /* Lose the client's ACK of the initial ticket: it stays outstanding. */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+
+ /* No ticket reissue on PHA completion, so the server parks at TLS_ST_OK. */
+ if (!TEST_true(SSL_set_num_tickets(server, 0)))
+ goto end;
+
+ /* Request PHA. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_set_session_id_context(server,
+ (const unsigned char *)"reciprocal_ku_test", 18))
+ || !TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+
+ if (!extra_pha) {
+ /* The client answers the request before the server's KeyUpdate. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ }
+
+ /*
+ * The server's KeyUpdate goes out. Without extra_pha, the client's
+ * waiting PHA response is processed in the same call and, the
+ * KeyUpdate being unacknowledged, the server parks with a TLS_ST_OK
+ * continuation. With extra_pha, the response has not been sent yet.
+ */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending))
+ goto end;
+ if (!extra_pha
+ && (!TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_int_eq((int)sc->statem.deferred_key_update_state,
+ (int)TLS_ST_OK)))
+ goto end;
+
+ if (!extra_pha) {
+ /* The client reads the KeyUpdate and acknowledges it. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ }
+
+ /*
+ * The client sends its own KeyUpdate asking for a reply. Without
+ * extra_pha it goes out behind the ACK above. With extra_pha the
+ * client has not read anything yet: its KeyUpdate goes out first, the
+ * same call then reads the CertificateRequest and the server's
+ * KeyUpdate, and its PHA response is held back until its own
+ * KeyUpdate is installed.
+ */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The server reads the client's KeyUpdate and the ACK. Its own
+ * install is still withheld behind the ticket.
+ */
+ if (via_write) {
+ ret = SSL_write(server, "s", 1);
+ if (!TEST_int_eq(ret, -1))
+ goto end;
+ } else {
+ ret = SSL_read(server, buf, sizeof(buf));
+ }
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), rep_epoch))
+ goto end;
+
+ if (extra_pha) {
+ /*
+ * The client reads the server's ACK of its KeyUpdate, which
+ * installs it and releases the held PHA response. The server then
+ * completes PHA while still owing the reply.
+ */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE),
+ rep_epoch))
+ goto end;
+ }
+ } else {
+ /* Request PHA and let the client answer it. */
+ SSL_set_verify(server, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(cc->post_handshake_auth, SSL_PHA_REQUESTED))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ goto end;
+
+ /* Hold back the server's ACK of that response: it stays outstanding. */
+ while (BIO_ctrl_pending(SSL_get_rbio(client)) > 0) {
+ ret = BIO_read(SSL_get_rbio(client), ack1 + ack1len,
+ (int)(sizeof(ack1) - ack1len));
+ if (!TEST_int_gt(ret, 0))
+ goto end;
+ ack1len += (size_t)ret;
+ }
+ if (!TEST_size_t_gt(ack1len, 0))
+ goto end;
+
+ /* A ticket takes the client back to idle without retiring the response. */
+ if (!TEST_true(SSL_new_session_ticket(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(client)))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* The client's KeyUpdate is sent and withheld behind the response. */
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_NOT_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), rep_epoch))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Only its ACK is delivered: acknowledged, but the install is withheld. */
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_false(dtls_has_unacked_key_update(cc))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), rep_epoch))
+ goto end;
+
+ if (extra_pha
+ && (!TEST_true(SSL_verify_client_post_handshake(server))
+ || !TEST_int_eq(SSL_do_handshake(server), 1)))
+ goto end;
+
+ /* The server's KeyUpdate asks the client for a reply. */
+ if (!TEST_true(SSL_key_update(server, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The client reads everything the server sent. It owes the reply but
+ * must not build it while its install waits. With extra_pha it also
+ * defers the second PHA response.
+ */
+ if (via_write) {
+ ret = SSL_write(client, "c", 1);
+ if (!TEST_int_eq(ret, -1))
+ goto end;
+ } else {
+ ret = SSL_read(client, buf, sizeof(buf));
+ }
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(cc->d1->key_update_write_pending)
+ || !TEST_int_eq(cc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(cc, SSL3_CC_WRITE), rep_epoch))
+ goto end;
+ if (extra_pha
+ && !TEST_int_eq((int)cc->statem.deferred_key_update_state,
+ (int)TLS_ST_CR_CERT_REQ))
+ goto end;
+
+ /* Delivering the held ACK retires the first response. */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(client), ack1, (int)ack1len),
+ (int)ack1len))
+ goto end;
+ }
+
+ /*
+ * Recover anything lost by retransmission and let both sides run. The
+ * install can then complete, the reply goes out under the new epoch, and
+ * the peer acknowledges it. A wedged replier keeps retransmitting a reply
+ * the peer never accepts, and never converges.
+ */
+ for (i = 0; i < 10 && !converged; i++) {
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(),
+ ossl_seconds2time(1));
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(),
+ ossl_seconds2time(1));
+ if (!TEST_true(SSL_handle_events(server))
+ || !TEST_true(SSL_handle_events(client)))
+ goto end;
+ ret = SSL_read(client, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ converged = dtls1_get_epoch(repc, SSL3_CC_WRITE) == rep_epoch + 2
+ && !repc->d1->key_update_write_pending
+ && dtls1_get_epoch(reqc, SSL3_CC_WRITE) == req_epoch + 1
+ && !reqc->d1->key_update_write_pending;
+ }
+ if (!TEST_true(converged)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(reqc, SSL3_CC_READ), rep_epoch + 2)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(repc, SSL3_CC_READ), req_epoch + 1))
+ goto end;
+
+ /* Application data flows both ways under the final epochs. */
+ if (!TEST_int_eq(SSL_write(server, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(client, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(client, "c", 1), 1)
+ || !TEST_int_eq(SSL_read(server, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'c'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+struct overlap_pump_st {
+ SSL *peer;
+ int stage;
+ int pumping;
+ int failed;
+};
+
+/*
+ * BIO callback on the writer's write filter. Each datagram the writer gets
+ * accepted drives the peer one step, so the peer's replies are in flight
+ * while the writer is still inside a single SSL_write() call:
+ *
+ * stage 0: the writer's ACK of the peer's first KeyUpdate (A) is accepted.
+ * The peer consumes it and sends a second requested KeyUpdate (C).
+ * stage 1: the writer's reciprocal KeyUpdate (B) is accepted. The peer
+ * consumes it and ACKs it.
+ * stage 2: the writer's ACK of C is accepted. The peer consumes it.
+ */
+static long overlap_pump_cb(BIO *b, int oper, const char *argp, size_t len,
+ int argi, long argl, int ret, size_t *processed)
+{
+ struct overlap_pump_st *pump = (struct overlap_pump_st *)BIO_get_callback_arg(b);
+ unsigned char buf[32];
+ int r;
+
+ if (oper != (BIO_CB_WRITE | BIO_CB_RETURN) || ret <= 0)
+ return ret;
+ if (pump == NULL || pump->peer == NULL || pump->pumping
+ || pump->stage > 2)
+ return ret;
+
+ pump->pumping = 1;
+ ERR_clear_error();
+ r = SSL_read(pump->peer, buf, sizeof(buf));
+ if (r > 0 || SSL_get_error(pump->peer, r) != SSL_ERROR_WANT_READ) {
+ pump->failed = 1;
+ goto done;
+ }
+
+ if (pump->stage == 0) {
+ if (SSL_key_update(pump->peer, SSL_KEY_UPDATE_REQUESTED) != 1) {
+ pump->failed = 1;
+ goto done;
+ }
+ ERR_clear_error();
+ r = SSL_do_handshake(pump->peer);
+ if (r > 0 || SSL_get_error(pump->peer, r) != SSL_ERROR_WANT_READ) {
+ pump->failed = 1;
+ goto done;
+ }
+ }
+ pump->stage++;
+
+done:
+ pump->pumping = 0;
+ return ret;
+}
+
+/*
+ * A pending SSL_write() must send the reciprocal KeyUpdate owed for a second
+ * requested KeyUpdate before it sends application data, even when that second
+ * request arrives while the write is still completing the first exchange.
+ *
+ * The write is first refused by the transport, then the peer sends a
+ * requested KeyUpdate (A) whose ACK the writer is also unable to send. While
+ * the original write is retried, a second requested KeyUpdate (C) arrives
+ * between the writer sending its reciprocal KeyUpdate (B) and receiving the
+ * ACK of it. The handshake_func() call in dtls1_write_app_data_bytes() that
+ * completes B therefore returns success with a new KeyUpdate still owed.
+ * RFC 8446 section 4.6.3 requires that response to precede the writer's next
+ * application record.
+ *
+ * idx 0: the server is the writer. idx 1: the client is the writer.
+ */
+static int test_dtls13_overlapping_keyupdate_before_app_data(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL *local, *peer;
+ SSL_CONNECTION *lc, *pc;
+ BIO *retry = NULL, *filter = NULL;
+ struct overlap_pump_st pump = { NULL, 0, 0, 0 };
+ static const unsigned char payload[] = "pending-write-payload";
+ unsigned char buf[2048];
+ uint64_t peer_read_epoch;
+ int ret, wrote, got, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+
+ local = (idx == 0) ? server : client;
+ peer = (idx == 0) ? client : server;
+ lc = SSL_CONNECTION_FROM_SSL(local);
+ pc = SSL_CONNECTION_FROM_SSL(peer);
+
+ /* Interpose a filter that can refuse local's writes on demand. */
+ if (!TEST_ptr(retry = BIO_new(bio_s_maybe_retry()))
+ || !TEST_true(BIO_up_ref(SSL_get_wbio(local))))
+ goto end;
+
+ filter = retry;
+ SSL_set0_wbio(local, BIO_push(retry, SSL_get_wbio(local)));
+ retry = NULL;
+
+ /* Refuse the next write: nothing reaches the peer. */
+ if (!TEST_long_eq(BIO_ctrl(filter, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT,
+ 0, NULL),
+ 1))
+ goto end;
+
+ ret = SSL_write(local, payload, sizeof(payload));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_WRITE))
+ goto end;
+
+ /* Reopen the filter: the refused record was dropped, not queued. */
+ if (!TEST_long_eq(BIO_ctrl(filter, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT,
+ 100, NULL),
+ 1))
+ goto end;
+
+ /* The peer requests a reciprocal KeyUpdate (A). */
+ if (!TEST_true(SSL_key_update(peer, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+
+ ret = SSL_do_handshake(peer);
+ if (!TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * Block the write that ACKs A. Local schedules its reciprocal KeyUpdate
+ * (B) but can neither ACK A nor send B yet.
+ */
+ if (!TEST_long_eq(BIO_ctrl(filter, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT,
+ 0, NULL),
+ 1))
+ goto end;
+
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_WRITE)
+ || !TEST_int_eq(lc->key_update, SSL_KEY_UPDATE_NOT_REQUESTED)
+ || !TEST_true(SSL_in_init(local)))
+ goto end;
+
+ /* Reopen the filter and arm the peer to react to local's writes. */
+ if (!TEST_long_eq(BIO_ctrl(filter, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT,
+ 100, NULL),
+ 1))
+ goto end;
+
+ pump.peer = peer;
+ BIO_set_callback_ex(filter, overlap_pump_cb);
+ BIO_set_callback_arg(filter, (char *)&pump);
+
+ /* The completing retry of the original write: the call under test. */
+ ERR_clear_error();
+ ret = SSL_write(local, payload, sizeof(payload));
+ wrote = (ret == (int)sizeof(payload));
+
+ /*
+ * Both of the peer's requests and all three replies must have happened.
+ * The write either waits for the ACK of its reciprocal KeyUpdate or, if
+ * it can finish outright, has already sent the payload.
+ */
+ if (!TEST_false(pump.failed)
+ || !TEST_int_eq(pump.stage, 3)
+ || (!wrote
+ && !TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)))
+ goto end;
+
+ /*
+ * Everything the peer receives from here on was sent by the call above
+ * after the peer had already seen B. If application data comes out
+ * ahead of the reciprocal KeyUpdate for C, the peer returns it without
+ * its read epoch having advanced again.
+ */
+ peer_read_epoch = dtls1_get_epoch(pc, SSL3_CC_READ);
+ ERR_clear_error();
+ ret = SSL_read(peer, buf, sizeof(buf));
+ got = (ret == (int)sizeof(payload)
+ && memcmp(buf, payload, sizeof(payload)) == 0);
+ if (!TEST_uint64_t_eq(dtls1_get_epoch(pc, SSL3_CC_READ), peer_read_epoch + 1))
+ goto end;
+ if (!got && !TEST_int_eq(SSL_get_error(peer, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /* Local sees the peer's ACK of the reciprocal KeyUpdate. */
+ ERR_clear_error();
+ ret = SSL_read(local, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(local, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(local)))
+ goto end;
+
+ /* The payload is delivered exactly once, under the new epoch. */
+ if (!wrote) {
+ ERR_clear_error();
+ if (!TEST_int_eq(SSL_write(local, payload, sizeof(payload)),
+ (int)sizeof(payload)))
+ goto end;
+ }
+ if (!got) {
+ ERR_clear_error();
+ ret = SSL_read(peer, buf, sizeof(buf));
+ if (!TEST_int_eq(ret, (int)sizeof(payload))
+ || !TEST_mem_eq(buf, ret, payload, sizeof(payload)))
+ goto end;
+ }
+
+ /* Application data now flows both ways under the new epochs. */
+ if (!TEST_int_eq(SSL_write(local, "l", 1), 1)
+ || !TEST_int_eq(SSL_read(peer, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'l')
+ || !TEST_int_eq(SSL_write(peer, "p", 1), 1)
+ || !TEST_int_eq(SSL_read(local, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'p'))
+ goto end;
+
+ testresult = 1;
+end:
+ BIO_free(retry);
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+#define EPOCH_QUEUE_OLD "OLD-EPOCH-RECORD!!"
+#define EPOCH_QUEUE_NEW "NEW-EPOCH-RECORD!!"
+
+/*
+ * Application records from different DTLS 1.3 epochs must not collide when
+ * they are buffered. Sequence numbers restart in every epoch, so two records
+ * of different epochs can carry the same one.
+ *
+ * The sender writes one application record, then sends requested KeyUpdate
+ * A. The receiver processes A and moves its read epoch forward before that
+ * application record is delivered, so the record belongs to the previous
+ * epoch when it is buffered. The receiver sends its reciprocal KeyUpdate and
+ * waits for it to be acknowledged. That ACK is withheld while the sender
+ * completes its own update and sends a current-epoch application record,
+ * which is buffered as well. DTLS makes no ordering promise for application
+ * data, so returning both records in either order passes.
+ *
+ * idx 0: the server receives. idx 1: the client receives.
+ */
+static int test_dtls13_buffered_app_data_across_epochs(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL, *sender, *receiver;
+ unsigned char old_pkt[2048], new_pkt[2048], ack[2048], reciprocal_ack[2048];
+ SSL_CONNECTION *rc, *sc;
+ unsigned char buf[64];
+ uint64_t recv_read_epoch, recv_write_epoch, send_read_epoch, send_write_epoch;
+ int old_len = 0, new_len = 0, ack_len = 0, reciprocal_ack_len = 0;
+ int ret, i, got_old = 0, got_new = 0, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &server, &client, NULL, NULL))
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+
+ receiver = (idx == 0) ? server : client;
+ sender = (idx == 0) ? client : server;
+ rc = SSL_CONNECTION_FROM_SSL(receiver);
+ sc = SSL_CONNECTION_FROM_SSL(sender);
+
+ /* Remember where every epoch starts so each move can be checked. */
+ recv_read_epoch = dtls1_get_epoch(rc, SSL3_CC_READ);
+ recv_write_epoch = dtls1_get_epoch(rc, SSL3_CC_WRITE);
+ send_read_epoch = dtls1_get_epoch(sc, SSL3_CC_READ);
+ send_write_epoch = dtls1_get_epoch(sc, SSL3_CC_WRITE);
+
+ ERR_clear_error();
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The sender writes the old-epoch application record. Take it out of the
+ * transport instead of letting the receiver see it, so it can be
+ * delivered only after the receiver has processed requested KeyUpdate A.
+ * Exactly one datagram must be queued for the receiver.
+ */
+ if (!TEST_int_eq(SSL_write(sender, EPOCH_QUEUE_OLD, (int)strlen(EPOCH_QUEUE_OLD)),
+ (int)strlen(EPOCH_QUEUE_OLD))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0))
+ goto end;
+ old_len = BIO_read(SSL_get_rbio(receiver), old_pkt, sizeof(old_pkt));
+ if (!TEST_int_gt(old_len, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0))
+ goto end;
+
+ /* The sender sends requested KeyUpdate A and waits for its ACK. */
+ if (!TEST_true(SSL_key_update(sender, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ERR_clear_error();
+ ret = SSL_do_handshake(sender);
+ if (!TEST_int_eq(SSL_get_error(sender, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The receiver processes A, moving its read epoch forward, and parks
+ * waiting on its reciprocal KeyUpdate. Take what it sent back for A out
+ * of the transport so the sender does not see it yet.
+ */
+ ERR_clear_error();
+ ret = SSL_read(receiver, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_in_init(receiver))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(rc, SSL3_CC_READ), recv_read_epoch + 1)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(sender)), 0))
+ goto end;
+ ack_len = BIO_read(SSL_get_rbio(sender), ack, sizeof(ack));
+ if (!TEST_int_gt(ack_len, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(sender)), 0))
+ goto end;
+
+ /*
+ * Deliver the old-epoch record to the receiver by writing it back into
+ * the receiver's read queue. The receiver is already on the new read
+ * epoch, so the record is from the previous epoch, and since the
+ * receiver is still in init it is buffered.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(receiver), old_pkt, old_len), old_len))
+ goto end;
+ ERR_clear_error();
+ ret = SSL_read(receiver, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_in_init(receiver)))
+ goto end;
+
+ /*
+ * Deliver what the receiver sent for A to the sender by writing it back
+ * into the sender's read queue. The sender answers the receiver's
+ * reciprocal KeyUpdate with an ACK. Take that ACK out of the transport
+ * and withhold it, so the receiver stays parked waiting for it.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(sender), ack, ack_len), ack_len))
+ goto end;
+ ERR_clear_error();
+ ret = SSL_read(sender, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(sender, ret), SSL_ERROR_WANT_READ)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0))
+ goto end;
+ reciprocal_ack_len = BIO_read(SSL_get_rbio(receiver), reciprocal_ack,
+ sizeof(reciprocal_ack));
+ if (!TEST_int_gt(reciprocal_ack_len, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0))
+ goto end;
+
+ /*
+ * The sender completes its own update: it is no longer in init and has
+ * moved to the next write epoch, so the record written below is from a
+ * different epoch than the old one.
+ */
+ ERR_clear_error();
+ if (!TEST_int_eq(SSL_do_handshake(sender), 1)
+ || !TEST_false(SSL_in_init(sender))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), send_write_epoch + 1))
+ goto end;
+
+ /*
+ * The sender writes a current-epoch application record. Take it out of
+ * the transport (exactly one datagram) and write it into the receiver's
+ * read queue. The receiver is still parked waiting for the withheld ACK,
+ * so this record is buffered as well.
+ */
+ if (!TEST_int_eq(SSL_write(sender, EPOCH_QUEUE_NEW, (int)strlen(EPOCH_QUEUE_NEW)),
+ (int)strlen(EPOCH_QUEUE_NEW))
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0))
+ goto end;
+ new_len = BIO_read(SSL_get_rbio(receiver), new_pkt, sizeof(new_pkt));
+ if (!TEST_int_gt(new_len, 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(receiver)), 0)
+ || !TEST_int_eq(BIO_write(SSL_get_rbio(receiver), new_pkt, new_len), new_len))
+ goto end;
+ ERR_clear_error();
+ ret = SSL_read(receiver, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_in_init(receiver))
+ || !TEST_int_gt(SSL_pending(receiver), 0))
+ goto end;
+
+ /*
+ * Release the withheld ACK of the reciprocal KeyUpdate by writing it into
+ * the receiver's read queue. No more application data is written, so
+ * every later read can only return what was accepted above.
+ */
+ if (!TEST_int_eq(BIO_write(SSL_get_rbio(receiver), reciprocal_ack,
+ reciprocal_ack_len),
+ reciprocal_ack_len))
+ goto end;
+
+ /* Read until both buffered records have been returned, in either order. */
+ for (i = 0; i < 6 && !(got_old && got_new); i++) {
+ ERR_clear_error();
+ ret = SSL_read(receiver, buf, sizeof(buf));
+ if (ret == (int)strlen(EPOCH_QUEUE_OLD)
+ && memcmp(buf, EPOCH_QUEUE_OLD, (size_t)ret) == 0) {
+ got_old = 1;
+ } else if (ret == (int)strlen(EPOCH_QUEUE_NEW)
+ && memcmp(buf, EPOCH_QUEUE_NEW, (size_t)ret) == 0) {
+ got_new = 1;
+ } else if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)) {
+ goto end;
+ } else if (SSL_pending(receiver) == 0 && !SSL_in_init(receiver)) {
+ break;
+ }
+ }
+
+ if (!TEST_true(got_old) || !TEST_true(got_new))
+ goto end;
+
+ /*
+ * Let the receiver finish processing the released ACK, then check that
+ * both directions of both sides moved forward by exactly one epoch: the
+ * KeyUpdate exchange completed and no further update happened.
+ */
+ ERR_clear_error();
+ ret = SSL_read(receiver, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(receiver, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(receiver))
+ || !TEST_uint64_t_eq(dtls1_get_epoch(rc, SSL3_CC_READ), recv_read_epoch + 1)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(rc, SSL3_CC_WRITE), recv_write_epoch + 1)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_READ), send_read_epoch + 1)
+ || !TEST_uint64_t_eq(dtls1_get_epoch(sc, SSL3_CC_WRITE), send_write_epoch + 1))
+ goto end;
+
+ /* Application data now flows both ways under the new epochs. */
+ if (!TEST_int_eq(SSL_write(sender, "s", 1), 1)
+ || !TEST_int_eq(SSL_read(receiver, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 's')
+ || !TEST_int_eq(SSL_write(receiver, "r", 1), 1)
+ || !TEST_int_eq(SSL_read(sender, buf, 1), 1)
+ || !TEST_uchar_eq(buf[0], 'r'))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
#endif /* OPENSSL_NO_DTLS1_3 */
int setup_tests(void)
@@ -4302,6 +9320,8 @@ int setup_tests(void)
ADD_ALL_TESTS(test_dtls_crypt_sequence_number, OSSL_NELEM(cipher_names));
ADD_ALL_TESTS(test_seq_num_reconstruction, OSSL_NELEM(seq_num_tests));
#ifndef OPENSSL_NO_DTLS1_3
+ ADD_TEST(test_dtls13_epoch0_app_data);
+ ADD_TEST(test_dtls13_active_epoch2_app_data);
ADD_ALL_TESTS(test_dtls13_ack_length, 4);
ADD_TEST(test_dtls13_increment_epoch_max);
ADD_ALL_TESTS(test_dtls13_ack_coverage, 2);
@@ -4333,6 +9353,37 @@ int setup_tests(void)
ADD_TEST(test_dtls13_cert_req_preserves_flight);
ADD_ALL_TESTS(test_dtls13_pha_keyupdate_shared_wrl, 2);
ADD_TEST(test_dtls13_cert_req_finished_preserves_ticket);
+ ADD_TEST(test_dtls13_cert_req_explicit_ack_preserves_ticket);
+ ADD_TEST(test_dtls13_server_keyupdate_preserves_ack);
+ ADD_TEST(test_dtls13_server_keyupdate_preserves_pha);
+ ADD_TEST(test_dtls13_server_keyupdate_preserves_pha_ticket);
+ ADD_TEST(test_dtls13_server_keyupdate_pha_ticket_waits_for_earlier_ack);
+ ADD_TEST(test_dtls13_server_keyupdate_pha_ticket_survives_retransmit);
+ ADD_TEST(test_dtls13_keyupdate_ack_defers_write_keys);
+ ADD_TEST(test_dtls13_keyupdate_ack_defers_write_keys_ticket_ack_lost);
+ ADD_TEST(test_dtls13_keyupdate_ack_survives_entry_eviction);
+ ADD_TEST(test_dtls13_keyupdate_ack_survives_pha_completion_eviction);
+ ADD_TEST(test_dtls13_keyupdate_install_on_pha_cert_req_retirement);
+ ADD_TEST(test_dtls13_pha_install_failure);
+ ADD_ALL_TESTS(test_dtls13_pha_interleaved_app_data, 2);
+ ADD_TEST(test_dtls13_pha_ack_stops_timer);
+ ADD_TEST(test_dtls13_pha_second_request_survives_buffered_dispatch);
+ ADD_TEST(test_dtls13_pha_defer_waits_for_write_key_install);
+ ADD_TEST(test_dtls13_server_idle_waits_for_write_key_install);
+ ADD_TEST(test_dtls13_client_idle_waits_for_write_key_install);
+ ADD_TEST(test_dtls13_pha_completion_eviction_blocks_new_actions);
+ ADD_TEST(test_dtls13_requested_key_update_waits_for_write_key_install);
+ ADD_TEST(test_dtls13_reciprocal_keyupdate_app_data_classification);
+ ADD_ALL_TESTS(test_dtls13_reciprocal_keyupdate_no_app_data_classification, 2);
+ ADD_ALL_TESTS(test_dtls13_scheduled_keyupdate_shutdown, 2);
+ ADD_ALL_TESTS(test_dtls13_simultaneous_keyupdate_app_data_classification, 2);
+ ADD_TEST(test_dtls13_pha_response_app_data_classification);
+ ADD_TEST(test_dtls13_ticket_app_data_classification);
+ ADD_TEST(test_dtls13_client_keyupdate_app_data_classification);
+ ADD_ALL_TESTS(test_dtls13_pending_write_completes_before_reciprocal_keyupdate, 2);
+ ADD_ALL_TESTS(test_dtls13_reciprocal_keyupdate_waits_for_pending_install, 8);
+ ADD_ALL_TESTS(test_dtls13_overlapping_keyupdate_before_app_data, 2);
+ ADD_ALL_TESTS(test_dtls13_buffered_app_data_across_epochs, 2);
#endif
return 1;
}
diff --git a/test/dtlsssllistenertest.c b/test/dtlsssllistenertest.c
index f7129e191d..c356bc7956 100644
--- a/test/dtlsssllistenertest.c
+++ b/test/dtlsssllistenertest.c
@@ -1110,6 +1110,249 @@ end:
return testresult;
}
+/*
+ * Unauthenticated epoch-0 application data received before ClientHello must
+ * not survive the handshake and be delivered by SSL_read(). At this point
+ * the connection's maximum version is DTLS 1.3, but its read record layer
+ * still uses DTLS_ANY_VERSION with no cryptographic protection.
+ */
+static int test_dtls13_listener_epoch0_app_data(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *listener = NULL, *server = NULL, *client = NULL;
+ BIO_ADDR *client_addr = NULL;
+ const unsigned char injected[] = {
+ SSL3_RT_APPLICATION_DATA,
+ 0xfe, 0xfd, /* legacy record version: DTLS 1.2 */
+ 0, 0, /* epoch 0: no authentication */
+ 0, 0, 0, 0, 0, 63, /* leave ClientHello sequence 0 in the replay window */
+ 0, 4, /* payload length */
+ 'E', 'V', 'I', 'L'
+ };
+ const unsigned char msg[] = { 'G', 'O', 'O', 'D' };
+ const unsigned char reply[] = { 'S', 'A', 'F', 'E' };
+ unsigned char buf[64];
+ int ret, testresult = 0;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0))
+ || !TEST_true(create_dtls_listener_and_client_mem(sctx, cctx,
+ SSL_LISTENER_FLAG_NO_VALIDATE | SSL_LISTENER_FLAG_SINGLE_THREAD,
+ &listener, &client, &client_addr))
+ || !TEST_true(SSL_set_blocking_mode(listener, 0)))
+ goto end;
+
+ SSL_set_connect_state(client);
+ /* Bypass SSL_write(): these bytes have never been authenticated. */
+ if (!TEST_int_eq(BIO_write(SSL_get_wbio(client), injected,
+ sizeof(injected)),
+ sizeof(injected)))
+ goto end;
+
+ /*
+ * NO_VALIDATE promotes the connection after driving the first datagram,
+ * even if it was not a ClientHello. The datagram pair preserves the source
+ * address, so the subsequent real client traffic reaches this same SSL.
+ */
+ if (!TEST_ptr(server = SSL_accept_connection(listener,
+ SSL_ACCEPT_CONNECTION_NO_BLOCK))
+ || !TEST_ptr_null(SSL_get_rbio(server)))
+ goto end;
+
+ /* No ClientHello has been sent yet; this must be an ordinary retry. */
+ ret = SSL_accept(server);
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(server), TLS_ST_BEFORE))
+ goto end;
+
+ /*
+ * Do not use create_ssl_connection(): its intermediate SSL_read() calls
+ * could consume the injected bytes and fail before the handshake finishes.
+ * Disabling those reads leaves the delivery assertion to this test.
+ */
+ if (!TEST_true(create_bare_ssl_connection(server, client,
+ SSL_ERROR_NONE, 0, 0))
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_int_eq(SSL_version(server), DTLS1_3_VERSION)
+ || !TEST_int_eq(SSL_version(client), DTLS1_3_VERSION))
+ goto end;
+
+ /* Verify authenticated traffic in the other direction before the oracle. */
+ if (!TEST_int_eq(SSL_write(server, reply, sizeof(reply)), sizeof(reply))
+ || !TEST_int_eq(SSL_read(client, buf, sizeof(buf)), sizeof(reply))
+ || !TEST_mem_eq(buf, sizeof(reply), reply, sizeof(reply)))
+ goto end;
+
+ /*
+ * Both payloads have four bytes: accepting the injection fails the content
+ * comparison (EVIL instead of GOOD), after a successful real handshake.
+ */
+ if (!TEST_int_eq(SSL_write(client, msg, sizeof(msg)), sizeof(msg))
+ || !TEST_int_eq(SSL_read(server, buf, sizeof(buf)), sizeof(msg))
+ || !TEST_mem_eq(buf, sizeof(msg), msg, sizeof(msg)))
+ goto end;
+
+ /* No injected bytes may remain queued behind the authenticated record. */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_free(listener);
+ BIO_ADDR_free(client_addr);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+/*
+ * A connection accepted by a DTLS listener has no read BIO of its own --
+ * dtls_listener_create_conn_ssl() (ssl/d1_lib.c) calls SSL_set0_rbio(ssl,
+ * NULL), since such connections receive through the listener's shared demux
+ * queue instead. This exercises the same KeyUpdate/buffered-application-data
+ * race as test_dtls13_reciprocal_keyupdate_app_data_classification in
+ * dtls13_internal_test.c, but over a listener-accepted connection: one
+ * application datagram is queued immediately behind a KeyUpdate that
+ * requests a response, and the accepted connection must classify that as an
+ * ordinary SSL_ERROR_WANT_READ retry condition -- the same outcome as the
+ * ordinary BIO-pair case -- even with no rbio of its own.
+ *
+ * Since the accepted connection has no rbio, the held datagram can't be
+ * reinjected with a plain BIO_write() the way the ordinary test does: the
+ * demux has no per-datagram address to route it by. It's drained from the
+ * listener's shared rbio as raw bytes and reinjected via
+ * ossl_dgram_demux_inject() instead, tagged with the client's address, after
+ * first pumping the KeyUpdate itself into the connection's own queue so the
+ * two land in the right order.
+ */
+static int test_dtls13_listener_keyupdate_app_data_classification(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *listener = NULL, *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc;
+ BIO_ADDR *client_addr = NULL;
+ unsigned char buf[2048], held[2048];
+ int ret, held_len, testresult = 0;
+ int retc, err_code, abortctr = 0;
+ SSL_POLL_ITEM poll_item;
+ struct timeval poll_timeout;
+ size_t poll_result;
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), DTLS1_3_VERSION, DTLS1_3_VERSION,
+ &sctx, &cctx, cert, privkey))
+ || !TEST_true(SSL_CTX_set_num_tickets(sctx, 0)))
+ goto end;
+
+ if (!TEST_true(create_dtls_listener_and_client_mem(sctx, cctx,
+ SSL_LISTENER_FLAG_NO_VALIDATE | SSL_LISTENER_FLAG_SINGLE_THREAD,
+ &listener, &client, &client_addr)))
+ goto end;
+
+ /* Drive the connection until SSL_accept_connection returns the accepted server SSL. */
+ SSL_set_connect_state(client);
+ while (server == NULL) {
+ if (++abortctr > 100) {
+ TEST_error("Connection loop did not converge");
+ goto end;
+ }
+ retc = SSL_connect(client);
+ err_code = SSL_get_error(client, retc);
+ if (retc <= 0 && err_code != SSL_ERROR_WANT_READ
+ && err_code != SSL_ERROR_WANT_WRITE) {
+ TEST_error("SSL_connect failed (err %d)", err_code);
+ goto end;
+ }
+
+ poll_item.desc.type = BIO_POLL_DESCRIPTOR_TYPE_SSL;
+ poll_item.desc.value.ssl = listener;
+ poll_item.events = SSL_POLL_EVENT_IC;
+ poll_item.revents = 0;
+ poll_timeout.tv_sec = 0;
+ poll_timeout.tv_usec = 0;
+ if (!TEST_true(SSL_poll(&poll_item, 1, sizeof(poll_item), &poll_timeout, 0, &poll_result)))
+ goto end;
+ if (poll_result > 0 && (poll_item.revents & SSL_POLL_EVENT_IC) != 0)
+ server = SSL_accept_connection(listener, SSL_ACCEPT_CONNECTION_NO_BLOCK);
+ }
+ if (!TEST_ptr(server)
+ || !TEST_true(create_ssl_connection(server, client, SSL_ERROR_NONE)))
+ goto end;
+ sc = SSL_CONNECTION_FROM_SSL(server);
+
+ /* The accepted connection receives through the listener's queue, not a BIO of its own. */
+ if (!TEST_ptr_null(SSL_get_rbio(server)))
+ goto end;
+
+ /*
+ * The client writes application data under the current epoch, then
+ * requests a reciprocal KeyUpdate. Hold the application datagram back
+ * so it can be delivered right after the KeyUpdate -- both sit in the
+ * listener's shared rbio before the accepted connection reads either.
+ */
+ if (!TEST_int_eq(SSL_write(client, "ABCD", 4), 4))
+ goto end;
+ held_len = BIO_read(SSL_get_rbio(listener), held, sizeof(held));
+ if (!TEST_int_gt(held_len, 0))
+ goto end;
+
+ if (!TEST_true(SSL_key_update(client, SSL_KEY_UPDATE_REQUESTED)))
+ goto end;
+ ret = SSL_do_handshake(client);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ))
+ goto end;
+
+ /*
+ * The listener's rbio already has the KeyUpdate, still unpumped. Pump it
+ * into the accepted connection's own queue first -- otherwise injecting
+ * ABCD (below) would land ahead of it, since injection dispatches
+ * synchronously instead of waiting its turn behind whatever is still
+ * sitting in the raw network BIO.
+ */
+ if (!TEST_int_eq(ossl_dgram_demux_pump(sc->d1->rx->demux), DGRAM_DEMUX_PUMP_RES_OK))
+ goto end;
+
+ /*
+ * Inject ABCD directly into the accepted connection's demux queue, tagged
+ * with the client's address: a raw BIO_write on the listener's shared
+ * rbio carries no per-datagram source-address metadata, so the demux has
+ * no way to route it to this specific connection (it gets silently
+ * dropped instead of landing in this connection's queue).
+ */
+ if (!TEST_true(ossl_dgram_demux_inject(sc->d1->rx->demux, held, held_len,
+ client_addr, NULL)))
+ goto end;
+
+ /*
+ * The call under test: it processes the KeyUpdate -- scheduling the
+ * accepted connection's own reciprocal response -- then buffers the
+ * application record that follows while the connection's rbio is NULL,
+ * and sends the response before returning. This must report
+ * SSL_ERROR_WANT_READ, same as the ordinary BIO-pair case.
+ */
+ ret = SSL_read(server, buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(server, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(sc->d1->key_update_write_pending)
+ || !TEST_false(SSL_is_init_finished(server)))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(server);
+ SSL_free(client);
+ SSL_free(listener);
+ BIO_ADDR_free(client_addr);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
#ifndef OPENSSL_NO_DTLS1_2
/*
* Test mixed DTLS versions: DTLS 1.2 with HVR and DTLS 1.3 with HRR.
@@ -8546,6 +8789,8 @@ int setup_tests(void)
/* DTLS 1.3 connection tests */
ADD_TEST(test_dtls13_connection_with_hrr);
ADD_TEST(test_dtls13_connection_without_hrr);
+ ADD_TEST(test_dtls13_listener_epoch0_app_data);
+ ADD_TEST(test_dtls13_listener_keyupdate_app_data_classification);
/* Mixed version tests */
#ifndef OPENSSL_NO_DTLS1_2
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 4d1a032b53..c40d17eda3 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -10986,6 +10986,34 @@ static int test_key_update_local_in_write(int idx)
*/
if (!TEST_int_eq(SSL_read(peer, buf, sizeof(buf)), -1))
goto end;
+
+ /*
+ * That read only scheduled the peer's reciprocal KeyUpdate; it
+ * doesn't get constructed and sent until the peer is next entered.
+ * local has nothing new to read yet -- it only reads the ACK of
+ * its own original KeyUpdate here -- and that completes local's
+ * own install, but does not produce anything new for the peer to
+ * read in turn.
+ */
+ if (!TEST_int_eq(SSL_read(local, buf, sizeof(buf)), -1))
+ goto end;
+
+ /* This is what actually constructs and sends the reciprocal. */
+ if (!TEST_int_eq(SSL_read(peer, buf, sizeof(buf)), -1))
+ goto end;
+
+ /*
+ * The peer's reciprocal KeyUpdate is itself withheld from
+ * completing until local acknowledges it. local must read it and
+ * send that ACK before the peer's own deferred write-key install
+ * can finish and it stops being parked waiting for it.
+ */
+ if (!TEST_int_eq(SSL_read(local, buf, sizeof(buf)), -1))
+ goto end;
+
+ /* Let the peer process that ACK and complete its own install. */
+ if (!TEST_int_eq(SSL_read(peer, buf, sizeof(buf)), -1))
+ goto end;
}
/*
@@ -10998,16 +11026,13 @@ static int test_key_update_local_in_write(int idx)
if (testdtls) {
/*
- * DTLS1.3 the peer needs to write to send out
- * its key update
+ * Both sides' KeyUpdates are now fully installed (see above), so
+ * this is now just an ordinary write/read -- unlike local's first
+ * write back when the peer's reciprocal KeyUpdate still needed to
+ * be triggered and flushed as a side effect of it.
*/
- if (!TEST_int_eq(SSL_write(peer, mess, (int)strlen(mess)), -1))
- goto end;
-
- /*
- * The local side needs to read the ACK
- */
- if (!TEST_int_eq(SSL_read(local, buf, sizeof(buf)), -1))
+ if (!TEST_int_eq(SSL_write(peer, mess, (int)strlen(mess)), (int)strlen(mess))
+ || !TEST_int_eq(SSL_read(local, buf, sizeof(buf)), (int)strlen(mess)))
goto end;
}