Allow QUIC Key Update on first received 1-RTT packet
As per the specification, the first key update is allowed even if we haven't decrypted any packets: <<An endpoint MUST NOT initiate a key update prior to having confirmed the handshake (Section 4.1.2). An endpoint MUST NOT initiate a subsequent key update unless it has received an acknowledgment for a packet that was sent protected with keys from the current key phase.>> This issue was found during the IETF 110 hackathon, as another implementation was performing a key update immediately post handshake.
Protected by FLAGS_quic_reloadable_flag_quic_fix_key_update_on_first_packet.
PiperOrigin-RevId: 360781943
Change-Id: I678cd85ffdd4d014184fa6db86e8c42c64c421cd
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 0e9fa10..83b2a65 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -43,6 +43,7 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_version_rfcv1, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_goaway, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_key_update_on_first_packet, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_on_stream_reset, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_h3_datagram, false)
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 24034fd..51fa3ea 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -4335,6 +4335,7 @@
QUIC_BUG << "Failed to create next crypters";
return false;
}
+ key_update_performed_ = true;
current_key_phase_bit_ = !current_key_phase_bit_;
QUIC_DLOG(INFO) << ENDPOINT << "DoKeyUpdate: new current_key_phase_bit_="
<< current_key_phase_bit_;
@@ -4772,9 +4773,13 @@
<< " received key_phase=" << key_phase
<< " current_key_phase_bit_=" << current_key_phase_bit_;
if (key_phase != current_key_phase_bit_) {
- if (current_key_phase_first_received_packet_number_.IsInitialized() &&
- header.packet_number >
- current_key_phase_first_received_packet_number_) {
+ if ((current_key_phase_first_received_packet_number_.IsInitialized() &&
+ header.packet_number >
+ current_key_phase_first_received_packet_number_) ||
+ (GetQuicReloadableFlag(quic_fix_key_update_on_first_packet) &&
+ !current_key_phase_first_received_packet_number_.IsInitialized() &&
+ !key_update_performed_)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_fix_key_update_on_first_packet);
if (!next_decrypter_) {
next_decrypter_ =
visitor_->AdvanceKeysAndCreateCurrentOneRttDecrypter();
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 0307594..fe395ce 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -1117,6 +1117,8 @@
// The value of the current key phase bit, which is toggled when the keys are
// changed.
bool current_key_phase_bit_;
+ // Whether we have performed a key update at least once.
+ bool key_update_performed_ = false;
// Tracks the first packet received in the current key phase. Will be
// uninitialized before the first one-RTT packet has been received or after a
// locally initiated key update but before the first packet from the peer in
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index dafe1f7..9f829e5 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -15164,6 +15164,42 @@
EXPECT_EQ(1, visitor_.decrypted_first_packet_in_key_phase_count_);
}
+TEST_P(QuicFramerTest, KeyUpdateOnFirstReceivedPacket) {
+ if (!framer_.version().UsesTls()) {
+ // Key update is only used in QUIC+TLS.
+ return;
+ }
+ SetQuicReloadableFlag(quic_fix_key_update_on_first_packet, true);
+ ASSERT_TRUE(framer_.version().KnowsWhichDecrypterToUse());
+ // Doesn't use SetDecrypterLevel since we want to use StrictTaggingDecrypter
+ // instead of TestDecrypter.
+ framer_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<StrictTaggingDecrypter>(/*key=*/0));
+ framer_.SetKeyUpdateSupportForConnection(true);
+
+ QuicPacketHeader header;
+ header.destination_connection_id = FramerTestConnectionId();
+ header.reset_flag = false;
+ header.version_flag = false;
+ header.packet_number = QuicPacketNumber(123);
+
+ QuicFrames frames = {QuicFrame(QuicPaddingFrame())};
+
+ QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+ std::unique_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+ ASSERT_TRUE(data != nullptr);
+ std::unique_ptr<QuicEncryptedPacket> encrypted(
+ EncryptPacketWithTagAndPhase(*data, /*tag=*/1, /*phase=*/true));
+ ASSERT_TRUE(encrypted);
+
+ QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_SERVER);
+ EXPECT_TRUE(framer_.ProcessPacket(*encrypted));
+ // Processed valid packet with phase=1, key=1: do key update.
+ EXPECT_EQ(1u, visitor_.key_update_count());
+ EXPECT_EQ(1, visitor_.derive_next_key_count_);
+ EXPECT_EQ(1, visitor_.decrypted_first_packet_in_key_phase_count_);
+}
+
TEST_P(QuicFramerTest, ErrorWhenUnexpectedFrameTypeEncountered) {
if (!GetQuicReloadableFlag(quic_reject_unexpected_ietf_frame_types) ||
!VersionHasIetfQuicFrames(framer_.transport_version()) ||
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 97e2194..081791d 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -593,6 +593,7 @@
void QuicVersionInitializeSupportForIetfDraft() {
// Enable necessary flags.
+ SetQuicReloadableFlag(quic_fix_key_update_on_first_packet, true);
}
void QuicEnableVersion(const ParsedQuicVersion& version) {