Support token based address validation in IETF QUIC.

Protected by FLAGS_quic_reloadable_flag_quic_enable_token_based_address_validation.

PiperOrigin-RevId: 346305075
Change-Id: I65c7ad821518b1c33c96018928cad03f010056f8
diff --git a/quic/core/crypto/quic_crypto_server_config.h b/quic/core/crypto/quic_crypto_server_config.h
index cb19b46..ea229ab 100644
--- a/quic/core/crypto/quic_crypto_server_config.h
+++ b/quic/core/crypto/quic_crypto_server_config.h
@@ -417,6 +417,37 @@
   // Returns the number of configs this object owns.
   int NumberOfConfigs() const;
 
+  // NewSourceAddressToken returns a fresh source address token for the given
+  // IP address. |previous_tokens| is the received tokens, and can be empty.
+  // |cached_network_params| is optional, and can be nullptr.
+  std::string NewSourceAddressToken(
+      const CryptoSecretBoxer& crypto_secret_boxer,
+      const SourceAddressTokens& previous_tokens,
+      const QuicIpAddress& ip,
+      QuicRandom* rand,
+      QuicWallTime now,
+      const CachedNetworkParameters* cached_network_params) const;
+
+  // ParseSourceAddressToken parses the source address tokens contained in
+  // the encrypted |token|, and populates |tokens| with the parsed tokens.
+  // Returns HANDSHAKE_OK if |token| could be parsed, or the reason for the
+  // failure.
+  HandshakeFailureReason ParseSourceAddressToken(
+      const CryptoSecretBoxer& crypto_secret_boxer,
+      absl::string_view token,
+      SourceAddressTokens* tokens) const;
+
+  // ValidateSourceAddressTokens returns HANDSHAKE_OK if the source address
+  // tokens in |tokens| contain a valid and timely token for the IP address
+  // |ip| given that the current time is |now|. Otherwise it returns the
+  // reason for failure. |cached_network_params| is populated if the valid
+  // token contains a CachedNetworkParameters proto.
+  HandshakeFailureReason ValidateSourceAddressTokens(
+      const SourceAddressTokens& tokens,
+      const QuicIpAddress& ip,
+      QuicWallTime now,
+      CachedNetworkParameters* cached_network_params) const;
+
   // Callers retain the ownership of |rejection_observer| which must outlive the
   // config.
   void set_rejection_observer(RejectionObserver* rejection_observer) {
@@ -444,6 +475,10 @@
   bool pad_shlo() const { return pad_shlo_; }
   void set_pad_shlo(bool new_value) { pad_shlo_ = new_value; }
 
+  const CryptoSecretBoxer& source_address_token_boxer() const {
+    return source_address_token_boxer_;
+  }
+
  private:
   friend class test::QuicCryptoServerConfigPeer;
   friend struct QuicSignedServerConfig;
@@ -745,36 +780,6 @@
       const QuicServerConfigProtobuf& protobuf,
       bool is_fallback) const;
 
-  // NewSourceAddressToken returns a fresh source address token for the given
-  // IP address. |cached_network_params| is optional, and can be nullptr.
-  std::string NewSourceAddressToken(
-      const CryptoSecretBoxer& crypto_secret_boxer,
-      const SourceAddressTokens& previous_tokens,
-      const QuicIpAddress& ip,
-      QuicRandom* rand,
-      QuicWallTime now,
-      const CachedNetworkParameters* cached_network_params) const;
-
-  // ParseSourceAddressToken parses the source address tokens contained in
-  // the encrypted |token|, and populates |tokens| with the parsed tokens.
-  // Returns HANDSHAKE_OK if |token| could be parsed, or the reason for the
-  // failure.
-  HandshakeFailureReason ParseSourceAddressToken(
-      const CryptoSecretBoxer& crypto_secret_boxer,
-      absl::string_view token,
-      SourceAddressTokens* tokens) const;
-
-  // ValidateSourceAddressTokens returns HANDSHAKE_OK if the source address
-  // tokens in |tokens| contain a valid and timely token for the IP address
-  // |ip| given that the current time is |now|. Otherwise it returns the
-  // reason for failure. |cached_network_params| is populated if the valid
-  // token contains a CachedNetworkParameters proto.
-  HandshakeFailureReason ValidateSourceAddressTokens(
-      const SourceAddressTokens& tokens,
-      const QuicIpAddress& ip,
-      QuicWallTime now,
-      CachedNetworkParameters* cached_network_params) const;
-
   // ValidateSingleSourceAddressToken returns HANDSHAKE_OK if the source
   // address token in |token| is a timely token for the IP address |ip|
   // given that the current time is |now|. Otherwise it returns the reason
diff --git a/quic/core/frames/quic_frame.cc b/quic/core/frames/quic_frame.cc
index 7fe322d..b75ac8d 100644
--- a/quic/core/frames/quic_frame.cc
+++ b/quic/core/frames/quic_frame.cc
@@ -181,6 +181,7 @@
     case STOP_SENDING_FRAME:
     case HANDSHAKE_DONE_FRAME:
     case ACK_FREQUENCY_FRAME:
+    case NEW_TOKEN_FRAME:
       return true;
     default:
       return false;
@@ -209,6 +210,8 @@
       return frame.handshake_done_frame.control_frame_id;
     case ACK_FREQUENCY_FRAME:
       return frame.ack_frequency_frame->control_frame_id;
+    case NEW_TOKEN_FRAME:
+      return frame.new_token_frame->control_frame_id;
     default:
       return kInvalidControlFrameId;
   }
