123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "net/http/http_auth_handler_negotiate.h"
- #include <utility>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/check_op.h"
- #include "base/logging.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/values.h"
- #include "build/build_config.h"
- #include "build/chromeos_buildflags.h"
- #include "net/base/address_family.h"
- #include "net/base/address_list.h"
- #include "net/base/host_port_pair.h"
- #include "net/base/net_errors.h"
- #include "net/cert/x509_util.h"
- #include "net/dns/host_resolver.h"
- #include "net/http/http_auth.h"
- #include "net/http/http_auth_filter.h"
- #include "net/http/http_auth_preferences.h"
- #include "net/log/net_log_capture_mode.h"
- #include "net/log/net_log_event_type.h"
- #include "net/log/net_log_with_source.h"
- #include "net/ssl/ssl_info.h"
- #include "url/scheme_host_port.h"
- namespace net {
- using DelegationType = HttpAuth::DelegationType;
- namespace {
- base::Value NetLogParameterChannelBindings(
- const std::string& channel_binding_token,
- NetLogCaptureMode capture_mode) {
- base::Value::Dict dict;
- if (!NetLogCaptureIncludesSocketBytes(capture_mode))
- return base::Value(std::move(dict));
- dict.Set("token", base::HexEncode(channel_binding_token.data(),
- channel_binding_token.size()));
- return base::Value(std::move(dict));
- }
- // Uses |negotiate_auth_system_factory| to create the auth system, otherwise
- // creates the default auth system for each platform.
- std::unique_ptr<HttpAuthMechanism> CreateAuthSystem(
- #if !BUILDFLAG(IS_ANDROID)
- HttpAuthHandlerNegotiate::AuthLibrary* auth_library,
- #endif
- const HttpAuthPreferences* prefs,
- HttpAuthMechanismFactory negotiate_auth_system_factory) {
- if (negotiate_auth_system_factory)
- return negotiate_auth_system_factory.Run(prefs);
- #if BUILDFLAG(IS_ANDROID)
- return std::make_unique<net::android::HttpAuthNegotiateAndroid>(prefs);
- #elif BUILDFLAG(IS_WIN)
- return std::make_unique<HttpAuthSSPI>(auth_library,
- HttpAuth::AUTH_SCHEME_NEGOTIATE);
- #elif BUILDFLAG(IS_POSIX)
- return std::make_unique<HttpAuthGSSAPI>(auth_library,
- CHROME_GSS_SPNEGO_MECH_OID_DESC);
- #endif
- }
- } // namespace
- HttpAuthHandlerNegotiate::Factory::Factory(
- HttpAuthMechanismFactory negotiate_auth_system_factory)
- : negotiate_auth_system_factory_(negotiate_auth_system_factory) {}
- HttpAuthHandlerNegotiate::Factory::~Factory() = default;
- #if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(IS_POSIX)
- const std::string& HttpAuthHandlerNegotiate::Factory::GetLibraryNameForTesting()
- const {
- return auth_library_->GetLibraryNameForTesting();
- }
- #endif // !BUILDFLAG(IS_ANDROID) && BUILDFLAG(IS_POSIX)
- int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
- HttpAuthChallengeTokenizer* challenge,
- HttpAuth::Target target,
- const SSLInfo& ssl_info,
- const NetworkIsolationKey& network_isolation_key,
- const url::SchemeHostPort& scheme_host_port,
- CreateReason reason,
- int digest_nonce_count,
- const NetLogWithSource& net_log,
- HostResolver* host_resolver,
- std::unique_ptr<HttpAuthHandler>* handler) {
- #if BUILDFLAG(IS_WIN)
- if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
- return ERR_UNSUPPORTED_AUTH_SCHEME;
- // TODO(cbentzel): Move towards model of parsing in the factory
- // method and only constructing when valid.
- std::unique_ptr<HttpAuthHandler> tmp_handler(
- std::make_unique<HttpAuthHandlerNegotiate>(
- CreateAuthSystem(auth_library_.get(), http_auth_preferences(),
- negotiate_auth_system_factory_),
- http_auth_preferences(), host_resolver));
- #elif BUILDFLAG(IS_ANDROID)
- if (is_unsupported_ || !http_auth_preferences() ||
- http_auth_preferences()->AuthAndroidNegotiateAccountType().empty() ||
- reason == CREATE_PREEMPTIVE)
- return ERR_UNSUPPORTED_AUTH_SCHEME;
- // TODO(cbentzel): Move towards model of parsing in the factory
- // method and only constructing when valid.
- std::unique_ptr<HttpAuthHandler> tmp_handler(
- std::make_unique<HttpAuthHandlerNegotiate>(
- CreateAuthSystem(http_auth_preferences(),
- negotiate_auth_system_factory_),
- http_auth_preferences(), host_resolver));
- #elif BUILDFLAG(IS_POSIX)
- if (is_unsupported_)
- return ERR_UNSUPPORTED_AUTH_SCHEME;
- #if BUILDFLAG(IS_CHROMEOS)
- // Note: Don't set is_unsupported_ = true here. AllowGssapiLibraryLoad()
- // might change to true during a session.
- if (!http_auth_preferences()->AllowGssapiLibraryLoad())
- return ERR_UNSUPPORTED_AUTH_SCHEME;
- #endif // BUILDFLAG(IS_CHROMEOS)
- if (!auth_library_->Init(net_log)) {
- is_unsupported_ = true;
- return ERR_UNSUPPORTED_AUTH_SCHEME;
- }
- // TODO(ahendrickson): Move towards model of parsing in the factory
- // method and only constructing when valid.
- std::unique_ptr<HttpAuthHandler> tmp_handler(
- std::make_unique<HttpAuthHandlerNegotiate>(
- CreateAuthSystem(auth_library_.get(), http_auth_preferences(),
- negotiate_auth_system_factory_),
- http_auth_preferences(), host_resolver));
- #endif
- if (!tmp_handler->InitFromChallenge(challenge, target, ssl_info,
- network_isolation_key, scheme_host_port,
- net_log)) {
- return ERR_INVALID_RESPONSE;
- }
- handler->swap(tmp_handler);
- return OK;
- }
- HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
- std::unique_ptr<HttpAuthMechanism> auth_system,
- const HttpAuthPreferences* prefs,
- HostResolver* resolver)
- : auth_system_(std::move(auth_system)),
- resolver_(resolver),
- http_auth_preferences_(prefs) {}
- HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() = default;
- // Require identity on first pass instead of second.
- bool HttpAuthHandlerNegotiate::NeedsIdentity() {
- return auth_system_->NeedsIdentity();
- }
- bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
- if (target_ == HttpAuth::AUTH_PROXY)
- return true;
- if (!http_auth_preferences_)
- return false;
- return http_auth_preferences_->CanUseDefaultCredentials(scheme_host_port_);
- }
- bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() {
- return auth_system_->AllowsExplicitCredentials();
- }
- // The Negotiate challenge header looks like:
- // WWW-Authenticate: NEGOTIATE auth-data
- bool HttpAuthHandlerNegotiate::Init(
- HttpAuthChallengeTokenizer* challenge,
- const SSLInfo& ssl_info,
- const NetworkIsolationKey& network_isolation_key) {
- network_isolation_key_ = network_isolation_key;
- #if BUILDFLAG(IS_POSIX)
- if (!auth_system_->Init(net_log())) {
- VLOG(1) << "can't initialize GSSAPI library";
- return false;
- }
- // GSSAPI does not provide a way to enter username/password to
- // obtain a TGT. If the default credentials are not allowed for
- // a particular site (based on allowlist), fall back to a
- // different scheme.
- if (!AllowsDefaultCredentials())
- return false;
- #endif
- auth_system_->SetDelegation(GetDelegationType());
- auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE;
- score_ = 4;
- properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
- HttpAuth::AuthorizationResult auth_result =
- auth_system_->ParseChallenge(challenge);
- if (auth_result != HttpAuth::AUTHORIZATION_RESULT_ACCEPT)
- return false;
- // Try to extract channel bindings.
- if (ssl_info.is_valid())
- x509_util::GetTLSServerEndPointChannelBinding(*ssl_info.cert,
- &channel_bindings_);
- if (!channel_bindings_.empty())
- net_log().AddEvent(NetLogEventType::AUTH_CHANNEL_BINDINGS,
- [&](NetLogCaptureMode capture_mode) {
- return NetLogParameterChannelBindings(
- channel_bindings_, capture_mode);
- });
- return true;
- }
- int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
- const AuthCredentials* credentials,
- const HttpRequestInfo* request,
- CompletionOnceCallback callback,
- std::string* auth_token) {
- DCHECK(callback_.is_null());
- DCHECK(auth_token_ == nullptr);
- auth_token_ = auth_token;
- if (already_called_) {
- DCHECK((!has_credentials_ && credentials == nullptr) ||
- (has_credentials_ && credentials->Equals(credentials_)));
- next_state_ = STATE_GENERATE_AUTH_TOKEN;
- } else {
- already_called_ = true;
- if (credentials) {
- has_credentials_ = true;
- credentials_ = *credentials;
- }
- next_state_ = STATE_RESOLVE_CANONICAL_NAME;
- }
- int rv = DoLoop(OK);
- if (rv == ERR_IO_PENDING)
- callback_ = std::move(callback);
- return rv;
- }
- HttpAuth::AuthorizationResult
- HttpAuthHandlerNegotiate::HandleAnotherChallengeImpl(
- HttpAuthChallengeTokenizer* challenge) {
- return auth_system_->ParseChallenge(challenge);
- }
- std::string HttpAuthHandlerNegotiate::CreateSPN(
- const std::string& server,
- const url::SchemeHostPort& scheme_host_port) {
- // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
- // and in the form HTTP@<host>:<port> through GSSAPI
- // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
- //
- // However, reality differs from the specification. A good description of
- // the problems can be found here:
- // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
- //
- // Typically the <host> portion should be the canonical FQDN for the service.
- // If this could not be resolved, the original hostname in the URL will be
- // attempted instead. However, some intranets register SPNs using aliases
- // for the same canonical DNS name to allow multiple web services to reside
- // on the same host machine without requiring different ports. IE6 and IE7
- // have hotpatches that allow the default behavior to be overridden.
- // http://support.microsoft.com/kb/911149
- // http://support.microsoft.com/kb/938305
- //
- // According to the spec, the <port> option should be included if it is a
- // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
- // historically browsers have not included the port, even on non-standard
- // ports. IE6 required a hotpatch and a registry setting to enable
- // including non-standard ports, and IE7 and IE8 also require the same
- // registry setting, but no hotpatch. Firefox does not appear to have an
- // option to include non-standard ports as of 3.6.
- // http://support.microsoft.com/kb/908209
- //
- // Without any command-line flags, Chrome matches the behavior of Firefox
- // and IE. Users can override the behavior so aliases are allowed and
- // non-standard ports are included.
- int port = scheme_host_port.port();
- #if BUILDFLAG(IS_WIN)
- static const char kSpnSeparator = '/';
- #elif BUILDFLAG(IS_POSIX)
- static const char kSpnSeparator = '@';
- #endif
- if (port != 80 && port != 443 &&
- (http_auth_preferences_ &&
- http_auth_preferences_->NegotiateEnablePort())) {
- return base::StringPrintf("HTTP%c%s:%d", kSpnSeparator, server.c_str(),
- port);
- } else {
- return base::StringPrintf("HTTP%c%s", kSpnSeparator, server.c_str());
- }
- }
- void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
- int rv = DoLoop(result);
- if (rv != ERR_IO_PENDING)
- DoCallback(rv);
- }
- void HttpAuthHandlerNegotiate::DoCallback(int rv) {
- DCHECK(rv != ERR_IO_PENDING);
- DCHECK(!callback_.is_null());
- std::move(callback_).Run(rv);
- }
- int HttpAuthHandlerNegotiate::DoLoop(int result) {
- DCHECK(next_state_ != STATE_NONE);
- int rv = result;
- do {
- State state = next_state_;
- next_state_ = STATE_NONE;
- switch (state) {
- case STATE_RESOLVE_CANONICAL_NAME:
- DCHECK_EQ(OK, rv);
- rv = DoResolveCanonicalName();
- break;
- case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
- rv = DoResolveCanonicalNameComplete(rv);
- break;
- case STATE_GENERATE_AUTH_TOKEN:
- DCHECK_EQ(OK, rv);
- rv = DoGenerateAuthToken();
- break;
- case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
- rv = DoGenerateAuthTokenComplete(rv);
- break;
- default:
- NOTREACHED() << "bad state";
- rv = ERR_FAILED;
- break;
- }
- } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
- return rv;
- }
- int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
- next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
- if ((http_auth_preferences_ &&
- http_auth_preferences_->NegotiateDisableCnameLookup()) ||
- !resolver_)
- return OK;
- // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
- HostResolver::ResolveHostParameters parameters;
- parameters.include_canonical_name = true;
- resolve_host_request_ = resolver_->CreateRequest(
- scheme_host_port_, network_isolation_key_, net_log(), parameters);
- return resolve_host_request_->Start(base::BindOnce(
- &HttpAuthHandlerNegotiate::OnIOComplete, base::Unretained(this)));
- }
- int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
- DCHECK_NE(ERR_IO_PENDING, rv);
- std::string server = scheme_host_port_.host();
- if (resolve_host_request_) {
- if (rv == OK) {
- // Expect at most a single DNS alias representing the canonical name
- // because the `HostResolver` request was made with
- // `include_canonical_name`.
- DCHECK(resolve_host_request_->GetDnsAliasResults());
- DCHECK_LE(resolve_host_request_->GetDnsAliasResults()->size(), 1u);
- if (!resolve_host_request_->GetDnsAliasResults()->empty()) {
- server = *resolve_host_request_->GetDnsAliasResults()->begin();
- DCHECK(!server.empty());
- }
- } else {
- // Even in the error case, try to use origin_.host instead of
- // passing the failure on to the caller.
- VLOG(1) << "Problem finding canonical name for SPN for host "
- << scheme_host_port_.host() << ": " << ErrorToString(rv);
- rv = OK;
- }
- }
- next_state_ = STATE_GENERATE_AUTH_TOKEN;
- spn_ = CreateSPN(server, scheme_host_port_);
- resolve_host_request_ = nullptr;
- return rv;
- }
- int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
- next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
- AuthCredentials* credentials = has_credentials_ ? &credentials_ : nullptr;
- return auth_system_->GenerateAuthToken(
- credentials, spn_, channel_bindings_, auth_token_, net_log(),
- base::BindOnce(&HttpAuthHandlerNegotiate::OnIOComplete,
- base::Unretained(this)));
- }
- int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
- DCHECK_NE(ERR_IO_PENDING, rv);
- auth_token_ = nullptr;
- return rv;
- }
- DelegationType HttpAuthHandlerNegotiate::GetDelegationType() const {
- if (!http_auth_preferences_)
- return DelegationType::kNone;
- // TODO(cbentzel): Should delegation be allowed on proxies?
- if (target_ == HttpAuth::AUTH_PROXY)
- return DelegationType::kNone;
- return http_auth_preferences_->GetDelegationType(scheme_host_port_);
- }
- } // namespace net
|