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. 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 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. 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 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 169 HttpAuthController::~HttpAuthController() { 170 DCHECK(CalledOnValidThread()); 171 } 172 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 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 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 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 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 423 bool HttpAuthController::HaveAuthHandler() const { 424 return handler_.get() != NULL; 425 } 426 427 bool HttpAuthController::HaveAuth() const { 428 return handler_.get() && !identity_.invalid; 429 } 430 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 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 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 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 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 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 558 scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() { 559 DCHECK(CalledOnValidThread()); 560 return auth_info_; 561 } 562 563 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const { 564 DCHECK(CalledOnValidThread()); 565 return disabled_schemes_.find(scheme) != disabled_schemes_.end(); 566 } 567 568 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) { 569 DCHECK(CalledOnValidThread()); 570 disabled_schemes_.insert(scheme); 571 } 572 573 void HttpAuthController::DisableEmbeddedIdentity() { 574 DCHECK(CalledOnValidThread()); 575 embedded_identity_used_ = true; 576 } 577 578 } // namespace net 579