@@ -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
0 commit comments