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