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. 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 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. 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 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 170 HttpAuthController::~HttpAuthController() { 171 DCHECK(CalledOnValidThread()); 172 user_callback_ = NULL; 173 } 174 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 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 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 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 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 411 bool HttpAuthController::HaveAuthHandler() const { 412 return handler_.get() != NULL; 413 } 414 415 bool HttpAuthController::HaveAuth() const { 416 return handler_.get() && !identity_.invalid; 417 } 418 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 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 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 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 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 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 545 scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() { 546 DCHECK(CalledOnValidThread()); 547 return auth_info_; 548 } 549 550 bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const { 551 DCHECK(CalledOnValidThread()); 552 return disabled_schemes_.find(scheme) != disabled_schemes_.end(); 553 } 554 555 void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) { 556 DCHECK(CalledOnValidThread()); 557 disabled_schemes_.insert(scheme); 558 } 559 560 } // namespace net 561