Close Quic connections on AEAD integrity limit of received packets that fail authentication.

Protected by FLAGS_quic_reloadable_flag_quic_enable_aead_limits.

PiperOrigin-RevId: 337912276
Change-Id: I1bf2ba93e693a0b3326d8e6a9122aa8346f094b9
diff --git a/quic/core/crypto/aes_base_decrypter.cc b/quic/core/crypto/aes_base_decrypter.cc
index 5bd9c33..464e765 100644
--- a/quic/core/crypto/aes_base_decrypter.cc
+++ b/quic/core/crypto/aes_base_decrypter.cc
@@ -36,4 +36,17 @@
   return out;
 }
 
+QuicPacketCount AesBaseDecrypter::GetIntegrityLimit() const {
+  // For AEAD_AES_128_GCM ... endpoints that do not attempt to remove
+  // protection from packets larger than 2^11 bytes can attempt to remove
+  // protection from at most 2^57 packets.
+  // For AEAD_AES_256_GCM [the limit] is substantially larger than the limit for
+  // AEAD_AES_128_GCM. However, this document recommends that the same limit be
+  // applied to both functions as either limit is acceptably large.
+  // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-integrity-limit
+  static_assert(kMaxIncomingPacketSize <= 2048,
+                "This key limit requires limits on decryption payload sizes");
+  return 144115188075855872U;
+}
+
 }  // namespace quic
diff --git a/quic/core/crypto/aes_base_decrypter.h b/quic/core/crypto/aes_base_decrypter.h
index e3613e4..ced9810 100644
--- a/quic/core/crypto/aes_base_decrypter.h
+++ b/quic/core/crypto/aes_base_decrypter.h
@@ -21,6 +21,7 @@
   bool SetHeaderProtectionKey(absl::string_view key) override;
   std::string GenerateHeaderProtectionMask(
       QuicDataReader* sample_reader) override;
+  QuicPacketCount GetIntegrityLimit() const override;
 
  private:
   // The key used for packet number encryption.
diff --git a/quic/core/crypto/chacha20_poly1305_decrypter.cc b/quic/core/crypto/chacha20_poly1305_decrypter.cc
index e2f55aa..b860348 100644
--- a/quic/core/crypto/chacha20_poly1305_decrypter.cc
+++ b/quic/core/crypto/chacha20_poly1305_decrypter.cc
@@ -32,4 +32,12 @@
   return TLS1_CK_CHACHA20_POLY1305_SHA256;
 }
 
+QuicPacketCount ChaCha20Poly1305Decrypter::GetIntegrityLimit() const {
+  // For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets.
+  // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
+  static_assert(kMaxIncomingPacketSize < 16384,
+                "This key limit requires limits on decryption payload sizes");
+  return 68719476736U;
+}
+
 }  // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_decrypter.h b/quic/core/crypto/chacha20_poly1305_decrypter.h
index 50bb348..2e4b32b 100644
--- a/quic/core/crypto/chacha20_poly1305_decrypter.h
+++ b/quic/core/crypto/chacha20_poly1305_decrypter.h
@@ -33,6 +33,7 @@
   ~ChaCha20Poly1305Decrypter() override;
 
   uint32_t cipher_id() const override;
+  QuicPacketCount GetIntegrityLimit() const override;
 };
 
 }  // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc b/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc
index 8d98da8..7bf8478 100644
--- a/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc
+++ b/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc
@@ -34,4 +34,12 @@
   return TLS1_CK_CHACHA20_POLY1305_SHA256;
 }
 
+QuicPacketCount ChaCha20Poly1305TlsDecrypter::GetIntegrityLimit() const {
+  // For AEAD_CHACHA20_POLY1305, the integrity limit is 2^36 invalid packets.
+  // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-limits-on-aead-usage
+  static_assert(kMaxIncomingPacketSize < 16384,
+                "This key limit requires limits on decryption payload sizes");
+  return 68719476736U;
+}
+
 }  // namespace quic
diff --git a/quic/core/crypto/chacha20_poly1305_tls_decrypter.h b/quic/core/crypto/chacha20_poly1305_tls_decrypter.h
index 702fb8c..04ee5b8 100644
--- a/quic/core/crypto/chacha20_poly1305_tls_decrypter.h
+++ b/quic/core/crypto/chacha20_poly1305_tls_decrypter.h
@@ -31,6 +31,7 @@
   ~ChaCha20Poly1305TlsDecrypter() override;
 
   uint32_t cipher_id() const override;
