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 4b74ca0b93dd6caa20495c7efd8614df05c70f16..eca63596f1f39e129cdff48cdfb0f12e6f663143 100644 |
--- a/net/socket/ssl_client_socket_unittest.cc |
+++ b/net/socket/ssl_client_socket_unittest.cc |
@@ -816,10 +816,12 @@ class SSLClientSocketCertRequestInfoTest : public SSLClientSocketTest { |
class SSLClientSocketFalseStartTest : public SSLClientSocketTest { |
protected: |
- // Creates an SSLClientSocket with |client_config| attached to a |
- // FakeBlockingStreamSocket, returning both in |*out_raw_transport| and |
- // |*out_sock|. The FakeBlockingStreamSocket is owned by the SSLClientSocket, |
- // so |*out_raw_transport| is a raw pointer. |
+ // Creates an SSLClientSocket with |client_config| to a series of modifier |
+ // sockets: a SynchronousErrorStreamSocket which wraps a |
+ // FakeBlockingStreamSocket which wraps the real connection to the test |
+ // server. These are returned in |*out_sock|, |*out_raw_error_socket|, and |
+ // |*out_raw_blocking_socket|, respectively. Note that the last two are raw |
+ // pointers owned by |*out_sock|. |
// |
// The client socket will begin a connect using |callback| but stop before the |
// server's finished message is received. The finished message will be blocked |
@@ -828,46 +830,53 @@ class SSLClientSocketFalseStartTest : public SSLClientSocketTest { |
// the client successfully false started, |callback.WaitForResult()| will |
// return OK without unblocking transport reads. But Read() will still block.) |
// |
+ // |*out_raw_error_socket| may be used to cause reading the Finished to fail. |
+ // |
// Must be called after StartTestServer is called. |
void CreateAndConnectUntilServerFinishedReceived( |
const SSLConfig& client_config, |
TestCompletionCallback* callback, |
- FakeBlockingStreamSocket** out_raw_transport, |
+ FakeBlockingStreamSocket** out_raw_blocking_socket, |
+ SynchronousErrorStreamSocket** out_raw_error_socket, |
scoped_ptr<SSLClientSocket>* out_sock) { |
CHECK(test_server()); |
scoped_ptr<StreamSocket> real_transport( |
new TCPClientSocket(addr(), NULL, NetLog::Source())); |
- scoped_ptr<FakeBlockingStreamSocket> transport( |
+ scoped_ptr<FakeBlockingStreamSocket> blocking_socket( |
new FakeBlockingStreamSocket(real_transport.Pass())); |
- int rv = callback->GetResult(transport->Connect(callback->callback())); |
+ FakeBlockingStreamSocket* raw_blocking_socket = blocking_socket.get(); |
+ scoped_ptr<SynchronousErrorStreamSocket> error_socket( |
+ new SynchronousErrorStreamSocket(blocking_socket.Pass())); |
+ SynchronousErrorStreamSocket* raw_error_socket = error_socket.get(); |
+ int rv = callback->GetResult(error_socket->Connect(callback->callback())); |
EXPECT_EQ(OK, rv); |
- FakeBlockingStreamSocket* raw_transport = transport.get(); |
scoped_ptr<SSLClientSocket> sock = CreateSSLClientSocket( |
- transport.Pass(), test_server()->host_port_pair(), client_config); |
+ error_socket.Pass(), test_server()->host_port_pair(), client_config); |
// Connect. Stop before the client processes the first server leg |
// (ServerHello, etc.) |
- raw_transport->BlockReadResult(); |
+ raw_blocking_socket->BlockReadResult(); |
rv = sock->Connect(callback->callback()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- raw_transport->WaitForReadResult(); |
+ raw_blocking_socket->WaitForReadResult(); |
// Release the ServerHello and wait for the client to write |
// ClientKeyExchange, etc. (A proxy for waiting for the entirety of the |
// server's leg to complete, since it may span multiple reads.) |
EXPECT_FALSE(callback->have_result()); |
- raw_transport->BlockWrite(); |
- raw_transport->UnblockReadResult(); |
- raw_transport->WaitForWrite(); |
+ raw_blocking_socket->BlockWrite(); |
+ raw_blocking_socket->UnblockReadResult(); |
+ raw_blocking_socket->WaitForWrite(); |
// And, finally, release that and block the next server leg |
// (ChangeCipherSpec, Finished). |
- raw_transport->BlockReadResult(); |
- raw_transport->UnblockWrite(); |
+ raw_blocking_socket->BlockReadResult(); |
+ raw_blocking_socket->UnblockWrite(); |
- *out_raw_transport = raw_transport; |
+ *out_raw_error_socket = raw_error_socket; |
+ *out_raw_blocking_socket = raw_blocking_socket; |
*out_sock = sock.Pass(); |
} |
@@ -877,10 +886,12 @@ class SSLClientSocketFalseStartTest : public SSLClientSocketTest { |
ASSERT_TRUE(StartTestServer(server_options)); |
TestCompletionCallback callback; |
- FakeBlockingStreamSocket* raw_transport = NULL; |
+ FakeBlockingStreamSocket* raw_blocking_socket = nullptr; |
+ SynchronousErrorStreamSocket* raw_error_socket = nullptr; |
scoped_ptr<SSLClientSocket> sock; |
ASSERT_NO_FATAL_FAILURE(CreateAndConnectUntilServerFinishedReceived( |
- client_config, &callback, &raw_transport, &sock)); |
+ client_config, &callback, &raw_blocking_socket, &raw_error_socket, |
+ &sock)); |
if (expect_false_start) { |
// When False Starting, the handshake should complete before receiving the |
@@ -911,7 +922,7 @@ class SSLClientSocketFalseStartTest : public SSLClientSocketTest { |
rv = sock->Read(buf.get(), 4096, callback.callback()); |
// After releasing reads, the connection proceeds. |
- raw_transport->UnblockReadResult(); |
+ raw_blocking_socket->UnblockReadResult(); |
rv = callback.GetResult(rv); |
EXPECT_LT(0, rv); |
} else { |
@@ -2804,6 +2815,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; |
@@ -2981,8 +3071,8 @@ 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 that False Started sessions are not resumable before receiving the |
+// server Finished message. |
TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) { |
if (!SupportsAESGCM()) { |
LOG(WARNING) << "Skipping test because AES-GCM is not supported."; |
@@ -3003,12 +3093,14 @@ TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) { |
// Start a handshake up to the server Finished message. |
TestCompletionCallback callback; |
- FakeBlockingStreamSocket* raw_transport1 = NULL; |
+ FakeBlockingStreamSocket* raw_blocking_socket1 = nullptr; |
+ SynchronousErrorStreamSocket* raw_error_socket1 = nullptr; |
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. |
+ client_config, &callback, &raw_blocking_socket1, &raw_error_socket1, |
+ &sock1)); |
+ // Although |raw_blocking_socket1| has the server Finished blocked, the |
+ // handshake still completes. |
EXPECT_EQ(OK, callback.WaitForResult()); |
// Continue to block the client (|sock1|) from processing the Finished |
@@ -3023,7 +3115,64 @@ TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBeforeFinish) { |
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(); |
+ raw_blocking_socket1->WaitForReadResult(); |
+ |
+ // 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); |
+} |
+ |
+// Test that False Started sessions are not resumable if reading the server |
+// Finished message failed. |
Ryan Sleevi
2015/04/02 20:00:14
I didn't mean reading the server finished failed -
davidben
2015/04/03 00:37:11
Switched it to that. That was actually somewhat le
|
+TEST_F(SSLClientSocketFalseStartTest, NoSessionResumptionBadFinish) { |
+ 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_DHE_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_blocking_socket1 = nullptr; |
+ SynchronousErrorStreamSocket* raw_error_socket1 = nullptr; |
+ scoped_ptr<SSLClientSocket> sock1; |
+ ASSERT_NO_FATAL_FAILURE(CreateAndConnectUntilServerFinishedReceived( |
+ client_config, &callback, &raw_blocking_socket1, &raw_error_socket1, |
+ &sock1)); |
+ // Although |raw_blocking_socket1| has the server Finished blocked, the |
+ // handshake still completes. |
+ EXPECT_EQ(OK, callback.WaitForResult()); |
+ |
+ // Fail to read the Finished. |
+ raw_error_socket1->SetNextReadError(ERR_CONNECTION_RESET); |
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096)); |
+ EXPECT_EQ(ERR_CONNECTION_RESET, callback.GetResult(sock1->Read( |
+ buf.get(), 4096, callback.callback()))); |
// Drop the old socket. This is needed because the Python test server can't |
// service two sockets in parallel. |