blob: 612e52eb88d1ed27fff8b2ffaf6ce3b3728b6c4f [file] [log] [blame]
// 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/bind_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 "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_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"
namespace net {
using DelegationType = HttpAuth::DelegationType;
namespace {
base::Value NetLogParameterChannelBindings(
const std::string& channel_binding_token,
NetLogCaptureMode capture_mode) {
base::DictionaryValue dict;
if (!capture_mode.include_socket_bytes())
return std::move(dict);
dict.Clear();
dict.SetString("token", base::HexEncode(channel_binding_token.data(),
channel_binding_token.size()));
return 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<HttpNegotiateAuthSystem> CreateAuthSystem(
#if !defined(OS_ANDROID)
HttpAuthHandlerNegotiate::AuthLibrary* auth_library,
#endif
#if defined(OS_WIN)
ULONG max_token_length,
#endif
const HttpAuthPreferences* prefs,
HttpAuthHandlerFactory::NegotiateAuthSystemFactory
negotiate_auth_system_factory) {
if (negotiate_auth_system_factory)
return negotiate_auth_system_factory.Run(prefs);
#if defined(OS_ANDROID)
return std::make_unique<net::android::HttpAuthNegotiateAndroid>(prefs);
#elif defined(OS_WIN)
return std::make_unique<HttpAuthSSPI>(auth_library, "Negotiate", NEGOSSP_NAME,
max_token_length);
#elif defined(OS_POSIX)
return std::make_unique<HttpAuthGSSAPI>(auth_library, "Negotiate",
CHROME_GSS_SPNEGO_MECH_OID_DESC);
#endif
}
} // namespace
HttpAuthHandlerNegotiate::Factory::Factory(
NegotiateAuthSystemFactory negotiate_auth_system_factory)
: negotiate_auth_system_factory_(negotiate_auth_system_factory) {}
HttpAuthHandlerNegotiate::Factory::~Factory() = default;
#if !defined(OS_ANDROID) && defined(OS_POSIX)
const std::string& HttpAuthHandlerNegotiate::Factory::GetLibraryNameForTesting()
const {
return auth_library_->GetLibraryNameForTesting();
}
#endif // !defined(OS_ANDROID) && defined(OS_POSIX)
int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
HttpAuthChallengeTokenizer* challenge,
HttpAuth::Target target,
const SSLInfo& ssl_info,
const GURL& origin,
CreateReason reason,
int digest_nonce_count,
const NetLogWithSource& net_log,
HostResolver* host_resolver,
std::unique_ptr<HttpAuthHandler>* handler) {
#if defined(OS_WIN)
if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
return ERR_UNSUPPORTED_AUTH_SCHEME;
if (max_token_length_ == 0) {
int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME,
&max_token_length_);
if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
is_unsupported_ = true;
if (rv != OK)
return rv;
}
// TODO(cbentzel): Move towards model of parsing in the factory
// method and only constructing when valid.
std::unique_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerNegotiate(
CreateAuthSystem(auth_library_.get(), max_token_length_,
http_auth_preferences(), negotiate_auth_system_factory_),
http_auth_preferences(), host_resolver));
#elif defined(OS_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(new HttpAuthHandlerNegotiate(
CreateAuthSystem(http_auth_preferences(), negotiate_auth_system_factory_),
http_auth_preferences(), host_resolver));
#elif defined(OS_POSIX)
if (is_unsupported_)
return ERR_UNSUPPORTED_AUTH_SCHEME;
#if defined(OS_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
if (!auth_library_->Init()) {
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(new 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, origin,
net_log))
return ERR_INVALID_RESPONSE;
handler->swap(tmp_handler);
return OK;
}
HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
std::unique_ptr<HttpNegotiateAuthSystem> auth_system,
const HttpAuthPreferences* prefs,
HostResolver* resolver)
: auth_system_(std::move(auth_system)),
resolver_(resolver),
already_called_(false),
has_credentials_(false),
auth_token_(nullptr),
next_state_(STATE_NONE),
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(origin_);
}
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) {
#if defined(OS_POSIX)
if (!auth_system_->Init()) {
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 whitelist), 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,
base::Bind(&NetLogParameterChannelBindings, channel_bindings_));
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 GURL& origin) {
// 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 = origin.EffectiveIntPort();
#if defined(OS_WIN)
static const char kSpnSeparator = '/';
#elif defined(OS_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(
HostPortPair(origin_.host(), 0), 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 = origin_.host();
if (resolve_host_request_) {
if (rv == OK) {
DCHECK(resolve_host_request_->GetAddressResults());
const std::string& canonical_name =
resolve_host_request_->GetAddressResults().value().canonical_name();
if (!canonical_name.empty())
server = canonical_name;
} 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 "
<< origin_.host() << ": " << ErrorToString(rv);
rv = OK;
}
}
next_state_ = STATE_GENERATE_AUTH_TOKEN;
spn_ = CreateSPN(server, origin_);
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_,
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(origin_);
}
} // namespace net