@@ -246,6 +249,9 @@
     case ACK_FREQUENCY_FRAME:
       frame->ack_frequency_frame->control_frame_id = control_frame_id;
       return;
+    case NEW_TOKEN_FRAME:
+      frame->new_token_frame->control_frame_id = control_frame_id;
+      return;
     default:
       QUIC_BUG
           << "Try to set control frame id of a frame without control frame id";
@@ -286,6 +292,9 @@
     case ACK_FREQUENCY_FRAME:
       copy = QuicFrame(new QuicAckFrequencyFrame(*frame.ack_frequency_frame));
       break;
+    case NEW_TOKEN_FRAME:
+      copy = QuicFrame(new QuicNewTokenFrame(*frame.new_token_frame));
+      break;
     default:
       QUIC_BUG << "Try to copy a non-retransmittable control frame: " << frame;
       copy = QuicFrame(QuicPingFrame(kInvalidControlFrameId));
diff --git a/quic/core/frames/quic_new_token_frame.cc b/quic/core/frames/quic_new_token_frame.cc
index 6806cda..5ab0d3d 100644
--- a/quic/core/frames/quic_new_token_frame.cc
+++ b/quic/core/frames/quic_new_token_frame.cc
@@ -11,8 +11,9 @@
 namespace quic {
 
 QuicNewTokenFrame::QuicNewTokenFrame(QuicControlFrameId control_frame_id,
-                                     std::string token)
-    : control_frame_id(control_frame_id), token(token) {}
+                                     absl::string_view token)
+    : control_frame_id(control_frame_id),
+      token(std::string(token.data(), token.length())) {}
 
 std::ostream& operator<<(std::ostream& os, const QuicNewTokenFrame& s) {
   os << "{ control_frame_id: " << s.control_frame_id
diff --git a/quic/core/frames/quic_new_token_frame.h b/quic/core/frames/quic_new_token_frame.h
index abb0eec..a800cb6 100644
--- a/quic/core/frames/quic_new_token_frame.h
+++ b/quic/core/frames/quic_new_token_frame.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <ostream>
 
+#include "absl/strings/string_view.h"
 #include "net/third_party/quiche/src/quic/core/quic_buffer_allocator.h"
 #include "net/third_party/quiche/src/quic/core/quic_constants.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
@@ -17,7 +18,8 @@
 
 struct QUIC_EXPORT_PRIVATE QuicNewTokenFrame {
   QuicNewTokenFrame() = default;
-  QuicNewTokenFrame(QuicControlFrameId control_frame_id, std::string token);
+  QuicNewTokenFrame(QuicControlFrameId control_frame_id,
+                    absl::string_view token);
 
   friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
       std::ostream& os,
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index dda0c3e..2cd4fd0 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -1440,6 +1440,56 @@
             client_->SendCustomSynchronousRequest(headers, body));
 }
 
+TEST_P(EndToEndTest, AddressToken) {
+  ASSERT_TRUE(Initialize());
+  if (!version_.HasIetfQuicFrames()) {
+    return;
+  }
+
+  SendSynchronousFooRequestAndCheckResponse();
+  QuicSpdyClientSession* client_session = GetClientSession();
+  ASSERT_TRUE(client_session);
+  EXPECT_FALSE(client_session->EarlyDataAccepted());
+  EXPECT_FALSE(client_session->ReceivedInchoateReject());
+  EXPECT_FALSE(client_->client()->EarlyDataAccepted());
+  EXPECT_FALSE(client_->client()->ReceivedInchoateReject());
+
+  client_->Disconnect();
+
+  // The 0-RTT handshake should succeed.
+  client_->Connect();
+  EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable());
+  ASSERT_TRUE(client_->client()->connected());
+  SendSynchronousFooRequestAndCheckResponse();
+
+  client_session = GetClientSession();
+  ASSERT_TRUE(client_session);
+  EXPECT_TRUE(client_session->EarlyDataAccepted());
+  EXPECT_TRUE(client_->client()->EarlyDataAccepted());
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  if (server_connection != nullptr) {
+    if (GetQuicReloadableFlag(quic_enable_token_based_address_validation)) {
+      // Verify address is validated via validating token received in INITIAL
+      // packet.
+      EXPECT_FALSE(server_connection->GetStats()
+                       .address_validated_via_decrypting_packet);
+      EXPECT_TRUE(server_connection->GetStats().address_validated_via_token);
+    } else {
+      EXPECT_TRUE(server_connection->GetStats()
+                      .address_validated_via_decrypting_packet);
+      EXPECT_FALSE(server_connection->GetStats().address_validated_via_token);
+    }
+  } else {
+    ADD_FAILURE() << "Missing server connection";
+  }
+
+  server_thread_->Resume();
+
+  client_->Disconnect();
+}
+
 TEST_P(EndToEndTest, LargePostZeroRTTFailure) {
   // Send a request and then disconnect. This prepares the client to attempt
   // a 0-RTT handshake for the next request.
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
index 4abde42..23bbf40 100644
--- a/quic/core/http/quic_server_session_base_test.cc
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -492,7 +492,7 @@
 class MockTlsServerHandshaker : public TlsServerHandshaker {
  public:
   explicit MockTlsServerHandshaker(QuicServerSessionBase* session,
-                                   const QuicCryptoServerConfig& crypto_config)
+                                   const QuicCryptoServerConfig* crypto_config)
       : TlsServerHandshaker(session, crypto_config) {}
   MockTlsServerHandshaker(const MockTlsServerHandshaker&) = delete;
   MockTlsServerHandshaker& operator=(const MockTlsServerHandshaker&) = delete;
@@ -542,7 +542,7 @@
                                                quic_crypto_stream);
   } else {
     tls_server_stream =
-        new MockTlsServerHandshaker(session_.get(), crypto_config_);
+        new MockTlsServerHandshaker(session_.get(), &crypto_config_);
     QuicServerSessionBasePeer::SetCryptoStream(session_.get(),
                                                tls_server_stream);
   }
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 91f0a58..9f1a80b 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -170,6 +170,11 @@
   void OnOneRttPacketAcknowledged() override {}
   void OnHandshakePacketSent() override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
