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

Unified Diff: net/socket/ssl_client_socket_unittest.cc

Issue 2350483002: Improve error pages on client certificate failures. (Closed)
Patch Set: curly quotes Created 4 years, 3 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_impl.cc ('k') | no next file » | 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 8d5512d0a381648dec849b58a5f10fa8d176a618..6e9f4b49afc47af7eb40c4611601d2156b80e186 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -290,8 +290,8 @@ void ReadBufferingStreamSocket::OnReadCompleted(int result) {
// Simulates synchronously receiving an error during Read() or Write()
class SynchronousErrorStreamSocket : public WrappedStreamSocket {
public:
- explicit SynchronousErrorStreamSocket(
- std::unique_ptr<StreamSocket> transport);
+ explicit SynchronousErrorStreamSocket(std::unique_ptr<StreamSocket> transport)
+ : WrappedStreamSocket(std::move(transport)) {}
~SynchronousErrorStreamSocket() override {}
// Socket implementation:
@@ -323,23 +323,15 @@ class SynchronousErrorStreamSocket : public WrappedStreamSocket {
}
private:
- bool have_read_error_;
- int pending_read_error_;
+ bool have_read_error_ = false;
+ int pending_read_error_ = OK;
- bool have_write_error_;
- int pending_write_error_;
+ bool have_write_error_ = false;
+ int pending_write_error_ = OK;
DISALLOW_COPY_AND_ASSIGN(SynchronousErrorStreamSocket);
};
-SynchronousErrorStreamSocket::SynchronousErrorStreamSocket(
- std::unique_ptr<StreamSocket> transport)
- : WrappedStreamSocket(std::move(transport)),
- have_read_error_(false),
- pending_read_error_(OK),
- have_write_error_(false),
- pending_write_error_(OK) {}
-
int SynchronousErrorStreamSocket::Read(IOBuffer* buf,
int buf_len,
const CompletionCallback& callback) {
@@ -362,7 +354,8 @@ int SynchronousErrorStreamSocket::Write(IOBuffer* buf,
// semantics).
class FakeBlockingStreamSocket : public WrappedStreamSocket {
public:
- explicit FakeBlockingStreamSocket(std::unique_ptr<StreamSocket> transport);
+ explicit FakeBlockingStreamSocket(std::unique_ptr<StreamSocket> transport)
+ : WrappedStreamSocket(std::move(transport)) {}
~FakeBlockingStreamSocket() override {}
// Socket implementation:
@@ -383,6 +376,10 @@ class FakeBlockingStreamSocket : public WrappedStreamSocket {
void BlockReadResult();
void UnblockReadResult();
+ // Replaces the pending read with |data|. Returns true on success or false if
+ // the caller's reads were too small.
+ bool ReplaceReadResult(const std::string& data);
+
// Waits for the blocked Read() call to be complete at the underlying
// transport.
void WaitForReadResult();
@@ -402,24 +399,30 @@ class FakeBlockingStreamSocket : public WrappedStreamSocket {
// Handles completion from the underlying transport read.
void OnReadCompleted(int result);
+ // Finishes the current read.
+ void ReturnReadResult();
+
// True if read callbacks are blocked.
- bool should_block_read_;
+ bool should_block_read_ = false;
// The buffer for the pending read, or NULL if not consumed.
scoped_refptr<IOBuffer> pending_read_buf_;
+ // The size of the pending read buffer, or -1 if not set.
+ int pending_read_buf_len_ = -1;
+
// The user callback for the pending read call.
CompletionCallback pending_read_callback_;
// The result for the blocked read callback, or ERR_IO_PENDING if not
// completed.
- int pending_read_result_;
+ int pending_read_result_ = ERR_IO_PENDING;
// WaitForReadResult() wait loop.
std::unique_ptr<base::RunLoop> read_loop_;
// True if write calls are blocked.
- bool should_block_write_;
+ bool should_block_write_ = false;
// The buffer for the pending write, or NULL if not scheduled.
scoped_refptr<IOBuffer> pending_write_buf_;
@@ -428,20 +431,12 @@ class FakeBlockingStreamSocket : public WrappedStreamSocket {
CompletionCallback pending_write_callback_;
// The length for the pending write, or -1 if not scheduled.
- int pending_write_len_;
+ int pending_write_len_ = -1;
// WaitForWrite() wait loop.
std::unique_ptr<base::RunLoop> write_loop_;
};
-FakeBlockingStreamSocket::FakeBlockingStreamSocket(
- std::unique_ptr<StreamSocket> transport)
- : WrappedStreamSocket(std::move(transport)),
- should_block_read_(false),
- pending_read_result_(ERR_IO_PENDING),
- should_block_write_(false),
- pending_write_len_(-1) {}
-
int FakeBlockingStreamSocket::Read(IOBuffer* buf,
int len,
const CompletionCallback& callback) {
@@ -452,16 +447,16 @@ int FakeBlockingStreamSocket::Read(IOBuffer* buf,
int rv = transport_->Read(buf, len, base::Bind(
&FakeBlockingStreamSocket::OnReadCompleted, base::Unretained(this)));
- if (rv == ERR_IO_PENDING) {
+ if (rv == ERR_IO_PENDING || should_block_read_) {
// Save the callback to be called later.
pending_read_buf_ = buf;
+ pending_read_buf_len_ = len;
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;
+ // Save the read result.
+ if (rv != ERR_IO_PENDING) {
+ OnReadCompleted(rv);
+ rv = ERR_IO_PENDING;
+ }
}
return rv;
}
@@ -499,15 +494,23 @@ void FakeBlockingStreamSocket::UnblockReadResult() {
DCHECK(should_block_read_);
should_block_read_ = false;
- // If the operation is still pending in the underlying transport, immediately
- // return - OnReadCompleted() will handle invoking the callback once the
- // transport has completed.
- 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);
+ // If the operation has since completed, return the result to the caller.
+ if (pending_read_result_ != ERR_IO_PENDING)
+ ReturnReadResult();
+}
+
+bool FakeBlockingStreamSocket::ReplaceReadResult(const std::string& data) {
+ DCHECK(should_block_read_);
+ DCHECK_NE(ERR_IO_PENDING, pending_read_result_);
+ DCHECK(pending_read_buf_);
+ DCHECK_NE(-1, pending_read_buf_len_);
+
+ if (static_cast<size_t>(pending_read_buf_len_) < data.size())
+ return false;
+
+ memcpy(pending_read_buf_->data(), data.data(), data.size());
+ pending_read_result_ = data.size();
+ return true;
}
void FakeBlockingStreamSocket::WaitForReadResult() {
@@ -563,20 +566,24 @@ void FakeBlockingStreamSocket::OnReadCompleted(int result) {
DCHECK_EQ(ERR_IO_PENDING, pending_read_result_);
DCHECK(!pending_read_callback_.is_null());
- if (should_block_read_) {
- // Store the result so that the callback can be invoked once Unblock() is
- // called.
- pending_read_result_ = result;
+ pending_read_result_ = result;
- // Stop the WaitForReadResult() call if any.
+ if (should_block_read_) {
+ // Defer the result until UnblockReadResult is called.
if (read_loop_)
read_loop_->Quit();
- } else {
- // Either the Read() was never blocked or UnblockReadResult() was called
- // before the Read() completed. Either way, return the result to the caller.
- pending_read_buf_ = nullptr;
- base::ResetAndReturn(&pending_read_callback_).Run(result);
+ return;
}
+
+ ReturnReadResult();
+}
+
+void FakeBlockingStreamSocket::ReturnReadResult() {
+ int result = pending_read_result_;
+ pending_read_result_ = ERR_IO_PENDING;
+ pending_read_buf_ = nullptr;
+ pending_read_buf_len_ = -1;
+ base::ResetAndReturn(&pending_read_callback_).Run(result);
}
// CountingStreamSocket wraps an existing StreamSocket and maintains a count of
@@ -1023,6 +1030,25 @@ class SSLClientSocketChannelIDTest : public SSLClientSocketTest {
std::unique_ptr<ChannelIDService> channel_id_service_;
};
+// Returns a serialized unencrypted TLS 1.2 alert record for the given alert
+// value.
+std::string FormatTLS12Alert(uint8_t alert) {
+ std::string ret;
+ // ContentType.alert
+ ret.push_back(21);
+ // Record-layer version. Assume TLS 1.2.
+ ret.push_back(0x03);
+ ret.push_back(0x03);
+ // Record length.
+ ret.push_back(0);
+ ret.push_back(2);
+ // AlertLevel.fatal.
+ ret.push_back(2);
+ // The alert itself.
+ ret.push_back(alert);
+ return ret;
+}
+
} // namespace
TEST_F(SSLClientSocketTest, Connect) {
@@ -3240,9 +3266,6 @@ TEST_F(SSLClientSocketTest, SendGoodCert) {
SSLConfig ssl_config;
ssl_config.send_client_cert = true;
ssl_config.client_cert = ImportCertFromFile(certs_dir, "client_1.pem");
-
- // This is required to ensure that signing works with the client
- // certificate's private key.
ssl_config.client_private_key =
LoadPrivateKeyOpenSSL(certs_dir.AppendASCII("client_1.key"));
@@ -3433,4 +3456,279 @@ TEST_F(SSLClientSocketTest, PKPMoreImportantThanCT) {
EXPECT_TRUE(sock_->IsConnected());
}
+// Test that handshake_failure alerts at the ServerHello are mapped to
+// ERR_SSL_VERSION_OR_CIPHER_MISMATCH.
+TEST_F(SSLClientSocketTest, HandshakeFailureServerHello) {
+ ASSERT_TRUE(StartTestServer(SpawnedTestServer::SSLOptions()));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(),
+ SSLConfig()));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(40 /* AlertDescription.handshake_failure */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_SSL_VERSION_OR_CIPHER_MISMATCH));
+}
+
+// Test that handshake_failure alerts after the ServerHello but without a
+// CertificateRequest are mapped to ERR_SSL_PROTOCOL_ERROR.
+TEST_F(SSLClientSocketTest, HandshakeFailureNoClientCerts) {
+ ASSERT_TRUE(StartTestServer(SpawnedTestServer::SSLOptions()));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(),
+ SSLConfig()));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write its second flight.
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // Wait for the server's final flight.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(40 /* AlertDescription.handshake_failure */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_SSL_PROTOCOL_ERROR));
+}
+
+// Test that handshake_failure alerts after the ServerHello map to
+// ERR_BAD_SSL_CLIENT_AUTH_CERT if a client certificate was requested but not
+// supplied. TLS does not have an alert for this case, so handshake_failure is
+// common. See https://crbug.com/646567.
+TEST_F(SSLClientSocketTest, LateHandshakeFailureMissingClientCerts) {
+ // Request a client certificate.
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ASSERT_TRUE(StartTestServer(ssl_options));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ // Send no client certificate.
+ SSLConfig config;
+ config.send_client_cert = true;
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(), config));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write its second flight.
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // Wait for the server's final flight.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(40 /* AlertDescription.handshake_failure */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_BAD_SSL_CLIENT_AUTH_CERT));
+}
+
+// Test that handshake_failure alerts after the ServerHello map to
+// ERR_SSL_PROTOCOL_ERROR if received after sending a client certificate. It is
+// assumed servers will send a more appropriate alert in this case.
+TEST_F(SSLClientSocketTest, LateHandshakeFailureSendClientCerts) {
+ // Request a client certificate.
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ASSERT_TRUE(StartTestServer(ssl_options));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ // Send a client certificate.
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ SSLConfig config;
+ config.send_client_cert = true;
+ config.client_cert = ImportCertFromFile(certs_dir, "client_1.pem");
+ config.client_private_key =
+ LoadPrivateKeyOpenSSL(certs_dir.AppendASCII("client_1.key"));
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(), config));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write its second flight.
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // Wait for the server's final flight.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(40 /* AlertDescription.handshake_failure */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_SSL_PROTOCOL_ERROR));
+}
+
+// Test that access_denied alerts are mapped to ERR_SSL_PROTOCOL_ERROR if
+// received on a connection not requesting client certificates. This is an
+// incorrect use of the alert but is common. See https://crbug.com/630883.
+TEST_F(SSLClientSocketTest, AccessDeniedNoClientCerts) {
+ ASSERT_TRUE(StartTestServer(SpawnedTestServer::SSLOptions()));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(),
+ SSLConfig()));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write its second flight.
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // Wait for the server's final flight.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(49 /* AlertDescription.access_denied */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_SSL_PROTOCOL_ERROR));
+}
+
+// Test that access_denied alerts are mapped to ERR_BAD_SSL_CLIENT_AUTH_CERT if
+// received on a connection requesting client certificates.
+TEST_F(SSLClientSocketTest, AccessDeniedClientCerts) {
+ // Request a client certificate.
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ASSERT_TRUE(StartTestServer(ssl_options));
+
+ TestCompletionCallback callback;
+ std::unique_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr(), NULL, NULL, NetLog::Source()));
+ std::unique_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(std::move(real_transport)));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_THAT(rv, IsOk());
+
+ // Send a client certificate.
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ SSLConfig config;
+ config.send_client_cert = true;
+ config.client_cert = ImportCertFromFile(certs_dir, "client_1.pem");
+ config.client_private_key =
+ LoadPrivateKeyOpenSSL(certs_dir.AppendASCII("client_1.key"));
+ std::unique_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ std::move(transport), spawned_test_server()->host_port_pair(), config));
+
+ // Connect. Stop before the client processes ServerHello.
+ raw_transport->BlockReadResult();
+ rv = sock->Connect(callback.callback());
+ ASSERT_THAT(rv, IsError(ERR_IO_PENDING));
+ raw_transport->WaitForReadResult();
+
+ // Release the ServerHello and wait for the client to write its second flight.
+ raw_transport->BlockWrite();
+ raw_transport->UnblockReadResult();
+ raw_transport->WaitForWrite();
+
+ // Wait for the server's final flight.
+ raw_transport->BlockReadResult();
+ raw_transport->UnblockWrite();
+ raw_transport->WaitForReadResult();
+
+ // Replace it with an alert.
+ raw_transport->ReplaceReadResult(
+ FormatTLS12Alert(49 /* AlertDescription.access_denied */));
+ raw_transport->UnblockReadResult();
+
+ rv = callback.GetResult(rv);
+ EXPECT_THAT(rv, IsError(ERR_BAD_SSL_CLIENT_AUTH_CERT));
+}
+
} // namespace net
« no previous file with comments | « net/socket/ssl_client_socket_impl.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698