• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2010 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.
4 
5 #include "net/http/http_auth_handler_negotiate.h"
6 
7 #include "base/logging.h"
8 #include "base/string_util.h"
9 #include "base/stringprintf.h"
10 #include "base/utf_string_conversions.h"
11 #include "net/base/address_family.h"
12 #include "net/base/host_resolver.h"
13 #include "net/base/net_errors.h"
14 #include "net/http/http_auth_filter.h"
15 #include "net/http/url_security_manager.h"
16 
17 namespace net {
18 
Factory()19 HttpAuthHandlerNegotiate::Factory::Factory()
20     : disable_cname_lookup_(false),
21       use_port_(false),
22 #if defined(OS_WIN)
23       max_token_length_(0),
24       first_creation_(true),
25       is_unsupported_(false),
26 #endif
27       auth_library_(NULL) {
28 }
29 
~Factory()30 HttpAuthHandlerNegotiate::Factory::~Factory() {
31 }
32 
set_host_resolver(HostResolver * resolver)33 void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
34     HostResolver* resolver) {
35   resolver_ = resolver;
36 }
37 
CreateAuthHandler(HttpAuth::ChallengeTokenizer * challenge,HttpAuth::Target target,const GURL & origin,CreateReason reason,int digest_nonce_count,const BoundNetLog & net_log,scoped_ptr<HttpAuthHandler> * handler)38 int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
39     HttpAuth::ChallengeTokenizer* challenge,
40     HttpAuth::Target target,
41     const GURL& origin,
42     CreateReason reason,
43     int digest_nonce_count,
44     const BoundNetLog& net_log,
45     scoped_ptr<HttpAuthHandler>* handler) {
46 #if defined(OS_WIN)
47   if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
48     return ERR_UNSUPPORTED_AUTH_SCHEME;
49   if (max_token_length_ == 0) {
50     int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME,
51                                      &max_token_length_);
52     if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
53       is_unsupported_ = true;
54     if (rv != OK)
55       return rv;
56   }
57   // TODO(cbentzel): Move towards model of parsing in the factory
58   //                 method and only constructing when valid.
59   scoped_ptr<HttpAuthHandler> tmp_handler(
60       new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_,
61                                    url_security_manager(), resolver_,
62                                    disable_cname_lookup_, use_port_));
63   if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
64     return ERR_INVALID_RESPONSE;
65   handler->swap(tmp_handler);
66   return OK;
67 #elif defined(OS_POSIX)
68   // TODO(ahendrickson): Move towards model of parsing in the factory
69   //                     method and only constructing when valid.
70   scoped_ptr<HttpAuthHandler> tmp_handler(
71       new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(),
72                                    resolver_, disable_cname_lookup_,
73                                    use_port_));
74   if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
75     return ERR_INVALID_RESPONSE;
76   handler->swap(tmp_handler);
77   return OK;
78 #endif
79 }
80 
HttpAuthHandlerNegotiate(AuthLibrary * auth_library,ULONG max_token_length,URLSecurityManager * url_security_manager,HostResolver * resolver,bool disable_cname_lookup,bool use_port)81 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
82     AuthLibrary* auth_library,
83 #if defined(OS_WIN)
84     ULONG max_token_length,
85 #endif
86     URLSecurityManager* url_security_manager,
87     HostResolver* resolver,
88     bool disable_cname_lookup,
89     bool use_port)
90 #if defined(OS_WIN)
91     : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
92 #elif defined(OS_POSIX)
93     : auth_system_(auth_library, "Negotiate", CHROME_GSS_KRB5_MECH_OID_DESC),
94 #endif
95       disable_cname_lookup_(disable_cname_lookup),
96       use_port_(use_port),
97       ALLOW_THIS_IN_INITIALIZER_LIST(io_callback_(
98           this, &HttpAuthHandlerNegotiate::OnIOComplete)),
99       resolver_(resolver),
100       already_called_(false),
101       has_username_and_password_(false),
102       user_callback_(NULL),
103       auth_token_(NULL),
104       next_state_(STATE_NONE),
105       url_security_manager_(url_security_manager) {
106 }
107 
~HttpAuthHandlerNegotiate()108 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
109 }
110 
CreateSPN(const AddressList & address_list,const GURL & origin)111 std::wstring HttpAuthHandlerNegotiate::CreateSPN(
112     const AddressList& address_list, const GURL& origin) {
113   // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
114   // and in the form HTTP@<host>:<port> through GSSAPI
115   //   http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
116   //
117   // However, reality differs from the specification. A good description of
118   // the problems can be found here:
119   //   http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
120   //
121   // Typically the <host> portion should be the canonical FQDN for the service.
122   // If this could not be resolved, the original hostname in the URL will be
123   // attempted instead. However, some intranets register SPNs using aliases
124   // for the same canonical DNS name to allow multiple web services to reside
125   // on the same host machine without requiring different ports. IE6 and IE7
126   // have hotpatches that allow the default behavior to be overridden.
127   //   http://support.microsoft.com/kb/911149
128   //   http://support.microsoft.com/kb/938305
129   //
130   // According to the spec, the <port> option should be included if it is a
131   // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
132   // historically browsers have not included the port, even on non-standard
133   // ports. IE6 required a hotpatch and a registry setting to enable
134   // including non-standard ports, and IE7 and IE8 also require the same
135   // registry setting, but no hotpatch. Firefox does not appear to have an
136   // option to include non-standard ports as of 3.6.
137   //   http://support.microsoft.com/kb/908209
138   //
139   // Without any command-line flags, Chrome matches the behavior of Firefox
140   // and IE. Users can override the behavior so aliases are allowed and
141   // non-standard ports are included.
142   int port = origin.EffectiveIntPort();
143   std::string server;
144   if (!address_list.GetCanonicalName(&server))
145     server = origin.host();
146 #if defined(OS_WIN)
147   static const char kSpnSeparator = '/';
148 #elif defined(OS_POSIX)
149   static const char kSpnSeparator = '@';
150 #endif
151   if (port != 80 && port != 443 && use_port_) {
152     return ASCIIToWide(base::StringPrintf("HTTP%c%s:%d", kSpnSeparator,
153                                           server.c_str(), port));
154   } else {
155     return ASCIIToWide(base::StringPrintf("HTTP%c%s", kSpnSeparator,
156                                           server.c_str()));
157   }
158 }
159 
HandleAnotherChallenge(HttpAuth::ChallengeTokenizer * challenge)160 HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
161     HttpAuth::ChallengeTokenizer* challenge) {
162   return auth_system_.ParseChallenge(challenge);
163 }
164 
165 // Require identity on first pass instead of second.
NeedsIdentity()166 bool HttpAuthHandlerNegotiate::NeedsIdentity() {
167   return auth_system_.NeedsIdentity();
168 }
169 
AllowsDefaultCredentials()170 bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
171   if (target_ == HttpAuth::AUTH_PROXY)
172     return true;
173   if (!url_security_manager_)
174     return false;
175   return url_security_manager_->CanUseDefaultCredentials(origin_);
176 }
177 
178 // The Negotiate challenge header looks like:
179 //   WWW-Authenticate: NEGOTIATE auth-data
Init(HttpAuth::ChallengeTokenizer * challenge)180 bool HttpAuthHandlerNegotiate::Init(HttpAuth::ChallengeTokenizer* challenge) {
181 #if defined(OS_POSIX)
182   if (!auth_system_.Init()) {
183     VLOG(1) << "can't initialize GSSAPI library";
184     return false;
185   }
186   // GSSAPI does not provide a way to enter username/password to
187   // obtain a TGT. If the default credentials are not allowed for
188   // a particular site (based on whitelist), fall back to a
189   // different scheme.
190   if (!AllowsDefaultCredentials())
191     return false;
192 #endif
193   if (CanDelegate())
194     auth_system_.Delegate();
195   auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE;
196   score_ = 4;
197   properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
198   HttpAuth::AuthorizationResult auth_result =
199       auth_system_.ParseChallenge(challenge);
200   return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT);
201 }
202 
GenerateAuthTokenImpl(const string16 * username,const string16 * password,const HttpRequestInfo * request,CompletionCallback * callback,std::string * auth_token)203 int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
204     const string16* username,
205     const string16* password,
206     const HttpRequestInfo* request,
207     CompletionCallback* callback,
208     std::string* auth_token) {
209   DCHECK(user_callback_ == NULL);
210   DCHECK((username == NULL) == (password == NULL));
211   DCHECK(auth_token_ == NULL);
212   auth_token_ = auth_token;
213   if (already_called_) {
214     DCHECK((!has_username_and_password_ && username == NULL) ||
215            (has_username_and_password_ && *username == username_ &&
216             *password == password_));
217     next_state_ = STATE_GENERATE_AUTH_TOKEN;
218   } else {
219     already_called_ = true;
220     if (username) {
221       has_username_and_password_ = true;
222       username_ = *username;
223       password_ = *password;
224     }
225     next_state_ = STATE_RESOLVE_CANONICAL_NAME;
226   }
227   int rv = DoLoop(OK);
228   if (rv == ERR_IO_PENDING)
229     user_callback_ = callback;
230   return rv;
231 }
232 
OnIOComplete(int result)233 void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
234   int rv = DoLoop(result);
235   if (rv != ERR_IO_PENDING)
236     DoCallback(rv);
237 }
238 
DoCallback(int rv)239 void HttpAuthHandlerNegotiate::DoCallback(int rv) {
240   DCHECK(rv != ERR_IO_PENDING);
241   DCHECK(user_callback_);
242   CompletionCallback* callback = user_callback_;
243   user_callback_ = NULL;
244   callback->Run(rv);
245 }
246 
DoLoop(int result)247 int HttpAuthHandlerNegotiate::DoLoop(int result) {
248   DCHECK(next_state_ != STATE_NONE);
249 
250   int rv = result;
251   do {
252     State state = next_state_;
253     next_state_ = STATE_NONE;
254     switch (state) {
255       case STATE_RESOLVE_CANONICAL_NAME:
256         DCHECK_EQ(OK, rv);
257         rv = DoResolveCanonicalName();
258         break;
259       case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
260         rv = DoResolveCanonicalNameComplete(rv);
261         break;
262       case STATE_GENERATE_AUTH_TOKEN:
263         DCHECK_EQ(OK, rv);
264         rv = DoGenerateAuthToken();
265         break;
266       case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
267         rv = DoGenerateAuthTokenComplete(rv);
268         break;
269       default:
270         NOTREACHED() << "bad state";
271         rv = ERR_FAILED;
272         break;
273     }
274   } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
275 
276   return rv;
277 }
278 
DoResolveCanonicalName()279 int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
280   next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
281   if (disable_cname_lookup_ || !resolver_)
282     return OK;
283 
284   // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
285   DCHECK(!single_resolve_.get());
286   HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0));
287   info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
288   single_resolve_.reset(new SingleRequestHostResolver(resolver_));
289   return single_resolve_->Resolve(info, &address_list_, &io_callback_,
290                                   net_log_);
291 }
292 
DoResolveCanonicalNameComplete(int rv)293 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
294   DCHECK_NE(ERR_IO_PENDING, rv);
295   if (rv != OK) {
296     // Even in the error case, try to use origin_.host instead of
297     // passing the failure on to the caller.
298     VLOG(1) << "Problem finding canonical name for SPN for host "
299             << origin_.host() << ": " << ErrorToString(rv);
300     rv = OK;
301   }
302 
303   next_state_ = STATE_GENERATE_AUTH_TOKEN;
304   spn_ = CreateSPN(address_list_, origin_);
305   address_list_.Reset();
306   return rv;
307 }
308 
DoGenerateAuthToken()309 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
310   next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
311   string16* username = has_username_and_password_ ? &username_ : NULL;
312   string16* password = has_username_and_password_ ? &password_ : NULL;
313   // TODO(cbentzel): This should possibly be done async.
314   return auth_system_.GenerateAuthToken(username, password, spn_, auth_token_);
315 }
316 
DoGenerateAuthTokenComplete(int rv)317 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
318   DCHECK_NE(ERR_IO_PENDING, rv);
319   auth_token_ = NULL;
320   return rv;
321 }
322 
CanDelegate() const323 bool HttpAuthHandlerNegotiate::CanDelegate() const {
324   // TODO(cbentzel): Should delegation be allowed on proxies?
325   if (target_ == HttpAuth::AUTH_PROXY)
326     return false;
327   if (!url_security_manager_)
328     return false;
329   return url_security_manager_->CanDelegate(origin_);
330 }
331 
332 }  // namespace net
333