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