summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-14 20:12:45 +0000
committercbentzel@chromium.org <cbentzel@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-14 20:12:45 +0000
commite5ae96a15b687fffe178eb8c4a7ea79a1ddd679c (patch)
tree4ffb7e20097a482248f465eef2a7540d79d58e8f
parentda5922762971a646407390f5c8d88a2447b2effc (diff)
downloadchromium_src-e5ae96a15b687fffe178eb8c4a7ea79a1ddd679c.zip
chromium_src-e5ae96a15b687fffe178eb8c4a7ea79a1ddd679c.tar.gz
chromium_src-e5ae96a15b687fffe178eb8c4a7ea79a1ddd679c.tar.bz2
Kerberos uses an SPN (Service Principal Name) to identify a server. This is typically in the form "HTTP/host:port", with the ":port" suffix being optional, and the "HTTP/" prefix is fixed regardless of whether the service is accessed over HTTP or HTTPS.
The issue this is fixing is that the URL host may be an incomplete domain name, a numerical address, or an alias for a canonical DNS name. By default, Chrome will skip adding the optional port to the SPN, and will use the canonical DNS name for the server (which may be the original server name if it is an A or AAAA record). This matches IE and Firefox's default behavior. Some intranets are set up so the original host name should be used rather than the canonical name. The canonical name resolution can be disabled with the --disable-spnego-cname-lookup command line flag. Some intranets are also set up so the optional port should be specified when it is non-standard (non 80 or 443). Use the --enable-spnego-port command line flag. BUG=29862 TEST=net_unittests.exe --gtest_filter="*CanonicalName*" Review URL: http://codereview.chromium.org/1535019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44526 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/io_thread.cc13
-rw-r--r--chrome/common/chrome_switches.cc11
-rw-r--r--chrome/common/chrome_switches.h2
-rw-r--r--net/base/address_list.cc2
-rw-r--r--net/http/http_auth_handler.cc9
-rw-r--r--net/http/http_auth_handler.h20
-rw-r--r--net/http/http_auth_handler_factory.h10
-rw-r--r--net/http/http_auth_handler_negotiate.h40
-rw-r--r--net/http/http_auth_handler_negotiate_posix.cc15
-rw-r--r--net/http/http_auth_handler_negotiate_win.cc118
-rw-r--r--net/http/http_auth_handler_ntlm.cc12
-rw-r--r--net/http/http_auth_handler_ntlm.h3
-rw-r--r--net/http/http_auth_handler_ntlm_win.cc2
-rw-r--r--net/http/http_auth_sspi_win.cc15
-rw-r--r--net/http/http_auth_sspi_win.h8
-rw-r--r--net/http/http_network_transaction.cc30
-rw-r--r--net/http/http_network_transaction.h4
-rw-r--r--net/http/http_network_transaction_unittest.cc198
18 files changed, 480 insertions, 32 deletions
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index 9ab03d4..30da91b 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -22,6 +22,7 @@
#include "net/base/network_change_notifier.h"
#include "net/http/http_auth_filter.h"
#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_auth_handler_negotiate.h"
#include "net/url_request/url_request.h"
namespace {
@@ -213,6 +214,18 @@ net::HttpAuthHandlerFactory* IOThread::CreateDefaultAuthHandlerFactory() {
registry_factory->SetFilter("negotiate", negotiate_filter);
}
+ // Configure the Negotiate settings for the Kerberos SPN.
+ // TODO(cbentzel): Read the related IE registry settings on Windows builds.
+ // TODO(cbentzel): Ugly use of static_cast here.
+ net::HttpAuthHandlerNegotiate::Factory* negotiate_factory =
+ static_cast<net::HttpAuthHandlerNegotiate::Factory*>(
+ registry_factory->GetSchemeFactory("negotiate"));
+ DCHECK(negotiate_factory);
+ if (command_line.HasSwitch(switches::kDisableAuthNegotiateCnameLookup))
+ negotiate_factory->set_disable_cname_lookup(true);
+ if (command_line.HasSwitch(switches::kEnableAuthNegotiatePort))
+ negotiate_factory->set_use_port(true);
+
return registry_factory;
}
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 8e95e65..a1725e9 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -91,6 +91,12 @@ const char kDisableApplicationCache[] = "disable-application-cache";
// mechanism.
const char kDisableAudio[] = "disable-audio";
+// Disable CNAME lookup of the host when generating the Kerberos SPN for a
+// Negotiate challenge. See HttpAuthHandlerNegotiate::CreateSPN
+// for more background.
+extern const char kDisableAuthNegotiateCnameLookup[] =
+ "disable-auth-negotiate-cname-lookup";
+
// Disable limits on the number of backing stores. Can prevent blinking for
// users with many windows/tabs and lots of memory.
const char kDisableBackingStoreLimit[] = "disable-backing-store-limit";
@@ -227,6 +233,11 @@ const char kDumpHistogramsOnExit[] = "dump-histograms-on-exit";
// Enables AeroPeek for each tab. (This switch only works on Windows 7).
const char kEnableAeroPeekTabs[] = "enable-aero-peek-tabs";
+// Enable the inclusion of non-standard ports when generating the Kerberos SPN
+// in response to a Negotiate challenge. See HttpAuthHandlerNegotiate::CreateSPN
+// for more background.
+extern const char kEnableAuthNegotiatePort[] = "enable-auth-negotiate-port";
+
// Enables the benchmarking extensions.
const char kEnableBenchmarking[] = "enable-benchmarking";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index e2383ca..ca48aa7 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -40,6 +40,7 @@ extern const char kDiagnostics[];
extern const char kDisableAltWinstation[];
extern const char kDisableApplicationCache[];
extern const char kDisableAudio[];
+extern const char kDisableAuthNegotiateCnameLookup[];
extern const char kDisableBackingStoreLimit[];
extern const char kDisableByteRangeSupport[];
extern const char kDisableCustomJumpList[];
@@ -80,6 +81,7 @@ extern const char kDnsPrefetchDisable[];
extern const char kDomAutomationController[];
extern const char kDumpHistogramsOnExit[];
extern const char kEnableAeroPeekTabs[];
+extern const char kEnableAuthNegotiatePort[];
extern const char kEnableBenchmarking[];
extern const char kEnableExperimentalExtensionApis[];
extern const char kEnableExperimentalWebGL[];
diff --git a/net/base/address_list.cc b/net/base/address_list.cc
index 55a5992..89c9496 100644
--- a/net/base/address_list.cc
+++ b/net/base/address_list.cc
@@ -135,7 +135,7 @@ int AddressList::GetPort() const {
bool AddressList::GetCanonicalName(std::string* canonical_name) const {
DCHECK(canonical_name);
- if (!data_->head || !data_->head->ai_canonname)
+ if (!data_ || !data_->head->ai_canonname)
return false;
canonical_name->assign(data_->head->ai_canonname);
return true;
diff --git a/net/http/http_auth_handler.cc b/net/http/http_auth_handler.cc
index e61de2f..7aa114e 100644
--- a/net/http/http_auth_handler.cc
+++ b/net/http/http_auth_handler.cc
@@ -5,6 +5,7 @@
#include "net/http/http_auth_handler.h"
#include "base/logging.h"
+#include "net/base/net_errors.h"
namespace net {
@@ -28,4 +29,12 @@ bool HttpAuthHandler::InitFromChallenge(
return ok;
}
+int HttpAuthHandler::ResolveCanonicalName(net::HostResolver* host_resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ NOTREACHED();
+ LOG(ERROR) << ErrorToString(ERR_NOT_IMPLEMENTED);
+ return ERR_NOT_IMPLEMENTED;
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h
index d2eae13..663e04a 100644
--- a/net/http/http_auth_handler.h
+++ b/net/http/http_auth_handler.h
@@ -8,10 +8,13 @@
#include <string>
#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
#include "net/http/http_auth.h"
namespace net {
+class BoundNetLog;
+class HostResolver;
class HttpRequestInfo;
class ProxyInfo;
@@ -81,6 +84,11 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
// TODO(cbentzel): Add a pointer to Firefox documentation about risk.
virtual bool SupportsDefaultCredentials() { return false; }
+ // Returns whether the canonical DNS name for the origin host needs to be
+ // resolved. The Negotiate auth scheme typically uses the canonical DNS
+ // name when constructing the Kerberos SPN.
+ virtual bool NeedsCanonicalName() { return false; }
+
// TODO(cbentzel): Separate providing credentials from generating the
// authentication token in the API.
@@ -103,6 +111,14 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
const ProxyInfo* proxy,
std::string* auth_token) = 0;
+ // Resolves the canonical name for the |origin_| host. The canonical
+ // name is used by the Negotiate scheme to generate a valid Kerberos
+ // SPN.
+ // The return value is a net error code.
+ virtual int ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
protected:
enum Property {
ENCRYPTS_IDENTITY = 1 << 0,
@@ -121,14 +137,14 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> {
// scheme_, realm_, score_, properties_
virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) = 0;
- // The lowercase auth-scheme {"basic", "digest", "ntlm", ...}
+ // The lowercase auth-scheme {"basic", "digest", "ntlm", "negotiate"}
std::string scheme_;
// The realm. Used by "basic" and "digest".
std::string realm_;
// The {scheme, host, port} for the authentication target. Used by "ntlm"
- // to construct the service principal name.
+ // and "negotiate" to construct the service principal name.
GURL origin_;
// The score for this challenge. Higher numbers are better.
diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h
index f677455..587bb30 100644
--- a/net/http/http_auth_handler_factory.h
+++ b/net/http/http_auth_handler_factory.h
@@ -105,6 +105,13 @@ class HttpAuthHandlerRegistryFactory : public HttpAuthHandlerFactory {
void RegisterSchemeFactory(const std::string& scheme,
HttpAuthHandlerFactory* factory);
+ // Retrieve the factory for the specified |scheme|. If no factory exists
+ // for the |scheme|, NULL is returned. The returned factory must not be
+ // deleted by the caller, and it is guaranteed to be valid until either
+ // a new factory is registered for the same scheme, or until this
+ // registry factory is destroyed.
+ HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const;
+
// Creates an auth handler by dispatching out to the registered factories
// based on the first token in |challenge|.
virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
@@ -113,9 +120,6 @@ class HttpAuthHandlerRegistryFactory : public HttpAuthHandlerFactory {
scoped_refptr<HttpAuthHandler>* handler);
private:
- // Retrieve the factory for the specified |scheme|
- HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const;
-
typedef std::map<std::string, HttpAuthHandlerFactory*> FactoryMap;
FactoryMap factory_map_;
diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h
index f34664c..3450fc4 100644
--- a/net/http/http_auth_handler_negotiate.h
+++ b/net/http/http_auth_handler_negotiate.h
@@ -9,6 +9,7 @@
#include <string>
+#include "net/base/address_list.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_auth_handler_factory.h"
@@ -18,6 +19,8 @@
namespace net {
+class SingleRequestHostResolver;
+
// Handler for WWW-Authenticate: Negotiate protocol.
//
// See http://tools.ietf.org/html/rfc4178 and http://tools.ietf.org/html/rfc4559
@@ -30,6 +33,22 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
Factory();
virtual ~Factory();
+ // |disable_cname_lookup()| and |set_disable_cname_lookup()| get/set whether
+ // the auth handlers generated by this factory should skip looking up the
+ // canonical DNS name of the the host that they are authenticating to when
+ // generating the SPN. The default value is false.
+ bool disable_cname_lookup() const { return disable_cname_lookup_; }
+ void set_disable_cname_lookup(bool disable_cname_lookup) {
+ disable_cname_lookup_ = disable_cname_lookup;
+ }
+
+ // |use_port()| and |set_use_port()| get/set whether the auth handlers
+ // generated by this factory should include the port number of the server
+ // they are authenticating to when constructing a Kerberos SPN. The default
+ // value is false.
+ bool use_port() const { return use_port_; }
+ void set_use_port(bool use_port) { use_port_ = use_port; }
+
virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
HttpAuth::Target target,
const GURL& origin,
@@ -47,6 +66,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
}
#endif // defined(OS_WIN)
private:
+ bool disable_cname_lookup_;
+ bool use_port_;
#if defined(OS_WIN)
ULONG max_token_length_;
bool first_creation_;
@@ -56,7 +77,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
};
#if defined(OS_WIN)
- HttpAuthHandlerNegotiate(SSPILibrary* sspi_library, ULONG max_token_length);
+ HttpAuthHandlerNegotiate(SSPILibrary* sspi_library, ULONG max_token_length,
+ bool disable_cname_lookup, bool use_port);
#else
HttpAuthHandlerNegotiate();
#endif
@@ -67,6 +89,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
virtual bool SupportsDefaultCredentials();
+ virtual bool NeedsCanonicalName();
+
virtual int GenerateAuthToken(const std::wstring& username,
const std::wstring& password,
const HttpRequestInfo* request,
@@ -77,6 +101,10 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
const ProxyInfo* proxy,
std::string* auth_token);
+ virtual int ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log);
+
protected:
virtual bool Init(HttpAuth::ChallengeTokenizer* challenge);
@@ -84,7 +112,17 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler {
~HttpAuthHandlerNegotiate();
#if defined(OS_WIN)
+ void OnResolveCanonicalName(int result);
+ std::wstring CreateSPN(const AddressList& address_list, const GURL& orign);
+
HttpAuthSSPI auth_sspi_;
+ AddressList address_list_;
+ scoped_ptr<SingleRequestHostResolver> single_resolve_;
+ CompletionCallback* user_callback_;
+ CompletionCallbackImpl<HttpAuthHandlerNegotiate> resolve_cname_callback_;
+ bool disable_cname_lookup_;
+ bool use_port_;
+ std::wstring spn_;
#endif
};
diff --git a/net/http/http_auth_handler_negotiate_posix.cc b/net/http/http_auth_handler_negotiate_posix.cc
index 2b96597..7b57853 100644
--- a/net/http/http_auth_handler_negotiate_posix.cc
+++ b/net/http/http_auth_handler_negotiate_posix.cc
@@ -61,7 +61,20 @@ int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken(
return ERR_NOT_IMPLEMENTED;
}
-HttpAuthHandlerNegotiate::Factory::Factory() {
+bool HttpAuthHandlerNegotiate::NeedsCanonicalName() {
+ return false;
+}
+
+int HttpAuthHandlerNegotiate::ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ NOTREACHED();
+ LOG(ERROR) << ErrorToString(ERR_NOT_IMPLEMENTED);
+ return ERR_NOT_IMPLEMENTED;
+}
+
+HttpAuthHandlerNegotiate::Factory::Factory()
+ : disable_cname_lookup_(false), use_port_(false) {
}
HttpAuthHandlerNegotiate::Factory::~Factory() {
diff --git a/net/http/http_auth_handler_negotiate_win.cc b/net/http/http_auth_handler_negotiate_win.cc
index fb849d4..6c6e767 100644
--- a/net/http/http_auth_handler_negotiate_win.cc
+++ b/net/http/http_auth_handler_negotiate_win.cc
@@ -5,14 +5,23 @@
#include "net/http/http_auth_handler_negotiate.h"
#include "base/logging.h"
+#include "net/base/address_family.h"
+#include "net/base/host_resolver.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth_filter.h"
namespace net {
HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(SSPILibrary* library,
- ULONG max_token_length)
- : auth_sspi_(library, "Negotiate", NEGOSSP_NAME, max_token_length) {
+ ULONG max_token_length,
+ bool disable_cname_lookup,
+ bool use_port)
+ : auth_sspi_(library, "Negotiate", NEGOSSP_NAME, max_token_length),
+ user_callback_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(resolve_cname_callback_(
+ this, &HttpAuthHandlerNegotiate::OnResolveCanonicalName)),
+ disable_cname_lookup_(disable_cname_lookup),
+ use_port_(use_port) {
}
HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
@@ -27,7 +36,7 @@ int HttpAuthHandlerNegotiate::GenerateAuthToken(
return auth_sspi_.GenerateAuthToken(
&username,
&password,
- origin_,
+ spn_,
request,
proxy,
auth_token);
@@ -55,6 +64,100 @@ bool HttpAuthHandlerNegotiate::SupportsDefaultCredentials() {
return true;
}
+bool HttpAuthHandlerNegotiate::NeedsCanonicalName() {
+ if (!spn_.empty())
+ return false;
+ if (disable_cname_lookup_) {
+ spn_ = CreateSPN(address_list_, origin_);
+ address_list_.Reset();
+ return false;
+ }
+ return true;
+}
+
+int HttpAuthHandlerNegotiate::ResolveCanonicalName(HostResolver* resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
+ DCHECK(!single_resolve_.get());
+ DCHECK(!disable_cname_lookup_);
+ DCHECK(callback);
+
+ HostResolver::RequestInfo info(origin_.host(), 0);
+ info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
+ single_resolve_.reset(new SingleRequestHostResolver(resolver));
+ int rv = single_resolve_->Resolve(info, &address_list_,
+ &resolve_cname_callback_,
+ net_log);
+ if (rv == ERR_IO_PENDING) {
+ user_callback_ = callback;
+ return rv;
+ }
+ OnResolveCanonicalName(rv);
+ // Always return OK. OnResolveCanonicalName logs the error code if not
+ // OK and attempts to use the original origin_ hostname rather than failing
+ // the auth attempt completely.
+ return OK;
+}
+
+void HttpAuthHandlerNegotiate::OnResolveCanonicalName(int result) {
+ if (result != OK) {
+ // Even in the error case, try to use origin_.host instead of
+ // passing the failure on to the caller.
+ LOG(INFO) << "Problem finding canonical name for SPN for host "
+ << origin_.host() << ": " << ErrorToString(result);
+ result = OK;
+ }
+ spn_ = CreateSPN(address_list_, origin_);
+ address_list_.Reset();
+ if (user_callback_) {
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(result);
+ }
+}
+
+std::wstring HttpAuthHandlerNegotiate::CreateSPN(
+ const AddressList& address_list, const GURL& origin) {
+ // Kerberos SPNs are in the form HTTP/<host>:<port>
+ // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
+ //
+ // However, reality differs from the specification. A good description of
+ // the problems can be found here:
+ // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
+ //
+ // Typically the <host> portion should be the canonical FQDN for the service.
+ // If this could not be resolved, the original hostname in the URL will be
+ // attempted instead. However, some intranets register SPNs using aliases
+ // for the same canonical DNS name to allow multiple web services to reside
+ // on the same host machine without requiring different ports. IE6 and IE7
+ // have hotpatches that allow the default behavior to be overridden.
+ // http://support.microsoft.com/kb/911149
+ // http://support.microsoft.com/kb/938305
+ //
+ // According to the spec, the <port> option should be included if it is a
+ // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
+ // historically browsers have not included the port, even on non-standard
+ // ports. IE6 required a hotpatch and a registry setting to enable
+ // including non-standard ports, and IE7 and IE8 also require the same
+ // registry setting, but no hotpatch. Firefox does not appear to have an
+ // option to include non-standard ports as of 3.6.
+ // http://support.microsoft.com/kb/908209
+ //
+ // Without any command-line flags, Chrome matches the behavior of Firefox
+ // and IE. Users can override the behavior so aliases are allowed and
+ // non-standard ports are included.
+ int port = origin.EffectiveIntPort();
+ std::string server;
+ if (!address_list.GetCanonicalName(&server))
+ server = origin.host();
+ if (port != 80 && port != 443 && use_port_) {
+ return ASCIIToWide(StringPrintf("HTTP/%s:%d", server.c_str(), port));
+ } else {
+ return ASCIIToWide(StringPrintf("HTTP/%s", server.c_str()));
+ }
+}
+
int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken(
const HttpRequestInfo* request,
const ProxyInfo* proxy,
@@ -62,14 +165,16 @@ int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken(
return auth_sspi_.GenerateAuthToken(
NULL, // username
NULL, // password
- origin_,
+ spn_,
request,
proxy,
auth_token);
}
HttpAuthHandlerNegotiate::Factory::Factory()
- : max_token_length_(0),
+ : disable_cname_lookup_(false),
+ use_port_(false),
+ max_token_length_(0),
first_creation_(true),
is_unsupported_(false),
sspi_library_(SSPILibrary::GetDefault()) {
@@ -96,7 +201,8 @@ int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
// TODO(cbentzel): Move towards model of parsing in the factory
// method and only constructing when valid.
scoped_refptr<HttpAuthHandler> tmp_handler(
- new HttpAuthHandlerNegotiate(sspi_library_, max_token_length_));
+ new HttpAuthHandlerNegotiate(sspi_library_, max_token_length_,
+ disable_cname_lookup_, use_port_));
if (!tmp_handler->InitFromChallenge(challenge, target, origin))
return ERR_INVALID_RESPONSE;
handler->swap(tmp_handler);
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index dab239d..ed3eb3a 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -9,6 +9,7 @@
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
namespace net {
@@ -22,7 +23,7 @@ int HttpAuthHandlerNTLM::GenerateAuthToken(
return auth_sspi_.GenerateAuthToken(
&username,
&password,
- origin_,
+ CreateSPN(origin_),
request,
proxy,
auth_token);
@@ -109,4 +110,13 @@ bool HttpAuthHandlerNTLM::ParseChallenge(
#endif // defined(NTLM_SSPI)
}
+// static
+std::wstring HttpAuthHandlerNTLM::CreateSPN(const GURL& origin) {
+ // The service principal name of the destination server. See
+ // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx
+ std::wstring target(L"HTTP/");
+ target.append(ASCIIToWide(GetHostAndPort(origin)));
+ return target;
+}
+
} // namespace net
diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h
index 542bf92..f027471 100644
--- a/net/http/http_auth_handler_ntlm.h
+++ b/net/http/http_auth_handler_ntlm.h
@@ -145,6 +145,9 @@ class HttpAuthHandlerNTLM : public HttpAuthHandler {
void** out_token,
uint32* out_token_len);
+ // Create an NTLM SPN to identify the |origin| server.
+ static std::wstring CreateSPN(const GURL& origin);
+
#if defined(NTLM_SSPI)
HttpAuthSSPI auth_sspi_;
#endif
diff --git a/net/http/http_auth_handler_ntlm_win.cc b/net/http/http_auth_handler_ntlm_win.cc
index baa7f86..04c53f3 100644
--- a/net/http/http_auth_handler_ntlm_win.cc
+++ b/net/http/http_auth_handler_ntlm_win.cc
@@ -47,7 +47,7 @@ int HttpAuthHandlerNTLM::GenerateDefaultAuthToken(
return auth_sspi_.GenerateAuthToken(
NULL, // username
NULL, // password
- origin_,
+ CreateSPN(origin_),
request,
proxy,
auth_token);
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index 5349e76..1f3165d 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -12,7 +12,6 @@
#include "base/singleton.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
-#include "net/base/net_util.h"
#include "net/http/http_auth.h"
namespace net {
@@ -162,7 +161,7 @@ bool HttpAuthSSPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) {
int HttpAuthSSPI::GenerateAuthToken(const std::wstring* username,
const std::wstring* password,
- const GURL& origin,
+ const std::wstring& spn,
const HttpRequestInfo* request,
const ProxyInfo* proxy,
std::string* auth_token) {
@@ -178,7 +177,7 @@ int HttpAuthSSPI::GenerateAuthToken(const std::wstring* username,
void* out_buf;
int out_buf_len;
int rv = GetNextSecurityToken(
- origin,
+ spn,
static_cast<void *>(const_cast<char *>(
decoded_server_auth_token_.c_str())),
decoded_server_auth_token_.length(),
@@ -223,7 +222,7 @@ int HttpAuthSSPI::OnFirstRound(const std::wstring* username,
}
int HttpAuthSSPI::GetNextSecurityToken(
- const GURL& origin,
+ const std::wstring& spn,
const void * in_token,
int in_token_len,
void** out_token,
@@ -269,17 +268,11 @@ int HttpAuthSSPI::GetNextSecurityToken(
if (!out_buffer.pvBuffer)
return ERR_OUT_OF_MEMORY;
- // The service principal name of the destination server. See
- // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx
- std::wstring target(L"HTTP/");
- target.append(ASCIIToWide(GetHostAndPort(origin)));
- wchar_t* target_name = const_cast<wchar_t*>(target.c_str());
-
// This returns a token that is passed to the remote server.
status = library_->InitializeSecurityContext(
&cred_, // phCredential
ctxt_ptr, // phContext
- target_name, // pszTargetName
+ const_cast<wchar_t *>(spn.c_str()), // pszTargetName
0, // fContextReq
0, // Reserved1 (must be 0)
SECURITY_NATIVE_DREP, // TargetDataRep
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
index c925920..a6fb49e 100644
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -18,8 +18,6 @@
#include "net/http/http_auth.h"
-class GURL;
-
namespace net {
class HttpRequestInfo;
@@ -89,12 +87,14 @@ class HttpAuthSSPI {
// Generates an authentication token.
// The return value is an error code. If it's not |OK|, the value of
// |*auth_token| is unspecified.
+ // |spn| is the Service Principal Name of the server that the token is
+ // being generated for.
// If this is the first round of a multiple round scheme, credentials are
// obtained using |*username| and |*password|. If |username| and |password|
// are NULL, the default credentials are used instead.
int GenerateAuthToken(const std::wstring* username,
const std::wstring* password,
- const GURL& origin,
+ const std::wstring& spn,
const HttpRequestInfo* request,
const ProxyInfo* proxy,
std::string* auth_token);
@@ -104,7 +104,7 @@ class HttpAuthSSPI {
const std::wstring* password);
int GetNextSecurityToken(
- const GURL& origin,
+ const std::wstring& spn,
const void* in_token,
int in_token_len,
void** out_token,
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 467f63c..9078f35 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -574,6 +574,12 @@ int HttpNetworkTransaction::DoLoop(int result) {
TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS);
break;
+ case STATE_RESOLVE_CANONICAL_NAME:
+ rv = DoResolveCanonicalName();
+ break;
+ case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
+ rv = DoResolveCanonicalNameComplete(rv);
+ break;
case STATE_READ_BODY:
DCHECK_EQ(OK, rv);
TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
@@ -1116,6 +1122,23 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
return OK;
}
+int HttpNetworkTransaction::DoResolveCanonicalName() {
+ HttpAuthHandler* auth_handler = auth_handler_[pending_auth_target_];
+ DCHECK(auth_handler);
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
+ return auth_handler->ResolveCanonicalName(session_->host_resolver(),
+ &io_callback_, net_log_);
+}
+
+int HttpNetworkTransaction::DoResolveCanonicalNameComplete(int result) {
+ // The STATE_RESOLVE_CANONICAL_NAME state ends the Start sequence when the
+ // canonical name of the server needs to be determined. Normally
+ // DoReadHeadersComplete completes the sequence. The next state is
+ // intentionally not set as it should be STATE_NONE;
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return result;
+}
+
int HttpNetworkTransaction::DoReadBody() {
DCHECK(read_buf_);
DCHECK_GT(read_buf_len_, 0);
@@ -1917,6 +1940,13 @@ int HttpNetworkTransaction::HandleAuthChallenge() {
// pass the challenge information back to the client.
PopulateAuthChallenge(target, auth_origin);
}
+
+ // SPN determination (for Negotiate) requires a DNS lookup to find the
+ // canonical name. This needs to be done asynchronously to prevent blocking
+ // the IO thread.
+ if (auth_handler_[target]->NeedsCanonicalName())
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME;
+
return OK;
}
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 113c0b5..220e789 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -84,6 +84,8 @@ class HttpNetworkTransaction : public HttpTransaction {
STATE_SEND_REQUEST_COMPLETE,
STATE_READ_HEADERS,
STATE_READ_HEADERS_COMPLETE,
+ STATE_RESOLVE_CANONICAL_NAME,
+ STATE_RESOLVE_CANONICAL_NAME_COMPLETE,
STATE_READ_BODY,
STATE_READ_BODY_COMPLETE,
STATE_DRAIN_BODY_FOR_AUTH_RESTART,
@@ -130,6 +132,8 @@ class HttpNetworkTransaction : public HttpTransaction {
int DoSendRequestComplete(int result);
int DoReadHeaders();
int DoReadHeadersComplete(int result);
+ int DoResolveCanonicalName();
+ int DoResolveCanonicalNameComplete(int result);
int DoReadBody();
int DoReadBodyComplete(int result);
int DoDrainBodyForAuthRestart();
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index aa18df5..b9458d5 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -65,7 +65,6 @@ ProxyService* CreateFixedProxyService(const std::string& proxy) {
return ProxyService::CreateFixed(proxy_config);
}
-
HttpNetworkSession* CreateSession(SessionDependencies* session_deps) {
return new HttpNetworkSession(NULL,
session_deps->host_resolver,
@@ -4679,4 +4678,201 @@ TEST_F(HttpNetworkTransactionTest, FailNpnSpdyAndFallback) {
EXPECT_EQ("hello world", response_data);
}
+// MockAuthHandlerCanonical is used by the ResolveCanonicalName
+// HttpNetworkTransaction unit test below. Callers set up expectations for
+// whether the canonical name needs to be resolved.
+class MockAuthHandlerCanonical : public HttpAuthHandler {
+ public:
+ enum Resolve {
+ RESOLVE_INIT,
+ RESOLVE_SKIP,
+ RESOLVE_SYNC,
+ RESOLVE_ASYNC,
+ RESOLVE_TESTED,
+ };
+
+ MockAuthHandlerCanonical() : resolve_(RESOLVE_INIT), user_callback_(NULL) {}
+ virtual ~MockAuthHandlerCanonical() {}
+
+ void SetResolveExpectation(Resolve resolve) {
+ EXPECT_EQ(RESOLVE_INIT, resolve_);
+ resolve_ = resolve;
+ }
+
+ void ResetResolveExpectation() {
+ EXPECT_EQ(RESOLVE_TESTED, resolve_);
+ resolve_ = RESOLVE_INIT;
+ }
+
+ virtual bool NeedsCanonicalName() {
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ case RESOLVE_ASYNC:
+ return true;
+ case RESOLVE_SKIP:
+ resolve_ = RESOLVE_TESTED;
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ virtual int ResolveCanonicalName(HostResolver* host_resolver,
+ CompletionCallback* callback,
+ const BoundNetLog& net_log) {
+ EXPECT_NE(RESOLVE_TESTED, resolve_);
+ int rv = OK;
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ resolve_ = RESOLVE_TESTED;
+ break;
+ case RESOLVE_ASYNC:
+ EXPECT_TRUE(user_callback_ == NULL);
+ rv = ERR_IO_PENDING;
+ user_callback_ = callback;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(
+ this, &MockAuthHandlerCanonical::OnResolveCanonicalName));
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return rv;
+ }
+
+ void OnResolveCanonicalName() {
+ EXPECT_EQ(RESOLVE_ASYNC, resolve_);
+ EXPECT_TRUE(user_callback_ != NULL);
+ resolve_ = RESOLVE_TESTED;
+ CompletionCallback* callback = user_callback_;
+ user_callback_ = NULL;
+ callback->Run(OK);
+ }
+
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) {
+ scheme_ = "mock";
+ score_ = 1;
+ properties_ = 0;
+ return true;
+ }
+
+ virtual int GenerateAuthToken(const std::wstring& username,
+ const std::wstring& password,
+ const HttpRequestInfo* request,
+ const ProxyInfo* proxy,
+ std::string* auth_token) {
+ auth_token->assign("Mock AUTH myserver.example.com");
+ return OK;
+ }
+
+ virtual int GenerateDefaultAuthToken(const HttpRequestInfo* request,
+ const ProxyInfo* proxy,
+ std::string* auth_token) {
+ auth_token->assign("Mock DEFAULT_AUTH myserver.example.com");
+ return OK;
+ }
+
+ // The Factory class simply returns the same handler each time
+ // CreateAuthHandler is called.
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory() {}
+ virtual ~Factory() {}
+
+ void set_mock_handler(MockAuthHandlerCanonical* mock_handler) {
+ mock_handler_ = mock_handler;
+ }
+ MockAuthHandlerCanonical* mock_handler() const {
+ return mock_handler_.get();
+ }
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ scoped_refptr<HttpAuthHandler>* handler) {
+ *handler = mock_handler_;
+ return OK;
+ }
+
+ private:
+ scoped_refptr<MockAuthHandlerCanonical> mock_handler_;
+ };
+
+ private:
+ Resolve resolve_;
+ CompletionCallback* user_callback_;
+};
+
+// Tests that ResolveCanonicalName is handled correctly by the
+// HttpNetworkTransaction.
+TEST_F(HttpNetworkTransactionTest, ResolveCanonicalName) {
+ SessionDependencies session_deps;
+ scoped_refptr<MockAuthHandlerCanonical> auth_handler(
+ new MockAuthHandlerCanonical());
+ auth_handler->Init(NULL);
+ MockAuthHandlerCanonical::Factory* auth_factory(
+ new MockAuthHandlerCanonical::Factory());
+ auth_factory->set_mock_handler(auth_handler);
+ session_deps.http_auth_handler_factory.reset(auth_factory);
+
+ for (int i = 0; i < 2; ++i) {
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(CreateSession(&session_deps)));
+
+ // Set up expectations for this pass of the test. Many of the EXPECT calls
+ // are contained inside the MockAuthHandlerCanonical codebase in response to
+ // the expectations.
+ MockAuthHandlerCanonical::Resolve resolve = (i == 0) ?
+ MockAuthHandlerCanonical::RESOLVE_SYNC :
+ MockAuthHandlerCanonical::RESOLVE_ASYNC;
+ auth_handler->SetResolveExpectation(resolve);
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://myserver/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: myserver\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Mock myserver.example.com\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 14\r\n\r\n"),
+ MockRead("Unauthorized\r\n"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps.socket_factory.AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1, NULL);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt is set after the canonical name is resolved.
+ // If it isn't present or is incorrect, it indicates that the scheme
+ // did not complete correctly.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ EXPECT_EQ(L"myserver:80", response->auth_challenge->host_and_port);
+ EXPECT_EQ(L"", response->auth_challenge->realm);
+ EXPECT_EQ(L"mock", response->auth_challenge->scheme);
+ auth_handler->ResetResolveExpectation();
+ }
+}
+
} // namespace net