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