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; }