Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(741)

Unified Diff: net/socket/ssl_client_socket_unittest.cc

Issue 994263002: Rewrite session cache in OpenSSL ports. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: reebase Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/socket/ssl_client_socket_openssl.cc ('k') | net/socket/ssl_session_cache_openssl.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« no previous file with comments | « net/socket/ssl_client_socket_openssl.cc ('k') | net/socket/ssl_session_cache_openssl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698