+  std::string GetAddressToken() const override { return ""; }
+  bool ValidateAddressToken(absl::string_view /*token*/) const override {
+    return true;
+  }
 
   MOCK_METHOD(void, OnCanWrite, (), (override));
 
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc
index f2771fd..bc44969 100644
--- a/quic/core/http/quic_spdy_stream_test.cc
+++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -154,6 +154,11 @@
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
+  std::string GetAddressToken() const override { return ""; }
+  bool ValidateAddressToken(absl::string_view /*token*/) const override {
+    return true;
+  }
 
   MOCK_METHOD(void, OnCanWrite, (), (override));
 
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index ec037d0..88810eb 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1151,6 +1151,7 @@
     // Address is validated by successfully processing a HANDSHAKE or 1-RTT
     // packet.
     address_validated_ = true;
+    stats_.address_validated_via_decrypting_packet = true;
   }
   idle_network_detector_.OnPacketReceived(time_of_last_received_packet_);
 
@@ -1230,6 +1231,17 @@
   uber_received_packet_manager_.RecordPacketReceived(
       last_decrypted_packet_level_, last_header_,
       idle_network_detector_.time_of_last_received_packet());
+  if (GetQuicReloadableFlag(quic_enable_token_based_address_validation)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_token_based_address_validation, 2,
+                                 2);
+    if (EnforceAntiAmplificationLimit() && !header.retry_token.empty() &&
+        visitor_->ValidateToken(header.retry_token)) {
+      QUIC_DLOG(INFO) << ENDPOINT << "Address validated via token.";
+      QUIC_CODE_COUNT(quic_address_validated_via_token);
+      address_validated_ = true;
+      stats_.address_validated_via_token = true;
+    }
+  }
   DCHECK(connected_);
   return true;
 }
@@ -1725,6 +1737,17 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnNewTokenFrame(frame);
   }
+  if (GetQuicReloadableFlag(quic_enable_token_based_address_validation)) {
+    if (perspective_ == Perspective::IS_SERVER) {
+      CloseConnection(QUIC_INVALID_NEW_TOKEN,
+                      "Server received new token frame.",
+                      ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+      return false;
+    }
+    // NEW_TOKEN frame should insitgate ACKs.
+    MaybeUpdateAckTimeout();
+    visitor_->OnNewTokenReceived(frame.token);
+  }
   return true;
 }
 
@@ -5453,5 +5476,14 @@
       GetUnackedMapInitialCapacity());
 }
 
+void QuicConnection::SetSourceAddressTokenToSend(absl::string_view token) {
+  DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+  if (!packet_creator_.HasRetryToken()) {
+    // Ignore received tokens (via NEW_TOKEN frame) from previous connections
+    // when a RETRY token has been received.
+    packet_creator_.SetRetryToken(std::string(token.data(), token.length()));
+  }
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index c392e7c..13a3518 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -100,6 +100,9 @@
   // Called when a HANDSHAKE_DONE frame has been received.
   virtual void OnHandshakeDoneReceived() = 0;
 
+  // Called when a NEW_TOKEN frame has been received.
+  virtual void OnNewTokenReceived(absl::string_view token) = 0;
+
   // Called when a MAX_STREAMS frame has been received from the peer.
   virtual bool OnMaxStreamsFrame(const QuicMaxStreamsFrame& frame) = 0;
 
@@ -214,6 +217,11 @@
   // frame is serialized, but only on the server and only if forward secure
   // encryption has already been established.
   virtual void BeforeConnectionCloseSent() = 0;
+
+  // Called by the server to validate |token| in received INITIAL packets.
+  // Consider the client address gets validated (and therefore remove
+  // amplification factor) once the |token| gets successfully validated.
+  virtual bool ValidateToken(absl::string_view token) const = 0;
 };
 
 // Interface which gets callbacks from the QuicConnection at interesting
@@ -1156,6 +1164,8 @@
                    QuicPacketWriter* writer,
                    bool owns_writer);
 
+  void SetSourceAddressTokenToSend(absl::string_view token);
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
diff --git a/quic/core/quic_connection_stats.cc b/quic/core/quic_connection_stats.cc
index a693677..c28aa71 100644
--- a/quic/core/quic_connection_stats.cc
+++ b/quic/core/quic_connection_stats.cc
@@ -60,6 +60,9 @@
      << s.num_failed_authentication_packets_received;
   os << " num_tls_server_zero_rtt_packets_received_after_discarding_decrypter: "
      << s.num_tls_server_zero_rtt_packets_received_after_discarding_decrypter;
+  os << " address_validated_via_decrypting_packet: "
+     << s.address_validated_via_decrypting_packet;
+  os << " address_validated_via_token: " << s.address_validated_via_token;
   os << " }";
 
   return os;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 409e8ec..014e25c 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -183,6 +183,13 @@
   // was discarded, only on server connections.
   QuicPacketCount
       num_tls_server_zero_rtt_packets_received_after_discarding_decrypter = 0;