+  QuicPacketCount GetIntegrityLimit() const override;
 };
 
 }  // namespace quic
diff --git a/quic/core/crypto/null_decrypter.cc b/quic/core/crypto/null_decrypter.cc
index b34e6cd..2fc1ace 100644
--- a/quic/core/crypto/null_decrypter.cc
+++ b/quic/core/crypto/null_decrypter.cc
@@ -102,6 +102,10 @@
   return 0;
 }
 
+QuicPacketCount NullDecrypter::GetIntegrityLimit() const {
+  return std::numeric_limits<QuicPacketCount>::max();
+}
+
 bool NullDecrypter::ReadHash(QuicDataReader* reader, QuicUint128* hash) {
   uint64_t lo;
   uint32_t hi;
diff --git a/quic/core/crypto/null_decrypter.h b/quic/core/crypto/null_decrypter.h
index a760ab5..3c856f4 100644
--- a/quic/core/crypto/null_decrypter.h
+++ b/quic/core/crypto/null_decrypter.h
@@ -50,6 +50,7 @@
   absl::string_view GetNoncePrefix() const override;
 
   uint32_t cipher_id() const override;
+  QuicPacketCount GetIntegrityLimit() const override;
 
  private:
   bool ReadHash(QuicDataReader* reader, QuicUint128* hash);
diff --git a/quic/core/crypto/quic_decrypter.h b/quic/core/crypto/quic_decrypter.h
index 21712f6..4a6cc59 100644
--- a/quic/core/crypto/quic_decrypter.h
+++ b/quic/core/crypto/quic_decrypter.h
@@ -73,6 +73,10 @@
   // selector'.
   virtual uint32_t cipher_id() const = 0;
 
+  // Returns the maximum number of packets that can safely fail decryption with
+  // this decrypter.
+  virtual QuicPacketCount GetIntegrityLimit() 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/quic_connection.cc b/quic/core/quic_connection.cc
index a19eae0..fa02801 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -267,6 +267,7 @@
       support_key_update_for_connection_(false),
       enable_aead_limits_(GetQuicReloadableFlag(quic_enable_aead_limits) &&
                           version().UsesTls()),
+      num_failed_authentication_packets_received_(0),
       last_packet_decrypted_(false),
       last_size_(0),
       current_packet_data_(nullptr),
@@ -2210,6 +2211,29 @@
     debug_visitor_->OnUndecryptablePacket(decryption_level,
                                           /*dropped=*/!should_enqueue);
   }
+
+  if (has_decryption_key) {
+    num_failed_authentication_packets_received_++;
+    if (enable_aead_limits_) {
+      // Should always be non-null if has_decryption_key is true.
+      DCHECK(framer_.GetDecrypter(decryption_level));
+      const QuicPacketCount integrity_limit =
+          framer_.GetDecrypter(decryption_level)->GetIntegrityLimit();
+      QUIC_DVLOG(2) << ENDPOINT << "Checking AEAD integrity limits:"
+                    << " num_failed_authentication_packets_received_="
+                    << num_failed_authentication_packets_received_
+                    << " integrity_limit=" << integrity_limit;
+      if (num_failed_authentication_packets_received_ >= integrity_limit) {
+        const std::string error_details = quiche::QuicheStrCat(
+            "decrypter integrity limit reached:"
+            " num_failed_authentication_packets_received_=",
+            num_failed_authentication_packets_received_,
+            " integrity_limit=", integrity_limit);
+        CloseConnection(QUIC_AEAD_LIMIT_REACHED, error_details,
+                        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      }
+    }
+  }
 }
 
 bool QuicConnection::ShouldEnqueueUnDecryptablePacket(
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 73c6706..4be1b96 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1567,6 +1567,10 @@
   // update (if allowed) and/or closing the connection, as necessary.
   bool enable_aead_limits_;
 
+  // Counts the number of undecryptable packets received across all keys. Does
+  // not include packets where a decryption key for that level was absent.
+  QuicPacketCount num_failed_authentication_packets_received_;
+
   // 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 fd84b30..0ccfefe 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -128,6 +128,20 @@
   QuicPacketCount confidentiality_limit_;
 };
 
