| Index: net/socket/ssl_client_socket_unittest.cc
|
| diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
|
| index 004810a03145bdc7e62520be023049138eb90940..578456add59533a62a6da4152e8cfd3ea3d3ab55 100644
|
| --- a/net/socket/ssl_client_socket_unittest.cc
|
| +++ b/net/socket/ssl_client_socket_unittest.cc
|
| @@ -348,6 +348,9 @@ class FakeBlockingStreamSocket : public WrappedStreamSocket {
|
| int buf_len,
|
| const CompletionCallback& callback) override;
|
|
|
| + int pending_read_result() const { return pending_read_result_; }
|
| + IOBuffer* pending_read_buf() const { return pending_read_buf_.get(); }
|
| +
|
| // Blocks read results on the socket. Reads will not complete until
|
| // UnblockReadResult() has been called and a result is ready from the
|
| // underlying transport. Note: if BlockReadResult() is called while there is a
|
| @@ -377,6 +380,9 @@ class FakeBlockingStreamSocket : public WrappedStreamSocket {
|
| // True if read callbacks are blocked.
|
| bool should_block_read_;
|
|
|
| + // The buffer for the pending read, or NULL if not consumed.
|
| + scoped_refptr<IOBuffer> pending_read_buf_;
|
| +
|
| // The user callback for the pending read call.
|
| CompletionCallback pending_read_callback_;
|
|
|
| @@ -414,6 +420,7 @@ FakeBlockingStreamSocket::FakeBlockingStreamSocket(
|
| int FakeBlockingStreamSocket::Read(IOBuffer* buf,
|
| int len,
|
| const CompletionCallback& callback) {
|
| + DCHECK(!pending_read_buf_);
|
| DCHECK(pending_read_callback_.is_null());
|
| DCHECK_EQ(ERR_IO_PENDING, pending_read_result_);
|
| DCHECK(!callback.is_null());
|
| @@ -422,9 +429,11 @@ int FakeBlockingStreamSocket::Read(IOBuffer* buf,
|
| &FakeBlockingStreamSocket::OnReadCompleted, base::Unretained(this)));
|
| if (rv == ERR_IO_PENDING) {
|
| // Save the callback to be called later.
|
| + pending_read_buf_ = buf;
|
| pending_read_callback_ = callback;
|
| } else if (should_block_read_) {
|
| // Save the callback and read result to be called later.
|
| + pending_read_buf_ = buf;
|
| pending_read_callback_ = callback;
|
| OnReadCompleted(rv);
|
| rv = ERR_IO_PENDING;
|
| @@ -471,6 +480,7 @@ void FakeBlockingStreamSocket::UnblockReadResult() {
|
| if (pending_read_result_ == ERR_IO_PENDING)
|
| return;
|
| int result = pending_read_result_;
|
| + pending_read_buf_ = nullptr;
|
| pending_read_result_ = ERR_IO_PENDING;
|
| base::ResetAndReturn(&pending_read_callback_).Run(result);
|
| }
|
| @@ -538,7 +548,8 @@ void FakeBlockingStreamSocket::OnReadCompleted(int result) {
|
| read_loop_->Quit();
|
| } else {
|
| // Either the Read() was never blocked or UnblockReadResult() was called
|
| - // before the Read() completed. Either way, run the callback.
|
| + // before the Read() completed. Either way, return the result to the caller.
|
| + pending_read_buf_ = nullptr;
|
| base::ResetAndReturn(&pending_read_callback_).Run(result);
|
| }
|
| }
|
| @@ -2812,6 +2823,85 @@ TEST_F(SSLClientSocketTest, ReuseStates) {
|
| // attempt to read one byte extra.
|
| }
|
|
|
| +// Tests that basic session resumption works.
|
| +TEST_F(SSLClientSocketTest, SessionResumption) {
|
| + SpawnedTestServer::SSLOptions ssl_options;
|
| + ASSERT_TRUE(StartTestServer(ssl_options));
|
| +
|
| + // First, perform a full handshake.
|
| + SSLConfig ssl_config;
|
| + TestCompletionCallback callback;
|
| + scoped_ptr<StreamSocket> transport(
|
| + new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
|
| + transport.Pass(), test_server()->host_port_pair(), ssl_config));
|
| + ASSERT_EQ(OK, callback.GetResult(sock->Connect(callback.callback())));
|
| + SSLInfo ssl_info;
|
| + ASSERT_TRUE(sock->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| +
|
| + // The next connection should resume.
|
| + transport.reset(new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + sock = CreateSSLClientSocket(transport.Pass(),
|
| + test_server()->host_port_pair(), ssl_config);
|
| + ASSERT_EQ(OK, callback.GetResult(sock->Connect(callback.callback())));
|
| + ASSERT_TRUE(sock->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, ssl_info.handshake_type);
|
| +
|
| + // Using a different HostPortPair uses a different session cache key.
|
| + transport.reset(new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + sock = CreateSSLClientSocket(transport.Pass(),
|
| + HostPortPair("example.com", 443), ssl_config);
|
| + ASSERT_EQ(OK, callback.GetResult(sock->Connect(callback.callback())));
|
| + ASSERT_TRUE(sock->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| +
|
| + SSLClientSocket::ClearSessionCache();
|
| +
|
| + // After clearing the session cache, the next handshake doesn't resume.
|
| + transport.reset(new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + sock = CreateSSLClientSocket(transport.Pass(),
|
| + test_server()->host_port_pair(), ssl_config);
|
| + ASSERT_EQ(OK, callback.GetResult(sock->Connect(callback.callback())));
|
| + ASSERT_TRUE(sock->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| +}
|
| +
|
| +// Tests that connections with certificate errors do not add entries to the
|
| +// session cache.
|
| +TEST_F(SSLClientSocketTest, CertificateErrorNoResume) {
|
| + SpawnedTestServer::SSLOptions ssl_options;
|
| + ASSERT_TRUE(StartTestServer(ssl_options));
|
| +
|
| + cert_verifier_->set_default_result(ERR_CERT_COMMON_NAME_INVALID);
|
| +
|
| + SSLConfig ssl_config;
|
| + TestCompletionCallback callback;
|
| + scoped_ptr<StreamSocket> transport(
|
| + new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
|
| + transport.Pass(), test_server()->host_port_pair(), ssl_config));
|
| + EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID,
|
| + callback.GetResult(sock->Connect(callback.callback())));
|
| +
|
| + cert_verifier_->set_default_result(OK);
|
| +
|
| + // The next connection should perform a full handshake.
|
| + transport.reset(new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + ASSERT_EQ(OK, callback.GetResult(transport->Connect(callback.callback())));
|
| + sock = CreateSSLClientSocket(transport.Pass(),
|
| + test_server()->host_port_pair(), ssl_config);
|
| + ASSERT_EQ(OK, callback.GetResult(sock->Connect(callback.callback())));
|
| + SSLInfo ssl_info;
|
| + ASSERT_TRUE(sock->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| +}
|
| +
|
| // Tests that session caches are sharded by max_version.
|
| TEST_F(SSLClientSocketTest, FallbackShardSessionCache) {
|
| SpawnedTestServer::SSLOptions ssl_options;
|
| @@ -3105,9 +3195,9 @@ TEST_F(SSLClientSocketFalseStartTest, SessionResumption) {
|
| EXPECT_EQ(SSLInfo::HANDSHAKE_RESUME, ssl_info.handshake_type);
|
| }
|
|
|
| -// Test that sessions are not resumable before receiving the server Finished
|
| -// message.
|
| -TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) {
|
| +// Test that False Started sessions are not resumable before receiving the
|
| +// server Finished message.
|
| +TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinished) {
|
| if (!SupportsAESGCM()) {
|
| LOG(WARNING) << "Skipping test because AES-GCM is not supported.";
|
| return;
|
| @@ -3142,8 +3232,8 @@ TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) {
|
| //
|
| // The actual read on |sock1| will not complete until the Finished message is
|
| // processed; however, pump the underlying transport so that it is read from
|
| - // the socket. This still has the potential to race, but is generally unlikely
|
| - // due to socket buffer sizes.
|
| + // the socket. NOTE: This may flakily pass if the server's final flight
|
| + // doesn't come in one Read.
|
| scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
|
| int rv = sock1->Read(buf.get(), 4096, callback.callback());
|
| EXPECT_EQ(ERR_IO_PENDING, rv);
|
| @@ -3168,6 +3258,78 @@ TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) {
|
| EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| }
|
|
|
| +// Test that False Started sessions are not resumable if the server Finished
|
| +// message was bad.
|
| +TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBadFinished) {
|
| + if (!SupportsAESGCM()) {
|
| + LOG(WARNING) << "Skipping test because AES-GCM is not supported.";
|
| + return;
|
| + }
|
| +
|
| + // Start a server.
|
| + SpawnedTestServer::SSLOptions server_options;
|
| + server_options.key_exchanges =
|
| + SpawnedTestServer::SSLOptions::KEY_EXCHANGE_ECDHE_RSA;
|
| + server_options.bulk_ciphers =
|
| + SpawnedTestServer::SSLOptions::BULK_CIPHER_AES128GCM;
|
| + server_options.enable_npn = true;
|
| + ASSERT_TRUE(StartTestServer(server_options));
|
| +
|
| + SSLConfig client_config;
|
| + client_config.next_protos.push_back(kProtoHTTP11);
|
| +
|
| + // Start a handshake up to the server Finished message.
|
| + TestCompletionCallback callback;
|
| + FakeBlockingStreamSocket* raw_transport1 = NULL;
|
| + scoped_ptr<SSLClientSocket> sock1;
|
| + ASSERT_NO_FATAL_FAILURE(CreateAndConnectUntilServerFinishedReceived(
|
| + client_config, &callback, &raw_transport1, &sock1));
|
| + // Although raw_transport1 has the server Finished blocked, the handshake
|
| + // still completes.
|
| + EXPECT_EQ(OK, callback.WaitForResult());
|
| +
|
| + // Continue to block the client (|sock1|) from processing the Finished
|
| + // message, but allow it to arrive on the socket. This ensures that, from the
|
| + // server's point of view, it has completed the handshake and added the
|
| + // session to its session cache.
|
| + //
|
| + // The actual read on |sock1| will not complete until the Finished message is
|
| + // processed; however, pump the underlying transport so that it is read from
|
| + // the socket.
|
| + scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
|
| + int rv = sock1->Read(buf.get(), 4096, callback.callback());
|
| + EXPECT_EQ(ERR_IO_PENDING, rv);
|
| + raw_transport1->WaitForReadResult();
|
| +
|
| + // The server's second leg, or part of it, is now received but not yet sent to
|
| + // |sock1|. Before doing so, break the server's second leg.
|
| + int bytes_read = raw_transport1->pending_read_result();
|
| + ASSERT_LT(0, bytes_read);
|
| + raw_transport1->pending_read_buf()->data()[bytes_read - 1]++;
|
| +
|
| + // Unblock the Finished message. |sock1->Read| should now fail.
|
| + raw_transport1->UnblockReadResult();
|
| + EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, callback.GetResult(rv));
|
| +
|
| + // Drop the old socket. This is needed because the Python test server can't
|
| + // service two sockets in parallel.
|
| + sock1.reset();
|
| +
|
| + // Start a second connection.
|
| + scoped_ptr<StreamSocket> transport2(
|
| + new TCPClientSocket(addr(), &log_, NetLog::Source()));
|
| + EXPECT_EQ(OK, callback.GetResult(transport2->Connect(callback.callback())));
|
| + scoped_ptr<SSLClientSocket> sock2 = CreateSSLClientSocket(
|
| + transport2.Pass(), test_server()->host_port_pair(), client_config);
|
| + EXPECT_EQ(OK, callback.GetResult(sock2->Connect(callback.callback())));
|
| +
|
| + // No session resumption because the first connection never received a server
|
| + // Finished message.
|
| + SSLInfo ssl_info;
|
| + EXPECT_TRUE(sock2->GetSSLInfo(&ssl_info));
|
| + EXPECT_EQ(SSLInfo::HANDSHAKE_FULL, ssl_info.handshake_type);
|
| +}
|
| +
|
| // Connect to a server using channel id. It should allow the connection.
|
| TEST_F(SSLClientSocketChannelIDTest, SendChannelID) {
|
| SpawnedTestServer::SSLOptions ssl_options;
|
|
|