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