mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-03 18:56:09 +03:00
420 lines
15 KiB
C++
420 lines
15 KiB
C++
|
// Copyright 2012 The Chromium Authors
|
||
|
// 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/check_op.h"
|
||
|
#include "base/functional/bind.h"
|
||
|
#include "base/functional/callback_helpers.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::Dict NetLogParameterChannelBindings(
|
||
|
const std::string& channel_binding_token,
|
||
|
NetLogCaptureMode capture_mode) {
|
||
|
base::Value::Dict dict;
|
||
|
if (!NetLogCaptureIncludesSocketBytes(capture_mode))
|
||
|
return dict;
|
||
|
|
||
|
dict.Set("token", base::HexEncode(channel_binding_token.data(),
|
||
|
channel_binding_token.size()));
|
||
|
return 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 NetworkAnonymizationKey& network_anonymization_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) || BUILDFLAG(IS_LINUX)
|
||
|
// Note: Don't set is_unsupported_ = true here. AllowGssapiLibraryLoad()
|
||
|
// might change to true during a session.
|
||
|
if (!http_auth_preferences() ||
|
||
|
!http_auth_preferences()->AllowGssapiLibraryLoad()) {
|
||
|
return ERR_UNSUPPORTED_AUTH_SCHEME;
|
||
|
}
|
||
|
#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
|
||
|
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_anonymization_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 NetworkAnonymizationKey& network_anonymization_key) {
|
||
|
network_anonymization_key_ = network_anonymization_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,
|
||
|
// however ChromesOS provides the user an opportunity to enter their
|
||
|
// credentials and generate a new TGT on OS level (see b/260522530). 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_anonymization_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
|