| OLD | NEW |
| (Empty) |
| 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_handler_negotiate.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/bind_helpers.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/profiler/scoped_tracker.h" | |
| 11 #include "base/strings/stringprintf.h" | |
| 12 #include "net/base/address_family.h" | |
| 13 #include "net/base/net_errors.h" | |
| 14 #include "net/dns/host_resolver.h" | |
| 15 #include "net/dns/single_request_host_resolver.h" | |
| 16 #include "net/http/http_auth_filter.h" | |
| 17 #include "net/http/url_security_manager.h" | |
| 18 | |
| 19 namespace net { | |
| 20 | |
| 21 HttpAuthHandlerNegotiate::Factory::Factory() | |
| 22 : disable_cname_lookup_(false), | |
| 23 use_port_(false), | |
| 24 resolver_(NULL), | |
| 25 #if defined(OS_WIN) | |
| 26 max_token_length_(0), | |
| 27 first_creation_(true), | |
| 28 #endif | |
| 29 is_unsupported_(false) { | |
| 30 } | |
| 31 | |
| 32 HttpAuthHandlerNegotiate::Factory::~Factory() { | |
| 33 } | |
| 34 | |
| 35 void HttpAuthHandlerNegotiate::Factory::set_host_resolver( | |
| 36 HostResolver* resolver) { | |
| 37 resolver_ = resolver; | |
| 38 } | |
| 39 | |
| 40 int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler( | |
| 41 HttpAuthChallengeTokenizer* challenge, | |
| 42 HttpAuth::Target target, | |
| 43 const GURL& origin, | |
| 44 CreateReason reason, | |
| 45 int digest_nonce_count, | |
| 46 const BoundNetLog& net_log, | |
| 47 scoped_ptr<HttpAuthHandler>* handler) { | |
| 48 #if defined(OS_WIN) | |
| 49 if (is_unsupported_ || reason == CREATE_PREEMPTIVE) | |
| 50 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
| 51 if (max_token_length_ == 0) { | |
| 52 int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME, | |
| 53 &max_token_length_); | |
| 54 if (rv == ERR_UNSUPPORTED_AUTH_SCHEME) | |
| 55 is_unsupported_ = true; | |
| 56 if (rv != OK) | |
| 57 return rv; | |
| 58 } | |
| 59 // TODO(cbentzel): Move towards model of parsing in the factory | |
| 60 // method and only constructing when valid. | |
| 61 scoped_ptr<HttpAuthHandler> tmp_handler( | |
| 62 new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_, | |
| 63 url_security_manager(), resolver_, | |
| 64 disable_cname_lookup_, use_port_)); | |
| 65 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) | |
| 66 return ERR_INVALID_RESPONSE; | |
| 67 handler->swap(tmp_handler); | |
| 68 return OK; | |
| 69 #elif defined(OS_POSIX) | |
| 70 if (is_unsupported_) | |
| 71 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
| 72 if (!auth_library_->Init()) { | |
| 73 is_unsupported_ = true; | |
| 74 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
| 75 } | |
| 76 // TODO(ahendrickson): Move towards model of parsing in the factory | |
| 77 // method and only constructing when valid. | |
| 78 scoped_ptr<HttpAuthHandler> tmp_handler( | |
| 79 new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(), | |
| 80 resolver_, disable_cname_lookup_, | |
| 81 use_port_)); | |
| 82 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) | |
| 83 return ERR_INVALID_RESPONSE; | |
| 84 handler->swap(tmp_handler); | |
| 85 return OK; | |
| 86 #endif | |
| 87 } | |
| 88 | |
| 89 HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate( | |
| 90 AuthLibrary* auth_library, | |
| 91 #if defined(OS_WIN) | |
| 92 ULONG max_token_length, | |
| 93 #endif | |
| 94 URLSecurityManager* url_security_manager, | |
| 95 HostResolver* resolver, | |
| 96 bool disable_cname_lookup, | |
| 97 bool use_port) | |
| 98 #if defined(OS_WIN) | |
| 99 : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length), | |
| 100 #elif defined(OS_POSIX) | |
| 101 : auth_system_(auth_library, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC), | |
| 102 #endif | |
| 103 disable_cname_lookup_(disable_cname_lookup), | |
| 104 use_port_(use_port), | |
| 105 resolver_(resolver), | |
| 106 already_called_(false), | |
| 107 has_credentials_(false), | |
| 108 auth_token_(NULL), | |
| 109 next_state_(STATE_NONE), | |
| 110 url_security_manager_(url_security_manager) { | |
| 111 } | |
| 112 | |
| 113 HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() { | |
| 114 } | |
| 115 | |
| 116 std::string HttpAuthHandlerNegotiate::CreateSPN( | |
| 117 const AddressList& address_list, const GURL& origin) { | |
| 118 // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI, | |
| 119 // and in the form HTTP@<host>:<port> through GSSAPI | |
| 120 // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx | |
| 121 // | |
| 122 // However, reality differs from the specification. A good description of | |
| 123 // the problems can be found here: | |
| 124 // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-k
b911149-and-kb908209-are-not-the-soluton.aspx | |
| 125 // | |
| 126 // Typically the <host> portion should be the canonical FQDN for the service. | |
| 127 // If this could not be resolved, the original hostname in the URL will be | |
| 128 // attempted instead. However, some intranets register SPNs using aliases | |
| 129 // for the same canonical DNS name to allow multiple web services to reside | |
| 130 // on the same host machine without requiring different ports. IE6 and IE7 | |
| 131 // have hotpatches that allow the default behavior to be overridden. | |
| 132 // http://support.microsoft.com/kb/911149 | |
| 133 // http://support.microsoft.com/kb/938305 | |
| 134 // | |
| 135 // According to the spec, the <port> option should be included if it is a | |
| 136 // non-standard port (i.e. not 80 or 443 in the HTTP case). However, | |
| 137 // historically browsers have not included the port, even on non-standard | |
| 138 // ports. IE6 required a hotpatch and a registry setting to enable | |
| 139 // including non-standard ports, and IE7 and IE8 also require the same | |
| 140 // registry setting, but no hotpatch. Firefox does not appear to have an | |
| 141 // option to include non-standard ports as of 3.6. | |
| 142 // http://support.microsoft.com/kb/908209 | |
| 143 // | |
| 144 // Without any command-line flags, Chrome matches the behavior of Firefox | |
| 145 // and IE. Users can override the behavior so aliases are allowed and | |
| 146 // non-standard ports are included. | |
| 147 int port = origin.EffectiveIntPort(); | |
| 148 std::string server = address_list.canonical_name(); | |
| 149 if (server.empty()) | |
| 150 server = origin.host(); | |
| 151 #if defined(OS_WIN) | |
| 152 static const char kSpnSeparator = '/'; | |
| 153 #elif defined(OS_POSIX) | |
| 154 static const char kSpnSeparator = '@'; | |
| 155 #endif | |
| 156 if (port != 80 && port != 443 && use_port_) { | |
| 157 return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(), | |
| 158 port); | |
| 159 } else { | |
| 160 return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str()); | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge( | |
| 165 HttpAuthChallengeTokenizer* challenge) { | |
| 166 return auth_system_.ParseChallenge(challenge); | |
| 167 } | |
| 168 | |
| 169 // Require identity on first pass instead of second. | |
| 170 bool HttpAuthHandlerNegotiate::NeedsIdentity() { | |
| 171 return auth_system_.NeedsIdentity(); | |
| 172 } | |
| 173 | |
| 174 bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() { | |
| 175 if (target_ == HttpAuth::AUTH_PROXY) | |
| 176 return true; | |
| 177 if (!url_security_manager_) | |
| 178 return false; | |
| 179 return url_security_manager_->CanUseDefaultCredentials(origin_); | |
| 180 } | |
| 181 | |
| 182 bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() { | |
| 183 return auth_system_.AllowsExplicitCredentials(); | |
| 184 } | |
| 185 | |
| 186 // The Negotiate challenge header looks like: | |
| 187 // WWW-Authenticate: NEGOTIATE auth-data | |
| 188 bool HttpAuthHandlerNegotiate::Init(HttpAuthChallengeTokenizer* challenge) { | |
| 189 #if defined(OS_POSIX) | |
| 190 if (!auth_system_.Init()) { | |
| 191 VLOG(1) << "can't initialize GSSAPI library"; | |
| 192 return false; | |
| 193 } | |
| 194 // GSSAPI does not provide a way to enter username/password to | |
| 195 // obtain a TGT. If the default credentials are not allowed for | |
| 196 // a particular site (based on whitelist), fall back to a | |
| 197 // different scheme. | |
| 198 if (!AllowsDefaultCredentials()) | |
| 199 return false; | |
| 200 #endif | |
| 201 if (CanDelegate()) | |
| 202 auth_system_.Delegate(); | |
| 203 auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE; | |
| 204 score_ = 4; | |
| 205 properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED; | |
| 206 HttpAuth::AuthorizationResult auth_result = | |
| 207 auth_system_.ParseChallenge(challenge); | |
| 208 return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT); | |
| 209 } | |
| 210 | |
| 211 int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl( | |
| 212 const AuthCredentials* credentials, const HttpRequestInfo* request, | |
| 213 const CompletionCallback& callback, std::string* auth_token) { | |
| 214 DCHECK(callback_.is_null()); | |
| 215 DCHECK(auth_token_ == NULL); | |
| 216 auth_token_ = auth_token; | |
| 217 if (already_called_) { | |
| 218 DCHECK((!has_credentials_ && credentials == NULL) || | |
| 219 (has_credentials_ && credentials->Equals(credentials_))); | |
| 220 next_state_ = STATE_GENERATE_AUTH_TOKEN; | |
| 221 } else { | |
| 222 already_called_ = true; | |
| 223 if (credentials) { | |
| 224 has_credentials_ = true; | |
| 225 credentials_ = *credentials; | |
| 226 } | |
| 227 next_state_ = STATE_RESOLVE_CANONICAL_NAME; | |
| 228 } | |
| 229 int rv = DoLoop(OK); | |
| 230 if (rv == ERR_IO_PENDING) | |
| 231 callback_ = callback; | |
| 232 return rv; | |
| 233 } | |
| 234 | |
| 235 void HttpAuthHandlerNegotiate::OnIOComplete(int result) { | |
| 236 // TODO(vadimt): Remove ScopedTracker below once crbug.com/436634 is fixed. | |
| 237 tracked_objects::ScopedTracker tracking_profile( | |
| 238 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
| 239 "436634 HttpAuthHandlerNegotiate::OnIOComplete")); | |
| 240 | |
| 241 int rv = DoLoop(result); | |
| 242 if (rv != ERR_IO_PENDING) | |
| 243 DoCallback(rv); | |
| 244 } | |
| 245 | |
| 246 void HttpAuthHandlerNegotiate::DoCallback(int rv) { | |
| 247 DCHECK(rv != ERR_IO_PENDING); | |
| 248 DCHECK(!callback_.is_null()); | |
| 249 CompletionCallback callback = callback_; | |
| 250 callback_.Reset(); | |
| 251 callback.Run(rv); | |
| 252 } | |
| 253 | |
| 254 int HttpAuthHandlerNegotiate::DoLoop(int result) { | |
| 255 DCHECK(next_state_ != STATE_NONE); | |
| 256 | |
| 257 int rv = result; | |
| 258 do { | |
| 259 State state = next_state_; | |
| 260 next_state_ = STATE_NONE; | |
| 261 switch (state) { | |
| 262 case STATE_RESOLVE_CANONICAL_NAME: | |
| 263 DCHECK_EQ(OK, rv); | |
| 264 rv = DoResolveCanonicalName(); | |
| 265 break; | |
| 266 case STATE_RESOLVE_CANONICAL_NAME_COMPLETE: | |
| 267 rv = DoResolveCanonicalNameComplete(rv); | |
| 268 break; | |
| 269 case STATE_GENERATE_AUTH_TOKEN: | |
| 270 DCHECK_EQ(OK, rv); | |
| 271 rv = DoGenerateAuthToken(); | |
| 272 break; | |
| 273 case STATE_GENERATE_AUTH_TOKEN_COMPLETE: | |
| 274 rv = DoGenerateAuthTokenComplete(rv); | |
| 275 break; | |
| 276 default: | |
| 277 NOTREACHED() << "bad state"; | |
| 278 rv = ERR_FAILED; | |
| 279 break; | |
| 280 } | |
| 281 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); | |
| 282 | |
| 283 return rv; | |
| 284 } | |
| 285 | |
| 286 int HttpAuthHandlerNegotiate::DoResolveCanonicalName() { | |
| 287 next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE; | |
| 288 if (disable_cname_lookup_ || !resolver_) | |
| 289 return OK; | |
| 290 | |
| 291 // TODO(cbentzel): Add reverse DNS lookup for numeric addresses. | |
| 292 DCHECK(!single_resolve_.get()); | |
| 293 HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0)); | |
| 294 info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME); | |
| 295 single_resolve_.reset(new SingleRequestHostResolver(resolver_)); | |
| 296 return single_resolve_->Resolve( | |
| 297 info, | |
| 298 DEFAULT_PRIORITY, | |
| 299 &address_list_, | |
| 300 base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete, | |
| 301 base::Unretained(this)), | |
| 302 net_log_); | |
| 303 } | |
| 304 | |
| 305 int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) { | |
| 306 DCHECK_NE(ERR_IO_PENDING, rv); | |
| 307 if (rv != OK) { | |
| 308 // Even in the error case, try to use origin_.host instead of | |
| 309 // passing the failure on to the caller. | |
| 310 VLOG(1) << "Problem finding canonical name for SPN for host " | |
| 311 << origin_.host() << ": " << ErrorToString(rv); | |
| 312 rv = OK; | |
| 313 } | |
| 314 | |
| 315 next_state_ = STATE_GENERATE_AUTH_TOKEN; | |
| 316 spn_ = CreateSPN(address_list_, origin_); | |
| 317 address_list_ = AddressList(); | |
| 318 return rv; | |
| 319 } | |
| 320 | |
| 321 int HttpAuthHandlerNegotiate::DoGenerateAuthToken() { | |
| 322 next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; | |
| 323 AuthCredentials* credentials = has_credentials_ ? &credentials_ : NULL; | |
| 324 // TODO(cbentzel): This should possibly be done async. | |
| 325 return auth_system_.GenerateAuthToken(credentials, spn_, auth_token_); | |
| 326 } | |
| 327 | |
| 328 int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) { | |
| 329 DCHECK_NE(ERR_IO_PENDING, rv); | |
| 330 auth_token_ = NULL; | |
| 331 return rv; | |
| 332 } | |
| 333 | |
| 334 bool HttpAuthHandlerNegotiate::CanDelegate() const { | |
| 335 // TODO(cbentzel): Should delegation be allowed on proxies? | |
| 336 if (target_ == HttpAuth::AUTH_PROXY) | |
| 337 return false; | |
| 338 if (!url_security_manager_) | |
| 339 return false; | |
| 340 return url_security_manager_->CanDelegate(origin_); | |
| 341 } | |
| 342 | |
| 343 } // namespace net | |
| OLD | NEW |