+
+  // True if address is validated via decrypting HANDSHAKE or 1-RTT packet.
+  bool address_validated_via_decrypting_packet = false;
+
+  // True if address is validated via validating token received in INITIAL
+  // packet.
+  bool address_validated_via_token = false;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index d216d9d..bf3fe0a 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -12895,6 +12895,72 @@
   EXPECT_FALSE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet());
 }
 
+TEST_P(QuicConnectionTest, NewTokenFrameInstigateAcks) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  SetQuicReloadableFlag(quic_enable_token_based_address_validation, true);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  QuicNewTokenFrame* new_token = new QuicNewTokenFrame();
+  EXPECT_CALL(visitor_, OnNewTokenReceived(_));
+  ProcessFramePacket(QuicFrame(new_token));
+
+  // Ensure that this has caused the ACK alarm to be set.
+  EXPECT_TRUE(connection_.HasPendingAcks());
+}
+
+TEST_P(QuicConnectionTest, ServerClosesConnectionOnNewTokenFrame) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  SetQuicReloadableFlag(quic_enable_token_based_address_validation, true);
+  set_perspective(Perspective::IS_SERVER);
+  QuicNewTokenFrame* new_token = new QuicNewTokenFrame();
+  EXPECT_CALL(visitor_, OnNewTokenReceived(_)).Times(0);
+  EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
+  EXPECT_CALL(visitor_, BeforeConnectionCloseSent());
+  ProcessFramePacket(QuicFrame(new_token));
+  EXPECT_FALSE(connection_.connected());
+}
+
+TEST_P(QuicConnectionTest, OverrideRetryTokenWithRetryPacket) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  std::string address_token = "TestAddressToken";
+  connection_.SetSourceAddressTokenToSend(address_token);
+  EXPECT_EQ(QuicPacketCreatorPeer::GetRetryToken(
+                QuicConnectionPeer::GetPacketCreator(&connection_)),
+            address_token);
+  // Passes valid retry and verify token gets overridden.
+  TestClientRetryHandling(/*invalid_retry_tag=*/false,
+                          /*missing_original_id_in_config=*/false,
+                          /*wrong_original_id_in_config=*/false,
+                          /*missing_retry_id_in_config=*/false,
+                          /*wrong_retry_id_in_config=*/false);
+}
+
+TEST_P(QuicConnectionTest, DonotOverrideRetryTokenWithAddressToken) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  // Passes valid retry and verify token gets overridden.
+  TestClientRetryHandling(/*invalid_retry_tag=*/false,
+                          /*missing_original_id_in_config=*/false,
+                          /*wrong_original_id_in_config=*/false,
+                          /*missing_retry_id_in_config=*/false,
+                          /*wrong_retry_id_in_config=*/false);
+  std::string retry_token = QuicPacketCreatorPeer::GetRetryToken(
+      QuicConnectionPeer::GetPacketCreator(&connection_));
+
+  std::string address_token = "TestAddressToken";
+  connection_.SetSourceAddressTokenToSend(address_token);
+  EXPECT_EQ(QuicPacketCreatorPeer::GetRetryToken(
+                QuicConnectionPeer::GetPacketCreator(&connection_)),
+            retry_token);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_control_frame_manager.cc b/quic/core/quic_control_frame_manager.cc
index c4bee6e..6a64cb6 100644
--- a/quic/core/quic_control_frame_manager.cc
+++ b/quic/core/quic_control_frame_manager.cc
@@ -131,6 +131,12 @@
                                           ack_frequency_frame.max_ack_delay)));
 }
 
