Add support for accepting 0-RTT in TlsServerHandshaker This adds support at the crypto handshake layer for supporting 0-RTT TLS handshakes on the server. Part of this support includes receiving a signal from the application layer, via a new method QuicCryptoStream::SetServerApplicationStateForResumption. This method replaces the previously client-only QuicCryptoClientStream::OnApplicationState. Introduce quic 0-rtt tls support, protected by quic_enable_zero_rtt_for_tls PiperOrigin-RevId: 315331343 Change-Id: Ife83cf526be38bd4f5c8a3de0e6cd4c40be6f7ae
diff --git a/quic/core/http/quic_spdy_client_session_base.cc b/quic/core/http/quic_spdy_client_session_base.cc index 04f8594..86592bb 100644 --- a/quic/core/http/quic_spdy_client_session_base.cc +++ b/quic/core/http/quic_spdy_client_session_base.cc
@@ -240,8 +240,8 @@ HttpEncoder::SerializeSettingsFrame(frame, &buffer); auto serialized_data = std::make_unique<ApplicationState>( buffer.get(), buffer.get() + frame_length); - static_cast<QuicCryptoClientStreamBase*>(GetMutableCryptoStream()) - ->OnApplicationState(std::move(serialized_data)); + GetMutableCryptoStream()->SetServerApplicationStateForResumption( + std::move(serialized_data)); } } // namespace quic
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 4376d8a..cb6a7f6 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -857,7 +857,7 @@ ConnectionCloseBehavior::SILENT_CLOSE); } -bool QuicSpdySession::SetApplicationState(ApplicationState* cached_state) { +bool QuicSpdySession::ResumeApplicationState(ApplicationState* cached_state) { DCHECK_EQ(perspective(), Perspective::IS_CLIENT); DCHECK(VersionUsesHttp3(transport_version()));
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index cdd81a0..069cceb 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -378,7 +378,7 @@ void OnStreamCreated(QuicSpdyStream* stream); // Decode SETTINGS from |cached_state| and apply it to the session. - bool SetApplicationState(ApplicationState* cached_state) override; + bool ResumeApplicationState(ApplicationState* cached_state) override; protected: // Override CreateIncomingStream(), CreateOutgoingBidirectionalStream() and
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index 8223617..7b4e0d7 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -140,6 +140,8 @@ HandshakeState GetHandshakeState() const override { return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START; } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} const QuicCryptoNegotiatedParameters& crypto_negotiated_params() const override { return *params_;
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc index 7ab9aa0..e769a9d 100644 --- a/quic/core/http/quic_spdy_stream_test.cc +++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -127,6 +127,8 @@ HandshakeState GetHandshakeState() const override { return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START; } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} const QuicCryptoNegotiatedParameters& crypto_negotiated_params() const override { return *params_;
diff --git a/quic/core/quic_crypto_client_handshaker.h b/quic/core/quic_crypto_client_handshaker.h index f99d36e..90f011d 100644 --- a/quic/core/quic_crypto_client_handshaker.h +++ b/quic/core/quic_crypto_client_handshaker.h
@@ -55,7 +55,7 @@ void OnConnectionClosed(QuicErrorCode /*error*/, ConnectionCloseSource /*source*/) override; void OnHandshakeDoneReceived() override; - void OnApplicationState( + 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 36dc4cd..62a261d 100644 --- a/quic/core/quic_crypto_client_stream.cc +++ b/quic/core/quic_crypto_client_stream.cc
@@ -126,9 +126,10 @@ handshaker_->OnHandshakeDoneReceived(); } -void QuicCryptoClientStream::OnApplicationState( +void QuicCryptoClientStream::SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) { - handshaker_->OnApplicationState(std::move(application_state)); + handshaker_->SetServerApplicationStateForResumption( + std::move(application_state)); } } // namespace quic
diff --git a/quic/core/quic_crypto_client_stream.h b/quic/core/quic_crypto_client_stream.h index 23f83c7..be99fb2 100644 --- a/quic/core/quic_crypto_client_stream.h +++ b/quic/core/quic_crypto_client_stream.h
@@ -63,9 +63,6 @@ // client. Does not count update messages that were received prior // to handshake confirmation. virtual int num_scup_messages_received() const = 0; - - virtual void OnApplicationState( - std::unique_ptr<ApplicationState> application_state) = 0; }; class QUIC_EXPORT_PRIVATE QuicCryptoClientStream @@ -167,7 +164,7 @@ virtual void OnHandshakeDoneReceived() = 0; // Called when application state is received. - virtual void OnApplicationState( + virtual void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) = 0; }; @@ -223,10 +220,9 @@ ConnectionCloseSource source) override; void OnHandshakeDoneReceived() override; HandshakeState GetHandshakeState() const override; - size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; - - void OnApplicationState( + void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) override; + size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; std::string chlo_hash() const;
diff --git a/quic/core/quic_crypto_server_stream.cc b/quic/core/quic_crypto_server_stream.cc index c7dd2d9..b21745c 100644 --- a/quic/core/quic_crypto_server_stream.cc +++ b/quic/core/quic_crypto_server_stream.cc
@@ -375,6 +375,12 @@ return one_rtt_packet_decrypted_ ? HANDSHAKE_COMPLETE : HANDSHAKE_START; } +void QuicCryptoServerStream::SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*state*/) { + // QUIC Crypto doesn't need to remember any application state as part of doing + // 0-RTT resumption, so this function is a no-op. +} + size_t QuicCryptoServerStream::BufferSizeLimitForLevel( EncryptionLevel level) const { return QuicCryptoHandshaker::BufferSizeLimitForLevel(level);
diff --git a/quic/core/quic_crypto_server_stream.h b/quic/core/quic_crypto_server_stream.h index 3aaf0ef..9ed7764 100644 --- a/quic/core/quic_crypto_server_stream.h +++ b/quic/core/quic_crypto_server_stream.h
@@ -56,6 +56,8 @@ const override; CryptoMessageParser* crypto_message_parser() override; HandshakeState GetHandshakeState() const override; + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> state) override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; // From QuicCryptoHandshaker
diff --git a/quic/core/quic_crypto_stream.h b/quic/core/quic_crypto_stream.h index 49d3704..54d1b25 100644 --- a/quic/core/quic_crypto_stream.h +++ b/quic/core/quic_crypto_stream.h
@@ -100,6 +100,21 @@ // Returns current handshake state. virtual HandshakeState GetHandshakeState() const = 0; + // Called to provide the server-side application state that must be checked + // when performing a 0-RTT TLS resumption. + // + // On a client, this may be called at any time; 0-RTT tickets will not be + // cached until this function is called. When a 0-RTT resumption is attempted, + // QuicSession::SetApplicationState will be called with the state provided by + // a call to this function on a previous connection. + // + // On a server, this function must be called before commencing the handshake, + // otherwise 0-RTT tickets will not be issued. On subsequent connections, + // 0-RTT will be rejected if the data passed into this function does not match + // the data passed in on the connection where the 0-RTT ticket was issued. + virtual void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> state) = 0; + // Returns the maximum number of bytes that can be buffered at a particular // encryption level |level|. virtual size_t BufferSizeLimitForLevel(EncryptionLevel level) const;
diff --git a/quic/core/quic_crypto_stream_test.cc b/quic/core/quic_crypto_stream_test.cc index 3f086f9..f763d2d 100644 --- a/quic/core/quic_crypto_stream_test.cc +++ b/quic/core/quic_crypto_stream_test.cc
@@ -61,6 +61,8 @@ void OnHandshakePacketSent() override {} void OnHandshakeDoneReceived() override {} HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} private: QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index c9e3216..61019a4 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -461,15 +461,15 @@ // Called on clients by the crypto handshaker to provide application state // necessary for sending application data in 0-RTT. The state provided here is // the same state that was provided to the crypto handshaker in - // QuicCryptoClientStream::OnApplicationState on a previous connection. - // Application protocols that require state to be carried over from the - // previous connection to support 0-RTT data must implement this method to - // ingest this state. For example, an HTTP/3 QuicSession would implement this - // function to process the remembered server SETTINGS frame and apply those - // SETTINGS to 0-RTT data. This function returns true if the application state - // has been successfully processed, and false if there was an error processing - // the cached state and the connection should be closed. - virtual bool SetApplicationState(ApplicationState* /*cached_state*/) { + // QuicCryptoStream::SetServerApplicationStateForResumption on a previous + // connection. Application protocols that require state to be carried over + // from the previous connection to support 0-RTT data must implement this + // method to ingest this state. For example, an HTTP/3 QuicSession would + // implement this function to process the remembered server SETTINGS and apply + // those SETTINGS to 0-RTT data. This function returns true if the application + // state has been successfully processed, and false if there was an error + // processing the cached state and the connection should be closed. + virtual bool ResumeApplicationState(ApplicationState* /*cached_state*/) { return true; }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc index 258bfa2..5f0f9b7 100644 --- a/quic/core/quic_session_test.cc +++ b/quic/core/quic_session_test.cc
@@ -134,6 +134,8 @@ HandshakeState GetHandshakeState() const override { return one_rtt_keys_available() ? HANDSHAKE_COMPLETE : HANDSHAKE_START; } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} MOCK_METHOD(void, OnCanWrite, (), (override)); bool HasPendingCryptoRetransmission() const override { return false; }
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc index 864040c..cf754ec 100644 --- a/quic/core/tls_client_handshaker.cc +++ b/quic/core/tls_client_handshaker.cc
@@ -149,7 +149,7 @@ session()->OnConfigNegotiated(); if (has_application_state_) { - if (!session()->SetApplicationState(cached_state->application_state)) { + if (!session()->ResumeApplicationState(cached_state->application_state)) { QUIC_BUG << "Unable to parse cached application state."; CloseConnection(QUIC_HANDSHAKE_FAILED, "Client failed to parse cached application state."); @@ -617,7 +617,7 @@ TlsHandshaker::WriteMessage(level, data); } -void TlsClientHandshaker::OnApplicationState( +void TlsClientHandshaker::SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) { DCHECK_EQ(STATE_HANDSHAKE_COMPLETE, state_); received_application_state_ = std::move(application_state);
diff --git a/quic/core/tls_client_handshaker.h b/quic/core/tls_client_handshaker.h index ad35ba1..573c055 100644 --- a/quic/core/tls_client_handshaker.h +++ b/quic/core/tls_client_handshaker.h
@@ -71,7 +71,7 @@ void WriteMessage(EncryptionLevel level, quiche::QuicheStringPiece data) override; - void OnApplicationState( + void SetServerApplicationStateForResumption( std::unique_ptr<ApplicationState> application_state) override; void AllowEmptyAlpnForTests() { allow_empty_alpn_for_tests_ = true; }
diff --git a/quic/core/tls_handshaker_test.cc b/quic/core/tls_handshaker_test.cc index 5a2bd64..caf9a9c 100644 --- a/quic/core/tls_handshaker_test.cc +++ b/quic/core/tls_handshaker_test.cc
@@ -192,6 +192,8 @@ HandshakeState GetHandshakeState() const override { return handshaker()->GetHandshakeState(); } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} const std::vector<std::pair<std::string, EncryptionLevel>>& pending_writes() { return pending_writes_;
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc index 60aa64f..057e86d 100644 --- a/quic/core/tls_server_handshaker.cc +++ b/quic/core/tls_server_handshaker.cc
@@ -15,6 +15,7 @@ #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" #include "net/third_party/quiche/src/common/platform/api/quiche_arraysize.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" namespace quic { @@ -95,15 +96,6 @@ // Configure the SSL to be a server. SSL_set_accept_state(ssl()); - - if (GetQuicReloadableFlag(quic_enable_zero_rtt_for_tls)) { - // TODO(b/152551499): Properly set early data context. This change is to - // temporarily unblock QuicSpdyClientSessionTest.IetfZeroRttSetup which - // assumes that the server will sent an early data capable ticket, and then - // accept early data on resumption. - uint8_t context[] = {0}; - SSL_set_quic_early_data_context(ssl(), context, QUICHE_ARRAYSIZE(context)); - } } TlsServerHandshaker::~TlsServerHandshaker() { @@ -133,8 +125,7 @@ } bool TlsServerHandshaker::IsZeroRtt() const { - // TODO(nharper): Support 0-RTT with TLS 1.3 in QUIC. - return false; + return SSL_early_data_accepted(ssl()); } bool TlsServerHandshaker::IsResumption() const { @@ -207,6 +198,11 @@ return HANDSHAKE_START; } +void TlsServerHandshaker::SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> state) { + application_state_ = std::move(state); +} + size_t TlsServerHandshaker::BufferSizeLimitForLevel( EncryptionLevel level) const { return TlsHandshaker::BufferSizeLimitForLevel(level); @@ -329,6 +325,17 @@ server_params_bytes.size()) != 1) { return false; } + if (application_state_) { + std::vector<uint8_t> early_data_context; + if (!SerializeTransportParametersForTicket( + server_params, *application_state_, &early_data_context)) { + QUIC_BUG << "Failed to serialize Transport Parameters for ticket."; + return false; + } + SSL_set_quic_early_data_context(ssl(), early_data_context.data(), + early_data_context.size()); + application_state_.reset(nullptr); + } return true; }
diff --git a/quic/core/tls_server_handshaker.h b/quic/core/tls_server_handshaker.h index 9d4bc5c..13b734e 100644 --- a/quic/core/tls_server_handshaker.h +++ b/quic/core/tls_server_handshaker.h
@@ -61,6 +61,8 @@ const override; CryptoMessageParser* crypto_message_parser() override; HandshakeState GetHandshakeState() const override; + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> state) override; size_t BufferSizeLimitForLevel(EncryptionLevel level) const override; void SetWriteSecret(EncryptionLevel level, const SSL_CIPHER* cipher, @@ -185,6 +187,8 @@ std::string cert_verify_sig_; std::unique_ptr<ProofSource::Details> proof_source_details_; + std::unique_ptr<ApplicationState> application_state_; + // Pre-shared key used during the handshake. std::string pre_shared_key_;
diff --git a/quic/core/tls_server_handshaker_test.cc b/quic/core/tls_server_handshaker_test.cc index d91df58..70550fc 100644 --- a/quic/core/tls_server_handshaker_test.cc +++ b/quic/core/tls_server_handshaker_test.cc
@@ -47,9 +47,12 @@ TlsServerHandshakerTest() : server_compressed_certs_cache_( QuicCompressedCertsCache::kQuicCompressedCertsCacheSize), - server_id_(kServerHostname, kServerPort, false), - client_crypto_config_(crypto_test_utils::ProofVerifierForTesting(), - std::make_unique<test::SimpleSessionCache>()) { + server_id_(kServerHostname, kServerPort, false) { + SetQuicReloadableFlag(quic_enable_tls_resumption, true); + SetQuicReloadableFlag(quic_enable_zero_rtt_for_tls, true); + client_crypto_config_ = std::make_unique<QuicCryptoClientConfig>( + crypto_test_utils::ProofVerifierForTesting(), + std::make_unique<test::SimpleSessionCache>()); InitializeServerConfig(); InitializeServer(); InitializeFakeClient(); @@ -65,7 +68,6 @@ } void InitializeServerConfig() { - SetQuicReloadableFlag(quic_enable_tls_resumption, true); auto ticket_crypter = std::make_unique<TestTicketCrypter>(); ticket_crypter_ = ticket_crypter.get(); auto proof_source = std::make_unique<FakeProofSource>(); @@ -126,7 +128,7 @@ CreateClientSessionForTest( server_id_, QuicTime::Delta::FromSeconds(100000), supported_versions_, helpers_.back().get(), alarm_factories_.back().get(), - &client_crypto_config_, &client_connection_, &client_session); + client_crypto_config_.get(), &client_connection_, &client_session); const std::string default_alpn = AlpnForVersion(client_connection_->version()); ON_CALL(*client_session, GetAlpnsToOffer()) @@ -212,7 +214,7 @@ // Client state. PacketSavingConnection* client_connection_; - QuicCryptoClientConfig client_crypto_config_; + std::unique_ptr<QuicCryptoClientConfig> client_crypto_config_; std::unique_ptr<TestQuicSpdyClientSession> client_session_; crypto_test_utils::FakeClientOptions client_options_; @@ -472,6 +474,53 @@ EXPECT_EQ(moved_messages_counts_.second, 0u); } +TEST_F(TlsServerHandshakerTest, ZeroRttResumption) { + std::vector<uint8_t> application_state = {0, 1, 2, 3}; + + // Do the first handshake + server_stream()->SetServerApplicationStateForResumption( + std::make_unique<ApplicationState>(application_state)); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_FALSE(client_stream()->IsResumption()); + EXPECT_FALSE(server_stream()->IsZeroRtt()); + + // Now do another handshake + InitializeServer(); + server_stream()->SetServerApplicationStateForResumption( + std::make_unique<ApplicationState>(application_state)); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_TRUE(client_stream()->IsResumption()); + EXPECT_TRUE(server_stream()->IsZeroRtt()); +} + +TEST_F(TlsServerHandshakerTest, ZeroRttRejectOnApplicationStateChange) { + std::vector<uint8_t> original_application_state = {1, 2}; + std::vector<uint8_t> new_application_state = {3, 4}; + + // Do the first handshake + server_stream()->SetServerApplicationStateForResumption( + std::make_unique<ApplicationState>(original_application_state)); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_FALSE(client_stream()->IsResumption()); + EXPECT_FALSE(server_stream()->IsZeroRtt()); + + // Do another handshake, but change the application state + InitializeServer(); + server_stream()->SetServerApplicationStateForResumption( + std::make_unique<ApplicationState>(new_application_state)); + InitializeFakeClient(); + CompleteCryptoHandshake(); + ExpectHandshakeSuccessful(); + EXPECT_TRUE(client_stream()->IsResumption()); + EXPECT_FALSE(server_stream()->IsZeroRtt()); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/test_tools/crypto_test_utils.cc b/quic/test_tools/crypto_test_utils.cc index ddeba47..eacbca5 100644 --- a/quic/test_tools/crypto_test_utils.cc +++ b/quic/test_tools/crypto_test_utils.cc
@@ -242,7 +242,12 @@ TestQuicSpdyServerSession server_session( server_conn, *server_quic_config, client_conn->supported_versions(), crypto_config, &compressed_certs_cache); + // Call SetServerApplicationStateForResumption so that the fake server + // supports 0-RTT in TLS. server_session.Initialize(); + server_session.GetMutableCryptoStream() + ->SetServerApplicationStateForResumption( + std::make_unique<ApplicationState>()); EXPECT_CALL(*server_session.helper(), CanAcceptClientHello(testing::_, testing::_, testing::_, testing::_, testing::_))
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc index ed668d7..bc10611 100644 --- a/quic/test_tools/quic_test_utils.cc +++ b/quic/test_tools/quic_test_utils.cc
@@ -756,8 +756,8 @@ &push_promise_index_, config, supported_versions) { - // TODO(b/153726130): Consider adding OnApplicationState calls in tests and - // set |has_application_state| to true. + // TODO(b/153726130): Consider adding SetServerApplicationStateForResumption + // calls in tests and set |has_application_state| to true. crypto_stream_ = std::make_unique<QuicCryptoClientStream>( server_id, this, crypto_test_utils::ProofVerifyContextForTesting(), crypto_config, this, /*has_application_state = */ false);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index 39f052b..8bf8549 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -839,6 +839,8 @@ void OnHandshakePacketSent() override {} void OnHandshakeDoneReceived() override {} HandshakeState GetHandshakeState() const override { return HANDSHAKE_START; } + void SetServerApplicationStateForResumption( + std::unique_ptr<ApplicationState> /*application_state*/) override {} private: QuicReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;