1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/http/http_auth_handler_negotiate.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/strings/stringprintf.h"
11 #include "net/base/address_family.h"
12 #include "net/base/net_errors.h"
13 #include "net/dns/host_resolver.h"
14 #include "net/dns/single_request_host_resolver.h"
15 #include "net/http/http_auth_filter.h"
16 #include "net/http/url_security_manager.h"
20 HttpAuthHandlerNegotiate::Factory::Factory()
21 : disable_cname_lookup_(false),
26 first_creation_(true),
28 is_unsupported_(false) {
31 HttpAuthHandlerNegotiate::Factory::~Factory() {
34 void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
35 HostResolver
* resolver
) {
39 int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
40 HttpAuthChallengeTokenizer
* challenge
,
41 HttpAuth::Target target
,
44 int digest_nonce_count
,
45 const BoundNetLog
& net_log
,
46 scoped_ptr
<HttpAuthHandler
>* handler
) {
48 if (is_unsupported_
|| reason
== CREATE_PREEMPTIVE
)
49 return ERR_UNSUPPORTED_AUTH_SCHEME
;
50 if (max_token_length_
== 0) {
51 int rv
= DetermineMaxTokenLength(auth_library_
.get(), NEGOSSP_NAME
,
53 if (rv
== ERR_UNSUPPORTED_AUTH_SCHEME
)
54 is_unsupported_
= true;
58 // TODO(cbentzel): Move towards model of parsing in the factory
59 // method and only constructing when valid.
60 scoped_ptr
<HttpAuthHandler
> tmp_handler(
61 new HttpAuthHandlerNegotiate(auth_library_
.get(), max_token_length_
,
62 url_security_manager(), resolver_
,
63 disable_cname_lookup_
, use_port_
));
64 if (!tmp_handler
->InitFromChallenge(challenge
, target
, origin
, net_log
))
65 return ERR_INVALID_RESPONSE
;
66 handler
->swap(tmp_handler
);
68 #elif defined(OS_POSIX)
70 return ERR_UNSUPPORTED_AUTH_SCHEME
;
71 if (!auth_library_
->Init()) {
72 is_unsupported_
= true;
73 return ERR_UNSUPPORTED_AUTH_SCHEME
;
75 // TODO(ahendrickson): Move towards model of parsing in the factory
76 // method and only constructing when valid.
77 scoped_ptr
<HttpAuthHandler
> tmp_handler(
78 new HttpAuthHandlerNegotiate(auth_library_
.get(), url_security_manager(),
79 resolver_
, disable_cname_lookup_
,
81 if (!tmp_handler
->InitFromChallenge(challenge
, target
, origin
, net_log
))
82 return ERR_INVALID_RESPONSE
;
83 handler
->swap(tmp_handler
);
88 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
89 AuthLibrary
* auth_library
,
91 ULONG max_token_length
,
93 URLSecurityManager
* url_security_manager
,
94 HostResolver
* resolver
,
95 bool disable_cname_lookup
,
98 : auth_system_(auth_library
, "Negotiate", NEGOSSP_NAME
, max_token_length
),
99 #elif defined(OS_POSIX)
100 : auth_system_(auth_library
, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC
),
102 disable_cname_lookup_(disable_cname_lookup
),
105 already_called_(false),
106 has_credentials_(false),
108 next_state_(STATE_NONE
),
109 url_security_manager_(url_security_manager
) {
112 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
115 std::string
HttpAuthHandlerNegotiate::CreateSPN(
116 const AddressList
& address_list
, const GURL
& origin
) {
117 // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
118 // and in the form HTTP@<host>:<port> through GSSAPI
119 // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
121 // However, reality differs from the specification. A good description of
122 // the problems can be found here:
123 // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
125 // Typically the <host> portion should be the canonical FQDN for the service.
126 // If this could not be resolved, the original hostname in the URL will be
127 // attempted instead. However, some intranets register SPNs using aliases
128 // for the same canonical DNS name to allow multiple web services to reside
129 // on the same host machine without requiring different ports. IE6 and IE7
130 // have hotpatches that allow the default behavior to be overridden.
131 // http://support.microsoft.com/kb/911149
132 // http://support.microsoft.com/kb/938305
134 // According to the spec, the <port> option should be included if it is a
135 // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
136 // historically browsers have not included the port, even on non-standard
137 // ports. IE6 required a hotpatch and a registry setting to enable
138 // including non-standard ports, and IE7 and IE8 also require the same
139 // registry setting, but no hotpatch. Firefox does not appear to have an
140 // option to include non-standard ports as of 3.6.
141 // http://support.microsoft.com/kb/908209
143 // Without any command-line flags, Chrome matches the behavior of Firefox
144 // and IE. Users can override the behavior so aliases are allowed and
145 // non-standard ports are included.
146 int port
= origin
.EffectiveIntPort();
147 std::string server
= address_list
.canonical_name();
149 server
= origin
.host();
151 static const char kSpnSeparator
= '/';
152 #elif defined(OS_POSIX)
153 static const char kSpnSeparator
= '@';
155 if (port
!= 80 && port
!= 443 && use_port_
) {
156 return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator
, server
.c_str(),
159 return base::StringPrintf("HTTP%c%s", kSpnSeparator
, server
.c_str());
163 HttpAuth::AuthorizationResult
HttpAuthHandlerNegotiate::HandleAnotherChallenge(
164 HttpAuthChallengeTokenizer
* challenge
) {
165 return auth_system_
.ParseChallenge(challenge
);
168 // Require identity on first pass instead of second.
169 bool HttpAuthHandlerNegotiate::NeedsIdentity() {
170 return auth_system_
.NeedsIdentity();
173 bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
174 if (target_
== HttpAuth::AUTH_PROXY
)
176 if (!url_security_manager_
)
178 return url_security_manager_
->CanUseDefaultCredentials(origin_
);
181 bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() {
182 return auth_system_
.AllowsExplicitCredentials();
185 // The Negotiate challenge header looks like:
186 // WWW-Authenticate: NEGOTIATE auth-data
187 bool HttpAuthHandlerNegotiate::Init(HttpAuthChallengeTokenizer
* challenge
) {
188 #if defined(OS_POSIX)
189 if (!auth_system_
.Init()) {
190 VLOG(1) << "can't initialize GSSAPI library";
193 // GSSAPI does not provide a way to enter username/password to
194 // obtain a TGT. If the default credentials are not allowed for
195 // a particular site (based on whitelist), fall back to a
197 if (!AllowsDefaultCredentials())
201 auth_system_
.Delegate();
202 auth_scheme_
= HttpAuth::AUTH_SCHEME_NEGOTIATE
;
204 properties_
= ENCRYPTS_IDENTITY
| IS_CONNECTION_BASED
;
205 HttpAuth::AuthorizationResult auth_result
=
206 auth_system_
.ParseChallenge(challenge
);
207 return (auth_result
== HttpAuth::AUTHORIZATION_RESULT_ACCEPT
);
210 int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
211 const AuthCredentials
* credentials
, const HttpRequestInfo
* request
,
212 const CompletionCallback
& callback
, std::string
* auth_token
) {
213 DCHECK(callback_
.is_null());
214 DCHECK(auth_token_
== NULL
);
215 auth_token_
= auth_token
;
216 if (already_called_
) {
217 DCHECK((!has_credentials_
&& credentials
== NULL
) ||
218 (has_credentials_
&& credentials
->Equals(credentials_
)));
219 next_state_
= STATE_GENERATE_AUTH_TOKEN
;
221 already_called_
= true;
223 has_credentials_
= true;
224 credentials_
= *credentials
;
226 next_state_
= STATE_RESOLVE_CANONICAL_NAME
;
229 if (rv
== ERR_IO_PENDING
)
230 callback_
= callback
;
234 void HttpAuthHandlerNegotiate::OnIOComplete(int result
) {
235 int rv
= DoLoop(result
);
236 if (rv
!= ERR_IO_PENDING
)
240 void HttpAuthHandlerNegotiate::DoCallback(int rv
) {
241 DCHECK(rv
!= ERR_IO_PENDING
);
242 DCHECK(!callback_
.is_null());
243 CompletionCallback callback
= callback_
;
248 int HttpAuthHandlerNegotiate::DoLoop(int result
) {
249 DCHECK(next_state_
!= STATE_NONE
);
253 State state
= next_state_
;
254 next_state_
= STATE_NONE
;
256 case STATE_RESOLVE_CANONICAL_NAME
:
258 rv
= DoResolveCanonicalName();
260 case STATE_RESOLVE_CANONICAL_NAME_COMPLETE
:
261 rv
= DoResolveCanonicalNameComplete(rv
);
263 case STATE_GENERATE_AUTH_TOKEN
:
265 rv
= DoGenerateAuthToken();
267 case STATE_GENERATE_AUTH_TOKEN_COMPLETE
:
268 rv
= DoGenerateAuthTokenComplete(rv
);
271 NOTREACHED() << "bad state";
275 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_NONE
);
280 int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
281 next_state_
= STATE_RESOLVE_CANONICAL_NAME_COMPLETE
;
282 if (disable_cname_lookup_
|| !resolver_
)
285 // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
286 DCHECK(!single_resolve_
.get());
287 HostResolver::RequestInfo
info(HostPortPair(origin_
.host(), 0));
288 info
.set_host_resolver_flags(HOST_RESOLVER_CANONNAME
);
289 single_resolve_
.reset(new SingleRequestHostResolver(resolver_
));
290 return single_resolve_
->Resolve(
294 base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete
,
295 base::Unretained(this)),
299 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv
) {
300 DCHECK_NE(ERR_IO_PENDING
, rv
);
302 // Even in the error case, try to use origin_.host instead of
303 // passing the failure on to the caller.
304 VLOG(1) << "Problem finding canonical name for SPN for host "
305 << origin_
.host() << ": " << ErrorToString(rv
);
309 next_state_
= STATE_GENERATE_AUTH_TOKEN
;
310 spn_
= CreateSPN(address_list_
, origin_
);
311 address_list_
= AddressList();
315 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
316 next_state_
= STATE_GENERATE_AUTH_TOKEN_COMPLETE
;
317 AuthCredentials
* credentials
= has_credentials_
? &credentials_
: NULL
;
318 // TODO(cbentzel): This should possibly be done async.
319 return auth_system_
.GenerateAuthToken(credentials
, spn_
, auth_token_
);
322 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv
) {
323 DCHECK_NE(ERR_IO_PENDING
, rv
);
328 bool HttpAuthHandlerNegotiate::CanDelegate() const {
329 // TODO(cbentzel): Should delegation be allowed on proxies?
330 if (target_
== HttpAuth::AUTH_PROXY
)
332 if (!url_security_manager_
)
334 return url_security_manager_
->CanDelegate(origin_
);