Do QUIC Key Update when approaching AEAD Confidentiality Limit, and close connection if reached.
Protected by FLAGS_quic_reloadable_flag_quic_enable_aead_limits.
PiperOrigin-RevId: 337581109
Change-Id: Ie021fd42be174cfb1c069159ecca0bf34bd99731
diff --git a/quic/core/crypto/aes_base_encrypter.cc b/quic/core/crypto/aes_base_encrypter.cc
index 436b7bd..f4a152f 100644
--- a/quic/core/crypto/aes_base_encrypter.cc
+++ b/quic/core/crypto/aes_base_encrypter.cc
@@ -35,4 +35,13 @@
return out;
}
+QuicPacketCount AesBaseEncrypter::GetConfidentialityLimit() const {
+ // For AEAD_AES_128_GCM and AEAD_AES_256_GCM, the confidentiality limit is
+ // 2^25 encrypted packets.
+ // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
+ static_assert(kMaxOutgoingPacketSize < 16384,
+ "This key limit requires limits on encryption payload sizes");
+ return 33554432U;
+}
+
} // namespace quic
diff --git a/quic/core/crypto/aes_base_encrypter.h b/quic/core/crypto/aes_base_encrypter.h
index 7339640..6b1c98c 100644
--- a/quic/core/crypto/aes_base_encrypter.h
+++ b/quic/core/crypto/aes_base_encrypter.h
@@ -20,6 +20,7 @@
bool SetHeaderProtectionKey(absl::string_view key) override;
std::string GenerateHeaderProtectionMask(absl::string_view sample) override;
+ QuicPacketCount GetConfidentialityLimit() const override;
private:
// The key used for packet number encryption.
diff --git a/quic/core/crypto/chacha20_poly1305_encrypter.cc b/quic/core/crypto/chacha20_poly1305_encrypter.cc
index 37b899f..f66f31c 100644
--- a/quic/core/crypto/chacha20_poly1305_encrypter.cc
+++ b/quic/core/crypto/chacha20_poly1305_encrypter.cc
@@ -27,4 +27,11 @@
ChaCha20Poly1305Encrypter::~ChaCha20Poly1305Encrypter() {}
+QuicPacketCount ChaCha20Poly1305Encrypter::GetConfidentialityLimit() const {
+ // For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the
+ // number of possible packets (2^62) and so can be disregarded.
+ // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
+ return std::numeric_limits<QuicPacketCount>::max();
+}
+
} // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_encrypter.h b/quic/core/crypto/chacha20_poly1305_encrypter.h
index 7200788..98f6ff0 100644
--- a/quic/core/crypto/chacha20_poly1305_encrypter.h
+++ b/quic/core/crypto/chacha20_poly1305_encrypter.h
@@ -29,6 +29,8 @@
ChaCha20Poly1305Encrypter& operator=(const ChaCha20Poly1305Encrypter&) =
delete;
~ChaCha20Poly1305Encrypter() override;
+
+ QuicPacketCount GetConfidentialityLimit() const override;
};
} // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc b/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc
index c650b27..a03a14c 100644
--- a/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc
+++ b/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc
@@ -27,4 +27,11 @@
ChaCha20Poly1305TlsEncrypter::~ChaCha20Poly1305TlsEncrypter() {}
+QuicPacketCount ChaCha20Poly1305TlsEncrypter::GetConfidentialityLimit() const {
+ // For AEAD_CHACHA20_POLY1305, the confidentiality limit is greater than the
+ // number of possible packets (2^62) and so can be disregarded.
+ // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
+ return std::numeric_limits<QuicPacketCount>::max();
+}
+
} // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_tls_encrypter.h b/quic/core/crypto/chacha20_poly1305_tls_encrypter.h
index 0ef7ae8..f83141f 100644
--- a/quic/core/crypto/chacha20_poly1305_tls_encrypter.h
+++ b/quic/core/crypto/chacha20_poly1305_tls_encrypter.h
@@ -27,6 +27,8 @@
ChaCha20Poly1305TlsEncrypter& operator=(const ChaCha20Poly1305TlsEncrypter&) =
delete;
~ChaCha20Poly1305TlsEncrypter() override;
+
+ QuicPacketCount GetConfidentialityLimit() const override;
};
} // namespace quic
diff --git a/quic/core/crypto/null_encrypter.cc b/quic/core/crypto/null_encrypter.cc
index 1d5dd8b..f379ff2 100644
--- a/quic/core/crypto/null_encrypter.cc
+++ b/quic/core/crypto/null_encrypter.cc
@@ -83,6 +83,10 @@
return plaintext_size + GetHashLength();
}
+QuicPacketCount NullEncrypter::GetConfidentialityLimit() const {
+ return std::numeric_limits<QuicPacketCount>::max();
+}
+
absl::string_view NullEncrypter::GetKey() const {
return absl::string_view();
}
diff --git a/quic/core/crypto/null_encrypter.h b/quic/core/crypto/null_encrypter.h
index bdfd2e7..82cb2c1 100644
--- a/quic/core/crypto/null_encrypter.h
+++ b/quic/core/crypto/null_encrypter.h
@@ -41,6 +41,7 @@
size_t GetIVSize() const override;
size_t GetMaxPlaintextSize(size_t ciphertext_size) const override;
size_t GetCiphertextSize(size_t plaintext_size) const override;
+ QuicPacketCount GetConfidentialityLimit() const override;
absl::string_view GetKey() const override;
absl::string_view GetNoncePrefix() const override;
diff --git a/quic/core/crypto/quic_encrypter.h b/quic/core/crypto/quic_encrypter.h
index 89d7fed..b454997 100644
--- a/quic/core/crypto/quic_encrypter.h
+++ b/quic/core/crypto/quic_encrypter.h
@@ -57,6 +57,10 @@
// to plaintext of size |plaintext_size|.
virtual size_t GetCiphertextSize(size_t plaintext_size) const = 0;
+ // Returns the maximum number of packets that can be safely encrypted with
+ // this encrypter.
+ virtual QuicPacketCount GetConfidentialityLimit() const = 0;
+
// For use by unit tests only.
virtual absl::string_view GetKey() const = 0;
virtual absl::string_view GetNoncePrefix() const = 0;
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index dddeb1f..025e257 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -5003,6 +5003,53 @@
server_thread_->Resume();
}
+TEST_P(EndToEndTest, KeyUpdateInitiatedByConfidentialityLimit) {
+ SetQuicReloadableFlag(quic_enable_aead_limits, true);
+ SetQuicReloadableFlag(quic_key_update_supported, true);
+ SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 4U);
+
+ if (!version_.UsesTls()) {
+ // Key Update is only supported in TLS handshake.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ QuicConnection* client_connection = GetClientConnection();
+ ASSERT_TRUE(client_connection);
+ EXPECT_EQ(0u, client_connection->GetStats().key_update_count);
+
+ server_thread_->WaitUntil(
+ [this]() {
+ QuicConnection* server_connection = GetServerConnection();
+ if (server_connection != nullptr) {
+ EXPECT_EQ(0u, server_connection->GetStats().key_update_count);
+ } else {
+ ADD_FAILURE() << "Missing server connection";
+ }
+ return true;
+ },
+ QuicTime::Delta::FromSeconds(5));
+
+ SendSynchronousFooRequestAndCheckResponse();
+ SendSynchronousFooRequestAndCheckResponse();
+ SendSynchronousFooRequestAndCheckResponse();
+ // Don't know exactly how many packets will be sent in each request/response,
+ // so just test that at least one key update occurred.
+ EXPECT_LE(1u, client_connection->GetStats().key_update_count);
+
+ server_thread_->Pause();
+ QuicConnection* server_connection = GetServerConnection();
+ if (server_connection) {
+ QuicConnectionStats server_stats = server_connection->GetStats();
+ EXPECT_LE(1u, server_stats.key_update_count);
+ } else {
+ ADD_FAILURE() << "Missing server connection";
+ }
+ server_thread_->Resume();
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 4dec406..c3fa8fb 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -265,6 +265,8 @@
direct_peer_address_(initial_peer_address),
active_effective_peer_migration_type_(NO_CHANGE),
support_key_update_for_connection_(false),
+ enable_aead_limits_(GetQuicReloadableFlag(quic_enable_aead_limits) &&
+ version().UsesTls()),
last_packet_decrypted_(false),
last_size_(0),
current_packet_data_(nullptr),
@@ -3067,11 +3069,17 @@
handshake_packet_sent_ = true;
}
- if (packet->encryption_level == ENCRYPTION_FORWARD_SECURE &&
- !lowest_packet_sent_in_current_key_phase_.IsInitialized()) {
- QUIC_DLOG(INFO) << ENDPOINT << "lowest_packet_sent_in_current_key_phase_ = "
- << packet_number;
- lowest_packet_sent_in_current_key_phase_ = packet_number;
+ if (packet->encryption_level == ENCRYPTION_FORWARD_SECURE) {
+ if (!lowest_packet_sent_in_current_key_phase_.IsInitialized()) {
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "lowest_packet_sent_in_current_key_phase_ = "
+ << packet_number;
+ lowest_packet_sent_in_current_key_phase_ = packet_number;
+ }
+ if (!is_termination_packet &&
+ MaybeHandleAeadConfidentialityLimits(*packet)) {
+ return true;
+ }
}
if (in_flight || !retransmission_alarm_->IsSet()) {
@@ -3095,6 +3103,110 @@
return true;
}
+bool QuicConnection::MaybeHandleAeadConfidentialityLimits(
+ const SerializedPacket& packet) {
+ if (!enable_aead_limits_) {
+ return false;
+ }
+
+ if (packet.encryption_level != ENCRYPTION_FORWARD_SECURE) {
+ QUIC_BUG
+ << "MaybeHandleAeadConfidentialityLimits called on non 1-RTT packet";
+ return false;
+ }
+ if (!lowest_packet_sent_in_current_key_phase_.IsInitialized()) {
+ QUIC_BUG << "lowest_packet_sent_in_current_key_phase_ must be initialized "
+ "before calling MaybeHandleAeadConfidentialityLimits";
+ return false;
+ }
+
+ // Calculate the number of packets encrypted from the packet number, which is
+ // simpler than keeping another counter. The packet number space may be
+ // sparse, so this might overcount, but doing a key update earlier than
+ // necessary would only improve security and has negligible cost.
+ if (packet.packet_number < lowest_packet_sent_in_current_key_phase_) {
+ const std::string error_details = quiche::QuicheStrCat(
+ "packet_number(", packet.packet_number.ToString(),
+ ") < lowest_packet_sent_in_current_key_phase_ (",
+ lowest_packet_sent_in_current_key_phase_.ToString(), ")");
+ QUIC_BUG << error_details;
+ CloseConnection(QUIC_INTERNAL_ERROR, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return true;
+ }
+ const QuicPacketCount num_packets_encrypted_in_current_key_phase =
+ packet.packet_number - lowest_packet_sent_in_current_key_phase_ + 1;
+
+ const QuicPacketCount confidentiality_limit =
+ framer_.GetOneRttEncrypterConfidentialityLimit();
+
+ // Attempt to initiate a key update before reaching the AEAD
+ // confidentiality limit when the number of packets sent in the current
+ // key phase gets within |kKeyUpdateConfidentialityLimitOffset| packets of
+ // the limit, unless overridden by
+ // FLAGS_quic_key_update_confidentiality_limit.
+ constexpr QuicPacketCount kKeyUpdateConfidentialityLimitOffset = 1000;
+ QuicPacketCount key_update_limit = 0;
+ if (confidentiality_limit > kKeyUpdateConfidentialityLimitOffset) {
+ key_update_limit =
+ confidentiality_limit - kKeyUpdateConfidentialityLimitOffset;
+ }
+ const QuicPacketCount key_update_limit_override =
+ GetQuicFlag(FLAGS_quic_key_update_confidentiality_limit);
+ if (key_update_limit_override) {
+ key_update_limit = key_update_limit_override;
+ }
+
+ QUIC_DVLOG(2) << ENDPOINT << "Checking AEAD confidentiality limits: "
+ << "num_packets_encrypted_in_current_key_phase="
+ << num_packets_encrypted_in_current_key_phase
+ << " key_update_limit=" << key_update_limit
+ << " confidentiality_limit=" << confidentiality_limit
+ << " IsKeyUpdateAllowed()=" << IsKeyUpdateAllowed();
+
+ if (num_packets_encrypted_in_current_key_phase >= confidentiality_limit) {
+ // Reached the confidentiality limit without initiating a key update,
+ // must close the connection.
+ const std::string error_details = quiche::QuicheStrCat(
+ "encrypter confidentiality limit reached: "
+ "num_packets_encrypted_in_current_key_phase=",
+ num_packets_encrypted_in_current_key_phase,
+ " key_update_limit=", key_update_limit,
+ " confidentiality_limit=", confidentiality_limit,
+ " IsKeyUpdateAllowed()=", IsKeyUpdateAllowed());
+ CloseConnection(QUIC_AEAD_LIMIT_REACHED, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return true;
+ }
+
+ if (IsKeyUpdateAllowed() &&
+ num_packets_encrypted_in_current_key_phase >= key_update_limit) {
+ // Approaching the confidentiality limit, initiate key update so that
+ // the next set of keys will be ready for the next packet before the
+ // limit is reached.
+ if (key_update_limit_override) {
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "reached FLAGS_quic_key_update_confidentiality_limit, "
+ "initiating key update: "
+ << "num_packets_encrypted_in_current_key_phase="
+ << num_packets_encrypted_in_current_key_phase
+ << " key_update_limit=" << key_update_limit
+ << " confidentiality_limit=" << confidentiality_limit;
+ } else {
+ QUIC_DLOG(INFO) << ENDPOINT
+ << "approaching AEAD confidentiality limit, "
+ "initiating key update: "
+ << "num_packets_encrypted_in_current_key_phase="
+ << num_packets_encrypted_in_current_key_phase
+ << " key_update_limit=" << key_update_limit
+ << " confidentiality_limit=" << confidentiality_limit;
+ }
+ InitiateKeyUpdate();
+ }
+
+ return false;
+}
+
void QuicConnection::FlushPackets() {
if (!connected_) {
return;
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 9531581..26629ba 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1252,6 +1252,12 @@
// writer is write blocked.
bool WritePacket(SerializedPacket* packet);
+ // Enforce AEAD Confidentiality limits by iniating key update or closing
+ // connection if too many packets have been encrypted with the current key.
+ // Returns true if the connection was closed. Should not be called for
+ // termination packets.
+ bool MaybeHandleAeadConfidentialityLimits(const SerializedPacket& packet);
+
// Flush packets buffered in the writer, if any.
void FlushPackets();
@@ -1552,6 +1558,10 @@
// key update but before the first packet has been sent.
QuicPacketNumber lowest_packet_sent_in_current_key_phase_;
+ // Honor the AEAD confidentiality and integrity limits by initiating key
+ // update (if allowed) and/or closing the connection, as necessary.
+ bool enable_aead_limits_;
+
// True if the last packet has gotten far enough in the framer to be
// decrypted.
bool last_packet_decrypted_;
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 62f4b8a..738bc2e 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -110,6 +110,24 @@
}
}
+// A NullEncrypterWithConfidentialityLimit is a NullEncrypter that allows
+// specifying the confidentiality limit on the maximum number of packets that
+// may be encrypted per key phase in TLS+QUIC.
+class NullEncrypterWithConfidentialityLimit : public NullEncrypter {
+ public:
+ NullEncrypterWithConfidentialityLimit(Perspective perspective,
+ QuicPacketCount confidentiality_limit)
+ : NullEncrypter(perspective),
+ confidentiality_limit_(confidentiality_limit) {}
+
+ QuicPacketCount GetConfidentialityLimit() const override {
+ return confidentiality_limit_;
+ }
+
+ private:
+ QuicPacketCount confidentiality_limit_;
+};
+
class TestConnectionHelper : public QuicConnectionHelperInterface {
public:
TestConnectionHelper(MockClock* clock, MockRandom* random_generator)
@@ -12041,6 +12059,8 @@
std::make_unique<TaggingEncrypter>(0x01));
SetDecrypter(ENCRYPTION_FORWARD_SECURE,
std::make_unique<StrictTaggingDecrypter>(0x01));
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
connection_.OnHandshakeComplete();
peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
@@ -12150,6 +12170,288 @@
EXPECT_FALSE(connection_.GetDiscardPreviousOneRttKeysAlarm()->IsSet());
}
+TEST_P(QuicConnectionTest, InitiateKeyUpdateApproachingConfidentialityLimit) {
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+ SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 3U);
+
+ std::string error_details;
+ TransportParameters params;
+ // Key update is enabled.
+ params.key_update_not_yet_supported = false;
+ QuicConfig config;
+ EXPECT_THAT(config.ProcessTransportParameters(
+ params, /* is_resumption = */ false, &error_details),
+ IsQuicNoError());
+ config.SetKeyUpdateSupportedLocally();
+ QuicConfigPeer::SetNegotiated(&config, true);
+ if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+ QuicConfigPeer::SetReceivedOriginalConnectionId(
+ &config, connection_.connection_id());
+ QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+ &config, connection_.connection_id());
+ }
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+
+ MockFramerVisitor peer_framer_visitor_;
+ peer_framer_.set_visitor(&peer_framer_visitor_);
+
+ use_tagging_decrypter();
+
+ uint8_t current_tag = 0x01;
+
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<TaggingEncrypter>(current_tag));
+ SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<StrictTaggingDecrypter>(current_tag));
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ connection_.OnHandshakeComplete();
+
+ peer_framer_.SetKeyUpdateSupportForConnection(true);
+ peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<TaggingEncrypter>(current_tag));
+
+ const QuicConnectionStats& stats = connection_.GetStats();
+
+ for (int packet_num = 1; packet_num <= 8; ++packet_num) {
+ if (packet_num == 3 || packet_num == 6) {
+ current_tag++;
+ EXPECT_CALL(visitor_, AdvanceKeysAndCreateCurrentOneRttDecrypter())
+ .WillOnce([current_tag]() {
+ return std::make_unique<StrictTaggingDecrypter>(current_tag);
+ });
+ EXPECT_CALL(visitor_, CreateCurrentOneRttEncrypter())
+ .WillOnce([current_tag]() {
+ return std::make_unique<TaggingEncrypter>(current_tag);
+ });
+ }
+ // Send packet.
+ QuicPacketNumber last_packet;
+ SendStreamDataToPeer(packet_num, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_EQ(QuicPacketNumber(packet_num), last_packet);
+ if (packet_num >= 6) {
+ EXPECT_EQ(2U, stats.key_update_count);
+ } else if (packet_num >= 3) {
+ EXPECT_EQ(1U, stats.key_update_count);
+ } else {
+ EXPECT_EQ(0U, stats.key_update_count);
+ }
+
+ if (packet_num == 4 || packet_num == 7) {
+ // Pretend that peer accepts the key update.
+ EXPECT_CALL(peer_framer_visitor_,
+ AdvanceKeysAndCreateCurrentOneRttDecrypter())
+ .WillOnce([current_tag]() {
+ return std::make_unique<StrictTaggingDecrypter>(current_tag);
+ });
+ EXPECT_CALL(peer_framer_visitor_, CreateCurrentOneRttEncrypter())
+ .WillOnce([current_tag]() {
+ return std::make_unique<TaggingEncrypter>(current_tag);
+ });
+ peer_framer_.DoKeyUpdate();
+ }
+ // Receive ack for packet.
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame1 = InitAckFrame(packet_num);
+ ProcessAckPacket(&frame1);
+ }
+}
+
+TEST_P(QuicConnectionTest,
+ CloseConnectionOnConfidentialityLimitKeyUpdateNotAllowed) {
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+ // Set key update confidentiality limit to 1 packet.
+ SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 1U);
+ // Use confidentiality limit for connection close of 3 packets.
+ constexpr size_t kConfidentialityLimit = 3U;
+
+ std::string error_details;
+ TransportParameters params;
+ // Key update is enabled.
+ params.key_update_not_yet_supported = false;
+ QuicConfig config;
+ EXPECT_THAT(config.ProcessTransportParameters(
+ params, /* is_resumption = */ false, &error_details),
+ IsQuicNoError());
+ config.SetKeyUpdateSupportedLocally();
+ QuicConfigPeer::SetNegotiated(&config, true);
+ if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+ QuicConfigPeer::SetReceivedOriginalConnectionId(
+ &config, connection_.connection_id());
+ QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+ &config, connection_.connection_id());
+ }
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<NullEncrypterWithConfidentialityLimit>(
+ Perspective::IS_CLIENT, kConfidentialityLimit));
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ connection_.OnHandshakeComplete();
+
+ QuicPacketNumber last_packet;
+ // Send 3 packets without receiving acks for any of them. Key update will not
+ // be allowed, so the confidentiality limit should be reached, forcing the
+ // connection to be closed.
+ SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ SendStreamDataToPeer(2, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+ SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_FALSE(connection_.connected());
+ const QuicConnectionStats& stats = connection_.GetStats();
+ EXPECT_EQ(0U, stats.key_update_count);
+ TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
+TEST_P(QuicConnectionTest,
+ CloseConnectionOnConfidentialityLimitKeyUpdateNotSupportedByPeer) {
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+ // Set key update confidentiality limit to 1 packet.
+ SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 1U);
+ // Use confidentiality limit for connection close of 3 packets.
+ constexpr size_t kConfidentialityLimit = 3U;
+
+ std::string error_details;
+ TransportParameters params;
+ // Key update not enabled for this connection as peer doesn't support it.
+ params.key_update_not_yet_supported = true;
+ QuicConfig config;
+ EXPECT_THAT(config.ProcessTransportParameters(
+ params, /* is_resumption = */ false, &error_details),
+ IsQuicNoError());
+ // Key update is supported locally.
+ config.SetKeyUpdateSupportedLocally();
+ QuicConfigPeer::SetNegotiated(&config, true);
+ if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+ QuicConfigPeer::SetReceivedOriginalConnectionId(
+ &config, connection_.connection_id());
+ QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+ &config, connection_.connection_id());
+ }
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<NullEncrypterWithConfidentialityLimit>(
+ Perspective::IS_CLIENT, kConfidentialityLimit));
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ connection_.OnHandshakeComplete();
+
+ QuicPacketNumber last_packet;
+ // Send 3 packets and receive acks for them. Since key update is not enabled
+ // the confidentiality limit should be reached, forcing the connection to be
+ // closed.
+ SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ // Receive ack for packet.
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame1 = InitAckFrame(1);
+ ProcessAckPacket(&frame1);
+
+ SendStreamDataToPeer(2, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ // Receive ack for packet.
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame2 = InitAckFrame(2);
+ ProcessAckPacket(&frame2);
+
+ EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+ SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_FALSE(connection_.connected());
+ const QuicConnectionStats& stats = connection_.GetStats();
+ EXPECT_EQ(0U, stats.key_update_count);
+ TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
+TEST_P(QuicConnectionTest,
+ CloseConnectionOnConfidentialityLimitKeyUpdateNotEnabledLocally) {
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+ // Set key update confidentiality limit to 1 packet.
+ SetQuicFlag(FLAGS_quic_key_update_confidentiality_limit, 1U);
+ // Use confidentiality limit for connection close of 3 packets.
+ constexpr size_t kConfidentialityLimit = 3U;
+
+ std::string error_details;
+ TransportParameters params;
+ // Key update is supported by peer but not locally
+ // (config.SetKeyUpdateSupportedLocally is not called.)
+ params.key_update_not_yet_supported = false;
+ QuicConfig config;
+ EXPECT_THAT(config.ProcessTransportParameters(
+ params, /* is_resumption = */ false, &error_details),
+ IsQuicNoError());
+ QuicConfigPeer::SetNegotiated(&config, true);
+ if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+ QuicConfigPeer::SetReceivedOriginalConnectionId(
+ &config, connection_.connection_id());
+ QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+ &config, connection_.connection_id());
+ }
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<NullEncrypterWithConfidentialityLimit>(
+ Perspective::IS_CLIENT, kConfidentialityLimit));
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ connection_.OnHandshakeComplete();
+
+ QuicPacketNumber last_packet;
+ // Send 3 packets and receive acks for them. Since key update is not enabled
+ // the confidentiality limit should be reached, forcing the connection to be
+ // closed.
+ SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ // Receive ack for packet.
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame1 = InitAckFrame(1);
+ ProcessAckPacket(&frame1);
+
+ SendStreamDataToPeer(2, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_TRUE(connection_.connected());
+ // Receive ack for packet.
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame2 = InitAckFrame(2);
+ ProcessAckPacket(&frame2);
+
+ EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+ SendStreamDataToPeer(3, "foo", 0, NO_FIN, &last_packet);
+ EXPECT_FALSE(connection_.connected());
+ const QuicConnectionStats& stats = connection_.GetStats();
+ EXPECT_EQ(0U, stats.key_update_count);
+ TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
TEST_P(QuicConnectionTest, SendAckFrequencyFrame) {
if (!version().HasIetfQuicFrames()) {
return;
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index 63d96f1..75bbebd 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -233,6 +233,8 @@
RETURN_STRING_LITERAL(QUIC_ZERO_RTT_RESUMPTION_LIMIT_REDUCED);
RETURN_STRING_LITERAL(QUIC_SILENT_IDLE_TIMEOUT);
RETURN_STRING_LITERAL(QUIC_MISSING_WRITE_KEYS);
+ RETURN_STRING_LITERAL(QUIC_KEY_UPDATE_ERROR);
+ RETURN_STRING_LITERAL(QUIC_AEAD_LIMIT_REACHED);
RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
// Intentionally have no default case, so we'll break the build
@@ -271,6 +273,8 @@
RETURN_STRING_LITERAL(PROTOCOL_VIOLATION);
RETURN_STRING_LITERAL(INVALID_TOKEN);
RETURN_STRING_LITERAL(CRYPTO_BUFFER_EXCEEDED);
+ RETURN_STRING_LITERAL(KEY_UPDATE_ERROR);
+ RETURN_STRING_LITERAL(AEAD_LIMIT_REACHED);
// CRYPTO_ERROR is handled in the if before this switch, these cases do not
// change behavior and are only here to make the compiler happy.
case CRYPTO_ERROR_FIRST:
@@ -634,6 +638,10 @@
return {true, static_cast<uint64_t>(PROTOCOL_VIOLATION)};
case QUIC_MISSING_WRITE_KEYS:
return {true, static_cast<uint64_t>(INTERNAL_ERROR)};
+ case QUIC_KEY_UPDATE_ERROR:
+ return {true, static_cast<uint64_t>(KEY_UPDATE_ERROR)};
+ case QUIC_AEAD_LIMIT_REACHED:
+ return {true, static_cast<uint64_t>(AEAD_LIMIT_REACHED)};
case QUIC_LAST_ERROR:
return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)};
}
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index e0cb0ed..4771272 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -500,8 +500,15 @@
// Try to write data without the right write keys.
QUIC_MISSING_WRITE_KEYS = 170,
+ // An endpoint detected errors in performing key updates.
+ QUIC_KEY_UPDATE_ERROR = 172,
+
+ // An endpoint has reached the confidentiality or integrity limit for the
+ // AEAD algorithm used by the given connection.
+ QUIC_AEAD_LIMIT_REACHED = 173,
+
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 172,
+ QUIC_LAST_ERROR = 174,
};
// QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
// or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
@@ -533,6 +540,8 @@
PROTOCOL_VIOLATION = 0xA,
INVALID_TOKEN = 0xB,
CRYPTO_BUFFER_EXCEEDED = 0xD,
+ KEY_UPDATE_ERROR = 0xE,
+ AEAD_LIMIT_REACHED = 0xF,
CRYPTO_ERROR_FIRST = 0x100,
CRYPTO_ERROR_LAST = 0x1FF,
};
diff --git a/quic/core/quic_error_codes_test.cc b/quic/core/quic_error_codes_test.cc
index 0f567df..ee4cacd 100644
--- a/quic/core/quic_error_codes_test.cc
+++ b/quic/core/quic_error_codes_test.cc
@@ -53,6 +53,10 @@
EXPECT_EQ("INVALID_TOKEN", QuicIetfTransportErrorCodeString(INVALID_TOKEN));
EXPECT_EQ("CRYPTO_BUFFER_EXCEEDED",
QuicIetfTransportErrorCodeString(CRYPTO_BUFFER_EXCEEDED));
+ EXPECT_EQ("KEY_UPDATE_ERROR",
+ QuicIetfTransportErrorCodeString(KEY_UPDATE_ERROR));
+ EXPECT_EQ("AEAD_LIMIT_REACHED",
+ QuicIetfTransportErrorCodeString(AEAD_LIMIT_REACHED));
EXPECT_EQ("Unknown(1024)",
QuicIetfTransportErrorCodeString(
@@ -74,7 +78,7 @@
if (ietf_error_code.is_transport_close) {
QuicIetfTransportErrorCodes transport_error_code =
static_cast<QuicIetfTransportErrorCodes>(ietf_error_code.error_code);
- bool is_valid_transport_error_code = transport_error_code <= 0x0d;
+ bool is_valid_transport_error_code = transport_error_code <= 0x0f;
EXPECT_TRUE(is_valid_transport_error_code) << internal_error_code_string;
} else {
// Non-transport errors are application errors, either HTTP/3 or QPACK.
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 6842211..ede689c 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -4639,6 +4639,14 @@
return min_plaintext_size;
}
+QuicPacketCount QuicFramer::GetOneRttEncrypterConfidentialityLimit() const {
+ if (!encrypter_[ENCRYPTION_FORWARD_SECURE]) {
+ QUIC_BUG << "1-RTT encrypter not set";
+ return 0;
+ }
+ return encrypter_[ENCRYPTION_FORWARD_SECURE]->GetConfidentialityLimit();
+}
+
bool QuicFramer::DecryptPayload(absl::string_view encrypted,
absl::string_view associated_data,
const QuicPacketHeader& header,
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index 48a7877..763d0b8 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -585,6 +585,10 @@
// to ciphertext no larger than |ciphertext_size|.
size_t GetMaxPlaintextSize(size_t ciphertext_size);
+ // Returns the maximum number of packets that can be safely encrypted with
+ // the active AEAD. 1-RTT keys must be set before calling this method.
+ QuicPacketCount GetOneRttEncrypterConfidentialityLimit() const;
+
const std::string& detailed_error() { return detailed_error_; }
// The minimum packet number length required to represent |packet_number|.
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index da4319d..e21b2ef 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -126,6 +126,9 @@
size_t GetCiphertextSize(size_t plaintext_size) const override {
return plaintext_size;
}
+ QuicPacketCount GetConfidentialityLimit() const override {
+ return std::numeric_limits<QuicPacketCount>::max();
+ }
absl::string_view GetKey() const override { return absl::string_view(); }
absl::string_view GetNoncePrefix() const override {
return absl::string_view();