| // 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_server_session.h" | |
| #include <cstddef> | |
| #include <memory> | |
| #include <string> | |
| #include "url/gurl.h" | |
| #include "url/origin.h" | |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_compressed_certs_cache.h" | |
| #include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" | |
| #include "net/third_party/quiche/src/quic/core/frames/quic_stream_frame.h" | |
| #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" | |
| #include "net/third_party/quiche/src/quic/core/quic_versions.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" | |
| #include "net/third_party/quiche/src/quic/quic_transport/quic_transport_protocol.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" | |
| #include "net/third_party/quiche/src/quic/test_tools/quic_transport_test_tools.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 { | |
| namespace test { | |
| namespace { | |
| using testing::_; | |
| using testing::AnyNumber; | |
| using testing::DoAll; | |
| using testing::HasSubstr; | |
| using testing::Return; | |
| using testing::SaveArg; | |
| constexpr char kTestOrigin[] = "https://test-origin.test"; | |
| constexpr char kTestOriginClientIndication[] = | |
| "\0\0" // key (0x0000, origin) | |
| "\0\x18" // length | |
| "https://test-origin.test" // value | |
| "\0\x01" // key (0x0001, path) | |
| "\0\x05" // length | |
| "/test"; // value | |
| const url::Origin GetTestOrigin() { | |
| return url::Origin::Create(GURL(kTestOrigin)); | |
| } | |
| const std::string GetTestOriginClientIndication() { | |
| return std::string(kTestOriginClientIndication, | |
| sizeof(kTestOriginClientIndication) - 1); | |
| } | |
| ParsedQuicVersionVector GetVersions() { | |
| return {DefaultVersionForQuicTransport()}; | |
| } | |
| class QuicTransportServerSessionTest : public QuicTest { | |
| public: | |
| QuicTransportServerSessionTest() | |
| : connection_(&helper_, | |
| &alarm_factory_, | |
| Perspective::IS_SERVER, | |
| GetVersions()), | |
| crypto_config_(QuicCryptoServerConfig::TESTING, | |
| QuicRandom::GetInstance(), | |
| crypto_test_utils::ProofSourceForTesting(), | |
| KeyExchangeSource::Default()), | |
| compressed_certs_cache_( | |
| QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) { | |
| QuicEnableVersion(DefaultVersionForQuicTransport()); | |
| connection_.AdvanceTime(QuicTime::Delta::FromSeconds(100000)); | |
| crypto_test_utils::SetupCryptoServerConfigForTest( | |
| helper_.GetClock(), helper_.GetRandomGenerator(), &crypto_config_); | |
| session_ = std::make_unique<QuicTransportServerSession>( | |
| &connection_, nullptr, DefaultQuicConfig(), GetVersions(), | |
| &crypto_config_, &compressed_certs_cache_, &visitor_); | |
| session_->Initialize(); | |
| crypto_stream_ = session_->GetMutableCryptoStream(); | |
| ON_CALL(visitor_, ProcessPath(_)) | |
| .WillByDefault(DoAll(SaveArg<0>(&path_), Return(true))); | |
| } | |
| void Connect() { | |
| crypto_test_utils::FakeClientOptions options; | |
| options.only_tls_versions = true; | |
| crypto_test_utils::HandshakeWithFakeClient( | |
| &helper_, &alarm_factory_, &connection_, crypto_stream_, | |
| QuicServerId("test.example.com", 443), options, QuicTransportAlpn()); | |
| } | |
| void ReceiveIndication(quiche::QuicheStringPiece indication) { | |
| QUIC_LOG(INFO) << "Receiving indication: " | |
| << quiche::QuicheTextUtils::HexDump(indication); | |
| constexpr size_t kChunkSize = 1024; | |
| // Shard the indication, since some of the tests cause it to not fit into a | |
| // single frame. | |
| for (size_t i = 0; i < indication.size(); i += kChunkSize) { | |
| QuicStreamFrame frame(ClientIndicationStream(), /*fin=*/false, i, | |
| indication.substr(i, i + kChunkSize)); | |
| session_->OnStreamFrame(frame); | |
| } | |
| session_->OnStreamFrame(QuicStreamFrame(ClientIndicationStream(), | |
| /*fin=*/true, indication.size(), | |
| quiche::QuicheStringPiece())); | |
| } | |
| void ReceiveIndicationWithPath(quiche::QuicheStringPiece path) { | |
| constexpr char kTestOriginClientIndicationPrefix[] = | |
| "\0\0" // key (0x0000, origin) | |
| "\0\x18" // length | |
| "https://test-origin.test" // value | |
| "\0\x01"; // key (0x0001, path) | |
| std::string indication{kTestOriginClientIndicationPrefix, | |
| sizeof(kTestOriginClientIndicationPrefix) - 1}; | |
| indication.push_back(static_cast<char>(path.size() >> 8)); | |
| indication.push_back(static_cast<char>(path.size() & 0xff)); | |
| indication += std::string{path}; | |
| ReceiveIndication(indication); | |
| } | |
| protected: | |
| MockAlarmFactory alarm_factory_; | |
| MockQuicConnectionHelper helper_; | |
| PacketSavingConnection connection_; | |
| QuicCryptoServerConfig crypto_config_; | |
| std::unique_ptr<QuicTransportServerSession> session_; | |
| QuicCompressedCertsCache compressed_certs_cache_; | |
| testing::NiceMock<MockServerVisitor> visitor_; | |
| QuicCryptoServerStreamBase* crypto_stream_; | |
| GURL path_; | |
| }; | |
| TEST_F(QuicTransportServerSessionTest, SuccessfulHandshake) { | |
| Connect(); | |
| url::Origin origin; | |
| EXPECT_CALL(visitor_, CheckOrigin(_)) | |
| .WillOnce(DoAll(SaveArg<0>(&origin), Return(true))); | |
| ReceiveIndication(GetTestOriginClientIndication()); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| EXPECT_EQ(origin, GetTestOrigin()); | |
| EXPECT_EQ(path_.path(), "/test"); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, PiecewiseClientIndication) { | |
| Connect(); | |
| size_t i = 0; | |
| for (; i < sizeof(kTestOriginClientIndication) - 2; i++) { | |
| QuicStreamFrame frame( | |
| ClientIndicationStream(), false, i, | |
| quiche::QuicheStringPiece(&kTestOriginClientIndication[i], 1)); | |
| session_->OnStreamFrame(frame); | |
| } | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| QuicStreamFrame last_frame( | |
| ClientIndicationStream(), true, i, | |
| quiche::QuicheStringPiece(&kTestOriginClientIndication[i], 1)); | |
| session_->OnStreamFrame(last_frame); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, OriginRejected) { | |
| Connect(); | |
| EXPECT_CALL(connection_, | |
| CloseConnection(_, HasSubstr("Origin check failed"), _)); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(false)); | |
| ReceiveIndication(GetTestOriginClientIndication()); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| std::string MakeUnknownField(quiche::QuicheStringPiece payload) { | |
| std::string buffer; | |
| buffer.resize(payload.size() + 4); | |
| QuicDataWriter writer(buffer.size(), &buffer[0]); | |
| EXPECT_TRUE(writer.WriteUInt16(0xffff)); | |
| EXPECT_TRUE(writer.WriteUInt16(payload.size())); | |
| EXPECT_TRUE(writer.WriteStringPiece(payload)); | |
| EXPECT_EQ(writer.remaining(), 0u); | |
| return buffer; | |
| } | |
| TEST_F(QuicTransportServerSessionTest, SkipUnusedFields) { | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| ReceiveIndication(GetTestOriginClientIndication() + | |
| MakeUnknownField("foobar")); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, SkipLongUnusedFields) { | |
| const size_t bytes = | |
| ClientIndicationMaxSize() - GetTestOriginClientIndication().size() - 4; | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| ReceiveIndication(GetTestOriginClientIndication() + | |
| MakeUnknownField(std::string(bytes, 'a'))); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, ClientIndicationTooLong) { | |
| Connect(); | |
| EXPECT_CALL( | |
| connection_, | |
| CloseConnection(_, HasSubstr("Client indication size exceeds"), _)) | |
| .Times(AnyNumber()); | |
| ReceiveIndication(GetTestOriginClientIndication() + | |
| MakeUnknownField(std::string(65534, 'a'))); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, NoOrigin) { | |
| Connect(); | |
| EXPECT_CALL(connection_, CloseConnection(_, HasSubstr("No origin"), _)); | |
| ReceiveIndication(MakeUnknownField("foobar")); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, EmptyClientIndication) { | |
| Connect(); | |
| EXPECT_CALL(connection_, CloseConnection(_, HasSubstr("No origin"), _)); | |
| ReceiveIndication(""); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, MalformedIndicationHeader) { | |
| Connect(); | |
| EXPECT_CALL(connection_, | |
| CloseConnection(_, HasSubstr("Expected 16-bit key"), _)); | |
| ReceiveIndication("\xff"); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, FieldTooShort) { | |
| Connect(); | |
| EXPECT_CALL( | |
| connection_, | |
| CloseConnection(_, HasSubstr("Failed to read value for key 257"), _)); | |
| ReceiveIndication("\x01\x01\x01\x01"); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, InvalidOrigin) { | |
| const std::string kEmptyOriginIndication(4, '\0'); | |
| Connect(); | |
| EXPECT_CALL( | |
| connection_, | |
| CloseConnection(_, HasSubstr("Unable to parse the specified origin"), _)); | |
| ReceiveIndication(kEmptyOriginIndication); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, PathWithQuery) { | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| ReceiveIndicationWithPath("/test?foo=bar"); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| EXPECT_EQ(path_.path(), "/test"); | |
| EXPECT_EQ(path_.query(), "foo=bar"); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, PathNormalization) { | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| ReceiveIndicationWithPath("/foo/../bar"); | |
| EXPECT_TRUE(session_->IsSessionReady()); | |
| EXPECT_EQ(path_.path(), "/bar"); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, EmptyPath) { | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| EXPECT_CALL(connection_, | |
| CloseConnection(_, HasSubstr("Path must begin with a '/'"), _)); | |
| ReceiveIndicationWithPath(""); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| TEST_F(QuicTransportServerSessionTest, UnprefixedPath) { | |
| Connect(); | |
| EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true)); | |
| EXPECT_CALL(connection_, | |
| CloseConnection(_, HasSubstr("Path must begin with a '/'"), _)); | |
| ReceiveIndicationWithPath("test"); | |
| EXPECT_FALSE(session_->IsSessionReady()); | |
| } | |
| } // namespace | |
| } // namespace test | |
| } // namespace quic |