• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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_controller.h"
6 
7 #include "base/metrics/histogram.h"
8 #include "base/string_util.h"
9 #include "base/threading/platform_thread.h"
10 #include "base/utf_string_conversions.h"
11 #include "net/base/auth.h"
12 #include "net/base/host_resolver.h"
13 #include "net/base/net_util.h"
14 #include "net/http/http_auth_handler.h"
15 #include "net/http/http_auth_handler_factory.h"
16 #include "net/http/http_network_session.h"
17 #include "net/http/http_request_headers.h"
18 #include "net/http/http_request_info.h"
19 #include "net/http/http_response_headers.h"
20 
21 namespace net {
22 
23 namespace {
24 
25 // Returns a log message for all the response headers related to the auth
26 // challenge.
AuthChallengeLogMessage(HttpResponseHeaders * headers)27 std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) {
28   std::string msg;
29   std::string header_val;
30   void* iter = NULL;
31   while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) {
32     msg.append("\n  Has header Proxy-Authenticate: ");
33     msg.append(header_val);
34   }
35 
36   iter = NULL;
37   while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) {
38     msg.append("\n  Has header WWW-Authenticate: ");
39     msg.append(header_val);
40   }
41 
42   // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
43   // authentication with a "Proxy-Support: Session-Based-Authentication"
44   // response header.
45   iter = NULL;
46   while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) {
47     msg.append("\n  Has header Proxy-Support: ");
48     msg.append(header_val);
49   }
50 
51   return msg;
52 }
53 
54 enum AuthEvent {
55   AUTH_EVENT_START = 0,
56   AUTH_EVENT_REJECT,
57   AUTH_EVENT_MAX,
58 };
59 
60 enum AuthTarget {
61   AUTH_TARGET_PROXY = 0,
62   AUTH_TARGET_SECURE_PROXY,
63   AUTH_TARGET_SERVER,
64   AUTH_TARGET_SECURE_SERVER,
65   AUTH_TARGET_MAX,
66 };
67 
DetermineAuthTarget(const HttpAuthHandler * handler)68 AuthTarget DetermineAuthTarget(const HttpAuthHandler* handler) {
69   switch (handler->target()) {
70     case HttpAuth::AUTH_PROXY:
71       if (handler->origin().SchemeIsSecure())
72         return AUTH_TARGET_SECURE_PROXY;
73       else
74         return AUTH_TARGET_PROXY;
75     case HttpAuth::AUTH_SERVER:
76       if (handler->origin().SchemeIsSecure())
77         return AUTH_TARGET_SECURE_SERVER;
78       else
79         return AUTH_TARGET_SERVER;
80     default:
81       NOTREACHED();
82       return AUTH_TARGET_MAX;
83   }
84 }
85 
86 // Records the number of authentication events per authentication scheme.
HistogramAuthEvent(HttpAuthHandler * handler,AuthEvent auth_event)87 void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) {
88 #if !defined(NDEBUG)
89   // Note: The on-same-thread check is intentionally not using a lock
90   // to protect access to first_thread. This method is meant to be only
91   // used on the same thread, in which case there are no race conditions. If
92   // there are race conditions (say, a read completes during a partial write),
93   // the DCHECK will correctly fail.
94   static base::PlatformThreadId first_thread =
95       base::PlatformThread::CurrentId();
96   DCHECK_EQ(first_thread, base::PlatformThread::CurrentId());
97 #endif
98 
99   HttpAuth::Scheme auth_scheme = handler->auth_scheme();
100   DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX);
101 
102   // Record start and rejection events for authentication.
103   //
104   // The results map to:
105   //   Basic Start: 0
106   //   Basic Reject: 1
107   //   Digest Start: 2
108   //   Digest Reject: 3
109   //   NTLM Start: 4
110   //   NTLM Reject: 5
111   //   Negotiate Start: 6
112   //   Negotiate Reject: 7
113   static const int kEventBucketsEnd =
114       HttpAuth::AUTH_SCHEME_MAX * AUTH_EVENT_MAX;
115   int event_bucket = auth_scheme * AUTH_EVENT_MAX + auth_event;
116   DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd);
117   UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket,
118                             kEventBucketsEnd);
119 
120   // Record the target of the authentication.
121   //
122   // The results map to:
123   //   Basic Proxy: 0
124   //   Basic Secure Proxy: 1
125   //   Basic Server: 2
126   //   Basic Secure Server: 3
127   //   Digest Proxy: 4
128   //   Digest Secure Proxy: 5
129   //   Digest Server: 6
130   //   Digest Secure Server: 7
131   //   NTLM Proxy: 8
132   //   NTLM Secure Proxy: 9
133   //   NTLM Server: 10
134   //   NTLM Secure Server: 11
135   //   Negotiate Proxy: 12
136   //   Negotiate Secure Proxy: 13
137   //   Negotiate Server: 14
138   //   Negotiate Secure Server: 15
139   if (auth_event != AUTH_EVENT_START)
140     return;
141   static const int kTargetBucketsEnd =
142       HttpAuth::AUTH_SCHEME_MAX * AUTH_TARGET_MAX;
143   AuthTarget auth_target = DetermineAuthTarget(handler);
144   int target_bucket = auth_scheme * AUTH_TARGET_MAX + auth_target;
145   DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd);
146   UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket,
147                             kTargetBucketsEnd);
148 }
149 
150 }  // namespace
151 
HttpAuthController(HttpAuth::Target target,const GURL & auth_url,HttpAuthCache * http_auth_cache,HttpAuthHandlerFactory * http_auth_handler_factory)152 HttpAuthController::HttpAuthController(
153     HttpAuth::Target target,
154     const GURL& auth_url,
155     HttpAuthCache* http_auth_cache,
156     HttpAuthHandlerFactory* http_auth_handler_factory)
157     : target_(target),
158       auth_url_(auth_url),
159       auth_origin_(auth_url.GetOrigin()),
160       auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()),
161       embedded_identity_used_(false),
162       default_credentials_used_(false),
163       http_auth_cache_(http_auth_cache),
164       http_auth_handler_factory_(http_auth_handler_factory),
165       ALLOW_THIS_IN_INITIALIZER_LIST(
166           io_callback_(this, &HttpAuthController::OnIOComplete)),
167       user_callback_(NULL) {
168 }
169 
~HttpAuthController()170 HttpAuthController::~HttpAuthController() {
171   DCHECK(CalledOnValidThread());
172   user_callback_ = NULL;
173 }
174 
MaybeGenerateAuthToken(const HttpRequestInfo * request,CompletionCallback * callback,const BoundNetLog & net_log)175 int HttpAuthController::MaybeGenerateAuthToken(const HttpRequestInfo* request,
176                                                CompletionCallback* callback,
177                                                const BoundNetLog& net_log) {
178   DCHECK(CalledOnValidThread());
179   bool needs_auth = HaveAuth() || SelectPreemptiveAuth(net_log);
180   if (!needs_auth)
181     return OK;
182   const string16* username = NULL;
183   const string16* password = NULL;
184   if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) {
185     username = &identity_.username;
186     password = &identity_.password;
187   }
188   DCHECK(auth_token_.empty());
189   DCHECK(NULL == user_callback_);
190   int rv = handler_->GenerateAuthToken(username,
191                                        password,
192                                        request,
193                                        &io_callback_,
194                                        &auth_token_);
195   if (DisableOnAuthHandlerResult(rv))
196     rv = OK;
197   if (rv == ERR_IO_PENDING)
198     user_callback_ = callback;
199   else
200     OnIOComplete(rv);
201   return rv;
202 }
203 
SelectPreemptiveAuth(const BoundNetLog & net_log)204 bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) {
205   DCHECK(CalledOnValidThread());
206   DCHECK(!HaveAuth());
207   DCHECK(identity_.invalid);
208 
209   // Don't do preemptive authorization if the URL contains a username/password,
210   // since we must first be challenged in order to use the URL's identity.
211   if (auth_url_.has_username())
212     return false;
213 
214   // SelectPreemptiveAuth() is on the critical path for each request, so it
215   // is expected to be fast. LookupByPath() is fast in the common case, since
216   // the number of http auth cache entries is expected to be very small.
217   // (For most users in fact, it will be 0.)
218   HttpAuthCache::Entry* entry = http_auth_cache_->LookupByPath(
219       auth_origin_, auth_path_);
220   if (!entry)
221     return false;
222 
223   // Try to create a handler using the previous auth challenge.
224   scoped_ptr<HttpAuthHandler> handler_preemptive;
225   int rv_create = http_auth_handler_factory_->
226       CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_,
227                                             auth_origin_,
228                                             entry->IncrementNonceCount(),
229                                             net_log, &handler_preemptive);
230   if (rv_create != OK)
231     return false;
232 
233   // Set the state
234   identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
235   identity_.invalid = false;
236   identity_.username = entry->username();
237   identity_.password = entry->password();
238   handler_.swap(handler_preemptive);
239   return true;
240 }
241 
AddAuthorizationHeader(HttpRequestHeaders * authorization_headers)242 void HttpAuthController::AddAuthorizationHeader(
243     HttpRequestHeaders* authorization_headers) {
244   DCHECK(CalledOnValidThread());
245   DCHECK(HaveAuth());
246   // auth_token_ can be empty if we encountered a permanent error with
247   // the auth scheme and want to retry.
248   if (!auth_token_.empty()) {
249     authorization_headers->SetHeader(
250         HttpAuth::GetAuthorizationHeaderName(target_), auth_token_);
251     auth_token_.clear();
252   }
253 }
254 
HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers,bool do_not_send_server_auth,bool establishing_tunnel,const BoundNetLog & net_log)255 int HttpAuthController::HandleAuthChallenge(
256     scoped_refptr<HttpResponseHeaders> headers,
257     bool do_not_send_server_auth,
258     bool establishing_tunnel,
259     const BoundNetLog& net_log) {
260   DCHECK(CalledOnValidThread());
261   DCHECK(headers);
262   DCHECK(auth_origin_.is_valid());
263   VLOG(1) << "The " << HttpAuth::GetAuthTargetString(target_) << " "
264           << auth_origin_ << " requested auth "
265           << AuthChallengeLogMessage(headers.get());
266 
267   // Give the existing auth handler first try at the authentication headers.
268   // This will also evict the entry in the HttpAuthCache if the previous
269   // challenge appeared to be rejected, or is using a stale nonce in the Digest
270   // case.
271   if (HaveAuth()) {
272     std::string challenge_used;
273     HttpAuth::AuthorizationResult result = HttpAuth::HandleChallengeResponse(
274         handler_.get(), headers, target_, disabled_schemes_, &challenge_used);
275     switch (result) {
276       case HttpAuth::AUTHORIZATION_RESULT_ACCEPT:
277         break;
278       case HttpAuth::AUTHORIZATION_RESULT_INVALID:
279         InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
280         break;
281       case HttpAuth::AUTHORIZATION_RESULT_REJECT:
282         HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT);
283         InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
284         break;
285       case HttpAuth::AUTHORIZATION_RESULT_STALE:
286         if (http_auth_cache_->UpdateStaleChallenge(auth_origin_,
287                                                    handler_->realm(),
288                                                    handler_->auth_scheme(),
289                                                    challenge_used)) {
290           InvalidateCurrentHandler(INVALIDATE_HANDLER);
291         } else {
292           // It's possible that a server could incorrectly issue a stale
293           // response when the entry is not in the cache. Just evict the
294           // current value from the cache.
295           InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
296         }
297         break;
298       case HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM:
299         // If the server changes the authentication realm in a
300         // subsequent challenge, invalidate cached credentials for the
301         // previous realm.  If the server rejects a preemptive
302         // authorization and requests credentials for a different
303         // realm, we keep the cached credentials.
304         InvalidateCurrentHandler(
305             (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) ?
306             INVALIDATE_HANDLER :
307             INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
308         break;
309       default:
310         NOTREACHED();
311         break;
312     }
313   }
314 
315   identity_.invalid = true;
316 
317   bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER ||
318                         !do_not_send_server_auth);
319   if (!handler_.get() && can_send_auth) {
320     // Find the best authentication challenge that we support.
321     HttpAuth::ChooseBestChallenge(http_auth_handler_factory_,
322                                   headers, target_, auth_origin_,
323                                   disabled_schemes_, net_log,
324                                   &handler_);
325     if (handler_.get())
326       HistogramAuthEvent(handler_.get(), AUTH_EVENT_START);
327   }
328 
329   if (!handler_.get()) {
330     if (establishing_tunnel) {
331       LOG(ERROR) << "Can't perform auth to the "
332                  << HttpAuth::GetAuthTargetString(target_) << " "
333                  << auth_origin_ << " when establishing a tunnel"
334                  << AuthChallengeLogMessage(headers.get());
335 
336       // We are establishing a tunnel, we can't show the error page because an
337       // active network attacker could control its contents.  Instead, we just
338       // fail to establish the tunnel.
339       DCHECK(target_ == HttpAuth::AUTH_PROXY);
340       return ERR_PROXY_AUTH_UNSUPPORTED;
341     }
342     // We found no supported challenge -- let the transaction continue
343     // so we end up displaying the error page.
344     return OK;
345   }
346 
347   if (handler_->NeedsIdentity()) {
348     // Pick a new auth identity to try, by looking to the URL and auth cache.
349     // If an identity to try is found, it is saved to identity_.
350     SelectNextAuthIdentityToTry();
351   } else {
352     // Proceed with the existing identity or a null identity.
353     identity_.invalid = false;
354   }
355 
356   // From this point on, we are restartable.
357 
358   if (identity_.invalid) {
359     // We have exhausted all identity possibilities, all we can do now is
360     // pass the challenge information back to the client.
361     PopulateAuthChallenge();
362   } else {
363     auth_info_ = NULL;
364   }
365 
366   return OK;
367 }
368 
ResetAuth(const string16 & username,const string16 & password)369 void HttpAuthController::ResetAuth(const string16& username,
370                                    const string16& password) {
371   DCHECK(CalledOnValidThread());
372   DCHECK(identity_.invalid || (username.empty() && password.empty()));
373 
374   if (identity_.invalid) {
375     // Update the username/password.
376     identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
377     identity_.invalid = false;
378     identity_.username = username;
379     identity_.password = password;
380   }
381 
382   DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
383 
384   // Add the auth entry to the cache before restarting. We don't know whether
385   // the identity is valid yet, but if it is valid we want other transactions
386   // to know about it. If an entry for (origin, handler->realm()) already
387   // exists, we update it.
388   //
389   // If identity_.source is HttpAuth::IDENT_SRC_NONE or
390   // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no
391   // identity because identity is not required yet or we're using default
392   // credentials.
393   //
394   // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in
395   // round 1 and round 2, which is redundant but correct.  It would be nice
396   // to add an auth entry to the cache only once, preferrably in round 1.
397   // See http://crbug.com/21015.
398   switch (identity_.source) {
399     case HttpAuth::IDENT_SRC_NONE:
400     case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS:
401       break;
402     default:
403       http_auth_cache_->Add(auth_origin_, handler_->realm(),
404                             handler_->auth_scheme(), handler_->challenge(),
405                             identity_.username, identity_.password,
406                             auth_path_);
407       break;
408   }
409 }
410 
HaveAuthHandler() const411 bool HttpAuthController::HaveAuthHandler() const {
412   return handler_.get() != NULL;
413 }
414 
HaveAuth() const415 bool HttpAuthController::HaveAuth() const {
416   return handler_.get() && !identity_.invalid;
417 }
418 
InvalidateCurrentHandler(InvalidateHandlerAction action)419 void HttpAuthController::InvalidateCurrentHandler(
420     InvalidateHandlerAction action) {
421   DCHECK(CalledOnValidThread());
422 
423   if (action == INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS)
424     InvalidateRejectedAuthFromCache();
425   handler_.reset();
426   identity_ = HttpAuth::Identity();
427 }
428 
InvalidateRejectedAuthFromCache()429 void HttpAuthController::InvalidateRejectedAuthFromCache() {
430   DCHECK(CalledOnValidThread());
431   DCHECK(HaveAuth());
432 
433   // Clear the cache entry for the identity we just failed on.
434   // Note: we require the username/password to match before invalidating
435   // since the entry in the cache may be newer than what we used last time.
436   http_auth_cache_->Remove(auth_origin_, handler_->realm(),
437                            handler_->auth_scheme(), identity_.username,
438                            identity_.password);
439 }
440 
SelectNextAuthIdentityToTry()441 bool HttpAuthController::SelectNextAuthIdentityToTry() {
442   DCHECK(CalledOnValidThread());
443   DCHECK(handler_.get());
444   DCHECK(identity_.invalid);
445 
446   // Try to use the username/password encoded into the URL first.
447   if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() &&
448       !embedded_identity_used_) {
449     identity_.source = HttpAuth::IDENT_SRC_URL;
450     identity_.invalid = false;
451     // Extract the username:password from the URL.
452     GetIdentityFromURL(auth_url_,
453                        &identity_.username,
454                        &identity_.password);
455     embedded_identity_used_ = true;
456     // TODO(eroman): If the password is blank, should we also try combining
457     // with a password from the cache?
458     return true;
459   }
460 
461   // Check the auth cache for a realm entry.
462   HttpAuthCache::Entry* entry =
463       http_auth_cache_->Lookup(auth_origin_, handler_->realm(),
464                                handler_->auth_scheme());
465 
466   if (entry) {
467     identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
468     identity_.invalid = false;
469     identity_.username = entry->username();
470     identity_.password = entry->password();
471     return true;
472   }
473 
474   // Use default credentials (single sign on) if this is the first attempt
475   // at identity.  Do not allow multiple times as it will infinite loop.
476   // We use default credentials after checking the auth cache so that if
477   // single sign-on doesn't work, we won't try default credentials for future
478   // transactions.
479   if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) {
480     identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS;
481     identity_.invalid = false;
482     default_credentials_used_ = true;
483     return true;
484   }
485 
486   return false;
487 }
488 
PopulateAuthChallenge()489 void HttpAuthController::PopulateAuthChallenge() {
490   DCHECK(CalledOnValidThread());
491 
492   // Populates response_.auth_challenge with the authentication challenge info.
493   // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
494 
495   auth_info_ = new AuthChallengeInfo;
496   auth_info_->is_proxy = target_ == HttpAuth::AUTH_PROXY;
497   auth_info_->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin_));
498   auth_info_->scheme = ASCIIToWide(
499       HttpAuth::SchemeToString(handler_->auth_scheme()));
500   // TODO(eroman): decode realm according to RFC 2047.
501   auth_info_->realm = ASCIIToWide(handler_->realm());
502 }
503 
DisableOnAuthHandlerResult(int result)504 bool HttpAuthController::DisableOnAuthHandlerResult(int result) {
505   DCHECK(CalledOnValidThread());
506 
507   switch (result) {
508     // Occurs with GSSAPI, if the user has not already logged in.
509     case ERR_MISSING_AUTH_CREDENTIALS:
510 
511     // Can occur with GSSAPI or SSPI if the underlying library reports
512     // a permanent error.
513     case ERR_UNSUPPORTED_AUTH_SCHEME:
514 
515     // These two error codes represent failures we aren't handling.
516     case ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
517     case ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
518 
519     // Can be returned by SSPI if the authenticating authority or
520     // target is not known.
521     case ERR_MISCONFIGURED_AUTH_ENVIRONMENT:
522 
523       // In these cases, disable the current scheme as it cannot
524       // succeed.
525       DisableAuthScheme(handler_->auth_scheme());
526       auth_token_.clear();
527       return true;
528 
529     default:
530       return false;
531   }
532 }
533 
OnIOComplete(int result)534 void HttpAuthController::OnIOComplete(int result) {
535   DCHECK(CalledOnValidThread());
536   if (DisableOnAuthHandlerResult(result))
537     result = OK;
538   if (user_callback_) {
539     CompletionCallback* c = user_callback_;
540     user_callback_ = NULL;
541     c->Run(result);
542   }
543 }
544 
auth_info()545 scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() {
546   DCHECK(CalledOnValidThread());
547   return auth_info_;
548 }
549 
IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const550 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
551   DCHECK(CalledOnValidThread());
552   return disabled_schemes_.find(scheme) != disabled_schemes_.end();
553 }
554 
DisableAuthScheme(HttpAuth::Scheme scheme)555 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
556   DCHECK(CalledOnValidThread());
557   disabled_schemes_.insert(scheme);
558 }
559 
560 }  // namespace net
561