+void QuicControlFrameManager::WriteOrBufferNewToken(absl::string_view token) {
+  QUIC_DVLOG(1) << "Writing NEW_TOKEN frame";
+  WriteOrBufferQuicFrame(
+      QuicFrame(new QuicNewTokenFrame(++last_control_frame_id_, token)));
+}
+
 void QuicControlFrameManager::WritePing() {
   QUIC_DVLOG(1) << "Writing PING_FRAME";
   if (HasBufferedFrames()) {
diff --git a/quic/core/quic_control_frame_manager.h b/quic/core/quic_control_frame_manager.h
index 493e7f0..f5bc0b6 100644
--- a/quic/core/quic_control_frame_manager.h
+++ b/quic/core/quic_control_frame_manager.h
@@ -89,6 +89,10 @@
   void WriteOrBufferAckFrequency(
       const QuicAckFrequencyFrame& ack_frequency_frame);
 
+  // Tries to send a NEW_TOKEN frame. Buffers the frame if it cannot be sent
+  // immediately.
+  void WriteOrBufferNewToken(absl::string_view token);
+
   // Sends a PING_FRAME. Do not send PING if there is buffered frames.
   void WritePing();
 
diff --git a/quic/core/quic_crypto_client_handshaker.cc b/quic/core/quic_crypto_client_handshaker.cc
index 32ba936..dd721ba 100644
--- a/quic/core/quic_crypto_client_handshaker.cc
+++ b/quic/core/quic_crypto_client_handshaker.cc
@@ -170,6 +170,11 @@
   DCHECK(false);
 }
 
+void QuicCryptoClientHandshaker::OnNewTokenReceived(
+    absl::string_view /*token*/) {
+  DCHECK(false);
+}
+
 size_t QuicCryptoClientHandshaker::BufferSizeLimitForLevel(
     EncryptionLevel level) const {
   return QuicCryptoHandshaker::BufferSizeLimitForLevel(level);
diff --git a/quic/core/quic_crypto_client_handshaker.h b/quic/core/quic_crypto_client_handshaker.h
index 49fa405..f90b85f 100644
--- a/quic/core/quic_crypto_client_handshaker.h
+++ b/quic/core/quic_crypto_client_handshaker.h
@@ -60,6 +60,7 @@
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override;
   void OnHandshakeDoneReceived() override;
+  void OnNewTokenReceived(absl::string_view token) override;
   void SetServerApplicationStateForResumption(
       std::unique_ptr<ApplicationState> /*application_state*/) override {
     QUICHE_NOTREACHED();
diff --git a/quic/core/quic_crypto_client_stream.cc b/quic/core/quic_crypto_client_stream.cc
index 94a8948..00f0157 100644
--- a/quic/core/quic_crypto_client_stream.cc
+++ b/quic/core/quic_crypto_client_stream.cc
@@ -144,6 +144,21 @@
   handshaker_->OnHandshakeDoneReceived();
 }
 
+void QuicCryptoClientStream::OnNewTokenReceived(absl::string_view token) {
+  handshaker_->OnNewTokenReceived(token);
+}
+
+std::string QuicCryptoClientStream::GetAddressToken() const {
+  DCHECK(false);
+  return "";
+}
+
+bool QuicCryptoClientStream::ValidateAddressToken(
+    absl::string_view /*token*/) const {
+  DCHECK(false);
+  return false;
+}
+
 void QuicCryptoClientStream::SetServerApplicationStateForResumption(
     std::unique_ptr<ApplicationState> application_state) {
   handshaker_->SetServerApplicationStateForResumption(
diff --git a/quic/core/quic_crypto_client_stream.h b/quic/core/quic_crypto_client_stream.h
index 123bdfe..bfdc07e 100644
--- a/quic/core/quic_crypto_client_stream.h
+++ b/quic/core/quic_crypto_client_stream.h
@@ -179,6 +179,9 @@
     // Called when handshake done has been received.
     virtual void OnHandshakeDoneReceived() = 0;
 
+    // Called when new token has been received.
+    virtual void OnNewTokenReceived(absl::string_view token) = 0;
+
     // Called when application state is received.
     virtual void SetServerApplicationStateForResumption(
         std::unique_ptr<ApplicationState> application_state) = 0;
@@ -236,6 +239,9 @@
   void OnConnectionClosed(QuicErrorCode error,
                           ConnectionCloseSource source) override;
   void OnHandshakeDoneReceived() override;
+  void OnNewTokenReceived(absl::string_view token) override;
+  std::string GetAddressToken() const override;
+  bool ValidateAddressToken(absl::string_view token) const override;
   HandshakeState GetHandshakeState() const override;
   void SetServerApplicationStateForResumption(
       std::unique_ptr<ApplicationState> application_state) override;
diff --git a/quic/core/quic_crypto_server_stream.cc b/quic/core/quic_crypto_server_stream.cc
index 2bad8ec..21bfbd5 100644
--- a/quic/core/quic_crypto_server_stream.cc
+++ b/quic/core/quic_crypto_server_stream.cc
@@ -340,6 +340,21 @@
   DCHECK(false);
 }
 
+void QuicCryptoServerStream::OnNewTokenReceived(absl::string_view /*token*/) {
+  DCHECK(false);
+}
+
+std::string QuicCryptoServerStream::GetAddressToken() const {
+  DCHECK(false);
+  return "";
+}
+
+bool QuicCryptoServerStream::ValidateAddressToken(
+    absl::string_view /*token*/) const {
+  DCHECK(false);
+  return false;
+}
+
 bool QuicCryptoServerStream::ShouldSendExpectCTHeader() const {
   return signed_config_->proof.send_expect_ct_header;
 }
diff --git a/quic/core/quic_crypto_server_stream.h b/quic/core/quic_crypto_server_stream.h
index c99a136..8d8f637 100644
--- a/quic/core/quic_crypto_server_stream.h
+++ b/quic/core/quic_crypto_server_stream.h
@@ -47,6 +47,9 @@
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override {}
   void OnHandshakeDoneReceived() override;
+  void OnNewTokenReceived(absl::string_view token) override;
+  std::string GetAddressToken() const override;
+  bool ValidateAddressToken(absl::string_view token) const override;
   bool ShouldSendExpectCTHeader() const override;
   const ProofSource::Details* ProofSourceDetails() const override;
 
diff --git a/quic/core/quic_crypto_server_stream_base.cc b/quic/core/quic_crypto_server_stream_base.cc
index 8ea63ba..c926e02 100644
--- a/quic/core/quic_crypto_server_stream_base.cc
+++ b/quic/core/quic_crypto_server_stream_base.cc
@@ -38,7 +38,7 @@
           crypto_config, compressed_certs_cache, session, helper));
     case PROTOCOL_TLS1_3:
       return std::unique_ptr<TlsServerHandshaker>(
-          new TlsServerHandshaker(session, *crypto_config));
+          new TlsServerHandshaker(session, crypto_config));
     case PROTOCOL_UNSUPPORTED:
       break;
   }
diff --git a/quic/core/quic_crypto_stream.h b/quic/core/quic_crypto_stream.h
index ea15ded..26dc878 100644
--- a/quic/core/quic_crypto_stream.h
+++ b/quic/core/quic_crypto_stream.h
@@ -104,6 +104,15 @@
   // Called when a handshake done frame has been received.
   virtual void OnHandshakeDoneReceived() = 0;
 
