blob: 9935839fb29145cbbe9c35505152e5dee66cc26f [file] [log] [blame]
// Copyright (c) 2017 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 <string>
#include <utility>
#include "net/third_party/quiche/src/quic/core/crypto/tls_client_connection.h"
#include "net/third_party/quiche/src/quic/core/crypto/tls_server_connection.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/core/tls_client_handshaker.h"
#include "net/third_party/quiche/src/quic/core/tls_server_handshaker.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.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/fake_proof_source.h"
#include "net/third_party/quiche/src/quic/test_tools/mock_quic_session_visitor.h"
#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.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"
namespace quic {
namespace test {
namespace {
using ::testing::_;
using ::testing::ElementsAreArray;
using ::testing::Return;
class TestProofVerifier : public ProofVerifier {
public:
TestProofVerifier()
: verifier_(crypto_test_utils::ProofVerifierForTesting()) {}
QuicAsyncStatus VerifyProof(
const std::string& hostname,
const uint16_t port,
const std::string& server_config,
QuicTransportVersion quic_version,
quiche::QuicheStringPiece 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<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback) override {
return verifier_->VerifyProof(
hostname, port, server_config, quic_version, chlo_hash, certs, cert_sct,
signature, context, error_details, details, std::move(callback));
}
QuicAsyncStatus VerifyCertChain(
const std::string& hostname,
const std::vector<std::string>& certs,
const std::string& ocsp_response,
const std::string& cert_sct,
const ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback) override {
if (!active_) {
return verifier_->VerifyCertChain(hostname, certs, ocsp_response,
cert_sct, context, error_details,
details, std::move(callback));
}
pending_ops_.push_back(std::make_unique<VerifyChainPendingOp>(
hostname, certs, ocsp_response, cert_sct, context, error_details,
details, std::move(callback), verifier_.get()));
return QUIC_PENDING;
}
std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
return nullptr;
}
void Activate() { active_ = true; }
size_t NumPendingCallbacks() const { return pending_ops_.size(); }
void InvokePendingCallback(size_t n) {
CHECK(NumPendingCallbacks() > n);
pending_ops_[n]->Run();
auto it = pending_ops_.begin() + n;
pending_ops_.erase(it);
}
private:
// Implementation of ProofVerifierCallback that fails if the callback is ever
// run.
class FailingProofVerifierCallback : public ProofVerifierCallback {
public:
void Run(bool /*ok*/,
const std::string& /*error_details*/,
std::unique_ptr<ProofVerifyDetails>* /*details*/) override {
FAIL();
}
};
class VerifyChainPendingOp {
public:
VerifyChainPendingOp(const std::string& hostname,
const std::vector<std::string>& certs,
const std::string& ocsp_response,
const std::string& cert_sct,
const ProofVerifyContext* context,
std::string* error_details,
std::unique_ptr<ProofVerifyDetails>* details,
std::unique_ptr<ProofVerifierCallback> callback,
ProofVerifier* delegate)
: hostname_(hostname),
certs_(certs),
ocsp_response_(ocsp_response),
cert_sct_(cert_sct),
context_(context),
error_details_(error_details),
details_(details),
callback_(std::move(callback)),
delegate_(delegate) {}
void Run() {
// TestProofVerifier depends on crypto_test_utils::ProofVerifierForTesting
// running synchronously. It passes a FailingProofVerifierCallback and
// runs the original callback after asserting that the verification ran
// synchronously.
QuicAsyncStatus status = delegate_->VerifyCertChain(
hostname_, certs_, ocsp_response_, cert_sct_, context_,
error_details_, details_,
std::make_unique<FailingProofVerifierCallback>());
ASSERT_NE(status, QUIC_PENDING);
callback_->Run(status == QUIC_SUCCESS, *error_details_, details_);
}
private:
std::string hostname_;
std::vector<std::string> certs_;
std::string ocsp_response_;
std::string cert_sct_;
const ProofVerifyContext* context_;
std::string* error_details_;
std::unique_ptr<ProofVerifyDetails>* details_;
std::unique_ptr<ProofVerifierCallback> callback_;
ProofVerifier* delegate_;
};
std::unique_ptr<ProofVerifier> verifier_;
bool active_ = false;
std::vector<std::unique_ptr<VerifyChainPendingOp>> pending_ops_;
};
class TestQuicCryptoStream : public QuicCryptoStream {
public:
explicit TestQuicCryptoStream(QuicSession* session)
: QuicCryptoStream(session) {}
~TestQuicCryptoStream() override = default;
virtual TlsHandshaker* handshaker() const = 0;
bool encryption_established() const override {
return handshaker()->encryption_established();
}
bool one_rtt_keys_available() const override {
return handshaker()->one_rtt_keys_available();
}
const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
const override {
return handshaker()->crypto_negotiated_params();
}
CryptoMessageParser* crypto_message_parser() override {
return handshaker()->crypto_message_parser();
}
void WriteCryptoData(EncryptionLevel level,
quiche::QuicheStringPiece data) override {
pending_writes_.push_back(std::make_pair(std::string(data), level));
}
void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
const std::vector<std::pair<std::string, EncryptionLevel>>& pending_writes() {
return pending_writes_;
}
// Sends the pending frames to |stream| and clears the array of pending
// writes.
void SendCryptoMessagesToPeer(QuicCryptoStream* stream) {
QUIC_LOG(INFO) << "Sending " << pending_writes_.size() << " frames";
// This is a minimal re-implementation of QuicCryptoStream::OnDataAvailable.
// It doesn't work to call QuicStream::OnStreamFrame because
// QuicCryptoStream::OnDataAvailable currently (as an implementation detail)
// relies on the QuicConnection to know the EncryptionLevel to pass into
// CryptoMessageParser::ProcessInput. Since the crypto messages in this test
// never reach the framer or connection and never get encrypted/decrypted,
// QuicCryptoStream::OnDataAvailable isn't able to call ProcessInput with
// the correct EncryptionLevel. Instead, that can be short-circuited by
// directly calling ProcessInput here.
for (size_t i = 0; i < pending_writes_.size(); ++i) {
if (!stream->crypto_message_parser()->ProcessInput(
pending_writes_[i].first, pending_writes_[i].second)) {
CloseConnectionWithDetails(
stream->crypto_message_parser()->error(),
stream->crypto_message_parser()->error_detail());
break;
}
}
pending_writes_.clear();
}
private:
std::vector<std::pair<std::string, EncryptionLevel>> pending_writes_;
};
class MockProofHandler : public QuicCryptoClientStream::ProofHandler {
public:
MockProofHandler() = default;
~MockProofHandler() override {}
MOCK_METHOD1(OnProofValid, void(const QuicCryptoClientConfig::CachedState&));
MOCK_METHOD1(OnProofVerifyDetailsAvailable, void(const ProofVerifyDetails&));
};
class TestQuicCryptoClientStream : public TestQuicCryptoStream {
public:
explicit TestQuicCryptoClientStream(QuicSession* session)
: TestQuicCryptoClientStream(session,
QuicServerId("test.example.com", 443),
std::make_unique<TestProofVerifier>()) {}
TestQuicCryptoClientStream(QuicSession* session,
const QuicServerId& server_id,
std::unique_ptr<ProofVerifier> proof_verifier)
: TestQuicCryptoStream(session),
crypto_config_(std::move(proof_verifier),
/*session_cache*/ nullptr),
handshaker_(new TlsClientHandshaker(
server_id,
this,
session,
crypto_test_utils::ProofVerifyContextForTesting(),
&crypto_config_,
&proof_handler_)) {}
~TestQuicCryptoClientStream() override = default;
TlsHandshaker* handshaker() const override { return handshaker_.get(); }
TlsClientHandshaker* client_handshaker() const { return handshaker_.get(); }
const MockProofHandler& proof_handler() { return proof_handler_; }
bool CryptoConnect() { return handshaker_->CryptoConnect(); }
TestProofVerifier* GetTestProofVerifier() const {
return static_cast<TestProofVerifier*>(crypto_config_.proof_verifier());
}
private:
MockProofHandler proof_handler_;
QuicCryptoClientConfig crypto_config_;
std::unique_ptr<TlsClientHandshaker> handshaker_;
};
class TestQuicCryptoServerStream : public TestQuicCryptoStream {
public:
TestQuicCryptoServerStream(QuicSession* session,
FakeProofSource* proof_source)
: TestQuicCryptoStream(session),
proof_source_(proof_source),
ssl_ctx_(TlsServerConnection::CreateSslCtx()),
handshaker_(new TlsServerHandshaker(this,
session,
ssl_ctx_.get(),
proof_source_)) {}
~TestQuicCryptoServerStream() override = default;
void CancelOutstandingCallbacks() {
handshaker_->CancelOutstandingCallbacks();
}
void OnPacketDecrypted(EncryptionLevel level) override {
handshaker_->OnPacketDecrypted(level);
}
TlsHandshaker* handshaker() const override { return handshaker_.get(); }
FakeProofSource* GetFakeProofSource() const { return proof_source_; }
private:
FakeProofSource* proof_source_;
bssl::UniquePtr<SSL_CTX> ssl_ctx_;
std::unique_ptr<TlsServerHandshaker> handshaker_;
};
void ExchangeHandshakeMessages(TestQuicCryptoStream* client,
TestQuicCryptoStream* server) {
while (!client->pending_writes().empty() ||
!server->pending_writes().empty()) {
client->SendCryptoMessagesToPeer(server);
server->SendCryptoMessagesToPeer(client);
}
}
class TlsHandshakerTest : public QuicTest {
public:
TlsHandshakerTest()
: client_conn_(new MockQuicConnection(
&conn_helper_,
&alarm_factory_,
Perspective::IS_CLIENT,
{ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99)})),
server_conn_(new MockQuicConnection(
&conn_helper_,
&alarm_factory_,
Perspective::IS_SERVER,
{ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99)})),
client_session_(client_conn_, /*create_mock_crypto_stream=*/false),
server_session_(server_conn_, /*create_mock_crypto_stream=*/false) {
client_stream_ = new TestQuicCryptoClientStream(&client_session_);
client_session_.SetCryptoStream(client_stream_);
server_stream_ =
new TestQuicCryptoServerStream(&server_session_, &proof_source_);
server_session_.SetCryptoStream(server_stream_);
client_session_.Initialize();
server_session_.Initialize();
EXPECT_FALSE(client_stream_->encryption_established());
EXPECT_FALSE(client_stream_->one_rtt_keys_available());
EXPECT_FALSE(server_stream_->encryption_established());
EXPECT_FALSE(server_stream_->one_rtt_keys_available());
const std::string default_alpn =
AlpnForVersion(client_session_.connection()->version());
ON_CALL(client_session_, GetAlpnsToOffer())
.WillByDefault(Return(std::vector<std::string>({default_alpn})));
ON_CALL(server_session_, SelectAlpn(_))
.WillByDefault(
[default_alpn](
const std::vector<quiche::QuicheStringPiece>& alpns) {
return std::find(alpns.begin(), alpns.end(), default_alpn);
});
}
void ExpectHandshakeSuccessful() {
EXPECT_TRUE(client_stream_->one_rtt_keys_available());
EXPECT_TRUE(client_stream_->encryption_established());
EXPECT_TRUE(server_stream_->one_rtt_keys_available());
EXPECT_TRUE(server_stream_->encryption_established());
EXPECT_TRUE(client_conn_->IsHandshakeComplete());
EXPECT_TRUE(server_conn_->IsHandshakeComplete());
const auto& client_crypto_params =
client_stream_->crypto_negotiated_params();
const auto& server_crypto_params =
server_stream_->crypto_negotiated_params();
// The TLS params should be filled in on the client.
EXPECT_NE(0, client_crypto_params.cipher_suite);
EXPECT_NE(0, client_crypto_params.key_exchange_group);
EXPECT_NE(0, client_crypto_params.peer_signature_algorithm);
// The cipher suite and key exchange group should match on the client and
// server.
EXPECT_EQ(client_crypto_params.cipher_suite,
server_crypto_params.cipher_suite);
EXPECT_EQ(client_crypto_params.key_exchange_group,
server_crypto_params.key_exchange_group);
// We don't support client certs on the server (yet), so the server
// shouldn't have a peer signature algorithm to report.
EXPECT_EQ(0, server_crypto_params.peer_signature_algorithm);
}
MockQuicConnectionHelper conn_helper_;
MockAlarmFactory alarm_factory_;
MockQuicConnection* client_conn_;
MockQuicConnection* server_conn_;
MockQuicSession client_session_;
MockQuicSession server_session_;
FakeProofSource proof_source_;
TestQuicCryptoClientStream* client_stream_;
TestQuicCryptoServerStream* server_stream_;
};
TEST_F(TlsHandshakerTest, CryptoHandshake) {
EXPECT_FALSE(client_conn_->IsHandshakeComplete());
EXPECT_FALSE(server_conn_->IsHandshakeComplete());
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(client_stream_->proof_handler(), OnProofVerifyDetailsAvailable);
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
}
TEST_F(TlsHandshakerTest, HandshakeWithAsyncProofSource) {
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
// Enable FakeProofSource to capture call to ComputeTlsSignature and run it
// asynchronously.
FakeProofSource* proof_source = server_stream_->GetFakeProofSource();
proof_source->Activate();
// Start handshake.
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ASSERT_EQ(proof_source->NumPendingCallbacks(), 1);
proof_source->InvokePendingCallback(0);
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
}
TEST_F(TlsHandshakerTest, CancelPendingProofSource) {
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
// Enable FakeProofSource to capture call to ComputeTlsSignature and run it
// asynchronously.
FakeProofSource* proof_source = server_stream_->GetFakeProofSource();
proof_source->Activate();
// Start handshake.
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ASSERT_EQ(proof_source->NumPendingCallbacks(), 1);
server_stream_ = nullptr;
proof_source->InvokePendingCallback(0);
}
TEST_F(TlsHandshakerTest, HandshakeWithAsyncProofVerifier) {
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
// Enable TestProofVerifier to capture call to VerifyCertChain and run it
// asynchronously.
TestProofVerifier* proof_verifier = client_stream_->GetTestProofVerifier();
proof_verifier->Activate();
EXPECT_CALL(client_stream_->proof_handler(), OnProofVerifyDetailsAvailable);
// Start handshake.
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ASSERT_EQ(proof_verifier->NumPendingCallbacks(), 1u);
proof_verifier->InvokePendingCallback(0);
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
}
TEST_F(TlsHandshakerTest, ClientSendsNoSNI) {
// Create a new client stream (and handshaker) with an empty server hostname.
client_stream_ =
new TestQuicCryptoClientStream(&client_session_, QuicServerId("", 443),
std::make_unique<FakeProofVerifier>());
client_session_.SetCryptoStream(client_stream_);
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(client_stream_->proof_handler(), OnProofVerifyDetailsAvailable);
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
EXPECT_EQ(server_stream_->crypto_negotiated_params().sni, "");
}
TEST_F(TlsHandshakerTest, ServerExtractSNI) {
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(client_stream_->proof_handler(), OnProofVerifyDetailsAvailable);
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
EXPECT_EQ(server_stream_->crypto_negotiated_params().sni, "test.example.com");
}
TEST_F(TlsHandshakerTest, ClientConnectionClosedOnTlsError) {
// Have client send ClientHello.
client_stream_->CryptoConnect();
EXPECT_CALL(*client_conn_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _));
// Send a zero-length ServerHello from server to client.
char bogus_handshake_message[] = {
// Handshake struct (RFC 8446 appendix B.3)
2, // HandshakeType server_hello
0, 0, 0, // uint24 length
};
server_stream_->WriteCryptoData(
ENCRYPTION_INITIAL,
quiche::QuicheStringPiece(bogus_handshake_message,
QUICHE_ARRAYSIZE(bogus_handshake_message)));
server_stream_->SendCryptoMessagesToPeer(client_stream_);
EXPECT_FALSE(client_stream_->one_rtt_keys_available());
}
TEST_F(TlsHandshakerTest, ServerConnectionClosedOnTlsError) {
EXPECT_CALL(*server_conn_, CloseConnection(QUIC_HANDSHAKE_FAILED, _, _));
// Send a zero-length ClientHello from client to server.
char bogus_handshake_message[] = {
// Handshake struct (RFC 8446 appendix B.3)
1, // HandshakeType client_hello
0, 0, 0, // uint24 length
};
client_stream_->WriteCryptoData(
ENCRYPTION_INITIAL,
quiche::QuicheStringPiece(bogus_handshake_message,
QUICHE_ARRAYSIZE(bogus_handshake_message)));
client_stream_->SendCryptoMessagesToPeer(server_stream_);
EXPECT_FALSE(server_stream_->one_rtt_keys_available());
}
TEST_F(TlsHandshakerTest, ClientNotSendingALPN) {
client_stream_->client_handshaker()->AllowEmptyAlpnForTests();
EXPECT_CALL(client_session_, GetAlpnsToOffer())
.WillOnce(Return(std::vector<std::string>()));
EXPECT_CALL(*client_conn_, CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not select ALPN", _));
EXPECT_CALL(*server_conn_,
CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not receive a known ALPN", _));
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
EXPECT_FALSE(client_stream_->one_rtt_keys_available());
EXPECT_FALSE(client_stream_->encryption_established());
EXPECT_FALSE(server_stream_->one_rtt_keys_available());
EXPECT_FALSE(server_stream_->encryption_established());
}
TEST_F(TlsHandshakerTest, ClientSendingBadALPN) {
const std::string kTestBadClientAlpn = "bad-client-alpn";
EXPECT_CALL(client_session_, GetAlpnsToOffer())
.WillOnce(Return(std::vector<std::string>({kTestBadClientAlpn})));
EXPECT_CALL(*client_conn_, CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not select ALPN", _));
EXPECT_CALL(*server_conn_,
CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not receive a known ALPN", _));
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
EXPECT_FALSE(client_stream_->one_rtt_keys_available());
EXPECT_FALSE(client_stream_->encryption_established());
EXPECT_FALSE(server_stream_->one_rtt_keys_available());
EXPECT_FALSE(server_stream_->encryption_established());
}
TEST_F(TlsHandshakerTest, ClientSendingTooManyALPNs) {
std::string long_alpn(250, 'A');
EXPECT_CALL(client_session_, GetAlpnsToOffer())
.WillOnce(Return(std::vector<std::string>({
long_alpn + "1",
long_alpn + "2",
long_alpn + "3",
long_alpn + "4",
long_alpn + "5",
long_alpn + "6",
long_alpn + "7",
long_alpn + "8",
})));
EXPECT_QUIC_BUG(client_stream_->CryptoConnect(), "Failed to set ALPN");
}
TEST_F(TlsHandshakerTest, ServerRequiresCustomALPN) {
const std::string kTestAlpn = "An ALPN That Client Did Not Offer";
EXPECT_CALL(server_session_, SelectAlpn(_))
.WillOnce(
[kTestAlpn](const std::vector<quiche::QuicheStringPiece>& alpns) {
return std::find(alpns.cbegin(), alpns.cend(), kTestAlpn);
});
EXPECT_CALL(*client_conn_, CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not select ALPN", _));
EXPECT_CALL(*server_conn_,
CloseConnection(QUIC_HANDSHAKE_FAILED,
"Server did not receive a known ALPN", _));
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
EXPECT_FALSE(client_stream_->one_rtt_keys_available());
EXPECT_FALSE(client_stream_->encryption_established());
EXPECT_FALSE(server_stream_->one_rtt_keys_available());
EXPECT_FALSE(server_stream_->encryption_established());
}
TEST_F(TlsHandshakerTest, CustomALPNNegotiation) {
EXPECT_CALL(*client_conn_, CloseConnection(_, _, _)).Times(0);
EXPECT_CALL(*server_conn_, CloseConnection(_, _, _)).Times(0);
const std::string kTestAlpn = "A Custom ALPN Value";
const std::vector<std::string> kTestAlpns(
{"foo", "bar", kTestAlpn, "something else"});
EXPECT_CALL(client_session_, GetAlpnsToOffer())
.WillRepeatedly(Return(kTestAlpns));
EXPECT_CALL(server_session_, SelectAlpn(_))
.WillOnce([kTestAlpn, kTestAlpns](
const std::vector<quiche::QuicheStringPiece>& alpns) {
EXPECT_THAT(alpns, ElementsAreArray(kTestAlpns));
return std::find(alpns.cbegin(), alpns.cend(), kTestAlpn);
});
EXPECT_CALL(client_session_,
OnAlpnSelected(quiche::QuicheStringPiece(kTestAlpn)));
EXPECT_CALL(server_session_,
OnAlpnSelected(quiche::QuicheStringPiece(kTestAlpn)));
client_stream_->CryptoConnect();
ExchangeHandshakeMessages(client_stream_, server_stream_);
ExpectHandshakeSuccessful();
}
} // namespace
} // namespace test
} // namespace quic