/* * Copyright 2016 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/p2p/quic/quicsession.h" #include #include #include #include "net/base/ip_endpoint.h" #include "net/quic/crypto/crypto_server_config_protobuf.h" #include "net/quic/crypto/quic_random.h" #include "net/quic/crypto/proof_source.h" #include "net/quic/crypto/proof_verifier.h" #include "net/quic/crypto/quic_crypto_client_config.h" #include "net/quic/crypto/quic_crypto_server_config.h" #include "net/quic/quic_crypto_client_stream.h" #include "net/quic/quic_crypto_server_stream.h" #include "webrtc/base/common.h" #include "webrtc/base/gunit.h" #include "webrtc/p2p/base/faketransportcontroller.h" #include "webrtc/p2p/quic/quicconnectionhelper.h" #include "webrtc/p2p/quic/reliablequicstream.h" using net::IPAddress; using net::IPEndPoint; using net::PerPacketOptions; using net::Perspective; using net::ProofVerifyContext; using net::ProofVerifyDetails; using net::QuicByteCount; using net::QuicClock; using net::QuicCompressedCertsCache; using net::QuicConfig; using net::QuicConnection; using net::QuicCryptoClientConfig; using net::QuicCryptoServerConfig; using net::QuicCryptoClientStream; using net::QuicCryptoServerStream; using net::QuicCryptoStream; using net::QuicErrorCode; using net::QuicPacketWriter; using net::QuicRandom; using net::QuicServerConfigProtobuf; using net::QuicServerId; using net::QuicStreamId; using net::WriteResult; using net::WriteStatus; using cricket::FakeTransportChannel; using cricket::QuicConnectionHelper; using cricket::QuicSession; using cricket::ReliableQuicStream; using cricket::TransportChannel; using rtc::Thread; // Timeout for running asynchronous operations within unit tests. static const int kTimeoutMs = 1000; // Testing SpdyPriority value for creating outgoing ReliableQuicStream. static const uint8_t kDefaultPriority = 3; // TExport keying material function static const char kExporterLabel[] = "label"; static const char kExporterContext[] = "context"; static const size_t kExporterContextLen = sizeof(kExporterContext); // Identifies QUIC server session static const QuicServerId kServerId("www.google.com", 443); // Used by QuicCryptoServerConfig to provide server credentials, returning a // canned response equal to |success|. class FakeProofSource : public net::ProofSource { public: explicit FakeProofSource(bool success) : success_(success) {} // ProofSource override. bool GetProof(const IPAddress& server_ip, const std::string& hostname, const std::string& server_config, net::QuicVersion quic_version, base::StringPiece chlo_hash, bool ecdsa_ok, scoped_refptr* out_certs, std::string* out_signature, std::string* out_leaf_cert_sct) override { if (success_) { std::vector certs; certs.push_back("Required to establish handshake"); *out_certs = new ProofSource::Chain(certs); *out_signature = "Signature"; *out_leaf_cert_sct = "Time"; } return success_; } private: // Whether or not obtaining proof source succeeds. bool success_; }; // Used by QuicCryptoClientConfig to verify server credentials, returning a // canned response of QUIC_SUCCESS if |success| is true. class FakeProofVerifier : public net::ProofVerifier { public: explicit FakeProofVerifier(bool success) : success_(success) {} // ProofVerifier override net::QuicAsyncStatus VerifyProof( const std::string& hostname, const uint16_t port, const std::string& server_config, net::QuicVersion quic_version, base::StringPiece chlo_hash, const std::vector& certs, const std::string& cert_sct, const std::string& signature, const ProofVerifyContext* context, std::string* error_details, std::unique_ptr* verify_details, net::ProofVerifierCallback* callback) override { return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE; } private: // Whether or not proof verification succeeds. bool success_; }; // Writes QUIC packets to a fake transport channel that simulates a network. class FakeQuicPacketWriter : public QuicPacketWriter { public: explicit FakeQuicPacketWriter(FakeTransportChannel* fake_channel) : fake_channel_(fake_channel) {} // Sends packets across the network. WriteResult WritePacket(const char* buffer, size_t buf_len, const IPAddress& self_address, const IPEndPoint& peer_address, PerPacketOptions* options) override { rtc::PacketOptions packet_options; int rv = fake_channel_->SendPacket(buffer, buf_len, packet_options, 0); net::WriteStatus status; if (rv > 0) { status = net::WRITE_STATUS_OK; } else if (fake_channel_->GetError() == EWOULDBLOCK) { status = net::WRITE_STATUS_BLOCKED; } else { status = net::WRITE_STATUS_ERROR; } return net::WriteResult(status, rv); } // Returns true if the writer buffers and subsequently rewrites data // when an attempt to write results in the underlying socket becoming // write blocked. bool IsWriteBlockedDataBuffered() const override { return true; } // Returns true if the network socket is not writable. bool IsWriteBlocked() const override { return !fake_channel_->writable(); } // Records that the socket has become writable, for example when an EPOLLOUT // is received or an asynchronous write completes. void SetWritable() override { fake_channel_->SetWritable(true); } // Returns the maximum size of the packet which can be written using this // writer for the supplied peer address. This size may actually exceed the // size of a valid QUIC packet. QuicByteCount GetMaxPacketSize( const IPEndPoint& peer_address) const override { return net::kMaxPacketSize; } private: FakeTransportChannel* fake_channel_; }; // Wrapper for QuicSession and transport channel that stores incoming data. class QuicSessionForTest : public QuicSession { public: QuicSessionForTest(std::unique_ptr connection, const net::QuicConfig& config, std::unique_ptr channel) : QuicSession(std::move(connection), config), channel_(std::move(channel)) { channel_->SignalReadPacket.connect( this, &QuicSessionForTest::OnChannelReadPacket); } // Called when channel has packets to read. void OnChannelReadPacket(TransportChannel* channel, const char* data, size_t size, const rtc::PacketTime& packet_time, int flags) { OnReadPacket(data, size); } // Called when peer receives incoming stream from another peer. void OnIncomingStream(ReliableQuicStream* stream) { stream->SignalDataReceived.connect(this, &QuicSessionForTest::OnDataReceived); last_incoming_stream_ = stream; } // Called when peer has data to read from incoming stream. void OnDataReceived(net::QuicStreamId id, const char* data, size_t length) { last_received_data_ = std::string(data, length); } std::string data() { return last_received_data_; } bool has_data() { return data().size() > 0; } FakeTransportChannel* channel() { return channel_.get(); } ReliableQuicStream* incoming_stream() { return last_incoming_stream_; } private: // Transports QUIC packets to/from peer. std::unique_ptr channel_; // Stores data received by peer once it is sent from the other peer. std::string last_received_data_; // Handles incoming streams from sender. ReliableQuicStream* last_incoming_stream_ = nullptr; }; // Simulates data transfer between two peers using QUIC. class QuicSessionTest : public ::testing::Test, public QuicCryptoClientStream::ProofHandler { public: QuicSessionTest() : quic_helper_(rtc::Thread::Current()), quic_compressed_certs_cache_( QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {} // Instantiates |client_peer_| and |server_peer_|. void CreateClientAndServerSessions(); std::unique_ptr CreateSession( std::unique_ptr channel, Perspective perspective); QuicCryptoClientStream* CreateCryptoClientStream(QuicSessionForTest* session, bool handshake_success); QuicCryptoServerStream* CreateCryptoServerStream(QuicSessionForTest* session, bool handshake_success); std::unique_ptr CreateConnection( FakeTransportChannel* channel, Perspective perspective); void StartHandshake(bool client_handshake_success, bool server_handshake_success); // Test handshake establishment and sending/receiving of data. void TestStreamConnection(QuicSessionForTest* from_session, QuicSessionForTest* to_session); // Test that client and server are not connected after handshake failure. void TestDisconnectAfterFailedHandshake(); // QuicCryptoClientStream::ProofHelper overrides. void OnProofValid( const QuicCryptoClientConfig::CachedState& cached) override {} void OnProofVerifyDetailsAvailable( const ProofVerifyDetails& verify_details) override {} protected: QuicConnectionHelper quic_helper_; QuicConfig config_; QuicClock clock_; QuicCompressedCertsCache quic_compressed_certs_cache_; std::unique_ptr client_peer_; std::unique_ptr server_peer_; }; // Initializes "client peer" who begins crypto handshake and "server peer" who // establishes encryption with client. void QuicSessionTest::CreateClientAndServerSessions() { std::unique_ptr channel1( new FakeTransportChannel("channel1", 0)); std::unique_ptr channel2( new FakeTransportChannel("channel2", 0)); // Prevent channel1->OnReadPacket and channel2->OnReadPacket from calling // themselves in a loop, which causes to future packets to be recursively // consumed while the current thread blocks consumption of current ones. channel2->SetAsync(true); // Configure peers to send packets to each other. channel1->Connect(); channel2->Connect(); channel1->SetDestination(channel2.get()); client_peer_ = CreateSession(std::move(channel1), Perspective::IS_CLIENT); server_peer_ = CreateSession(std::move(channel2), Perspective::IS_SERVER); } std::unique_ptr QuicSessionTest::CreateSession( std::unique_ptr channel, Perspective perspective) { std::unique_ptr quic_connection = CreateConnection(channel.get(), perspective); return std::unique_ptr(new QuicSessionForTest( std::move(quic_connection), config_, std::move(channel))); } QuicCryptoClientStream* QuicSessionTest::CreateCryptoClientStream( QuicSessionForTest* session, bool handshake_success) { QuicCryptoClientConfig* client_config = new QuicCryptoClientConfig(new FakeProofVerifier(handshake_success)); return new QuicCryptoClientStream( kServerId, session, new ProofVerifyContext(), client_config, this); } QuicCryptoServerStream* QuicSessionTest::CreateCryptoServerStream( QuicSessionForTest* session, bool handshake_success) { QuicCryptoServerConfig* server_config = new QuicCryptoServerConfig("TESTING", QuicRandom::GetInstance(), new FakeProofSource(handshake_success)); // Provide server with serialized config string to prove ownership. QuicCryptoServerConfig::ConfigOptions options; QuicServerConfigProtobuf* primary_config = server_config->GenerateConfig( QuicRandom::GetInstance(), &clock_, options); server_config->AddConfig(primary_config, clock_.WallNow()); bool use_stateless_rejects_if_peer_supported = false; return new QuicCryptoServerStream( server_config, &quic_compressed_certs_cache_, use_stateless_rejects_if_peer_supported, session); } std::unique_ptr QuicSessionTest::CreateConnection( FakeTransportChannel* channel, Perspective perspective) { FakeQuicPacketWriter* writer = new FakeQuicPacketWriter(channel); IPAddress ip(0, 0, 0, 0); bool owns_writer = true; return std::unique_ptr(new QuicConnection( 0, net::IPEndPoint(ip, 0), &quic_helper_, writer, owns_writer, perspective, net::QuicSupportedVersions())); } void QuicSessionTest::StartHandshake(bool client_handshake_success, bool server_handshake_success) { server_peer_->StartServerHandshake( CreateCryptoServerStream(server_peer_.get(), server_handshake_success)); client_peer_->StartClientHandshake( CreateCryptoClientStream(client_peer_.get(), client_handshake_success)); } void QuicSessionTest::TestStreamConnection(QuicSessionForTest* from_session, QuicSessionForTest* to_session) { // Wait for crypto handshake to finish then check if encryption established. ASSERT_TRUE_WAIT(from_session->IsCryptoHandshakeConfirmed() && to_session->IsCryptoHandshakeConfirmed(), kTimeoutMs); ASSERT_TRUE(from_session->IsEncryptionEstablished()); ASSERT_TRUE(to_session->IsEncryptionEstablished()); std::string from_key; std::string to_key; bool from_success = from_session->ExportKeyingMaterial( kExporterLabel, kExporterContext, kExporterContextLen, &from_key); ASSERT_TRUE(from_success); bool to_success = to_session->ExportKeyingMaterial( kExporterLabel, kExporterContext, kExporterContextLen, &to_key); ASSERT_TRUE(to_success); EXPECT_EQ(from_key.size(), kExporterContextLen); EXPECT_EQ(from_key, to_key); // Now we can establish encrypted outgoing stream. ReliableQuicStream* outgoing_stream = from_session->CreateOutgoingDynamicStream(kDefaultPriority); ASSERT_NE(nullptr, outgoing_stream); EXPECT_TRUE(from_session->HasOpenDynamicStreams()); outgoing_stream->SignalDataReceived.connect( from_session, &QuicSessionForTest::OnDataReceived); to_session->SignalIncomingStream.connect( to_session, &QuicSessionForTest::OnIncomingStream); // Send a test message from peer 1 to peer 2. const char kTestMessage[] = "Hello, World!"; outgoing_stream->Write(kTestMessage, strlen(kTestMessage)); // Wait for peer 2 to receive messages. ASSERT_TRUE_WAIT(to_session->has_data(), kTimeoutMs); ReliableQuicStream* incoming = to_session->incoming_stream(); ASSERT_TRUE(incoming); EXPECT_TRUE(to_session->HasOpenDynamicStreams()); EXPECT_EQ(to_session->data(), kTestMessage); // Send a test message from peer 2 to peer 1. const char kTestResponse[] = "Response"; incoming->Write(kTestResponse, strlen(kTestResponse)); // Wait for peer 1 to receive messages. ASSERT_TRUE_WAIT(from_session->has_data(), kTimeoutMs); EXPECT_EQ(from_session->data(), kTestResponse); } // Client and server should disconnect when proof verification fails. void QuicSessionTest::TestDisconnectAfterFailedHandshake() { EXPECT_TRUE_WAIT(!client_peer_->connection()->connected(), kTimeoutMs); EXPECT_TRUE_WAIT(!server_peer_->connection()->connected(), kTimeoutMs); EXPECT_FALSE(client_peer_->IsEncryptionEstablished()); EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed()); EXPECT_FALSE(server_peer_->IsEncryptionEstablished()); EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed()); } // Establish encryption then send message from client to server. TEST_F(QuicSessionTest, ClientToServer) { CreateClientAndServerSessions(); StartHandshake(true, true); TestStreamConnection(client_peer_.get(), server_peer_.get()); } // Establish encryption then send message from server to client. TEST_F(QuicSessionTest, ServerToClient) { CreateClientAndServerSessions(); StartHandshake(true, true); TestStreamConnection(server_peer_.get(), client_peer_.get()); } // Make client fail to verify proof from server. TEST_F(QuicSessionTest, ClientRejection) { CreateClientAndServerSessions(); StartHandshake(false, true); TestDisconnectAfterFailedHandshake(); } // Make server fail to give proof to client. TEST_F(QuicSessionTest, ServerRejection) { CreateClientAndServerSessions(); StartHandshake(true, false); TestDisconnectAfterFailedHandshake(); } // Test that data streams are not created before handshake. TEST_F(QuicSessionTest, CannotCreateDataStreamBeforeHandshake) { CreateClientAndServerSessions(); EXPECT_EQ(nullptr, server_peer_->CreateOutgoingDynamicStream(5)); EXPECT_EQ(nullptr, client_peer_->CreateOutgoingDynamicStream(5)); } // Test that closing a QUIC stream causes the QuicSession to remove it. TEST_F(QuicSessionTest, CloseQuicStream) { CreateClientAndServerSessions(); StartHandshake(true, true); ASSERT_TRUE_WAIT(client_peer_->IsCryptoHandshakeConfirmed() && server_peer_->IsCryptoHandshakeConfirmed(), kTimeoutMs); ReliableQuicStream* stream = client_peer_->CreateOutgoingDynamicStream(5); ASSERT_NE(nullptr, stream); EXPECT_FALSE(client_peer_->IsClosedStream(stream->id())); stream->Close(); EXPECT_TRUE(client_peer_->IsClosedStream(stream->id())); }