+  // Called when a new token frame has been received.
+  virtual void OnNewTokenReceived(absl::string_view token) = 0;
+
+  // Called to get an address token.
+  virtual std::string GetAddressToken() const = 0;
+
+  // Called to validate |token|.
+  virtual bool ValidateAddressToken(absl::string_view token) const = 0;
+
   // Returns current handshake state.
   virtual HandshakeState GetHandshakeState() const = 0;
 
diff --git a/quic/core/quic_crypto_stream_test.cc b/quic/core/quic_crypto_stream_test.cc
index 5593e53..2cc5087 100644
--- a/quic/core/quic_crypto_stream_test.cc
+++ b/quic/core/quic_crypto_stream_test.cc
@@ -63,6 +63,11 @@
   void OnOneRttPacketAcknowledged() override {}
   void OnHandshakePacketSent() override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
+  std::string GetAddressToken() const override { return ""; }
+  bool ValidateAddressToken(absl::string_view /*token*/) const override {
+    return true;
+  }
   HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; }
   void SetServerApplicationStateForResumption(
       std::unique_ptr<ApplicationState> /*application_state*/) override {}
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 58f3bf8..f3a9a4b 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -34,6 +34,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_server_on_wire_ping, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_token_based_address_validation, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_extract_x509_subject_using_certificate_view, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true)
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 798693f..c621b94 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -11165,8 +11165,9 @@
   uint8_t expected_token_value[] = {0x00, 0x01, 0x02, 0x03,
                                     0x04, 0x05, 0x06, 0x07};
 
-  QuicNewTokenFrame frame(0, std::string((const char*)(expected_token_value),
-                                         sizeof(expected_token_value)));
+  QuicNewTokenFrame frame(0,
+                          absl::string_view((const char*)(expected_token_value),
+                                            sizeof(expected_token_value)));
 
   QuicFrames frames = {QuicFrame(&frame)};
 
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index effa114..08ac9c7 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -2058,5 +2058,9 @@
   return true;
 }
 
+bool QuicPacketCreator::HasRetryToken() const {
+  return !retry_token_.empty();
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_packet_creator.h b/quic/core/quic_packet_creator.h
index f1ace67..2605740 100644
--- a/quic/core/quic_packet_creator.h
+++ b/quic/core/quic_packet_creator.h
@@ -468,6 +468,9 @@
   // different from the current one, flush all the queue frames first.
   void SetDefaultPeerAddress(QuicSocketAddress address);
 
+  // Return true if retry_token_ is not empty.
+  bool HasRetryToken() const;
+
   bool let_connection_handle_pings() const {
     return let_connection_handle_pings_;
   }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 1328f8a..b0fdb3c 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -378,6 +378,11 @@
   GetMutableCryptoStream()->OnHandshakeDoneReceived();
 }
 
+void QuicSession::OnNewTokenReceived(absl::string_view token) {
+  DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+  GetMutableCryptoStream()->OnNewTokenReceived(token);
+}
+
 // static
 void QuicSession::RecordConnectionCloseAtServer(QuicErrorCode error,
                                                 ConnectionCloseSource source) {
@@ -1662,6 +1667,22 @@
     // Server sends HANDSHAKE_DONE to signal confirmation of the handshake
     // to the client.
     control_frame_manager_.WriteOrBufferHandshakeDone();
+    if (GetQuicReloadableFlag(quic_enable_token_based_address_validation) &&
+        connection()->version().HasIetfQuicFrames()) {
+      QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_token_based_address_validation,
+                                   1, 2);
+      std::string address_token = GetCryptoStream()->GetAddressToken();
+      if (!address_token.empty()) {
+        const size_t buf_len = address_token.length() + 1;
+        auto buffer = std::make_unique<char[]>(buf_len);
+        QuicDataWriter writer(buf_len, buffer.get());
+        // Add prefix 0 for token sent in NEW_TOKEN frame.
+        writer.WriteUInt8(0);
+        writer.WriteBytes(address_token.data(), address_token.length());
+        control_frame_manager_.WriteOrBufferNewToken(
+            absl::string_view(buffer.get(), buf_len));
+      }
+    }
   }
 }
 
@@ -2572,5 +2593,15 @@
   connection_->MigratePath(self_address, peer_address, writer, owns_writer);
 }
 
