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();
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc index 6b092c8..d6395d0 100644 --- a/quic/test_tools/quic_connection_peer.cc +++ b/quic/test_tools/quic_connection_peer.cc
@@ -297,6 +297,12 @@ } // static +void QuicConnectionPeer::SetEnableAeadLimits(QuicConnection* connection, + bool enabled) { + connection->enable_aead_limits_ = enabled; +} + +// static void QuicConnectionPeer::SendConnectionClosePacket(QuicConnection* connection, QuicErrorCode error, const std::string& details) {
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h index bf404fb..ecc98c5 100644 --- a/quic/test_tools/quic_connection_peer.h +++ b/quic/test_tools/quic_connection_peer.h
@@ -124,6 +124,7 @@ PacketHeaderFormat format); static void AddBytesReceived(QuicConnection* connection, size_t length); static void SetAddressValidated(QuicConnection* connection); + static void SetEnableAeadLimits(QuicConnection* connection, bool enabled); static void SendConnectionClosePacket(QuicConnection* connection, QuicErrorCode error,
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index a55f9a8..a46a11b 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -1859,6 +1859,10 @@ return plaintext_size + kTagSize; } + 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 {