diff options
author | cbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 11:27:02 +0000 |
---|---|---|
committer | cbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 11:27:02 +0000 |
commit | 6cc4218f3320d1ed11553c4a89a360a8db143ca7 (patch) | |
tree | 49bd71667097cbfae482321451df754afd643a18 /net | |
parent | b05938fdfa5c1d830be859d4339d3992c6d12410 (diff) | |
download | chromium_src-6cc4218f3320d1ed11553c4a89a360a8db143ca7.zip chromium_src-6cc4218f3320d1ed11553c4a89a360a8db143ca7.tar.gz chromium_src-6cc4218f3320d1ed11553c4a89a360a8db143ca7.tar.bz2 |
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=Run with --use-spdy=npn, force connection through a digest authenticating proxy, and browse a site which advertises Alternate-Protocol through http URLs.
Review URL: http://codereview.chromium.org/3028021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54528 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 | 39 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 142 | ||||
-rw-r--r-- | net/socket/socket_test_util.cc | 1 |
5 files changed, 200 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 aa557db..a3624f3 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 //----------------------------------------------------------------------------- @@ -682,15 +693,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; } } @@ -779,9 +782,25 @@ int HttpNetworkTransaction::DoInitConnection() { request_->referrer, disable_resolver_cache); if (proxy_info_.is_http()) { + 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); + } + } establishing_tunnel_ = using_ssl_; http_proxy_params = new HttpProxySocketParams(proxy_tcp_params, - request_->url, endpoint_, + authentication_url, + endpoint_, session_, using_ssl_); } else { DCHECK(proxy_info_.is_socks()); diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 5714543..ea087d0 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -6373,4 +6373,146 @@ 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(false, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + 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. + // After the failure, a tunnel is established to www.google.com using + // Proxy-Authorization headers. There is then a SPDY request round. + // + // NOTE: Despite the "Proxy-Connection: Close", these are done on the + // same MockTCPClientSocket since the underlying HttpNetworkClientSocket + // does a Disconnect and Connect on the same socket, rather than trying + // to obtain a new one. + // + // NOTE: Originally, the proxy response to the second CONNECT request + // simply 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_2[] = { + // First connection attempt without Proxy-Authorization. + 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"), + + // Second connection attempt with Proxy-Authorization. + 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), + }; + const char kRejectConnectResponse[] = ("HTTP/1.1 407 Unauthorized\r\n" + "Proxy-Authenticate: Mock\r\n" + "Proxy-Connection: close\r\n" + "\r\n"); + const char kAcceptConnectResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; + MockRead data_reads_2[] = { + // First connection attempt fails + MockRead(false, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, 1), + MockRead(true, kRejectConnectResponse, + arraysize(kRejectConnectResponse) - 1, 1), + + // Second connection attempt passes + MockRead(true, kAcceptConnectResponse, + arraysize(kAcceptConnectResponse) -1, 4), + + // SPDY response + CreateMockRead(*resp.get(), 6), + CreateMockRead(*data.get(), 6), + MockRead(true, 0, 0, 6), + }; + scoped_refptr<OrderedSocketData> data_2( + new OrderedSocketData(data_reads_2, arraysize(data_reads_2), + data_writes_2, arraysize(data_writes_2))); + + 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.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 diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index ce7f3fc06..142a39e 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc @@ -187,6 +187,7 @@ int MockTCPClientSocket::Connect(net::CompletionCallback* callback) { if (connected_) return net::OK; connected_ = true; + peer_closed_connection_ = false; if (data_->connect_data().async) { RunCallbackAsync(callback, data_->connect_data().result); return net::ERR_IO_PENDING; |