Implement a QuicSession subclass for QuicTransport client. gfe-relnote: n/a (code not used in production) PiperOrigin-RevId: 272427421 Change-Id: I1a8ebe085ce26eaa9ec19c0be9b6f4bb7c2ad73e
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc index 235d810..2fab3e1 100644 --- a/quic/core/http/quic_spdy_client_session_test.cc +++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -165,7 +165,8 @@ server_max_incoming_streams); } crypto_test_utils::HandshakeWithFakeServer( - &config, &helper_, &alarm_factory_, connection_, stream); + &config, &helper_, &alarm_factory_, connection_, stream, + AlpnForVersion(connection_->version())); } QuicCryptoClientConfig crypto_config_;
diff --git a/quic/core/quic_crypto_client_stream_test.cc b/quic/core/quic_crypto_client_stream_test.cc index f0a60e0..9969baf 100644 --- a/quic/core/quic_crypto_client_stream_test.cc +++ b/quic/core/quic_crypto_client_stream_test.cc
@@ -62,7 +62,8 @@ stream()->CryptoConnect(); QuicConfig config; crypto_test_utils::HandshakeWithFakeServer( - &config, &server_helper_, &alarm_factory_, connection_, stream()); + &config, &server_helper_, &alarm_factory_, connection_, stream(), + AlpnForVersion(connection_->version())); } QuicCryptoClientStream* stream() {
diff --git a/quic/core/quic_crypto_server_stream_test.cc b/quic/core/quic_crypto_server_stream_test.cc index e2ccfde..b73ea43 100644 --- a/quic/core/quic_crypto_server_stream_test.cc +++ b/quic/core/quic_crypto_server_stream_test.cc
@@ -21,6 +21,7 @@ #include "net/third_party/quiche/src/quic/core/quic_packets.h" #include "net/third_party/quiche/src/quic/core/quic_session.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" @@ -90,6 +91,12 @@ server_session_.reset(server_session); EXPECT_CALL(*server_session_->helper(), CanAcceptClientHello(_, _, _, _, _)) .Times(testing::AnyNumber()); + EXPECT_CALL(*server_session_, SelectAlpn(_)) + .WillRepeatedly([this](const std::vector<QuicStringPiece>& alpns) { + return std::find( + alpns.cbegin(), alpns.cend(), + AlpnForVersion(server_session_->connection()->version())); + }); crypto_test_utils::SetupCryptoServerConfigForTest( server_connection_->clock(), server_connection_->random_generator(), &server_crypto_config_);
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index 758106f..1805387 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -596,7 +596,7 @@ return false; } - bool IsHandshakeConfirmed() { return is_handshake_confirmed_; } + bool IsHandshakeConfirmed() const { return is_handshake_confirmed_; } private: friend class test::QuicSessionPeer;
diff --git a/quic/quic_transport/README.md b/quic/quic_transport/README.md new file mode 100644 index 0000000..e2f0849 --- /dev/null +++ b/quic/quic_transport/README.md
@@ -0,0 +1,7 @@ +# QuicTransport + +The files in this directory implement QuicTransport protocol as described in +<https://tools.ietf.org/html/draft-vvv-webtransport-quic>. + +Design doc: +https://docs.google.com/document/d/1UgviRBnZkMUq4OKcsAJvIQFX6UCXeCbOtX_wMgwD_es/edit#
diff --git a/quic/quic_transport/quic_transport_client_session.cc b/quic/quic_transport/quic_transport_client_session.cc new file mode 100644 index 0000000..35d7a82 --- /dev/null +++ b/quic/quic_transport/quic_transport_client_session.cc
@@ -0,0 +1,111 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quic_transport/quic_transport_client_session.h" + +#include <memory> + +#include "url/gurl.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_session.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" + +namespace quic { + +const char* kQuicTransportAlpn = "wq-draft01"; + +namespace { +// ProofHandler is primarily used by QUIC crypto to persist QUIC server configs +// and perform some of related debug logging. QuicTransport does not support +// QUIC crypto, so those methods are not called. +class DummyProofHandler : public QuicCryptoClientStream::ProofHandler { + public: + void OnProofValid( + const QuicCryptoClientConfig::CachedState& /*cached*/) override {} + void OnProofVerifyDetailsAvailable( + const ProofVerifyDetails& /*verify_details*/) override {} +}; +} // namespace + +QuicTransportClientSession::QuicTransportClientSession( + QuicConnection* connection, + Visitor* owner, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + url::Origin origin) + : QuicSession(connection, + owner, + config, + supported_versions, + /*num_expected_unidirectional_static_streams*/ 0), + origin_(origin) { + for (const ParsedQuicVersion& version : supported_versions) { + QUIC_BUG_IF(version.handshake_protocol != PROTOCOL_TLS1_3) + << "QuicTransport requires TLS 1.3 handshake"; + } + // ProofHandler API is not used by TLS 1.3. + static DummyProofHandler* proof_handler = new DummyProofHandler(); + crypto_stream_ = std::make_unique<QuicCryptoClientStream>( + server_id, this, crypto_config->proof_verifier()->CreateDefaultContext(), + crypto_config, proof_handler); +} + +void QuicTransportClientSession::OnCryptoHandshakeEvent( + CryptoHandshakeEvent event) { + QuicSession::OnCryptoHandshakeEvent(event); + if (event != HANDSHAKE_CONFIRMED) { + return; + } + + auto it = config()->received_custom_transport_parameters().find( + WebAcceptedOriginsParameter()); + if (it == config()->received_custom_transport_parameters().end()) { + connection()->CloseConnection( + QUIC_HANDSHAKE_FAILED, + "QuicTransport requires web_accepted_origins transport parameter", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + + QUIC_DLOG(INFO) << "QuicTransport using origin: " << origin_.Serialize(); + QUIC_DLOG(INFO) << "QuicTransport origins offered: " << it->second; + + if (CheckOrigin(it->second)) { + is_origin_valid_ = true; + } else { + QUIC_DLOG(ERROR) << "Origin check failed for " << origin_ + << ", allowed origin list: " << it->second; + connection()->CloseConnection( + QUIC_HANDSHAKE_FAILED, "QuicTransport origin check failed", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + } +} + +bool QuicTransportClientSession::CheckOrigin( + QuicStringPiece raw_accepted_origins) { + if (raw_accepted_origins == "*") { + return true; + } + + std::vector<QuicStringPiece> accepted_origins = + QuicTextUtils::Split(raw_accepted_origins, ','); + for (QuicStringPiece raw_origin : accepted_origins) { + url::Origin accepted_origin = + url::Origin::Create(GURL(std::string(raw_origin))); + QUIC_DVLOG(1) << "QuicTransport offered origin normalized: " + << accepted_origin.Serialize(); + if (accepted_origin.IsSameOriginWith(origin_)) { + return true; + } + } + return false; +} + +} // namespace quic
diff --git a/quic/quic_transport/quic_transport_client_session.h b/quic/quic_transport/quic_transport_client_session.h new file mode 100644 index 0000000..7e1cf73 --- /dev/null +++ b/quic/quic_transport/quic_transport_client_session.h
@@ -0,0 +1,78 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_QUIC_TRANSPORT_QUIC_TRANSPORT_SESSION_H_ +#define QUICHE_QUIC_QUIC_TRANSPORT_QUIC_TRANSPORT_SESSION_H_ + +#include <cstdint> +#include <memory> + +#include "url/origin.h" +#include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_client_config.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" +#include "net/third_party/quiche/src/quic/core/quic_connection.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" +#include "net/third_party/quiche/src/quic/core/quic_server_id.h" +#include "net/third_party/quiche/src/quic/core/quic_session.h" +#include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" + +namespace quic { + +// The web_accepted_origins transport parameter ID. +constexpr TransportParameters::TransportParameterId +WebAcceptedOriginsParameter() { + return static_cast<TransportParameters::TransportParameterId>(0xffc8); +} + +// The ALPN used by QuicTransport. +QUIC_EXPORT extern const char* kQuicTransportAlpn; + +// A client session for the QuicTransport protocol. +class QUIC_EXPORT QuicTransportClientSession : public QuicSession { + public: + QuicTransportClientSession(QuicConnection* connection, + Visitor* owner, + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + url::Origin origin); + + std::vector<std::string> GetAlpnsToOffer() const override { + return std::vector<std::string>({kQuicTransportAlpn}); + } + + void CryptoConnect() { crypto_stream_->CryptoConnect(); } + + bool ShouldKeepConnectionAlive() const override { return true; } + + QuicCryptoStream* GetMutableCryptoStream() override { + return crypto_stream_.get(); + } + const QuicCryptoStream* GetCryptoStream() const override { + return crypto_stream_.get(); + } + + bool IsSessionReady() const { + return IsCryptoHandshakeConfirmed() && is_origin_valid_; + } + + void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) override; + + protected: + // Accepts the list of accepted origins in a format specified in + // <https://tools.ietf.org/html/draft-vvv-webtransport-quic-00#section-3.2>, + // and verifies that at least one of them matches |origin_|. + bool CheckOrigin(QuicStringPiece raw_accepted_origins); + + std::unique_ptr<QuicCryptoClientStream> crypto_stream_; + url::Origin origin_; + bool is_origin_valid_ = false; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QUIC_TRANSPORT_QUIC_TRANSPORT_SESSION_H_
diff --git a/quic/quic_transport/quic_transport_client_session_test.cc b/quic/quic_transport/quic_transport_client_session_test.cc new file mode 100644 index 0000000..c500e2d --- /dev/null +++ b/quic/quic_transport/quic_transport_client_session_test.cc
@@ -0,0 +1,157 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/quic_transport/quic_transport_client_session.h" + +#include <memory> + +#include "url/gurl.h" +#include "net/third_party/quiche/src/quic/core/quic_server_id.h" +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { +namespace { + +using testing::_; +using testing::ElementsAre; + +const char* kTestOrigin = "https://test-origin.test"; +const char* kTestOriginInsecure = "http://test-origin.test"; +url::Origin GetTestOrigin() { + GURL origin_url(kTestOrigin); + return url::Origin::Create(origin_url); +} + +ParsedQuicVersionVector GetVersions() { + return {ParsedQuicVersion{PROTOCOL_TLS1_3, QUIC_VERSION_99}}; +} + +class TestClientSession : public QuicTransportClientSession { + public: + using QuicTransportClientSession::QuicTransportClientSession; + + class Stream : public QuicStream { + public: + using QuicStream::QuicStream; + void OnDataAvailable() override {} + }; + + QuicStream* CreateIncomingStream(QuicStreamId id) override { + auto stream = std::make_unique<Stream>( + id, this, /*is_static=*/false, + QuicUtils::GetStreamType(id, connection()->perspective(), + /*peer_initiated=*/true)); + QuicStream* result = stream.get(); + ActivateStream(std::move(stream)); + return result; + } + + QuicStream* CreateIncomingStream(PendingStream* /*pending*/) override { + QUIC_NOTREACHED(); + return nullptr; + } +}; + +class QuicTransportClientSessionTest : public QuicTest { + protected: + QuicTransportClientSessionTest() + : connection_(&helper_, + &alarm_factory_, + Perspective::IS_CLIENT, + GetVersions()), + server_id_("test.example.com", 443), + crypto_config_(crypto_test_utils::ProofVerifierForTesting()) { + SetQuicReloadableFlag(quic_supports_tls_handshake, true); + session_ = std::make_unique<TestClientSession>( + &connection_, nullptr, DefaultQuicConfig(), GetVersions(), server_id_, + &crypto_config_, GetTestOrigin()); + session_->Initialize(); + crypto_stream_ = static_cast<QuicCryptoClientStream*>( + session_->GetMutableCryptoStream()); + } + + void ConnectWithOriginList(std::string accepted_origins) { + session_->CryptoConnect(); + QuicConfig server_config = DefaultQuicConfig(); + server_config + .custom_transport_parameters_to_send()[WebAcceptedOriginsParameter()] = + accepted_origins; + crypto_test_utils::HandshakeWithFakeServer( + &server_config, &helper_, &alarm_factory_, &connection_, crypto_stream_, + kQuicTransportAlpn); + } + + MockAlarmFactory alarm_factory_; + MockQuicConnectionHelper helper_; + + PacketSavingConnection connection_; + QuicServerId server_id_; + QuicCryptoClientConfig crypto_config_; + std::unique_ptr<TestClientSession> session_; + QuicCryptoClientStream* crypto_stream_; +}; + +TEST_F(QuicTransportClientSessionTest, HasValidAlpn) { + EXPECT_THAT(session_->GetAlpnsToOffer(), ElementsAre(kQuicTransportAlpn)); +} + +TEST_F(QuicTransportClientSessionTest, SuccessfulConnection) { + ConnectWithOriginList(GetTestOrigin().Serialize()); + EXPECT_TRUE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, SuccessfulConnectionManyOrigins) { + ConnectWithOriginList( + QuicStrCat("http://example.org,", kTestOrigin, ",https://example.com")); + EXPECT_TRUE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, SuccessfulConnectionWildcardOrigin) { + ConnectWithOriginList("*"); + EXPECT_TRUE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, OriginMismatch) { + EXPECT_CALL(connection_, + CloseConnection(_, "QuicTransport origin check failed", _)); + ConnectWithOriginList("https://obviously-wrong-website.test"); + EXPECT_FALSE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, OriginSchemaMismatch) { + EXPECT_CALL(connection_, + CloseConnection(_, "QuicTransport origin check failed", _)); + ConnectWithOriginList(kTestOriginInsecure); + EXPECT_FALSE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, OriginListMissing) { + EXPECT_CALL( + connection_, + CloseConnection( + _, "QuicTransport requires web_accepted_origins transport parameter", + _)); + session_->CryptoConnect(); + QuicConfig server_config = DefaultQuicConfig(); + crypto_test_utils::HandshakeWithFakeServer( + &server_config, &helper_, &alarm_factory_, &connection_, crypto_stream_, + kQuicTransportAlpn); + EXPECT_FALSE(session_->IsSessionReady()); +} + +TEST_F(QuicTransportClientSessionTest, OriginListEmpty) { + EXPECT_CALL(connection_, + CloseConnection(_, "QuicTransport origin check failed", _)); + ConnectWithOriginList(""); + EXPECT_FALSE(session_->IsSessionReady()); +} + +} // namespace +} // namespace test +} // namespace quic
diff --git a/quic/test_tools/crypto_test_utils.cc b/quic/test_tools/crypto_test_utils.cc index ce0cb6e..7e9b882 100644 --- a/quic/test_tools/crypto_test_utils.cc +++ b/quic/test_tools/crypto_test_utils.cc
@@ -45,6 +45,8 @@ namespace { +using testing::_; + // CryptoFramerVisitor is a framer visitor that records handshake messages. class CryptoFramerVisitor : public CryptoFramerVisitorInterface { public: @@ -210,7 +212,8 @@ MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, PacketSavingConnection* client_conn, - QuicCryptoClientStream* client) { + QuicCryptoClientStream* client, + std::string alpn) { PacketSavingConnection* server_conn = new PacketSavingConnection( helper, alarm_factory, Perspective::IS_SERVER, ParsedVersionOfIndex(client_conn->supported_versions(), 0)); @@ -234,6 +237,10 @@ .Times(testing::AnyNumber()); EXPECT_CALL(*server_conn, OnCanWrite()).Times(testing::AnyNumber()); EXPECT_CALL(*client_conn, OnCanWrite()).Times(testing::AnyNumber()); + EXPECT_CALL(server_session, SelectAlpn(_)) + .WillRepeatedly([alpn](const std::vector<QuicStringPiece>& alpns) { + return std::find(alpns.cbegin(), alpns.cend(), alpn); + }); // The client's handshake must have been started already. CHECK_NE(0u, client_conn->encrypted_packets_.size());
diff --git a/quic/test_tools/crypto_test_utils.h b/quic/test_tools/crypto_test_utils.h index 421c0f3d..4cee641 100644 --- a/quic/test_tools/crypto_test_utils.h +++ b/quic/test_tools/crypto_test_utils.h
@@ -69,7 +69,8 @@ MockQuicConnectionHelper* helper, MockAlarmFactory* alarm_factory, PacketSavingConnection* client_conn, - QuicCryptoClientStream* client); + QuicCryptoClientStream* client, + std::string alpn); // returns: the number of client hellos that the client sent. int HandshakeWithFakeClient(MockQuicConnectionHelper* helper,
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index c12587d..4246cfa 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -794,6 +794,9 @@ MOCK_METHOD1(CreateIncomingStream, QuicSpdyStream*(PendingStream* stream)); MOCK_METHOD0(CreateOutgoingBidirectionalStream, QuicSpdyStream*()); MOCK_METHOD0(CreateOutgoingUnidirectionalStream, QuicSpdyStream*()); + MOCK_CONST_METHOD1(SelectAlpn, + std::vector<QuicStringPiece>::const_iterator( + const std::vector<QuicStringPiece>&)); QuicCryptoServerStreamBase* CreateQuicCryptoServerStream( const QuicCryptoServerConfig* crypto_config, QuicCompressedCertsCache* compressed_certs_cache) override;