Home | History | Annotate | Download | only in http
      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