Skip to content

Commit 725b50c

Browse files
committed
[+] fix issue #756 key update initiator must wait for ACK before confirming
1 parent 96155cf commit 725b50c

10 files changed

Lines changed: 326 additions & 9 deletions

File tree

include/xquic/xqc_errno.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ typedef enum {
2525
TRA_INVALID_TOKEN = 0xB,
2626
TRA_APPLICATION_ERROR = 0xC,
2727
TRA_CRYPTO_BUFFER_EXCEEDED = 0xD,
28-
TRA_0RTT_TRANS_PARAMS_ERROR = 0xE, /**< MUST delete the current saved 0RTT transport parameters */
28+
TRA_KEY_UPDATE_ERROR = 0xE, /**< RFC 9001 §6.7: key update error */
29+
/*
30+
* Library-internal: 0-RTT transport parameter violation.
31+
* RFC 9000 §7.4.1 does not define a dedicated wire error code for this;
32+
* on the wire this value is non-standard (peer treats as INTERNAL_ERROR).
33+
* Kept as a distinct value so xqc_conn_should_clear_0rtt_ticket() can
34+
* distinguish it from other close reasons.
35+
*/
36+
TRA_0RTT_TRANS_PARAMS_ERROR = 0x54,
2937
/*
3038
* RFC 9000 Section 6.2 does not assign a CONNECTION_CLOSE code for
3139
* the Version Negotiation abort path, because the client cannot

scripts/case_test.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3880,7 +3880,7 @@ sleep 1
38803880
clear_log
38813881
echo -e "check_clear_0rtt_ticket_flag_in_close_notify...\c"
38823882
${CLIENT_BIN} -l d -T 1 -s 4800 -U 1 -Q 65535 -E > stdlog
3883-
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:14, clear_0rtt_ticket:1" stdlog`
3883+
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:84, clear_0rtt_ticket:1" stdlog`
38843884
errlog=`grep_err_log`
38853885
if [ -n "$cli_res2" ] && [ -n "$errlog" ]; then
38863886
echo ">>>>>>>> pass:1"
@@ -3902,7 +3902,7 @@ sleep 1
39023902
clear_log
39033903
echo -e "check_clear_0rtt_ticket_flag_in_h3_close_notify...\c"
39043904
${CLIENT_BIN} -l d -s 4800 -Q 65535 -E > stdlog
3905-
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:14, clear_0rtt_ticket:1" stdlog`
3905+
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:84, clear_0rtt_ticket:1" stdlog`
39063906
errlog=`grep_err_log`
39073907
if [ -n "$cli_res2" ] && [ -n "$errlog" ]; then
39083908
echo ">>>>>>>> pass:1"
@@ -3924,7 +3924,7 @@ sleep 1
39243924
clear_log
39253925
echo -e "check_clear_0rtt_ticket_flag_in_h3_close_notify...\c"
39263926
${CLIENT_BIN} -l d -s 4800 -Q 65535 -E > stdlog
3927-
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:14, clear_0rtt_ticket:1" stdlog`
3927+
cli_res2=`grep "should_clear_0rtt_ticket, conn_err:84, clear_0rtt_ticket:1" stdlog`
39283928
errlog=`grep_err_log`
39293929
if [ -n "$cli_res2" ] && [ -n "$errlog" ]; then
39303930
echo ">>>>>>>> pass:1"
@@ -5185,7 +5185,7 @@ fi
51855185

51865186
# test 701: server reduces max_streams_bidi after first connection,
51875187
# client detects reduction on 0-RTT resumption and closes with
5188-
# TRANSPORT_PARAMETER_ERROR (0x0E = conn_err:14)
5188+
# TRA_0RTT_TRANS_PARAMS_ERROR (0x54 = conn_err:84)
51895189
killall test_server 2> /dev/null
51905190
clear_log
51915191
rm -f test_session xqc_token tp_localhost
@@ -5196,7 +5196,7 @@ sleep 1
51965196
${CLIENT_BIN} -s 1024 -l d -t 1 -E > stdlog
51975197
# second connection: 0-RTT with reduced max_streams_bidi on server
51985198
${CLIENT_BIN} -s 1024 -l d -t 1 -E > stdlog
5199-
conn_err=`grep "conn_err:14" stdlog`
5199+
conn_err=`grep "conn_err:84" stdlog`
52005200
if [ -n "$conn_err" ]; then
52015201
echo ">>>>>>>> pass:1"
52025202
case_print_result "0RTT_param_reduction" "pass"

src/transport/xqc_conn.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,9 @@ xqc_conn_init_key_update_ctx(xqc_connection_t *conn)
657657
ctx->first_recv_pktno = 0;
658658
ctx->enc_pkt_cnt = 0;
659659

660-
ctx->initiate_time_guard = 0;
660+
ctx->initiate_time_guard = 0;
661+
ctx->key_update_initiator = XQC_FALSE;
662+
ctx->key_update_not_confirmed = XQC_FALSE;
661663
}
662664

663665
static inline void
@@ -6115,7 +6117,8 @@ xqc_conn_tls_transport_params_cb(const uint8_t *tp, size_t len, void *user_data)
61156117
*/
61166118
if (conn->conn_type == XQC_CONN_TYPE_CLIENT
61176119
&& (conn->conn_flag & XQC_CONN_FLAG_HAS_0RTT)
6118-
&& xqc_tls_is_early_data_accepted(conn->tls) == XQC_TLS_EARLY_DATA_ACCEPT)
6120+
&& (xqc_tls_is_early_data_accepted(conn->tls) == XQC_TLS_EARLY_DATA_ACCEPT
6121+
|| (conn->conn_flag & XQC_CONN_FLAG_0RTT_OK)))
61196122
{
61206123
xqc_trans_settings_t *remembered = &conn->remote_settings;
61216124

src/transport/xqc_conn.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ typedef struct {
288288
uint64_t enc_pkt_cnt; /* number of packet encrypt with each key phase */
289289
xqc_usec_t initiate_time_guard; /* time limit for initiating next key update */
290290

291+
/* RFC 9001 §6.1: initiator must wait for ACK before considering update confirmed */
292+
xqc_bool_t key_update_initiator; /* XQC_TRUE if this endpoint initiated the current key update */
293+
294+
/* RFC 9001 §6.1/6.2: TRUE while key update awaits peer confirmation */
295+
xqc_bool_t key_update_not_confirmed;
296+
291297
} xqc_key_update_ctx_t;
292298

293299
typedef struct xqc_ping_record_s {

src/transport/xqc_packet_parser.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,16 @@ xqc_packet_encrypt_buf(xqc_connection_t *conn, xqc_packet_out_t *packet_out,
672672
return ret;
673673
}
674674

675+
/*
676+
* RFC 9001 §6.1: Initiator MUST NOT initiate a subsequent key update
677+
* until it receives an ACK for a packet sent with the new key phase.
678+
* This is enforced by the first_sent_pktno <= ctl_largest_acked check
679+
* in the key update trigger condition (line 660 above).
680+
* Mark as initiator so ACK processing can log confirmation.
681+
*/
682+
conn->key_update_ctx.key_update_initiator = XQC_TRUE;
683+
conn->key_update_ctx.key_update_not_confirmed = XQC_TRUE;
684+
675685
ret = xqc_conn_confirm_key_update(conn);
676686
if (ret != XQC_OK) {
677687
xqc_log(conn->log, XQC_LOG_ERROR, "|xqc_conn_confirm_key_update error|");
@@ -761,6 +771,29 @@ xqc_packet_decrypt(xqc_connection_t *conn, xqc_packet_in_t *packet_in)
761771

762772
/* check key phase, determine weather to update read keys */
763773
xqc_uint_t key_phase = XQC_PACKET_SHORT_HEADER_KEY_PHASE(header);
774+
775+
/*
776+
* RFC 9001 §6.2: Consecutive key update detection.
777+
* If key_update_not_confirmed is TRUE, we are still awaiting confirmation
778+
* of our own key update. A new key_phase change from the peer before that
779+
* confirmation means the peer updated keys twice without waiting.
780+
* Treat as KEY_UPDATE_ERROR.
781+
*/
782+
if (packet_in->pi_pkt.pkt_type == XQC_PTYPE_SHORT_HEADER && level == XQC_ENC_LEV_1RTT
783+
&& key_phase != conn->key_update_ctx.next_in_key_phase
784+
&& packet_in->pi_pkt.pkt_num > conn->key_update_ctx.first_recv_pktno
785+
&& conn->key_update_ctx.key_update_not_confirmed)
786+
{
787+
xqc_log(conn->log, XQC_LOG_ERROR,
788+
"|consecutive key update from peer|pkt_num:%ui|key_phase:%ui|"
789+
"expected:%ui|first_recv_pktno:%ui|",
790+
packet_in->pi_pkt.pkt_num, key_phase,
791+
conn->key_update_ctx.next_in_key_phase,
792+
conn->key_update_ctx.first_recv_pktno);
793+
XQC_CONN_ERR(conn, TRA_KEY_UPDATE_ERROR);
794+
return -XQC_EPROTO;
795+
}
796+
764797
if (packet_in->pi_pkt.pkt_type == XQC_PTYPE_SHORT_HEADER && level == XQC_ENC_LEV_1RTT
765798
&& key_phase != conn->key_update_ctx.next_in_key_phase
766799
&& packet_in->pi_pkt.pkt_num > conn->key_update_ctx.first_recv_pktno
@@ -808,6 +841,11 @@ xqc_packet_decrypt(xqc_connection_t *conn, xqc_packet_in_t *packet_in)
808841
return ret;
809842
}
810843

844+
/* Responder: confirmed by sending with new keys (TX_WRITE sets TRUE).
845+
* Clear initiator flag in case a prior initiated update was pending. */
846+
conn->key_update_ctx.key_update_initiator = XQC_FALSE;
847+
conn->key_update_ctx.key_update_not_confirmed = XQC_FALSE;
848+
811849
ret = xqc_conn_confirm_key_update(conn);
812850
if (ret != XQC_OK) {
813851
xqc_log(conn->log, XQC_LOG_WARN, "|xqc_conn_confirm_key_update error|");
@@ -821,6 +859,14 @@ xqc_packet_decrypt(xqc_connection_t *conn, xqc_packet_in_t *packet_in)
821859
}
822860
}
823861

862+
/*
863+
* TODO: RFC 9001 §6.4 — detect old-key packets with higher pkt_num
864+
* than new-key packets. xquic selects keys by key_phase bit (not trial
865+
* decryption), so an old-key-phase packet with pkt_num > first_recv_pktno
866+
* during the 3*PTO retention window should be treated as KEY_UPDATE_ERROR.
867+
* Requires tracking max pkt_num per key phase. See issue #756 BUG3.
868+
*/
869+
824870
return XQC_OK;
825871
}
826872

src/transport/xqc_send_ctl.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,26 @@ xqc_send_ctl_on_ack_received(xqc_send_ctl_t *send_ctl, xqc_pn_ctl_t *pn_ctl, xqc
958958
return XQC_OK;
959959
}
960960

961+
/*
962+
* RFC 9001 §6.1: Key update confirmation for the initiator.
963+
* The initiator considers the key update confirmed once the peer ACKs a
964+
* packet sent with the new key phase. The practical enforcement is that
965+
* the next key update cannot be initiated until first_sent_pktno <=
966+
* ctl_largest_acked (checked at the trigger site in xqc_packet_parser.c).
967+
* Here we just log the event and clear the initiator flag.
968+
*/
969+
if (pns == XQC_PNS_APP_DATA
970+
&& conn->key_update_ctx.key_update_initiator
971+
&& send_ctl->ctl_largest_acked[pns] != XQC_MAX_UINT64_VALUE
972+
&& send_ctl->ctl_largest_acked[pns] >= conn->key_update_ctx.first_sent_pktno)
973+
{
974+
conn->key_update_ctx.key_update_initiator = XQC_FALSE;
975+
conn->key_update_ctx.key_update_not_confirmed = XQC_FALSE;
976+
xqc_log(conn->log, XQC_LOG_DEBUG,
977+
"|key update confirmed by ACK|largest_acked:%ui|first_sent_pktno:%ui|",
978+
send_ctl->ctl_largest_acked[pns], conn->key_update_ctx.first_sent_pktno);
979+
}
980+
961981
if (update_largest_ack && has_ack_eliciting && ack_on_same_path) {
962982
if (!ignore_rtt) {
963983
/* 更新 ctl_latest_rtt */

tests/unittest/main.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ main()
105105
|| !CU_add_test(pSuite, "xqc_test_crypto_frame_dispatched_via_xqc_process_frame", xqc_test_crypto_frame_dispatched_via_xqc_process_frame)
106106
|| !CU_add_test(pSuite, "xqc_test_crypto_in_0rtt_emits_connection_close", xqc_test_crypto_in_0rtt_emits_connection_close)
107107
|| !CU_add_test(pSuite, "xqc_test_crypto", xqc_test_crypto)
108+
/* issue #823: key update initiator confirmation (RFC 9001 §6.1) */
109+
|| !CU_add_test(pSuite, "xqc_test_key_update_initiator_confirmation",
110+
xqc_test_key_update_initiator_confirmation)
111+
/* issue #756 BUG2: consecutive key update detection (RFC 9001 §6.2) */
112+
|| !CU_add_test(pSuite, "xqc_test_consecutive_key_update_detection",
113+
xqc_test_consecutive_key_update_detection)
108114
|| !CU_add_test(pSuite, "xqc_test_hp_sample_boundary", xqc_test_hp_sample_boundary)
109115
|| !CU_add_test(pSuite, "xqc_test_packet_encrypt_hp_sample_boundary", xqc_test_packet_encrypt_hp_sample_boundary)
110116
|| !CU_add_test(pSuite, "xqc_test_empty_pkt", xqc_test_empty_pkt)

tests/unittest/xqc_conn_test.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ xqc_0rtt_test_make_conn(xqc_cid_t *out_server_scid)
403403

404404
/* mark the connection as having 0-RTT */
405405
conn->conn_flag |= XQC_CONN_FLAG_HAS_0RTT;
406+
conn->conn_flag |= XQC_CONN_FLAG_0RTT_OK;
406407
/* clear any prior errors */
407408
conn->conn_err = 0;
408409
conn->conn_flag &= ~XQC_CONN_FLAG_ERROR;
@@ -625,7 +626,9 @@ xqc_test_0rtt_params_each_reduced(void)
625626

626627
/* reduce exactly one field below remembered */
627628
uint64_t *field = (uint64_t *)((char *)&params + cases[i].tp_offset);
628-
*field = cases[i].remembered_val - 1;
629+
*field = cases[i].remembered_val <= 4
630+
? cases[i].remembered_val - 1
631+
: cases[i].remembered_val / 2;
629632

630633
xqc_int_t err = xqc_0rtt_test_fire(conn, &params);
631634
CU_ASSERT_EQUAL(err, TRA_0RTT_TRANS_PARAMS_ERROR);

0 commit comments

Comments
 (0)