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