blob: 788a2cd5c0fb0173b8068ff240e11a8ca161f397 [file] [log] [blame]
// Copyright (c) 2016 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/quic/quartc/quartc_session.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/ip_endpoint.h"
#include "net/quic/core/crypto/crypto_server_config_protobuf.h"
#include "net/quic/core/crypto/proof_source.h"
#include "net/quic/core/crypto/proof_verifier.h"
#include "net/quic/core/crypto/quic_crypto_client_config.h"
#include "net/quic/core/crypto/quic_crypto_server_config.h"
#include "net/quic/core/crypto/quic_random.h"
#include "net/quic/core/quic_crypto_client_stream.h"
#include "net/quic/core/quic_crypto_server_stream.h"
#include "net/quic/core/quic_simple_buffer_allocator.h"
#include "net/quic/quartc/quartc_alarm_factory.h"
#include "net/quic/quartc/quartc_packet_writer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace test {
namespace {
static const char kExporterLabel[] = "label";
static const uint8_t kExporterContext[] = "context";
static const size_t kExporterContextLen = sizeof(kExporterContext);
static const size_t kOutputKeyLength = 20;
static QuartcStreamInterface::WriteParameters kDefaultWriteParam;
static QuartcSessionInterface::OutgoingStreamParameters kDefaultStreamParam;
static QuicByteCount kDefaultMaxPacketSize = 1200;
// Use the MessageLoop to simulate the asynchronous P2P communication. The
// RunLoop is used for handling the posted tasks.
void RunLoopWithTimeout() {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(200));
run_loop.Run();
}
// 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 QuicIpAddress& server_ip,
const std::string& hostname,
const std::string& server_config,
net::QuicVersion quic_version,
base::StringPiece chlo_hash,
const net::QuicTagVector& connection_options,
scoped_refptr<net::ProofSource::Chain>* out_certs,
net::QuicCryptoProof* proof) override {
if (success_) {
std::vector<std::string> certs;
certs.push_back("Required to establish handshake");
*out_certs = new ProofSource::Chain(certs);
proof->signature = "Signature";
proof->leaf_cert_scts = "Time";
}
return success_;
}
void GetProof(const QuicIpAddress& server_ip,
const std::string& hostname,
const std::string& server_config,
net::QuicVersion quic_version,
base::StringPiece chlo_hash,
const net::QuicTagVector& connection_options,
std::unique_ptr<Callback> callback) override {
LOG(INFO) << "GetProof() providing dummy credentials for insecure QUIC";
}
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<std::string>& certs,
const std::string& cert_sct,
const std::string& signature,
const ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<net::ProofVerifyDetails>* verify_details,
std::unique_ptr<net::ProofVerifierCallback> callback) override {
return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE;
}
net::QuicAsyncStatus VerifyCertChain(
const std::string& hostname,
const std::vector<std::string>& certs,
const net::ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<net::ProofVerifyDetails>* details,
std::unique_ptr<net::ProofVerifierCallback> callback) override {
LOG(INFO) << "VerifyProof() ignoring credentials and returning success";
return success_ ? net::QUIC_SUCCESS : net::QUIC_FAILURE;
}
private:
// Whether or not proof verification succeeds.
bool success_;
};
// Used by the FakeTransportChannel.
class FakeTransportChannelObserver {
public:
// Called when the other peer is trying to send message.
virtual void OnTransportChannelReadPacket(const std::string& data) = 0;
};
// Simulate the P2P communication transport. Used by the
// QuartcSessionInterface::Transport.
class FakeTransportChannel {
public:
void SetDestination(FakeTransportChannel* dest) {
if (!dest_) {
dest_ = dest;
dest_->SetDestination(this);
}
}
int SendPacket(const char* data, size_t len) {
// If the destination is not set.
if (!dest_) {
return -1;
}
if (async_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&FakeTransportChannel::send, base::Unretained(this),
std::string(data, len)));
} else {
send(std::string(data, len));
}
return static_cast<int>(len);
}
void send(const std::string& data) {
DCHECK(dest_);
DCHECK(dest_->observer());
dest_->observer()->OnTransportChannelReadPacket(data);
}
FakeTransportChannelObserver* observer() { return observer_; }
void SetObserver(FakeTransportChannelObserver* observer) {
observer_ = observer;
}
void SetAsync(bool async) { async_ = async; }
private:
// The writing destination of this channel.
FakeTransportChannel* dest_ = nullptr;
// The observer of this channel. Called when the received the data.
FakeTransportChannelObserver* observer_ = nullptr;
// If async, will send packets by "Post"-ing to message queue instead of
// synchronously "Send"-ing.
bool async_ = false;
};
// Used by the QuartcPacketWriter.
class FakeTransport : public QuartcSessionInterface::PacketTransport {
public:
FakeTransport(FakeTransportChannel* channel) : channel_(channel) {}
bool CanWrite() override { return true; }
int Write(const char* buffer, size_t buf_len) override {
DCHECK(channel_);
return channel_->SendPacket(buffer, buf_len);
}
private:
FakeTransportChannel* channel_;
};
class FakeQuartcSessionDelegate : public QuartcSessionInterface::Delegate {
public:
FakeQuartcSessionDelegate(QuartcStreamInterface::Delegate* stream_delegate)
: stream_delegate_(stream_delegate) {}
// Called when peers have established forward-secure encryption
void OnCryptoHandshakeComplete() override {
LOG(INFO) << "Crypto handshake complete!";
}
// Called when connection closes locally, or remotely by peer.
void OnConnectionClosed(int error_code, bool from_remote) override {
connected_ = false;
}
// Called when an incoming QUIC stream is created.
void OnIncomingStream(QuartcStreamInterface* quartc_stream) override {
last_incoming_stream_ = quartc_stream;
last_incoming_stream_->SetDelegate(stream_delegate_);
}
QuartcStreamInterface* incoming_stream() { return last_incoming_stream_; }
bool connected() { return connected_; }
private:
QuartcStreamInterface* last_incoming_stream_;
bool connected_ = true;
QuartcStream::Delegate* stream_delegate_;
};
class FakeQuartcStreamDelegate : public QuartcStreamInterface::Delegate {
public:
void OnReceived(QuartcStreamInterface* stream,
const char* data,
size_t size) override {
last_received_data_ = std::string(data, size);
}
void OnClose(QuartcStreamInterface* stream, int error_code) override {}
void OnBufferedAmountDecrease(QuartcStreamInterface* stream) override {}
std::string data() { return last_received_data_; }
private:
std::string last_received_data_;
};
class QuartcSessionForTest : public QuartcSession,
public FakeTransportChannelObserver {
public:
QuartcSessionForTest(std::unique_ptr<QuicConnection> connection,
const QuicConfig& config,
const std::string& remote_fingerprint_value,
Perspective perspective,
QuicConnectionHelperInterface* helper)
: QuartcSession(std::move(connection),
config,
remote_fingerprint_value,
perspective,
helper) {
stream_delegate_.reset(new FakeQuartcStreamDelegate);
session_delegate_.reset(
new FakeQuartcSessionDelegate(stream_delegate_.get()));
SetDelegate(session_delegate_.get());
}
// QuartcPacketWriter override.
void OnTransportChannelReadPacket(const std::string& data) override {
OnTransportReceived(data.c_str(), data.length());
}
std::string data() { return stream_delegate_->data(); }
bool has_data() { return !data().empty(); }
FakeQuartcSessionDelegate* session_delegate() {
return session_delegate_.get();
}
FakeQuartcStreamDelegate* stream_delegate() { return stream_delegate_.get(); }
private:
std::unique_ptr<FakeQuartcStreamDelegate> stream_delegate_;
std::unique_ptr<FakeQuartcSessionDelegate> session_delegate_;
};
class QuartcSessionTest : public ::testing::Test,
public QuicConnectionHelperInterface {
public:
~QuartcSessionTest() override {
// Check if there is message left in the message queue so that it won't
// affect other tests.
RunLoopWithTimeout();
}
void Init() {
client_channel_.reset(new FakeTransportChannel);
server_channel_.reset(new FakeTransportChannel);
// Make the channel asynchronous so that two peer will not keep calling each
// other when they exchange information.
client_channel_->SetAsync(true);
client_channel_->SetDestination(server_channel_.get());
client_transport_.reset(new FakeTransport(client_channel_.get()));
server_transport_.reset(new FakeTransport(server_channel_.get()));
client_writer_.reset(
new QuartcPacketWriter(client_transport_.get(), kDefaultMaxPacketSize));
server_writer_.reset(
new QuartcPacketWriter(server_transport_.get(), kDefaultMaxPacketSize));
}
// The parameters are used to control whether the handshake will success or
// not.
void CreateClientAndServerSessions(bool client_handshake_success = true,
bool server_handshake_success = true) {
Init();
client_peer_ = CreateSession(Perspective::IS_CLIENT);
server_peer_ = CreateSession(Perspective::IS_SERVER);
client_channel_->SetObserver(client_peer_.get());
server_channel_->SetObserver(server_peer_.get());
client_peer_->SetClientCryptoConfig(
new QuicCryptoClientConfig(std::unique_ptr<ProofVerifier>(
new FakeProofVerifier(client_handshake_success))));
QuicCryptoServerConfig* server_config = new QuicCryptoServerConfig(
"TESTING", QuicRandom::GetInstance(),
std::unique_ptr<FakeProofSource>(
new FakeProofSource(server_handshake_success)));
// Provide server with serialized config string to prove ownership.
QuicCryptoServerConfig::ConfigOptions options;
std::unique_ptr<QuicServerConfigProtobuf> primary_config(
server_config->GenerateConfig(QuicRandom::GetInstance(), &clock_,
options));
std::unique_ptr<CryptoHandshakeMessage> message(
server_config->AddConfig(std::move(primary_config), clock_.WallNow()));
server_peer_->SetServerCryptoConfig(server_config);
}
std::unique_ptr<QuartcSessionForTest> CreateSession(Perspective perspective) {
std::unique_ptr<QuicConnection> quic_connection =
CreateConnection(perspective);
std::string remote_fingerprint_value = "value";
QuicConfig config;
return std::unique_ptr<QuartcSessionForTest>(
new QuartcSessionForTest(std::move(quic_connection), config,
remote_fingerprint_value, perspective, this));
}
std::unique_ptr<QuicConnection> CreateConnection(Perspective perspective) {
QuartcPacketWriter* writer = perspective == Perspective::IS_CLIENT
? client_writer_.get()
: server_writer_.get();
QuicIpAddress ip;
ip.FromString("0.0.0.0");
bool owns_writer = false;
alarm_factory_.reset(new QuartcAlarmFactory(
base::ThreadTaskRunnerHandle::Get().get(), GetClock()));
return std::unique_ptr<QuicConnection>(new QuicConnection(
0, QuicSocketAddress(ip, 0), this /*QuicConnectionHelperInterface*/,
alarm_factory_.get(), writer, owns_writer, perspective,
AllSupportedVersions()));
}
void StartHandshake() {
server_peer_->StartCryptoHandshake();
client_peer_->StartCryptoHandshake();
RunLoopWithTimeout();
}
// Test handshake establishment and sending/receiving of data for two
// directions.
void TestStreamConnection() {
ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed() &&
client_peer_->IsCryptoHandshakeConfirmed());
ASSERT_TRUE(server_peer_->IsEncryptionEstablished());
ASSERT_TRUE(client_peer_->IsEncryptionEstablished());
uint8_t server_key[kOutputKeyLength];
uint8_t client_key[kOutputKeyLength];
bool use_context = true;
bool server_success = server_peer_->ExportKeyingMaterial(
kExporterLabel, kExporterContext, kExporterContextLen, use_context,
server_key, kOutputKeyLength);
ASSERT_TRUE(server_success);
bool client_success = client_peer_->ExportKeyingMaterial(
kExporterLabel, kExporterContext, kExporterContextLen, use_context,
client_key, kOutputKeyLength);
ASSERT_TRUE(client_success);
EXPECT_EQ(0, memcmp(server_key, client_key, sizeof(server_key)));
// Now we can establish encrypted outgoing stream.
QuartcStreamInterface* outgoing_stream =
server_peer_->CreateOutgoingStream(kDefaultStreamParam);
ASSERT_NE(nullptr, outgoing_stream);
EXPECT_TRUE(server_peer_->HasOpenDynamicStreams());
outgoing_stream->SetDelegate(server_peer_->stream_delegate());
// Send a test message from peer 1 to peer 2.
const char kTestMessage[] = "Hello";
outgoing_stream->Write(kTestMessage, strlen(kTestMessage),
kDefaultWriteParam);
RunLoopWithTimeout();
// Wait for peer 2 to receive messages.
ASSERT_TRUE(client_peer_->has_data());
QuartcStreamInterface* incoming =
client_peer_->session_delegate()->incoming_stream();
ASSERT_TRUE(incoming);
EXPECT_TRUE(client_peer_->HasOpenDynamicStreams());
EXPECT_EQ(client_peer_->data(), kTestMessage);
// Send a test message from peer 2 to peer 1.
const char kTestResponse[] = "Response";
incoming->Write(kTestResponse, strlen(kTestResponse), kDefaultWriteParam);
RunLoopWithTimeout();
// Wait for peer 1 to receive messages.
ASSERT_TRUE(server_peer_->has_data());
EXPECT_EQ(server_peer_->data(), kTestResponse);
}
// Test that client and server are not connected after handshake failure.
void TestDisconnectAfterFailedHandshake() {
EXPECT_TRUE(!client_peer_->session_delegate()->connected());
EXPECT_TRUE(!server_peer_->session_delegate()->connected());
EXPECT_FALSE(client_peer_->IsEncryptionEstablished());
EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed());
EXPECT_FALSE(server_peer_->IsEncryptionEstablished());
EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed());
}
const QuicClock* GetClock() const override { return &clock_; }
QuicRandom* GetRandomGenerator() override {
return QuicRandom::GetInstance();
}
QuicBufferAllocator* GetBufferAllocator() override {
return &buffer_allocator_;
}
protected:
std::unique_ptr<QuicAlarmFactory> alarm_factory_;
SimpleBufferAllocator buffer_allocator_;
QuicClock clock_;
std::unique_ptr<FakeTransportChannel> client_channel_;
std::unique_ptr<FakeTransportChannel> server_channel_;
std::unique_ptr<FakeTransport> client_transport_;
std::unique_ptr<FakeTransport> server_transport_;
std::unique_ptr<QuartcPacketWriter> client_writer_;
std::unique_ptr<QuartcPacketWriter> server_writer_;
std::unique_ptr<QuartcSessionForTest> client_peer_;
std::unique_ptr<QuartcSessionForTest> server_peer_;
};
TEST_F(QuartcSessionTest, StreamConnection) {
CreateClientAndServerSessions();
StartHandshake();
TestStreamConnection();
}
TEST_F(QuartcSessionTest, ClientRejection) {
CreateClientAndServerSessions(false /*client_handshake_success*/,
true /*server_handshake_success*/);
StartHandshake();
TestDisconnectAfterFailedHandshake();
}
TEST_F(QuartcSessionTest, ServerRejection) {
CreateClientAndServerSessions(true /*client_handshake_success*/,
false /*server_handshake_success*/);
StartHandshake();
TestDisconnectAfterFailedHandshake();
}
// Test that data streams are not created before handshake.
TEST_F(QuartcSessionTest, CannotCreateDataStreamBeforeHandshake) {
CreateClientAndServerSessions();
EXPECT_EQ(nullptr, server_peer_->CreateOutgoingStream(kDefaultStreamParam));
EXPECT_EQ(nullptr, client_peer_->CreateOutgoingStream(kDefaultStreamParam));
}
TEST_F(QuartcSessionTest, CloseQuartcStream) {
CreateClientAndServerSessions();
StartHandshake();
ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed() &&
server_peer_->IsCryptoHandshakeConfirmed());
QuartcStreamInterface* stream =
client_peer_->CreateOutgoingStream(kDefaultStreamParam);
ASSERT_NE(nullptr, stream);
uint32_t id = stream->stream_id();
EXPECT_FALSE(client_peer_->IsClosedStream(id));
stream->SetDelegate(client_peer_->stream_delegate());
stream->Close();
RunLoopWithTimeout();
EXPECT_TRUE(client_peer_->IsClosedStream(id));
}
} // namespace
} // namespace test
} // namespace net