+bool QuicSession::ValidateToken(absl::string_view token) const {
+  DCHECK_EQ(perspective_, Perspective::IS_SERVER);
+  if (token.empty() || token[0] != 0) {
+    // Validate the prefix for token received in NEW_TOKEN frame.
+    return false;
+  }
+  return GetCryptoStream()->ValidateAddressToken(
+      absl::string_view(token.data() + 1, token.length() - 1));
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 0eecfc8..8e572d3 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -112,6 +112,7 @@
   void OnGoAway(const QuicGoAwayFrame& frame) override;
   void OnMessageReceived(absl::string_view message) override;
   void OnHandshakeDoneReceived() override;
+  void OnNewTokenReceived(absl::string_view token) override;
   void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) override;
   void OnBlockedFrame(const QuicBlockedFrame& frame) override;
   void OnConnectionClosed(const QuicConnectionCloseFrame& frame,
@@ -152,6 +153,7 @@
       override;
   std::unique_ptr<QuicEncrypter> CreateCurrentOneRttEncrypter() override;
   void BeforeConnectionCloseSent() override {}
+  bool ValidateToken(absl::string_view token) const override;
 
   // QuicStreamFrameDataProducer
   WriteStreamDataResult WriteStreamData(QuicStreamId id,
@@ -586,6 +588,10 @@
     connection()->OnUserAgentIdKnown();
   }
 
+  void SetSourceAddressTokenToSend(absl::string_view token) {
+    connection()->SetSourceAddressTokenToSend(token);
+  }
+
   const QuicClock* GetClock() const {
     return connection()->helper()->GetClock();
   }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 9f0e4a1..bef40d9 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -141,6 +141,11 @@
   void OnOneRttPacketAcknowledged() override {}
   void OnHandshakePacketSent() override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
+  std::string GetAddressToken() const override { return ""; }
+  bool ValidateAddressToken(absl::string_view /*token*/) const override {
+    return true;
+  }
   HandshakeState GetHandshakeState() const override {
     return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START;
   }
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 6ea06b8..7e7c5fe 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -39,7 +39,16 @@
       pre_shared_key_(crypto_config->pre_shared_key()),
       crypto_negotiated_params_(new QuicCryptoNegotiatedParameters),
       has_application_state_(has_application_state),
-      tls_connection_(crypto_config->ssl_ctx(), this) {}
+      crypto_config_(crypto_config),
+      tls_connection_(crypto_config->ssl_ctx(), this) {
+  if (GetQuicReloadableFlag(quic_enable_token_based_address_validation)) {
+    std::string token =
+        crypto_config->LookupOrCreate(server_id)->source_address_token();
+    if (!token.empty()) {
+      session->SetSourceAddressTokenToSend(token);
+    }
+  }
+}
 
 TlsClientHandshaker::~TlsClientHandshaker() {}
 
@@ -346,6 +355,15 @@
   OnHandshakeConfirmed();
 }
 
