MEDIUM: quic: implement the AEAD confidentiality limit
RFC 9001 section 6.6 limits the number of packets which may be encrypted
with the same keys and requires a key update before this budget is
exhausted. HAProxy did not enforce this limit or initiate local key
updates.
Count successful packet encryptions in a new <count> member of
quic_tls_secrets. Check the 1-RTT TX count from qc_send() and attempt a
key update from seven eighths of the confidentiality limit, subject to
the handshake confirmation and acknowledgment requirements of RFC 9001
section 6.1. This leaves some budget to keep sending while these
requirements are not yet satisfied.
Also check the remaining budget from qc_may_build_pkt(): a single call
to qc_send() may encrypt several packets, so checking only on entry
cannot prevent the limit from being exceeded. Reserve the last allowed
encryption for CONNECTION_CLOSE. If no update is possible before this
reserve is reached, close with AEAD_LIMIT_REACHED while the current
keys can still protect the closing packet.
quic_tls_initiate_key_update() is the local counterpart of a peer-
initiated update. Reset the TX count and record the first packet number
of the new phase when rotating keys. Track locally initiated updates
with QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS so that packets still sent by the
peer with the previous keys can be decrypted without triggering another
rotation. Clear this flag when a packet authenticates with the current
keys.
For AES-GCM, the limit is 2^23 packets per key, approximately 10 GB for
1200-byte packets. Long-lived connections can therefore reach it.
ChaCha20-Poly1305 has a confidentiality limit greater than the QUIC
packet number space and does not require this check.
Validated with haterm and haload over HTTP/3 using AES-128-GCM. A
single-connection transfer triggered a local update naturally at
7,340,032 encrypted packets, without counter injection. The TX count
was reset to zero and the transfer completed successfully. Injected
boundary scenarios also verified that CONNECTION_CLOSE consumes the
reserved packet without exceeding the limit.
This introduces locally initiated key updates for the first time.
Keep it in 3.5 for interoperability exposure before considering a
backport.