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/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