+class StrictTaggingDecrypterWithIntegrityLimit : public StrictTaggingDecrypter {
+ public:
+  StrictTaggingDecrypterWithIntegrityLimit(uint8_t tag,
+                                           QuicPacketCount integrity_limit)
+      : StrictTaggingDecrypter(tag), integrity_limit_(integrity_limit) {}
+
+  QuicPacketCount GetIntegrityLimit() const override {
+    return integrity_limit_;
+  }
+
+ private:
+  QuicPacketCount integrity_limit_;
+};
+
 class TestConnectionHelper : public QuicConnectionHelperInterface {
  public:
   TestConnectionHelper(MockClock* clock, MockRandom* random_generator)
@@ -12457,6 +12471,252 @@
   TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
 }
 
+TEST_P(QuicConnectionTest, CloseConnectionOnIntegrityLimitDuringHandshake) {
+  if (!connection_.version().UsesTls()) {
+    return;
+  }
+
+  QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+
+  constexpr uint8_t correct_tag = 0x01;
+  constexpr uint8_t wrong_tag = 0xFE;
+  constexpr QuicPacketCount kIntegrityLimit = 3;
+
+  SetDecrypter(ENCRYPTION_HANDSHAKE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   correct_tag, kIntegrityLimit));
+  connection_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                           std::make_unique<TaggingEncrypter>(correct_tag));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+  peer_framer_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                            std::make_unique<TaggingEncrypter>(wrong_tag));
+  for (uint64_t i = 1; i <= kIntegrityLimit; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    if (i == kIntegrityLimit) {
+      EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+      EXPECT_CALL(visitor_, OnHandshakePacketSent()).Times(AnyNumber());
+    }
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_HANDSHAKE);
+  }
+  EXPECT_FALSE(connection_.connected());
+  TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
+TEST_P(QuicConnectionTest, CloseConnectionOnIntegrityLimitAfterHandshake) {
+  if (!connection_.version().UsesTls()) {
+    return;
+  }
+
+  QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+
+  constexpr uint8_t correct_tag = 0x01;
+  constexpr uint8_t wrong_tag = 0xFE;
+  constexpr QuicPacketCount kIntegrityLimit = 3;
+
+  use_tagging_decrypter();
+  SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   correct_tag, kIntegrityLimit));
+  connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                           std::make_unique<TaggingEncrypter>(correct_tag));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(wrong_tag));
+  for (uint64_t i = 1; i <= kIntegrityLimit; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    if (i == kIntegrityLimit) {
+      EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+    }
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+  }
+  EXPECT_FALSE(connection_.connected());
+  TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
+TEST_P(QuicConnectionTest,
+       CloseConnectionOnIntegrityLimitAcrossEncryptionLevels) {
+  if (!connection_.version().UsesTls()) {
+    return;
+  }
+
+  QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+
+  constexpr uint8_t correct_tag = 0x01;
+  constexpr uint8_t wrong_tag = 0xFE;
+  constexpr QuicPacketCount kIntegrityLimit = 4;
+
+  use_tagging_decrypter();
+  SetDecrypter(ENCRYPTION_HANDSHAKE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   correct_tag, kIntegrityLimit));
+  connection_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                           std::make_unique<TaggingEncrypter>(correct_tag));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+  peer_framer_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                            std::make_unique<TaggingEncrypter>(wrong_tag));
+  for (uint64_t i = 1; i <= 2; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_HANDSHAKE);
+  }
+
+  SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   correct_tag, kIntegrityLimit));
+  connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                           std::make_unique<TaggingEncrypter>(correct_tag));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+  connection_.RemoveEncrypter(ENCRYPTION_HANDSHAKE);
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(wrong_tag));
+  for (uint64_t i = 3; i <= kIntegrityLimit; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    if (i == kIntegrityLimit) {
+      EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+    }
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+  }
+  EXPECT_FALSE(connection_.connected());
+  TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
+TEST_P(QuicConnectionTest, IntegrityLimitDoesNotApplyWithoutDecryptionKey) {
+  if (!connection_.version().UsesTls()) {
+    return;
+  }
+
+  QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+
+  constexpr uint8_t correct_tag = 0x01;
+  constexpr uint8_t wrong_tag = 0xFE;
+  constexpr QuicPacketCount kIntegrityLimit = 3;
+
+  use_tagging_decrypter();
+  SetDecrypter(ENCRYPTION_HANDSHAKE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   correct_tag, kIntegrityLimit));
+  connection_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+                           std::make_unique<TaggingEncrypter>(correct_tag));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(wrong_tag));
+  for (uint64_t i = 1; i <= kIntegrityLimit * 2; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+  }
+  EXPECT_TRUE(connection_.connected());
+}
+
+TEST_P(QuicConnectionTest, CloseConnectionOnIntegrityLimitAcrossKeyPhases) {
+  if (!connection_.version().UsesTls()) {
+    return;
+  }
+
+  constexpr QuicPacketCount kIntegrityLimit = 4;
+
+  QuicConnectionPeer::SetEnableAeadLimits(&connection_, true);
+  TransportParameters params;
+  params.key_update_not_yet_supported = false;
+  QuicConfig config;
+  std::string error_details;
+  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();
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                           std::make_unique<TaggingEncrypter>(0x01));
+  SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+               std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+                   0x01, kIntegrityLimit));
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(0xFF));
+  for (uint64_t i = 1; i <= 2; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+  }
+
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(0x01));
+  // Send packet 1.
+  QuicPacketNumber last_packet;
+  SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
+  EXPECT_EQ(QuicPacketNumber(1u), last_packet);
+  // Receive ack for packet 1.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  QuicAckFrame frame1 = InitAckFrame(1);
+  ProcessAckPacket(&frame1);
+  // Key update should now be allowed, initiate it.
+  EXPECT_CALL(visitor_, AdvanceKeysAndCreateCurrentOneRttDecrypter())
+      .WillOnce([kIntegrityLimit]() {
+        return std::make_unique<StrictTaggingDecrypterWithIntegrityLimit>(
+            0x02, kIntegrityLimit);
+      });
+  EXPECT_CALL(visitor_, CreateCurrentOneRttEncrypter()).WillOnce([]() {
+    return std::make_unique<TaggingEncrypter>(0x02);
+  });
+  EXPECT_CALL(visitor_, OnKeyUpdate(KeyUpdateReason::kLocalForTests));
+  EXPECT_TRUE(connection_.InitiateKeyUpdate(KeyUpdateReason::kLocalForTests));
+
+  // Pretend that peer accepts the key update.
+  EXPECT_CALL(peer_framer_visitor_,
+              AdvanceKeysAndCreateCurrentOneRttDecrypter())
+      .WillOnce(
+          []() { return std::make_unique<StrictTaggingDecrypter>(0x02); });
+  EXPECT_CALL(peer_framer_visitor_, CreateCurrentOneRttEncrypter())
+      .WillOnce([]() { return std::make_unique<TaggingEncrypter>(0x02); });
+  peer_framer_.SetKeyUpdateSupportForConnection(true);
+  peer_framer_.DoKeyUpdate(KeyUpdateReason::kLocalForTests);
+
+  // Send packet 2.
+  SendStreamDataToPeer(2, "bar", 0, NO_FIN, &last_packet);
+  EXPECT_EQ(QuicPacketNumber(2u), last_packet);
+  // Receive ack for packet 2.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  QuicAckFrame frame2 = InitAckFrame(2);
+  ProcessAckPacket(&frame2);
+
+  // Do two more undecryptable packets. Integrity limit should be reached.
+  peer_framer_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<TaggingEncrypter>(0xFF));
+  for (uint64_t i = 3; i <= kIntegrityLimit; ++i) {
+    EXPECT_TRUE(connection_.connected());
+    if (i == kIntegrityLimit) {
+      EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+    }
+    ProcessDataPacketAtLevel(i, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+  }
+  EXPECT_FALSE(connection_.connected());
+  TestConnectionCloseQuicErrorCode(QUIC_AEAD_LIMIT_REACHED);
+}
+
 TEST_P(QuicConnectionTest, SendAckFrequencyFrame) {
   if (!version().HasIetfQuicFrames()) {
     return;
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 949d246..d9baee2 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -183,6 +183,9 @@
   }
   // Use a distinct value starting with 0xFFFFFF, which is never used by TLS.
   uint32_t cipher_id() const override { return 0xFFFFFFF2; }
+  QuicPacketCount GetIntegrityLimit() const override {
+    return std::numeric_limits<QuicPacketCount>::max();
+  }
   QuicPacketNumber packet_number_;
   std::string associated_data_;
   std::string ciphertext_;
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index cff9d1a..a5a774d 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -1927,6 +1927,9 @@
   }
   // Use a distinct value starting with 0xFFFFFF, which is never used by TLS.
   uint32_t cipher_id() const override { return 0xFFFFFFF0; }
+  QuicPacketCount GetIntegrityLimit() const override {
+    return std::numeric_limits<QuicPacketCount>::max();
+  }
 
  protected:
   virtual uint8_t GetTag(absl::string_view ciphertext) {