diff options
author | cbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-03 00:54:30 +0000 |
---|---|---|
committer | cbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-03 00:54:30 +0000 |
commit | f45c1ee803dc888a806c6f8e0d3fd680a5407a85 (patch) | |
tree | b3c93351c1437f146e4b97ace4e2078f25b9c9a8 /net | |
parent | 783a516a3fa720df98d4df5bb4296971cdd9d80b (diff) | |
download | chromium_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.cc | 16 | ||||
-rw-r--r-- | net/http/http_auth_handler_mock.h | 15 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 37 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 133 |
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 |