Commit a41c00d1cf for openssl.org
commit a41c00d1cfbd05140cb70ef59327df0b69f698c6
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date: Wed Sep 16 22:38:32 2026 +0900
DTLS: preserve outstanding flights after incomplete ACKs
Do not finish reading after a DTLS 1.3 ACK while sent records remain
unacknowledged. Finishing reading stops the retransmit timer and clears
the entire sent queue, even for an empty or partial ACK.
Restore the preceding handshake state when ACK processing continues.
This preserves application-data handling and allows the expected
post-handshake authentication response to follow an incomplete ACK.
Limit the ACK epoch restriction to the initial handshake. Subsequent
sending and receiving epochs can differ, and the old restriction
produced empty ACKs for repeated unilateral KeyUpdates. Those exchanges
previously depended on the receive-side coverage bug to complete.
Exercise empty, nonmatching, partial and duplicate ACKs on both peers,
followed by application data and complete acknowledgement. Correct two
existing KeyUpdate tests which relied on unrelated ACKs clearing the
sent queue.
The regression fails on the parent. All 15 selected DTLS and SSL recipes
pass with --strict-warnings, including the default and FIPS SSL API runs.
Fixes openssl#32853
Assisted-by: Codex:gpt-6-astra
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Ryan Hooper <ryanh@openssl.foundation>
Merge-date: Mon Sep 21 09:48:30 2026
Merged-from: https://github.com/openssl/openssl/pull/32854
diff --git a/include/internal/statem.h b/include/internal/statem.h
index e39b497e04..027853eccd 100644
--- a/include/internal/statem.h
+++ b/include/internal/statem.h
@@ -117,8 +117,10 @@ struct ossl_statem_st {
OSSL_HANDSHAKE_STATE hand_state;
/* The handshake state requested by an API call (e.g. HelloRequest) */
OSSL_HANDSHAKE_STATE request_state;
- /* The handshake state waiting for acknowledge */
+ /* The handshake state to resume after sending an ACK */
OSSL_HANDSHAKE_STATE deferred_ack_state;
+ /* The handshake state before receiving an ACK */
+ OSSL_HANDSHAKE_STATE pre_ack_hand_state;
ERROR_STATE error_state;
int in_init;
int read_state_first_init;
@@ -138,6 +140,8 @@ struct ossl_statem_st {
ossl_statem_finish_mutate_handshake_cb finish_mutate_handshake_cb;
void *mutatearg;
unsigned int write_in_progress : 1;
+ /* Send an ACK for an already processed post-handshake message */
+ unsigned int ack_for_retransmit : 1;
};
typedef struct ossl_statem_st OSSL_STATEM;
diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c
index 088049b1fa..abea35ae80 100644
--- a/ssl/statem/statem.c
+++ b/ssl/statem/statem.c
@@ -134,6 +134,7 @@ void ossl_statem_clear(SSL_CONNECTION *s)
s->statem.error_state = ERROR_STATE_NOERROR;
ossl_statem_set_in_init(s, 1);
s->statem.no_cert_verify = 0;
+ s->statem.ack_for_retransmit = 0;
}
/*
@@ -620,6 +621,16 @@ static SUB_STATE_RETURN read_state_machine(SSL_CONNECTION *s)
while (1) {
switch (st->read_state) {
case READ_STATE_HEADER:
+ /*
+ * Restore the state after an incomplete ACK before reading again:
+ * the DTLS record layer uses it to handle application data.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s)
+ && (st->hand_state == TLS_ST_CR_ACK
+ || st->hand_state == TLS_ST_SR_ACK)
+ && !transition(s, SSL3_MT_DUMMY))
+ return SUB_STATE_ERROR;
+
/* Get the state the peer wants to move to */
if (SSL_CONNECTION_IS_DTLS(s)) {
/*
@@ -631,6 +642,10 @@ static SUB_STATE_RETURN read_state_machine(SSL_CONNECTION *s)
}
if (ret == 0) {
+ /* Re-ACK a retransmission without completing our own flight. */
+ if (st->ack_for_retransmit)
+ return SUB_STATE_FINISHED;
+
/*
* If we're in DTLSv1.3 and in state TLS_ST_OK, then we must
* have received a post-handshake message. If we subsequently
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index d0283578a6..6f1ba3cfe2 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -95,6 +95,12 @@ static int ossl_statem_client13_read_transition(SSL_CONNECTION *s, int mt)
{
OSSL_STATEM *st = &s->statem;
+ if (st->hand_state == TLS_ST_CR_ACK) {
+ st->hand_state = st->pre_ack_hand_state;
+ if (mt == SSL3_MT_DUMMY)
+ return 1;
+ }
+
/*
* Note: There is no case for TLS_ST_CW_CLNT_HELLO, because we haven't
* yet negotiated TLSv1.3 at that point so that is handled by
@@ -179,9 +185,9 @@ static int ossl_statem_client13_read_transition(SSL_CONNECTION *s, int mt)
case TLS_ST_CW_KEY_UPDATE:
case TLS_ST_CW_FINISHED:
- case TLS_ST_CR_ACK:
case TLS_ST_OK:
if (mt == DTLS13_MT_ACK) {
+ st->pre_ack_hand_state = st->hand_state;
st->hand_state = TLS_ST_CR_ACK;
return 1;
}
@@ -419,6 +425,12 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
{
OSSL_STATEM *st = &s->statem;
+ if (st->ack_for_retransmit && st->hand_state != TLS_ST_CW_ACK) {
+ st->deferred_ack_state = st->hand_state;
+ st->hand_state = TLS_ST_CW_ACK;
+ return WRITE_TRAN_CONTINUE;
+ }
+
/*
* Note: There are no cases for TLS_ST_BEFORE because we haven't negotiated
* TLSv1.3 yet at that point. They are handled by
@@ -531,6 +543,11 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s)
return WRITE_TRAN_CONTINUE;
case TLS_ST_CW_ACK:
+ if (st->ack_for_retransmit) {
+ st->hand_state = st->deferred_ack_state;
+ st->ack_for_retransmit = 0;
+ return WRITE_TRAN_FINISHED;
+ }
st->hand_state = TLS_ST_OK;
return WRITE_TRAN_CONTINUE;
diff --git a/ssl/statem/statem_dtls.c b/ssl/statem/statem_dtls.c
index 1381c416f9..bff336e3ec 100644
--- a/ssl/statem/statem_dtls.c
+++ b/ssl/statem/statem_dtls.c
@@ -436,6 +436,8 @@ int dtls_get_message(SSL_CONNECTION *s, int *mt)
again:
if (!dtls_get_reassembled_message(s, &errtype, &tmplen)) {
+ if (s->statem.ack_for_retransmit)
+ return 0;
if (errtype == DTLS1_HM_BAD_FRAGMENT
|| errtype == DTLS1_HM_FRAGMENT_RETRY) {
/* bad fragment received */
@@ -846,6 +848,18 @@ static int dtls1_process_out_of_seq_message(SSL_CONNECTION *s,
goto err;
frag_len -= readbytes;
}
+ /*
+ * A lost ACK can cause an already processed post-handshake message to
+ * be retransmitted in a new record. ACK it without processing it again.
+ */
+ if (SSL_CONNECTION_IS_DTLS13(s)
+ && s->s3.tmp.record_epoch >= 3
+ && msg_hdr->seq < s->d1->handshake_read_seq
+ && dtls_msg_needs_ack(!s->server, msg_hdr->type)) {
+ if (!add_record_to_ack_list(s))
+ goto err;
+ s->statem.ack_for_retransmit = 1;
+ }
} else {
if (frag_len != msg_hdr->msg_len) {
return dtls1_reassemble_fragment(s, msg_hdr);
@@ -1247,11 +1261,13 @@ CON_FUNC_RETURN dtls_construct_ack(SSL_CONNECTION *s, WPACKET *pkt)
recnumnext = ossl_list_record_number_next(recnum);
- if (recnum->epoch <= dtls1_get_epoch(s, SSL3_CC_WRITE)) {
+ if (!SSL_IS_FIRST_HANDSHAKE(s)
+ || recnum->epoch <= dtls1_get_epoch(s, SSL3_CC_WRITE)) {
/*
* rfc9147:
* During the handshake, ACK records MUST be sent with an epoch which
- * is equal to or higher than the record which is being acknowledged
+ * is equal to or higher than the record which is being acknowledged.
+ * After the handshake, the sending and receiving epochs can differ.
*/
if (!WPACKET_put_bytes_u64(pkt, recnum->epoch)
|| !WPACKET_put_bytes_u64(pkt, recnum->seqnum)) {
@@ -1320,6 +1336,10 @@ MSG_PROCESS_RETURN dtls_process_ack(SSL_CONNECTION *s, PACKET *pkt)
}
}
+ /* 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;
+
return MSG_PROCESS_FINISHED_READING;
}
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 704b8c0756..8f1def3c9f 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -75,6 +75,12 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
{
OSSL_STATEM *st = &s->statem;
+ if (st->hand_state == TLS_ST_SR_ACK) {
+ st->hand_state = st->pre_ack_hand_state;
+ if (mt == SSL3_MT_DUMMY)
+ return 1;
+ }
+
/*
* Note: There is no case for TLS_ST_BEFORE because at that stage we have
* not negotiated TLSv1.3 yet, so that case is handled by
@@ -154,10 +160,10 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
}
break;
- case TLS_ST_SR_ACK:
case TLS_ST_SW_KEY_UPDATE:
case TLS_ST_SW_SESSION_TICKET:
if (mt == DTLS13_MT_ACK) {
+ st->pre_ack_hand_state = st->hand_state;
st->hand_state = TLS_ST_SR_ACK;
return 1;
}
@@ -191,6 +197,7 @@ static int ossl_statem_server13_read_transition(SSL_CONNECTION *s, int mt)
}
if (mt == DTLS13_MT_ACK) {
+ st->pre_ack_hand_state = st->hand_state;
st->hand_state = TLS_ST_SR_ACK;
return 1;
}
@@ -607,6 +614,12 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
OSSL_HANDSHAKE_STATE next_state;
OSSL_STATEM *st = &s->statem;
+ if (st->ack_for_retransmit && st->hand_state != TLS_ST_SW_ACK) {
+ st->deferred_ack_state = st->hand_state;
+ 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()
@@ -803,6 +816,10 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s)
case TLS_ST_SW_ACK:
st->hand_state = st->deferred_ack_state;
+ if (st->ack_for_retransmit) {
+ st->ack_for_retransmit = 0;
+ return WRITE_TRAN_FINISHED;
+ }
return WRITE_TRAN_CONTINUE;
}
diff --git a/test/dtls13_internal_test.c b/test/dtls13_internal_test.c
index 5a6d9b61e8..3d78654dac 100644
--- a/test/dtls13_internal_test.c
+++ b/test/dtls13_internal_test.c
@@ -274,6 +274,356 @@ end:
SSL_CTX_free(cctx);
return testresult;
}
+
+/* Exercise ACK coverage for the client's final flight and the server's tickets. */
+static int test_dtls13_ack_coverage(int server)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *serverssl = NULL, *clientssl = NULL, *sender, *peer;
+ SSL_CONNECTION *sc, *psc;
+ dtls_sent_msg *msg = NULL;
+ DTLS1_RECORD_NUMBER *recnum;
+ pitem *item;
+ piterator iter;
+ unsigned char ack[18], buf, discard[2048];
+ WPACKET pkt;
+ uint64_t epoch, seqnum;
+ size_t acklen, written;
+ OSSL_TIME timeout;
+ int i, 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)))
+ goto end;
+
+ /* An empty client Certificate and Finished give us two messages to ACK. */
+ if (!server)
+ SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER, NULL);
+ if (!TEST_true(SSL_CTX_set_num_tickets(sctx, server ? 2 : 0))
+ || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL)))
+ goto end;
+
+ ret = SSL_connect(clientssl);
+ if (!TEST_int_eq(SSL_get_error(clientssl, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_accept(serverssl);
+ if (!TEST_int_eq(SSL_get_error(serverssl, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ ret = SSL_connect(clientssl);
+ if (!TEST_int_eq(SSL_get_error(clientssl, ret), SSL_ERROR_WANT_READ))
+ goto end;
+ if (!TEST_int_eq(SSL_accept(serverssl), 1))
+ goto end;
+ /* SSL_write() must not finish the peer's handshake and ACK our test flight. */
+ if (!server
+ && (!TEST_int_gt(BIO_read(SSL_get_rbio(clientssl), discard, sizeof(discard)), 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(clientssl)), 0)))
+ goto end;
+
+ sender = server ? serverssl : clientssl;
+ peer = server ? clientssl : serverssl;
+ sc = SSL_CONNECTION_FROM_SSL(sender);
+ psc = SSL_CONNECTION_FROM_SSL(peer);
+ if (!TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_false(ossl_time_is_zero(sc->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)
+ msg = item->data;
+ if (!TEST_ptr(msg)
+ || !TEST_ptr(recnum = ossl_list_record_number_head(&msg->rec_nums)))
+ goto end;
+ epoch = recnum->epoch;
+ seqnum = recnum->seqnum;
+
+ /* Avoid timer expiry while inspecting ACK processing. */
+ timeout = sc->d1->next_timeout = ossl_time_add(ossl_time_now(), ossl_seconds2time(3600));
+
+ for (i = 0; i < 5; i++) {
+ int complete = i == 4;
+ int appdata = server || complete;
+
+ /* Empty, nonmatching, partial, duplicate, then the remaining record. */
+ if (complete) {
+ dtls_sent_msg *unacked = pqueue_peek(&sc->d1->sent_messages)->data;
+ uint64_t oldseq;
+
+ if (!TEST_ptr(recnum = ossl_list_record_number_head(&unacked->rec_nums)))
+ goto end;
+ oldseq = recnum->seqnum;
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_int_gt(DTLSv1_handle_timeout(sender), 0)
+ || !TEST_true(ossl_list_record_number_is_empty(&msg->rec_nums))
+ || !TEST_ptr(recnum = ossl_list_record_number_head(&unacked->rec_nums))
+ || !TEST_uint64_t_gt(recnum->seqnum, oldseq))
+ goto end;
+ sc->d1->next_timeout = timeout;
+ /* Only the unacknowledged message should have been retransmitted. */
+ msg = pqueue_peek(&sc->d1->sent_messages)->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)))
+ goto end;
+ if ((i != 0
+ && (!TEST_true(WPACKET_put_bytes_u64(&pkt, epoch))
+ || !TEST_true(WPACKET_put_bytes_u64(&pkt,
+ i == 1 ? seqnum + 1000 : 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(psc, SSL3_RT_ACK,
+ ack, acklen, &written),
+ 1)
+ || !TEST_size_t_eq(written, acklen)
+ || !TEST_int_eq(SSL_write(peer, "x", 1), 1)
+ || !TEST_int_gt(BIO_flush(psc->wbio), 0))
+ goto end;
+
+ /* Application data is buffered until the client's final ACK arrives. */
+ ret = SSL_read(sender, &buf, sizeof(buf));
+ if (!TEST_int_eq(SSL_get_error(sender, ret),
+ appdata ? SSL_ERROR_NONE : SSL_ERROR_WANT_READ)
+ || (appdata && !TEST_uchar_eq(buf, 'x'))
+ || !TEST_int_eq(SSL_get_state(sender), appdata ? TLS_ST_OK : TLS_ST_CW_FINISHED)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), complete ? 0 : 2)
+ || !TEST_int_eq(ossl_time_compare(sc->d1->next_timeout,
+ complete ? ossl_time_zero() : timeout),
+ 0)
+ || (!appdata && !TEST_size_t_eq(pqueue_size(sc->rlayer.d->buffered_app_data), i + 1))
+ || (!complete && !TEST_int_eq(ossl_list_record_number_is_empty(&msg->rec_nums), i >= 2)))
+ goto end;
+ }
+
+ testresult = 1;
+end:
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+static int ticket_count;
+
+static int count_ticket(SSL *ssl, SSL_SESSION *session)
+{
+ ticket_count++;
+ return 0;
+}
+
+/*
+ * Replace lost ticket ACKs after another loss or WANT_WRITE, with whole or
+ * fragmented retransmissions, and with or without an outstanding local flight.
+ */
+static int test_dtls13_ticket_ack_retransmit(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ BIO *retry = NULL;
+ piterator iter;
+ pitem *item;
+ unsigned char buf[2048];
+ unsigned int readseq, writeseq;
+ OSSL_TIME client_timeout = ossl_time_zero();
+ int i, ret, dropped, testresult = 0;
+ int pending_key_update = idx / 4;
+ int fragmented = (idx / 2) % 2;
+ int retry_write = idx % 2;
+
+ 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_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);
+ readseq = cc->d1->handshake_read_seq;
+ writeseq = cc->d1->next_handshake_write_seq;
+ if (!TEST_int_eq(ticket_count, 2)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2))
+ goto end;
+
+ if (pending_key_update) {
+ 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))
+ goto end;
+ client_timeout = cc->d1->next_timeout = ossl_time_add(ossl_time_now(), ossl_seconds2time(3600));
+ writeseq = cc->d1->next_handshake_write_seq;
+ }
+
+ if (fragmented) {
+ /* Refragment the tickets on retransmission, after processing them whole. */
+ SSL_set_options(server, SSL_OP_NO_QUERY_MTU);
+ if (!TEST_long_gt(SSL_set_mtu(server, 256), 0))
+ goto end;
+ }
+ if (retry_write) {
+ if (!TEST_ptr(retry = BIO_new(bio_s_maybe_retry()))
+ || !TEST_true(BIO_up_ref(SSL_get_wbio(client))))
+ goto end;
+ SSL_set0_wbio(client, BIO_push(retry, SSL_get_wbio(client)));
+ retry = NULL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ /*
+ * Lose the initial ACKs, then also lose the first replacement ACKs.
+ * In the pending-flight cases, discard the KeyUpdate too: the server
+ * must not process it or send an ACK for the client's local flight.
+ */
+ dropped = 0;
+ while (BIO_read(SSL_get_rbio(server), buf, sizeof(buf)) > 0)
+ dropped++;
+ if (!TEST_int_gt(dropped, 0))
+ goto end;
+ sc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_int_gt(DTLSv1_handle_timeout(server), 0))
+ goto end;
+ iter = pqueue_iterator(&sc->d1->sent_messages);
+ while ((item = pqueue_next(&iter)) != NULL) {
+ dtls_sent_msg *msg = item->data;
+ size_t records = ossl_list_record_number_num(&msg->rec_nums);
+
+ if (fragmented ? !TEST_size_t_gt(records, 1) : !TEST_size_t_eq(records, 1))
+ goto end;
+ }
+
+ if (retry_write) {
+ if (!TEST_long_eq(BIO_ctrl(SSL_get_wbio(client),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 0, NULL),
+ 1))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_WRITE))
+ goto end;
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_WRITE)
+ || !TEST_long_eq(BIO_ctrl(SSL_get_wbio(client),
+ MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 100, NULL),
+ 1))
+ goto end;
+ }
+ ret = SSL_read(client, buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_int_eq(SSL_get_state(client), pending_key_update ? TLS_ST_CW_KEY_UPDATE : TLS_ST_OK)
+ || !TEST_int_eq(ticket_count, 2)
+ || !TEST_uint_eq(cc->d1->handshake_read_seq, readseq)
+ || !TEST_uint_eq(cc->d1->next_handshake_write_seq, writeseq)
+ || !TEST_size_t_gt(BIO_ctrl_pending(SSL_get_rbio(server)), 0)
+ || !TEST_size_t_eq(pqueue_size(&sc->d1->sent_messages), 2)
+ || !TEST_false(ossl_time_is_zero(sc->d1->next_timeout))
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->sent_messages), pending_key_update ? 1 : 0)
+ || !TEST_int_eq(ossl_time_compare(cc->d1->next_timeout, client_timeout), 0))
+ goto end;
+ }
+
+ /* The deliberately lost KeyUpdate must still be waiting for its own ACK. */
+ if (pending_key_update) {
+ testresult = 1;
+ goto end;
+ }
+
+ 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_true(ossl_time_is_zero(sc->d1->next_timeout))
+ || !TEST_int_eq(DTLSv1_handle_timeout(server), 0)
+ || !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:
+ BIO_free(retry);
+ SSL_free(server);
+ SSL_free(client);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ return testresult;
+}
+
+static int test_dtls13_pha_ack_retransmit(void)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *server = NULL, *client = NULL;
+ SSL_CONNECTION *sc, *cc;
+ unsigned char buf, discard[2048];
+ 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);
+ 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;
+ 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(&cc->d1->sent_messages), 2))
+ goto end;
+
+ /* Drop the ACK for the PHA response and let the client retransmit. */
+ if (!TEST_int_gt(BIO_read(SSL_get_rbio(client), discard, sizeof(discard)), 0)
+ || !TEST_size_t_eq(BIO_ctrl_pending(SSL_get_rbio(client)), 0))
+ goto end;
+ cc->d1->next_timeout = ossl_time_subtract(ossl_time_now(), ossl_seconds2time(1));
+ if (!TEST_int_gt(DTLSv1_handle_timeout(client), 0))
+ goto end;
+ 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))
+ goto end;
+ ret = SSL_read(client, &buf, 1);
+ if (!TEST_int_eq(SSL_get_error(client, ret), SSL_ERROR_WANT_READ)
+ || !TEST_true(SSL_is_init_finished(server))
+ || !TEST_true(SSL_is_init_finished(client))
+ || !TEST_size_t_eq(pqueue_size(&cc->d1->sent_messages), 0)
+ || !TEST_true(ossl_time_is_zero(cc->d1->next_timeout))
+ || !TEST_int_eq(sc->post_handshake_auth, SSL_PHA_EXT_RECEIVED))
+ 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)
@@ -287,6 +637,14 @@ int setup_tests(void)
#ifndef OPENSSL_NO_DTLS1_3
ADD_ALL_TESTS(test_dtls13_ack_length, 4);
ADD_TEST(test_dtls13_increment_epoch_max);
+ ADD_ALL_TESTS(test_dtls13_ack_coverage, 2);
+ ADD_ALL_TESTS(test_dtls13_ticket_ack_retransmit, 8);
+ ADD_TEST(test_dtls13_pha_ack_retransmit);
#endif
return 1;
}
+
+void cleanup_tests(void)
+{
+ bio_s_maybe_retry_free();
+}
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 0727110b65..6addf17ba5 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -9182,6 +9182,11 @@ static int test_key_update_peer_in_read(int idx)
SSL_ERROR_NONE)))
goto end;
+ /* Process the session ticket ACKs before starting a KeyUpdate. */
+ if (testdtls
+ && !TEST_int_eq(SSL_read(serverssl, prbuf, sizeof(prbuf)), -1))
+ goto end;
+
local = idx == 0 ? clientssl : serverssl;
peer = idx == 0 ? serverssl : clientssl;
@@ -9312,15 +9317,13 @@ static int test_key_update_local_in_write(int idx)
const SSL_METHOD *smeth, *cmeth;
int vermin, vermax = 0;
int testdtls = idx >= 2;
- int expected_do_handshake_result = 1;
+ int expected_do_handshake_result = testdtls ? -1 : 1;
if (testdtls) {
smeth = DTLS_server_method();
cmeth = DTLS_client_method();
vermin = TLS1_3_VERSION;
idx -= 2;
- if (idx == 0)
- expected_do_handshake_result = -1;
#if defined(OSSL_NO_USABLE_DTLS1_3)
testresult = TEST_skip("No usable DTLSv1.3");
goto end;
@@ -9382,7 +9385,8 @@ static int test_key_update_local_in_write(int idx)
/* SSL_key_update will succeed because there is no pending write data */
if (!TEST_true(SSL_key_update(local, SSL_KEY_UPDATE_REQUESTED))
- || !TEST_int_eq(SSL_do_handshake(local), expected_do_handshake_result))
+ || !TEST_int_eq(SSL_do_handshake(local), expected_do_handshake_result)
+ || (testdtls && !TEST_int_eq(SSL_get_error(local, -1), SSL_ERROR_WANT_READ)))
goto end;
if (testdtls) {