+void TlsClientHandshaker::OnNewTokenReceived(absl::string_view token) {
+  if (token.empty()) {
+    return;
+  }
+  QuicCryptoClientConfig::CachedState* cached =
+      crypto_config_->LookupOrCreate(server_id_);
+  cached->set_source_address_token(token);
+}
+
 void TlsClientHandshaker::SetWriteSecret(
     EncryptionLevel level,
     const SSL_CIPHER* cipher,
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h
index aadfa0b..47ed09c 100644
--- a/quic/core/tls_client_handshaker.h
+++ b/quic/core/tls_client_handshaker.h
@@ -28,6 +28,7 @@
       public QuicCryptoClientStream::HandshakerInterface,
       public TlsClientConnection::Delegate {
  public:
+  // |crypto_config| must outlive TlsClientHandshaker.
   TlsClientHandshaker(const QuicServerId& server_id,
                       QuicCryptoStream* stream,
                       QuicSession* session,
@@ -67,6 +68,7 @@
   void OnConnectionClosed(QuicErrorCode error,
                           ConnectionCloseSource source) override;
   void OnHandshakeDoneReceived() override;
+  void OnNewTokenReceived(absl::string_view token) override;
   void SetWriteSecret(EncryptionLevel level,
                       const SSL_CIPHER* cipher,
                       const std::vector<uint8_t>& write_secret) override;
@@ -152,6 +154,8 @@
   // will always be non-null if a 0-RTT resumption is attempted.
   std::unique_ptr<QuicResumptionState> cached_state_;
 
+  QuicCryptoClientConfig* crypto_config_;  // Not owned.
+
   TlsClientConnection tls_connection_;
 
   // If |has_application_state_|, stores the tls session tickets before
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 83d160f..0ae78a3 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -86,13 +86,14 @@
 
 TlsServerHandshaker::TlsServerHandshaker(
     QuicSession* session,
-    const QuicCryptoServerConfig& crypto_config)
+    const QuicCryptoServerConfig* crypto_config)
     : TlsHandshaker(this, session),
       QuicCryptoServerStreamBase(session),
-      proof_source_(crypto_config.proof_source()),
-      pre_shared_key_(crypto_config.pre_shared_key()),
+      proof_source_(crypto_config->proof_source()),
+      pre_shared_key_(crypto_config->pre_shared_key()),
       crypto_negotiated_params_(new QuicCryptoNegotiatedParameters),
-      tls_connection_(crypto_config.ssl_ctx(), this) {
+      tls_connection_(crypto_config->ssl_ctx(), this),
+      crypto_config_(crypto_config) {
   DCHECK_EQ(PROTOCOL_TLS1_3,
             session->connection()->version().handshake_protocol);
 
@@ -167,6 +168,41 @@
   DCHECK(false);
 }
 
+void TlsServerHandshaker::OnNewTokenReceived(absl::string_view /*token*/) {
+  DCHECK(false);
+}
+
+std::string TlsServerHandshaker::GetAddressToken() const {
+  SourceAddressTokens empty_previous_tokens;
+  const QuicConnection* connection = session()->connection();
+  return crypto_config_->NewSourceAddressToken(
+      crypto_config_->source_address_token_boxer(), empty_previous_tokens,
+      connection->effective_peer_address().host(),
+      connection->random_generator(), connection->clock()->WallNow(),
+      /*cached_network_params=*/nullptr);
+}
+
+bool TlsServerHandshaker::ValidateAddressToken(absl::string_view token) const {
+  SourceAddressTokens tokens;
+  HandshakeFailureReason reason = crypto_config_->ParseSourceAddressToken(
+      crypto_config_->source_address_token_boxer(), token, &tokens);
+  if (reason != HANDSHAKE_OK) {
+    QUIC_DLOG(WARNING) << "Failed to parse source address token: "
+                       << CryptoUtils::HandshakeFailureReasonToString(reason);
+    return false;
+  }
+  reason = crypto_config_->ValidateSourceAddressTokens(
+      tokens, session()->connection()->effective_peer_address().host(),
+      session()->connection()->clock()->WallNow(),
+      /*cached_network_params=*/nullptr);
+  if (reason != HANDSHAKE_OK) {
+    QUIC_DLOG(WARNING) << "Failed to validate source address token: "
+                       << CryptoUtils::HandshakeFailureReasonToString(reason);
+    return false;
+  }
+  return true;
+}
+
 bool TlsServerHandshaker::ShouldSendExpectCTHeader() const {
   return false;
 }
diff --git a/quic/core/tls_server_handshaker.h b/quic/core/tls_server_handshaker.h
index 7334cd9..23bbc1b 100644
--- a/quic/core/tls_server_handshaker.h
+++ b/quic/core/tls_server_handshaker.h
@@ -27,8 +27,9 @@
       public TlsServerConnection::Delegate,
       public QuicCryptoServerStreamBase {
  public:
+  // |crypto_config| must outlive TlsServerHandshaker.
   TlsServerHandshaker(QuicSession* session,
-                      const QuicCryptoServerConfig& crypto_config);
+                      const QuicCryptoServerConfig* crypto_config);
   TlsServerHandshaker(const TlsServerHandshaker&) = delete;
   TlsServerHandshaker& operator=(const TlsServerHandshaker&) = delete;
 
@@ -52,6 +53,9 @@
   void OnConnectionClosed(QuicErrorCode error,
                           ConnectionCloseSource source) override;
   void OnHandshakeDoneReceived() override;
+  std::string GetAddressToken() const override;
+  bool ValidateAddressToken(absl::string_view token) const override;
+  void OnNewTokenReceived(absl::string_view token) override;
   bool ShouldSendExpectCTHeader() const override;
   const ProofSource::Details* ProofSourceDetails() const override;
 
@@ -199,6 +203,8 @@
   TlsServerConnection tls_connection_;
   const bool use_early_select_cert_ =
       GetQuicReloadableFlag(quic_tls_use_early_select_cert);
+
+  const QuicCryptoServerConfig* crypto_config_;  // Unowned.
 };
 
 }  // namespace quic
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index fe6e8f3..825b669 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -533,6 +533,7 @@
   MOCK_METHOD(void, OnGoAway, (const QuicGoAwayFrame& frame), (override));
   MOCK_METHOD(void, OnMessageReceived, (absl::string_view message), (override));
   MOCK_METHOD(void, OnHandshakeDoneReceived, (), (override));
+  MOCK_METHOD(void, OnNewTokenReceived, (absl::string_view token), (override));
   MOCK_METHOD(void,
               OnConnectionClosed,
               (const QuicConnectionCloseFrame& frame,
@@ -598,6 +599,7 @@
               (),
               (override));
   MOCK_METHOD(void, BeforeConnectionCloseSent, (), (override));
+  MOCK_METHOD(bool, ValidateToken, (absl::string_view), (const, override));
 };
 
 class MockQuicConnectionHelper : public QuicConnectionHelperInterface {
@@ -937,6 +939,11 @@
   void OnOneRttPacketAcknowledged() override {}
   void OnHandshakePacketSent() override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
+  std::string GetAddressToken() const override { return ""; }
+  bool ValidateAddressToken(absl::string_view /*token*/) const override {
+    return true;
+  }
   void OnConnectionClosed(QuicErrorCode /*error*/,
                           ConnectionCloseSource /*source*/) override {}
   HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; }
diff --git a/quic/test_tools/simulator/quic_endpoint.h b/quic/test_tools/simulator/quic_endpoint.h
index 681cd33..4485025 100644
--- a/quic/test_tools/simulator/quic_endpoint.h
+++ b/quic/test_tools/simulator/quic_endpoint.h
@@ -66,6 +66,7 @@
   void OnGoAway(const QuicGoAwayFrame& /*frame*/) override {}
   void OnMessageReceived(absl::string_view /*message*/) override {}
   void OnHandshakeDoneReceived() override {}
+  void OnNewTokenReceived(absl::string_view /*token*/) override {}
   void OnConnectionClosed(const QuicConnectionCloseFrame& /*frame*/,
                           ConnectionCloseSource /*source*/) override {}
   void OnWriteBlocked() override {}
@@ -103,6 +104,9 @@
     return nullptr;
   }
   void BeforeConnectionCloseSent() override {}
+  bool ValidateToken(absl::string_view /*token*/) const override {
+    return true;
+  }
 
   // End QuicConnectionVisitorInterface implementation.
 
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 66ee8c4..9f2d886 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -107,7 +107,7 @@
 class MockTlsServerHandshaker : public TlsServerHandshaker {
  public:
   explicit MockTlsServerHandshaker(QuicSession* session,
-                                   const QuicCryptoServerConfig& crypto_config)
+                                   const QuicCryptoServerConfig* crypto_config)
       : TlsServerHandshaker(session, crypto_config) {}
   MockTlsServerHandshaker(const MockTlsServerHandshaker&) = delete;
   MockTlsServerHandshaker& operator=(const MockTlsServerHandshaker&) = delete;
@@ -131,7 +131,7 @@
       return new MockQuicCryptoServerStream(
           crypto_config, compressed_certs_cache, session, helper);
     case PROTOCOL_TLS1_3:
-      return new MockTlsServerHandshaker(session, *crypto_config);
+      return new MockTlsServerHandshaker(session, crypto_config);
     case PROTOCOL_UNSUPPORTED:
       break;
   }