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