summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorcbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-03 00:54:30 +0000
committercbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-03 00:54:30 +0000
commitf45c1ee803dc888a806c6f8e0d3fd680a5407a85 (patch)
treeb3c93351c1437f146e4b97ace4e2078f25b9c9a8 /net
parent783a516a3fa720df98d4df5bb4296971cdd9d80b (diff)
downloadchromium_src-f45c1ee803dc888a806c6f8e0d3fd680a5407a85.zip
chromium_src-f45c1ee803dc888a806c6f8e0d3fd680a5407a85.tar.gz
chromium_src-f45c1ee803dc888a806c6f8e0d3fd680a5407a85.tar.bz2
SPDY Alternate-Protocol works through a Digest authentication proxy.
Digest authentication uses a uri field to prevent replay attacks. When authenticating to an HTTP proxy to establish a secure tunnel (via CONNECT), the uri should be the hostname of the server and the destination port, such as "www.example.com:443". When authenticating to an HTTP proxy for a non-secure content, the uri should be the path at the server, i.e. "/index.html". If the site we are trying to connect to previously advertised "Alternate-Protocol: 443:spdy-npn/1" a request to "http://www.example.com" will be attempted on a secure port. However, the URL passed into the digest authenticator was an unsecure one, and it decided to have a uri in the form "/index.html" rather than the correct "www.example.com:443". This causes persistent failure with the password and many password prompts. BUG=49865 TEST=net_unittests --gtest_filter="HttpNetworkTransactionTest.SpdyAlternateProtocolThroughProxy" Review URL: http://codereview.chromium.org/3040032 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54647 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_auth_handler_mock.cc16
-rw-r--r--net/http/http_auth_handler_mock.h15
-rw-r--r--net/http/http_network_transaction.cc37
-rw-r--r--net/http/http_network_transaction_unittest.cc133
4 files changed, 188 insertions, 13 deletions
diff --git a/net/http/http_auth_handler_mock.cc b/net/http/http_auth_handler_mock.cc
index 983105b..9f0011d 100644
--- a/net/http/http_auth_handler_mock.cc
+++ b/net/http/http_auth_handler_mock.cc
@@ -6,6 +6,7 @@
#include "base/message_loop.h"
#include "net/base/net_errors.h"
+#include "net/http/http_request_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
@@ -82,6 +83,7 @@ int HttpAuthHandlerMock::GenerateAuthTokenImpl(const string16* username,
CompletionCallback* callback,
std::string* auth_token) {
first_round_ = false;
+ request_url_ = request->url;
if (generate_async_) {
EXPECT_TRUE(user_callback_ == NULL);
EXPECT_TRUE(auth_token_ == NULL);
@@ -118,6 +120,14 @@ void HttpAuthHandlerMock::OnGenerateAuthToken() {
callback->Run(generate_rv_);
}
+HttpAuthHandlerMock::Factory::Factory()
+ : do_init_from_challenge_(false) {
+ // TODO(cbentzel): Default do_init_from_challenge_ to true.
+}
+
+HttpAuthHandlerMock::Factory::~Factory() {
+}
+
void HttpAuthHandlerMock::Factory::set_mock_handler(
HttpAuthHandler* handler, HttpAuth::Target target) {
EXPECT_TRUE(handlers_[target].get() == NULL);
@@ -134,7 +144,11 @@ int HttpAuthHandlerMock::Factory::CreateAuthHandler(
scoped_ptr<HttpAuthHandler>* handler) {
if (!handlers_[target].get())
return ERR_UNEXPECTED;
- handler->swap(handlers_[target]);
+ scoped_ptr<HttpAuthHandler> tmp_handler(handlers_[target].release());
+ if (do_init_from_challenge_ &&
+ !tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
return OK;
}
diff --git a/net/http/http_auth_handler_mock.h b/net/http/http_auth_handler_mock.h
index f9a676c..a6b0d48 100644
--- a/net/http/http_auth_handler_mock.h
+++ b/net/http/http_auth_handler_mock.h
@@ -10,6 +10,7 @@
#include "base/string16.h"
#include "base/task.h"
+#include "googleurl/src/gurl.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_auth_handler_factory.h"
@@ -46,15 +47,23 @@ class HttpAuthHandlerMock : public HttpAuthHandler {
connection_based_ = connection_based;
}
+ const GURL& request_url() const {
+ return request_url_;
+ }
+
// The Factory class simply returns the same handler each time
// CreateAuthHandler is called.
class Factory : public HttpAuthHandlerFactory {
public:
- Factory() {}
- virtual ~Factory() {}
+ Factory();
+ virtual ~Factory();
void set_mock_handler(HttpAuthHandler* handler, HttpAuth::Target target);
+ void set_do_init_from_challenge(bool do_init_from_challenge) {
+ do_init_from_challenge_ = do_init_from_challenge;
+ }
+
virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
HttpAuth::Target target,
const GURL& origin,
@@ -65,6 +74,7 @@ class HttpAuthHandlerMock : public HttpAuthHandler {
private:
scoped_ptr<HttpAuthHandler> handlers_[HttpAuth::AUTH_NUM_TARGETS];
+ bool do_init_from_challenge_;
};
protected:
@@ -89,6 +99,7 @@ class HttpAuthHandlerMock : public HttpAuthHandler {
std::string* auth_token_;
bool first_round_;
bool connection_based_;
+ GURL request_url_;
};
} // namespace net
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index fdf8e9f..09a4772 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -194,6 +194,17 @@ void ProcessAlternateProtocol(const HttpResponseHeaders& headers,
alternate_protocols->SetAlternateProtocolFor(host_port, port, protocol);
}
+GURL UpgradeUrlToHttps(const GURL& original_url) {
+ GURL::Replacements replacements;
+ // new_sheme and new_port need to be in scope here because GURL::Replacements
+ // references the memory contained by them directly.
+ const std::string new_scheme = "https";
+ const std::string new_port = IntToString(443);
+ replacements.SetSchemeStr(new_scheme);
+ replacements.SetPortStr(new_port);
+ return original_url.ReplaceComponents(replacements);
+}
+
} // namespace
//-----------------------------------------------------------------------------
@@ -674,15 +685,7 @@ int HttpNetworkTransaction::DoResolveProxy() {
endpoint_.set_port(alternate.port);
alternate_protocol_ = alternate.protocol;
alternate_protocol_mode_ = kUsingAlternateProtocol;
-
- url_canon::Replacements<char> replacements;
- replacements.SetScheme("https",
- url_parse::Component(0, strlen("https")));
- const std::string port_str = base::IntToString(endpoint_.port());
- replacements.SetPort(port_str.c_str(),
- url_parse::Component(0, port_str.size()));
- alternate_endpoint_url =
- curr_endpoint_url->ReplaceComponents(replacements);
+ alternate_endpoint_url = UpgradeUrlToHttps(*curr_endpoint_url);
curr_endpoint_url = &alternate_endpoint_url;
}
}
@@ -782,12 +785,26 @@ int HttpNetworkTransaction::DoInitConnection() {
if (proxy_info_.is_http()) {
scoped_refptr<HttpAuthController> http_proxy_auth;
+ GURL authentication_url = request_->url;
if (using_ssl_) {
+ if (!authentication_url.SchemeIs("https")) {
+ // If a proxy tunnel connection needs to be established due to
+ // an Alternate-Protocol, the URL needs to be changed to indicate
+ // https or digest authentication attempts will fail.
+ // For example, suppose the initial request was for
+ // "http://www.example.com/index.html". If this is an SSL
+ // upgrade due to alternate protocol, the digest authorization
+ // should have a uri="www.example.com:443" field rather than a
+ // "/index.html" entry, even though the original request URL has not
+ // changed.
+ authentication_url = UpgradeUrlToHttps(authentication_url);
+ }
http_proxy_auth = auth_controllers_[HttpAuth::AUTH_PROXY];
establishing_tunnel_ = true;
}
http_proxy_params = new HttpProxySocketParams(proxy_tcp_params,
- request_->url, endpoint_,
+ authentication_url,
+ endpoint_,
http_proxy_auth,
using_ssl_);
} else {
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index b5f614c..72fa8aa 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -6268,4 +6268,137 @@ TEST_F(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) {
HttpNetworkTransaction::SetUseAlternateProtocols(false);
}
+TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) {
+ // This test ensures that the URL passed into the proxy is upgraded
+ // to https when doing an Alternate Protocol upgrade.
+ HttpNetworkTransaction::SetUseAlternateProtocols(true);
+ HttpNetworkTransaction::SetNextProtos(
+ "\x08http/1.1\x07http1.1\x06spdy/2\x04spdy");
+
+ SessionDependencies session_deps(CreateFixedProxyService("myproxy:70"));
+ HttpAuthHandlerMock::Factory* auth_factory =
+ new HttpAuthHandlerMock::Factory();
+ HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
+ auth_factory->set_mock_handler(auth_handler, HttpAuth::AUTH_PROXY);
+ auth_factory->set_do_init_from_challenge(true);
+ session_deps.http_auth_handler_factory.reset(auth_factory);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com");
+ request.load_flags = 0;
+
+ // First round goes unauthenticated through the proxy.
+ MockWrite data_writes_1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads_1[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Alternate-Protocol: 443:npn-spdy/2\r\n"
+ "Proxy-Connection: close\r\n"
+ "\r\n"),
+ };
+ StaticSocketDataProvider data_1(data_reads_1, arraysize(data_reads_1),
+ data_writes_1, arraysize(data_writes_1));
+
+ // Second round tries to tunnel to www.google.com due to the
+ // Alternate-Protocol announcement in the first round. It fails due
+ // to a proxy authentication challenge.
+ MockWrite data_writes_2[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads_2[] = {
+ MockRead("HTTP/1.0 407 Unauthorized\r\n"
+ "Proxy-Authenticate: Mock\r\n"
+ "Proxy-Connection: close\r\n"
+ "\r\n"),
+ };
+ StaticSocketDataProvider data_2(data_reads_2, arraysize(data_reads_2),
+ data_writes_2, arraysize(data_writes_2));
+
+ // Third round establishes a tunnel to www.google.com due to the
+ // Alternate-Protocol announcement in the first round, and does a SPDY
+ // request round.
+ // TODO(cbentzel): Originally, this just returned another 407 so the unit test
+ // could skip the SSL connection establishment and SPDY framing issues. Alas,
+ // the retry-http-when-alternate-protocol fails logic kicks in, which was more
+ // complicated to set up expectations for than the SPDY session.
+ scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+ scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
+
+ MockWrite data_writes_3[] = {
+ // TUNNEL connection established
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n"
+ "\r\n"),
+ // SPDY request
+ CreateMockWrite(*req), // 3
+ };
+
+ const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n";
+ MockRead data_reads_3[] = {
+ // Proxy response
+ MockRead(true, kCONNECTResponse, arraysize(kCONNECTResponse) - 1, 1),
+ // SPDY response.
+ CreateMockRead(*resp.get(), 4),
+ CreateMockRead(*data.get(), 4),
+ MockRead(true, 0, 0, 4),
+ };
+ scoped_refptr<OrderedSocketData> data_3(
+ new OrderedSocketData(data_reads_3, arraysize(data_reads_3),
+ data_writes_3, arraysize(data_writes_3)));
+
+ SSLSocketDataProvider ssl(true, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "spdy/2";
+ ssl.was_npn_negotiated = true;
+
+ session_deps.socket_factory.AddSocketDataProvider(&data_1);
+ session_deps.socket_factory.AddSocketDataProvider(&data_2);
+ session_deps.socket_factory.AddSocketDataProvider(data_3.get());
+ session_deps.socket_factory.AddSSLSocketDataProvider(&ssl);
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
+
+ // First round should work and provide the Alternate-Protocol state.
+ TestCompletionCallback callback_1;
+ scoped_ptr<HttpTransaction> trans_1(new HttpNetworkTransaction(session));
+ int rv = trans_1->Start(&request, &callback_1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_1.WaitForResult());
+
+ // Second round should attempt a tunnel connect and get an auth challenge.
+ TestCompletionCallback callback_2;
+ scoped_ptr<HttpTransaction> trans_2(new HttpNetworkTransaction(session));
+ rv = trans_2->Start(&request, &callback_2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_2.WaitForResult());
+ const HttpResponseInfo* response = trans_2->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+
+ // Restart with auth. Tunnel should work and response received.
+ TestCompletionCallback callback_3;
+ rv = trans_2->RestartWithAuth(kFoo, kBar, &callback_3);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_3.WaitForResult());
+
+ // After all that work, these two lines (or actually, just the scheme) are
+ // what this test is all about. Make sure it happens correctly.
+ const GURL& request_url = auth_handler->request_url();
+ EXPECT_EQ("https", request_url.scheme());
+ EXPECT_EQ("www.google.com", request_url.host());
+
+ HttpNetworkTransaction::SetNextProtos("");
+ HttpNetworkTransaction::SetUseAlternateProtocols(false);
+}
+
} // namespace net