diff options
author | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 09:03:56 +0000 |
---|---|---|
committer | ukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-25 09:03:56 +0000 |
commit | aac499059df9f32e6b3ee691c17a4b6e6be3415b (patch) | |
tree | 89a29343a08d586bbe89034b317ebd4bfa881766 /net | |
parent | d380411308f56505d4e83a923942a5fb840d0d69 (diff) | |
download | chromium_src-aac499059df9f32e6b3ee691c17a4b6e6be3415b.zip chromium_src-aac499059df9f32e6b3ee691c17a4b6e6be3415b.tar.gz chromium_src-aac499059df9f32e6b3ee691c17a4b6e6be3415b.tar.bz2 |
secure proxy support in websocket
BUG=83950
TEST=net_unittest --gtest_filter=SocketStreamTest.* pass
Review URL: http://codereview.chromium.org/7468025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@93860 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/socket_stream/socket_stream.cc | 134 | ||||
-rw-r--r-- | net/socket_stream/socket_stream.h | 5 | ||||
-rw-r--r-- | net/socket_stream/socket_stream_metrics.h | 1 | ||||
-rw-r--r-- | net/socket_stream/socket_stream_unittest.cc | 105 |
4 files changed, 201 insertions, 44 deletions
diff --git a/net/socket_stream/socket_stream.cc b/net/socket_stream/socket_stream.cc index 74eabec..57af475 100644 --- a/net/socket_stream/socket_stream.cc +++ b/net/socket_stream/socket_stream.cc @@ -304,6 +304,49 @@ void SocketStream::Finish(int result) { Release(); } +int SocketStream::DidEstablishSSL(int result) { + if (IsCertificateError(result)) { + if (socket_->IsConnectedAndIdle()) { + result = HandleCertificateError(result); + } else { + // SSLClientSocket for Mac will report socket is not connected, + // if it returns cert verification error. It didn't perform + // SSLHandshake yet. + // So, we should restart establishing connection with the + // certificate in allowed bad certificates in |ssl_config_|. + // See also net/http/http_network_transaction.cc + // HandleCertificateError() and RestartIgnoringLastError(). + SSLClientSocket* ssl_socket = + reinterpret_cast<SSLClientSocket*>(socket_.get()); + SSLInfo ssl_info; + ssl_socket->GetSSLInfo(&ssl_info); + if (ssl_info.cert == NULL || + ssl_config_.IsAllowedBadCert(ssl_info.cert, NULL)) { + // If we already have the certificate in the set of allowed bad + // certificates, we did try it and failed again, so we should not + // retry again: the connection should fail at last. + next_state_ = STATE_CLOSE; + return result; + } + // Add the bad certificate to the set of allowed certificates in the + // SSL config object. + SSLConfig::CertAndStatus bad_cert; + if (!ssl_info.cert->GetDEREncoded(&bad_cert.der_cert)) { + next_state_ = STATE_CLOSE; + return result; + } + bad_cert.cert_status = ssl_info.cert_status; + ssl_config_.allowed_bad_certs.push_back(bad_cert); + // Restart connection ignoring the bad certificate. + socket_->Disconnect(); + socket_.reset(); + next_state_ = STATE_TCP_CONNECT; + return OK; + } + } + return result; +} + int SocketStream::DidEstablishConnection() { if (!socket_.get() || !socket_->IsConnected()) { next_state_ = STATE_CLOSE; @@ -440,6 +483,13 @@ void SocketStream::DoLoop(int result) { case STATE_SOCKS_CONNECT_COMPLETE: result = DoSOCKSConnectComplete(result); break; + case STATE_SECURE_PROXY_CONNECT: + DCHECK_EQ(OK, result); + result = DoSecureProxyConnect(); + break; + case STATE_SECURE_PROXY_CONNECT_COMPLETE: + result = DoSecureProxyConnectComplete(result); + break; case STATE_SSL_CONNECT: DCHECK_EQ(OK, result); result = DoSSLConnect(); @@ -616,11 +666,14 @@ int SocketStream::DoTcpConnectComplete(int result) { return result; } - if (proxy_mode_ == kTunnelProxy) - next_state_ = STATE_WRITE_TUNNEL_HEADERS; - else if (proxy_mode_ == kSOCKSProxy) + if (proxy_mode_ == kTunnelProxy) { + if (proxy_info_.is_https()) + next_state_ = STATE_SECURE_PROXY_CONNECT; + else + next_state_ = STATE_WRITE_TUNNEL_HEADERS; + } else if (proxy_mode_ == kSOCKSProxy) { next_state_ = STATE_SOCKS_CONNECT; - else if (is_secure()) { + } else if (is_secure()) { next_state_ = STATE_SSL_CONNECT; } else { result = DidEstablishConnection(); @@ -850,6 +903,35 @@ int SocketStream::DoSOCKSConnectComplete(int result) { return result; } +int SocketStream::DoSecureProxyConnect() { + DCHECK(factory_); + SSLClientSocketContext ssl_context; + ssl_context.cert_verifier = cert_verifier_; + ssl_context.origin_bound_cert_service = origin_bound_cert_service_; + // TODO(agl): look into plumbing SSLHostInfo here. + socket_.reset(factory_->CreateSSLClientSocket( + socket_.release(), + proxy_info_.proxy_server().host_port_pair(), + ssl_config_, + NULL /* ssl_host_info */, + ssl_context)); + next_state_ = STATE_SECURE_PROXY_CONNECT_COMPLETE; + metrics_->OnCountConnectionType(SocketStreamMetrics::SECURE_PROXY_CONNECTION); + return socket_->Connect(&io_callback_); +} + +int SocketStream::DoSecureProxyConnectComplete(int result) { + DCHECK_EQ(STATE_NONE, next_state_); + result = DidEstablishSSL(result); + if (next_state_ != STATE_NONE) + return result; + if (result == OK) + next_state_ = STATE_WRITE_TUNNEL_HEADERS; + else + next_state_ = STATE_CLOSE; + return result; +} + int SocketStream::DoSSLConnect() { DCHECK(factory_); SSLClientSocketContext ssl_context; @@ -867,46 +949,10 @@ int SocketStream::DoSSLConnect() { } int SocketStream::DoSSLConnectComplete(int result) { - if (IsCertificateError(result)) { - if (socket_->IsConnectedAndIdle()) { - result = HandleCertificateError(result); - } else { - // SSLClientSocket for Mac will report socket is not connected, - // if it returns cert verification error. It didn't perform - // SSLHandshake yet. - // So, we should restart establishing connection with the - // certificate in allowed bad certificates in |ssl_config_|. - // See also net/http/http_network_transaction.cc - // HandleCertificateError() and RestartIgnoringLastError(). - SSLClientSocket* ssl_socket = - reinterpret_cast<SSLClientSocket*>(socket_.get()); - SSLInfo ssl_info; - ssl_socket->GetSSLInfo(&ssl_info); - if (ssl_info.cert == NULL || - ssl_config_.IsAllowedBadCert(ssl_info.cert, NULL)) { - // If we already have the certificate in the set of allowed bad - // certificates, we did try it and failed again, so we should not - // retry again: the connection should fail at last. - next_state_ = STATE_CLOSE; - return result; - } - // Add the bad certificate to the set of allowed certificates in the - // SSL config object. - SSLConfig::CertAndStatus bad_cert; - if (!ssl_info.cert->GetDEREncoded(&bad_cert.der_cert)) { - next_state_ = STATE_CLOSE; - return result; - } - bad_cert.cert_status = ssl_info.cert_status; - ssl_config_.allowed_bad_certs.push_back(bad_cert); - // Restart connection ignoring the bad certificate. - socket_->Disconnect(); - socket_.reset(); - next_state_ = STATE_TCP_CONNECT; - return OK; - } - } - + DCHECK_EQ(STATE_NONE, next_state_); + result = DidEstablishSSL(result); + if (next_state_ != STATE_NONE) + return result; // TODO(toyoshim): Upgrade to SPDY through TLS NPN extension if possible. // If we use HTTPS and this is the first connection to the SPDY server, // we should take care of TLS NPN extension here. diff --git a/net/socket_stream/socket_stream.h b/net/socket_stream/socket_stream.h index 19331f4..5e5b339 100644 --- a/net/socket_stream/socket_stream.h +++ b/net/socket_stream/socket_stream.h @@ -228,6 +228,8 @@ class NET_API SocketStream : public base::RefCountedThreadSafe<SocketStream> { STATE_READ_TUNNEL_HEADERS_COMPLETE, STATE_SOCKS_CONNECT, STATE_SOCKS_CONNECT_COMPLETE, + STATE_SECURE_PROXY_CONNECT, + STATE_SECURE_PROXY_CONNECT_COMPLETE, STATE_SSL_CONNECT, STATE_SSL_CONNECT_COMPLETE, STATE_READ_WRITE, @@ -255,6 +257,7 @@ class NET_API SocketStream : public base::RefCountedThreadSafe<SocketStream> { // notifications will be sent to delegate. void Finish(int result); + int DidEstablishSSL(int result); int DidEstablishConnection(); int DidReceiveData(int result); int DidSendData(int result); @@ -279,6 +282,8 @@ class NET_API SocketStream : public base::RefCountedThreadSafe<SocketStream> { int DoReadTunnelHeadersComplete(int result); int DoSOCKSConnect(); int DoSOCKSConnectComplete(int result); + int DoSecureProxyConnect(); + int DoSecureProxyConnectComplete(int result); int DoSSLConnect(); int DoSSLConnectComplete(int result); int DoReadWrite(int result); diff --git a/net/socket_stream/socket_stream_metrics.h b/net/socket_stream/socket_stream_metrics.h index 83666fe..e69ba25 100644 --- a/net/socket_stream/socket_stream_metrics.h +++ b/net/socket_stream/socket_stream_metrics.h @@ -32,6 +32,7 @@ class NET_TEST SocketStreamMetrics { TUNNEL_CONNECTION, SOCKS_CONNECTION, SSL_CONNECTION, + SECURE_PROXY_CONNECTION, NUM_CONNECTION_TYPES, }; diff --git a/net/socket_stream/socket_stream_unittest.cc b/net/socket_stream/socket_stream_unittest.cc index 090ecff..80bff3c 100644 --- a/net/socket_stream/socket_stream_unittest.cc +++ b/net/socket_stream/socket_stream_unittest.cc @@ -535,4 +535,109 @@ TEST_F(SocketStreamTest, SwitchAfterPending) { EXPECT_EQ(net::ERR_PROTOCOL_SWITCHED, events[1].error_code); } +// Test a connection though a secure proxy. +TEST_F(SocketStreamTest, SecureProxyConnectError) { + MockClientSocketFactory mock_socket_factory; + MockWrite data_writes[] = { + MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" + "Host: example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n") + }; + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n"), + MockRead("Proxy-agent: Apache/2.2.8\r\n"), + MockRead("\r\n"), + // SocketStream::DoClose is run asynchronously. Socket can be read after + // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate + // server doesn't close the connection. + MockRead(true, ERR_IO_PENDING) + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + mock_socket_factory.AddSocketDataProvider(&data); + SSLSocketDataProvider ssl(false, ERR_SSL_PROTOCOL_ERROR); + mock_socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + scoped_ptr<SocketStreamEventRecorder> delegate( + new SocketStreamEventRecorder(&callback)); + delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, + base::Unretained(delegate.get()))); + + scoped_refptr<SocketStream> socket_stream( + new SocketStream(GURL("ws://example.com/demo"), delegate.get())); + + socket_stream->set_context(new TestURLRequestContext("https://myproxy:70")); + MockHostResolver host_resolver; + socket_stream->SetHostResolver(&host_resolver); + socket_stream->SetClientSocketFactory(&mock_socket_factory); + + socket_stream->Connect(); + + callback.WaitForResult(); + + const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); + ASSERT_EQ(3U, events.size()); + + EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, + events[0].event_type); + EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type); + EXPECT_EQ(net::ERR_SSL_PROTOCOL_ERROR, events[1].error_code); + EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[2].event_type); +} + +// Test a connection though a secure proxy. +TEST_F(SocketStreamTest, SecureProxyConnect) { + MockClientSocketFactory mock_socket_factory; + MockWrite data_writes[] = { + MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" + "Host: example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n") + }; + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n"), + MockRead("Proxy-agent: Apache/2.2.8\r\n"), + MockRead("\r\n"), + // SocketStream::DoClose is run asynchronously. Socket can be read after + // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate + // server doesn't close the connection. + MockRead(true, ERR_IO_PENDING) + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + mock_socket_factory.AddSocketDataProvider(&data); + SSLSocketDataProvider ssl(false, OK); + mock_socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + scoped_ptr<SocketStreamEventRecorder> delegate( + new SocketStreamEventRecorder(&callback)); + delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, + base::Unretained(delegate.get()))); + + scoped_refptr<SocketStream> socket_stream( + new SocketStream(GURL("ws://example.com/demo"), delegate.get())); + + socket_stream->set_context(new TestURLRequestContext("https://myproxy:70")); + MockHostResolver host_resolver; + socket_stream->SetHostResolver(&host_resolver); + socket_stream->SetClientSocketFactory(&mock_socket_factory); + + socket_stream->Connect(); + + callback.WaitForResult(); + + const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); + ASSERT_EQ(4U, events.size()); + + EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, + events[0].event_type); + EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); + EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[2].event_type); + EXPECT_EQ(net::ERR_ABORTED, events[2].error_code); + EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type); +} + } // namespace net |