blob: e12a7839888c2416d5134d7c5321770d782a7beb [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_gssapi_posix.h"
#include <limits>
#include <string>
#include "base/base64.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "net/base/net_errors.h"
#include "net/http/http_auth_gssapi_posix.h"
#include "net/http/http_auth_multi_round_parse.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_values.h"
#include "net/log/net_log_with_source.h"
#include "net/net_buildflags.h"
namespace net {
using DelegationType = HttpAuth::DelegationType;
// Exported mechanism for GSSAPI. We always use SPNEGO:
// iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
6,
const_cast<char*>("\x2b\x06\x01\x05\x05\x02")
};
gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC =
&CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
OM_uint32 DelegationTypeToFlag(DelegationType delegation_type) {
switch (delegation_type) {
case DelegationType::kNone:
return 0;
case DelegationType::kByKdcPolicy:
return GSS_C_DELEG_POLICY_FLAG;
case DelegationType::kUnconstrained:
return GSS_C_DELEG_FLAG;
}
}
// ScopedBuffer releases a gss_buffer_t when it goes out of scope.
class ScopedBuffer {
public:
ScopedBuffer(gss_buffer_t buffer, GSSAPILibrary* gssapi_lib)
: buffer_(buffer), gssapi_lib_(gssapi_lib) {
DCHECK(gssapi_lib_);
}
~ScopedBuffer() {
if (buffer_ != GSS_C_NO_BUFFER) {
OM_uint32 minor_status = 0;
OM_uint32 major_status =
gssapi_lib_->release_buffer(&minor_status, buffer_);
DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
<< "Problem releasing buffer. major=" << major_status
<< ", minor=" << minor_status;
buffer_ = GSS_C_NO_BUFFER;
}
}
private:
gss_buffer_t buffer_;
GSSAPILibrary* gssapi_lib_;
DISALLOW_COPY_AND_ASSIGN(ScopedBuffer);
};
// ScopedName releases a gss_name_t when it goes out of scope.
class ScopedName {
public:
ScopedName(gss_name_t name, GSSAPILibrary* gssapi_lib)
: name_(name), gssapi_lib_(gssapi_lib) {
DCHECK(gssapi_lib_);
}
~ScopedName() {
if (name_ != GSS_C_NO_NAME) {
OM_uint32 minor_status = 0;
OM_uint32 major_status = gssapi_lib_->release_name(&minor_status, &name_);
if (major_status != GSS_S_COMPLETE) {
DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
<< "Problem releasing name. "
<< GetGssStatusValue(nullptr, "gss_release_name", major_status,
minor_status);
}
name_ = GSS_C_NO_NAME;
}
}
private:
gss_name_t name_;
GSSAPILibrary* gssapi_lib_;
DISALLOW_COPY_AND_ASSIGN(ScopedName);
};
bool OidEquals(const gss_OID left, const gss_OID right) {
if (left->length != right->length)
return false;
return 0 == memcmp(left->elements, right->elements, right->length);
}
base::Value GetGssStatusCodeValue(GSSAPILibrary* gssapi_lib,
OM_uint32 status,
OM_uint32 status_code_type) {
base::Value rv{base::Value::Type::DICTIONARY};
rv.SetIntKey("status", status);
// Message lookups aren't performed if there's no library or if the status
// indicates success.
if (!gssapi_lib || status == GSS_S_COMPLETE)
return rv;
// gss_display_status() can potentially return multiple strings by sending
// each string on successive invocations. State is maintained across these
// invocations in a caller supplied OM_uint32. After each successful call,
// the context is set to a non-zero value that should be passed as a message
// context to each successive gss_display_status() call. The initial and
// terminal values of this context storage is 0.
OM_uint32 message_context = 0;
// To account for the off chance that gss_display_status() misbehaves and gets
// into an infinite loop, we'll artificially limit the number of iterations to
// |kMaxDisplayIterations|. This limit is arbitrary.
constexpr size_t kMaxDisplayIterations = 8;
size_t iterations = 0;
// In addition, each message string is again arbitrarily limited to
// |kMaxMsgLength|. There's no real documented limit to work with here.
constexpr size_t kMaxMsgLength = 4096;
base::Value messages{base::Value::Type::LIST};
do {
gss_buffer_desc_struct message_buffer = GSS_C_EMPTY_BUFFER;
ScopedBuffer message_buffer_releaser(&message_buffer, gssapi_lib);
OM_uint32 minor_status = 0;
OM_uint32 major_status = gssapi_lib->display_status(
&minor_status, status, status_code_type, GSS_C_NO_OID, &message_context,
&message_buffer);
if (major_status != GSS_S_COMPLETE || message_buffer.length == 0 ||
!message_buffer.value) {
continue;
}
base::StringPiece message_string{
static_cast<const char*>(message_buffer.value),
std::min(kMaxMsgLength, message_buffer.length)};
// The returned string is almost assuredly ASCII, but be defensive.
if (!base::IsStringUTF8(message_string))
continue;
messages.Append(message_string);
} while (message_context != 0 && ++iterations < kMaxDisplayIterations);
if (messages.GetList().size() > 0)
rv.SetKey("message", std::move(messages));
return rv;
}
base::Value GetGssStatusValue(GSSAPILibrary* gssapi_lib,
base::StringPiece method,
OM_uint32 major_status,
OM_uint32 minor_status) {
base::Value params{base::Value::Type::DICTIONARY};
params.SetStringKey("function", method);
params.SetKey("major_status", GetGssStatusCodeValue(gssapi_lib, major_status,
GSS_C_GSS_CODE));
params.SetKey("minor_status", GetGssStatusCodeValue(gssapi_lib, minor_status,
GSS_C_MECH_CODE));
return params;
}
base::Value OidToValue(gss_OID oid) {
base::Value params(base::Value::Type::DICTIONARY);
if (!oid || oid->length == 0) {
params.SetStringKey("oid", "<Empty OID>");
return params;
}
params.SetIntKey("length", oid->length);
if (!oid->elements)
return params;
// Cap OID content at arbitrary limit 1k.
constexpr OM_uint32 kMaxOidDataSize = 1024;
params.SetKey(
"bytes",
NetLogBinaryValue(oid->elements, std::min(kMaxOidDataSize, oid->length)));
// Based on RFC 2744 Appendix A. Hardcoding the OIDs in the list below to
// avoid having a static dependency on the library.
static const struct {
const char* symbolic_name;
const gss_OID_desc oid_desc;
} kWellKnownOIDs[] = {
{"GSS_C_NT_USER_NAME",
{10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01")}},
{"GSS_C_NT_MACHINE_UID_NAME",
{10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02")}},
{"GSS_C_NT_STRING_UID_NAME",
{10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03")}},
{"GSS_C_NT_HOSTBASED_SERVICE_X",
{6, const_cast<char*>("\x2b\x06\x01\x05\x06\x02")}},
{"GSS_C_NT_HOSTBASED_SERVICE",
{10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")}},
{"GSS_C_NT_ANONYMOUS", {6, const_cast<char*>("\x2b\x06\01\x05\x06\x03")}},
{"GSS_C_NT_EXPORT_NAME",
{6, const_cast<char*>("\x2b\x06\x01\x05\x06\x04")}}};
for (auto& well_known_oid : kWellKnownOIDs) {
if (OidEquals(oid, const_cast<const gss_OID>(&well_known_oid.oid_desc)))
params.SetStringKey("oid", well_known_oid.symbolic_name);
}
return params;
}
base::Value GetDisplayNameValue(GSSAPILibrary* gssapi_lib,
const gss_name_t gss_name) {
OM_uint32 major_status = 0;
OM_uint32 minor_status = 0;
gss_buffer_desc_struct name = GSS_C_EMPTY_BUFFER;
gss_OID name_type = GSS_C_NO_OID;
base::Value rv{base::Value::Type::DICTIONARY};
major_status =
gssapi_lib->display_name(&minor_status, gss_name, &name, &name_type);
ScopedBuffer scoped_output_name(&name, gssapi_lib);
if (major_status != GSS_S_COMPLETE) {
rv.SetKey("error", GetGssStatusValue(gssapi_lib, "gss_display_name",
major_status, minor_status));
return rv;
}
auto name_string =
base::StringPiece(reinterpret_cast<const char*>(name.value), name.length);
rv.SetKey("name", base::IsStringUTF8(name_string)
? NetLogStringValue(name_string)
: NetLogBinaryValue(name.value, name.length));
rv.SetKey("type", OidToValue(name_type));
return rv;
}
base::Value ContextFlagsToValue(OM_uint32 flags) {
base::Value rv{base::Value::Type::DICTIONARY};
rv.SetStringKey("value", base::StringPrintf("0x%08x", flags));
rv.SetBoolKey("delegated", (flags & GSS_C_DELEG_FLAG) == GSS_C_DELEG_FLAG);
rv.SetBoolKey("mutual", (flags & GSS_C_MUTUAL_FLAG) == GSS_C_MUTUAL_FLAG);
return rv;
}
base::Value GetContextStateAsValue(GSSAPILibrary* gssapi_lib,
const gss_ctx_id_t context_handle) {
base::Value rv{base::Value::Type::DICTIONARY};
if (context_handle == GSS_C_NO_CONTEXT) {
rv.SetKey("error",
GetGssStatusValue(nullptr, "<none>", GSS_S_NO_CONTEXT, 0));
return rv;
}
OM_uint32 major_status = 0;
OM_uint32 minor_status = 0;
gss_name_t src_name = GSS_C_NO_NAME;
gss_name_t targ_name = GSS_C_NO_NAME;
OM_uint32 lifetime_rec = 0;
gss_OID mech_type = GSS_C_NO_OID;
OM_uint32 ctx_flags = 0;
int locally_initiated = 0;
int open = 0;
major_status = gssapi_lib->inquire_context(&minor_status,
context_handle,
&src_name,
&targ_name,
&lifetime_rec,
&mech_type,
&ctx_flags,
&locally_initiated,
&open);
if (major_status != GSS_S_COMPLETE) {
rv.SetKey("error", GetGssStatusValue(gssapi_lib, "gss_inquire_context",
major_status, minor_status));
return rv;
}
ScopedName scoped_src_name(src_name, gssapi_lib);
ScopedName scoped_targ_name(targ_name, gssapi_lib);
rv.SetKey("source", GetDisplayNameValue(gssapi_lib, src_name));
rv.SetKey("target", GetDisplayNameValue(gssapi_lib, targ_name));
// lifetime_rec is a uint32, while base::Value only takes ints. On 32 bit
// platforms uint32 doesn't fit on an int.
rv.SetStringKey("lifetime", base::NumberToString(lifetime_rec));
rv.SetKey("mechanism", OidToValue(mech_type));
rv.SetKey("flags", ContextFlagsToValue(ctx_flags));
rv.SetBoolKey("open", !!open);
return rv;
}
namespace {
// Return a NetLog value for the result of loading a library.
base::Value LibraryLoadResultParams(base::StringPiece library_name,
base::StringPiece load_result) {
base::Value params{base::Value::Type::DICTIONARY};
params.SetStringKey("library_name", library_name);
if (!load_result.empty())
params.SetStringKey("load_result", load_result);
return params;
}
} // namespace
GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name)
: gssapi_library_name_(gssapi_library_name) {}
GSSAPISharedLibrary::~GSSAPISharedLibrary() {
if (gssapi_library_) {
base::UnloadNativeLibrary(gssapi_library_);
gssapi_library_ = nullptr;
}
}
bool GSSAPISharedLibrary::Init(const NetLogWithSource& net_log) {
if (!initialized_)
InitImpl(net_log);
return initialized_;
}
bool GSSAPISharedLibrary::InitImpl(const NetLogWithSource& net_log) {
DCHECK(!initialized_);
gssapi_library_ = LoadSharedLibrary(net_log);
if (gssapi_library_ == nullptr)
return false;
initialized_ = true;
return true;
}
base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary(
const NetLogWithSource& net_log) {
const char* const* library_names;
size_t num_lib_names;
const char* user_specified_library[1];
if (!gssapi_library_name_.empty()) {
user_specified_library[0] = gssapi_library_name_.c_str();
library_names = user_specified_library;
num_lib_names = 1;
} else {
static const char* const kDefaultLibraryNames[] = {
#if defined(OS_MACOSX)
"/System/Library/Frameworks/GSS.framework/GSS"
#elif defined(OS_OPENBSD)
"libgssapi.so" // Heimdal - OpenBSD
#else
"libgssapi_krb5.so.2", // MIT Kerberos - FC, Suse10, Debian
"libgssapi.so.4", // Heimdal - Suse10, MDK
"libgssapi.so.2", // Heimdal - Gentoo
"libgssapi.so.1" // Heimdal - Suse9, CITI - FC, MDK, Suse10
#endif
};
library_names = kDefaultLibraryNames;
num_lib_names = base::size(kDefaultLibraryNames);
}
net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_LOAD);
// There has to be at least one candidate.
DCHECK_NE(0u, num_lib_names);
const char* library_name = nullptr;
base::NativeLibraryLoadError load_error;
for (size_t i = 0; i < num_lib_names; ++i) {
load_error = base::NativeLibraryLoadError();
library_name = library_names[i];
base::FilePath file_path(library_name);
// TODO(asanka): Move library loading to a separate thread.
// http://crbug.com/66702
base::ThreadRestrictions::ScopedAllowIO allow_io_temporarily;
base::NativeLibrary lib = base::LoadNativeLibrary(file_path, &load_error);
if (lib) {
if (BindMethods(lib, library_name, net_log)) {
net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
return LibraryLoadResultParams(library_name, "");
});
return lib;
}
base::UnloadNativeLibrary(lib);
}
}
// If loading failed, then log the result of the final attempt. Doing so
// is specially important on platforms where there's only one possible
// library. Doing so also always logs the failure when the GSSAPI library
// name is explicitly specified.
net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
return LibraryLoadResultParams(library_name, load_error.ToString());
});
return nullptr;
}
namespace {
base::Value BindFailureParams(base::StringPiece library_name,
base::StringPiece method) {
base::Value params{base::Value::Type::DICTIONARY};
params.SetStringKey("library_name", library_name);
params.SetStringKey("method", method);
return params;
}
void* BindUntypedMethod(base::NativeLibrary lib,
base::StringPiece library_name,
base::StringPiece method,
const NetLogWithSource& net_log) {
void* ptr = base::GetFunctionPointerFromNativeLibrary(lib, method);
if (ptr == nullptr) {
std::string method_string = method.as_string();
net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_BIND_FAILED,
[&] { return BindFailureParams(library_name, method); });
}
return ptr;
}
template <typename T>
bool BindMethod(base::NativeLibrary lib,
base::StringPiece library_name,
base::StringPiece method,
T* receiver,
const NetLogWithSource& net_log) {
*receiver = reinterpret_cast<T>(
BindUntypedMethod(lib, library_name, method, net_log));
return *receiver != nullptr;
}
} // namespace
bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib,
base::StringPiece name,
const NetLogWithSource& net_log) {
bool ok = true;
// It's unlikely for BindMethods() to fail if LoadNativeLibrary() succeeded. A
// failure in this function indicates an interoperability issue whose
// diagnosis requires knowing all the methods that are missing. Hence |ok| is
// updated in a manner that prevents short-circuiting the BindGssMethod()
// invocations.
ok &= BindMethod(lib, name, "gss_delete_sec_context", &delete_sec_context_,
net_log);
ok &= BindMethod(lib, name, "gss_display_name", &display_name_, net_log);
ok &= BindMethod(lib, name, "gss_display_status", &display_status_, net_log);
ok &= BindMethod(lib, name, "gss_import_name", &import_name_, net_log);
ok &= BindMethod(lib, name, "gss_init_sec_context", &init_sec_context_,
net_log);
ok &=
BindMethod(lib, name, "gss_inquire_context", &inquire_context_, net_log);
ok &= BindMethod(lib, name, "gss_release_buffer", &release_buffer_, net_log);
ok &= BindMethod(lib, name, "gss_release_name", &release_name_, net_log);
ok &=
BindMethod(lib, name, "gss_wrap_size_limit", &wrap_size_limit_, net_log);
if (LIKELY(ok))
return true;
delete_sec_context_ = nullptr;
display_name_ = nullptr;
display_status_ = nullptr;
import_name_ = nullptr;
init_sec_context_ = nullptr;
inquire_context_ = nullptr;
release_buffer_ = nullptr;
release_name_ = nullptr;
wrap_size_limit_ = nullptr;
return false;
}
OM_uint32 GSSAPISharedLibrary::import_name(
OM_uint32* minor_status,
const gss_buffer_t input_name_buffer,
const gss_OID input_name_type,
gss_name_t* output_name) {
DCHECK(initialized_);
return import_name_(minor_status, input_name_buffer, input_name_type,
output_name);
}
OM_uint32 GSSAPISharedLibrary::release_name(
OM_uint32* minor_status,
gss_name_t* input_name) {
DCHECK(initialized_);
return release_name_(minor_status, input_name);
}
OM_uint32 GSSAPISharedLibrary::release_buffer(
OM_uint32* minor_status,
gss_buffer_t buffer) {
DCHECK(initialized_);
return release_buffer_(minor_status, buffer);
}
OM_uint32 GSSAPISharedLibrary::display_name(
OM_uint32* minor_status,
const gss_name_t input_name,
gss_buffer_t output_name_buffer,
gss_OID* output_name_type) {
DCHECK(initialized_);
return display_name_(minor_status,
input_name,
output_name_buffer,
output_name_type);
}
OM_uint32 GSSAPISharedLibrary::display_status(
OM_uint32* minor_status,
OM_uint32 status_value,
int status_type,
const gss_OID mech_type,
OM_uint32* message_context,
gss_buffer_t status_string) {
DCHECK(initialized_);
return display_status_(minor_status, status_value, status_type, mech_type,
message_context, status_string);
}
OM_uint32 GSSAPISharedLibrary::init_sec_context(
OM_uint32* minor_status,
const gss_cred_id_t initiator_cred_handle,
gss_ctx_id_t* context_handle,
const gss_name_t target_name,
const gss_OID mech_type,
OM_uint32 req_flags,
OM_uint32 time_req,
const gss_channel_bindings_t input_chan_bindings,
const gss_buffer_t input_token,
gss_OID* actual_mech_type,
gss_buffer_t output_token,
OM_uint32* ret_flags,
OM_uint32* time_rec) {
DCHECK(initialized_);
return init_sec_context_(minor_status,
initiator_cred_handle,
context_handle,
target_name,
mech_type,
req_flags,
time_req,
input_chan_bindings,
input_token,
actual_mech_type,
output_token,
ret_flags,
time_rec);
}
OM_uint32 GSSAPISharedLibrary::wrap_size_limit(
OM_uint32* minor_status,
const gss_ctx_id_t context_handle,
int conf_req_flag,
gss_qop_t qop_req,
OM_uint32 req_output_size,
OM_uint32* max_input_size) {
DCHECK(initialized_);
return wrap_size_limit_(minor_status,
context_handle,
conf_req_flag,
qop_req,
req_output_size,
max_input_size);
}
OM_uint32 GSSAPISharedLibrary::delete_sec_context(
OM_uint32* minor_status,
gss_ctx_id_t* context_handle,
gss_buffer_t output_token) {
// This is called from the owner class' destructor, even if
// Init() is not called, so we can't assume |initialized_|
// is set.
if (!initialized_)
return 0;
return delete_sec_context_(minor_status,
context_handle,
output_token);
}
OM_uint32 GSSAPISharedLibrary::inquire_context(
OM_uint32* minor_status,
const gss_ctx_id_t context_handle,
gss_name_t* src_name,
gss_name_t* targ_name,
OM_uint32* lifetime_rec,
gss_OID* mech_type,
OM_uint32* ctx_flags,
int* locally_initiated,
int* open) {
DCHECK(initialized_);
return inquire_context_(minor_status,
context_handle,
src_name,
targ_name,
lifetime_rec,
mech_type,
ctx_flags,
locally_initiated,
open);
}
const std::string& GSSAPISharedLibrary::GetLibraryNameForTesting() {
return gssapi_library_name_;
}
ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib)
: security_context_(GSS_C_NO_CONTEXT),
gssapi_lib_(gssapi_lib) {
DCHECK(gssapi_lib_);
}
ScopedSecurityContext::~ScopedSecurityContext() {
if (security_context_ != GSS_C_NO_CONTEXT) {
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
OM_uint32 minor_status = 0;
OM_uint32 major_status = gssapi_lib_->delete_sec_context(
&minor_status, &security_context_, &output_token);
DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
<< "Problem releasing security_context. "
<< GetGssStatusValue(gssapi_lib_, "delete_sec_context", major_status,
minor_status);
security_context_ = GSS_C_NO_CONTEXT;
}
}
HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library,
const std::string& scheme,
gss_OID gss_oid)
: scheme_(scheme),
gss_oid_(gss_oid),
library_(library),
scoped_sec_context_(library) {
DCHECK(library_);
}
HttpAuthGSSAPI::~HttpAuthGSSAPI() = default;
bool HttpAuthGSSAPI::Init(const NetLogWithSource& net_log) {
if (!library_)
return false;
return library_->Init(net_log);
}
bool HttpAuthGSSAPI::NeedsIdentity() const {
return decoded_server_auth_token_.empty();
}
bool HttpAuthGSSAPI::AllowsExplicitCredentials() const {
return false;
}
void HttpAuthGSSAPI::SetDelegation(DelegationType delegation_type) {
delegation_type_ = delegation_type;
}
HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
HttpAuthChallengeTokenizer* tok) {
if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) {
return net::ParseFirstRoundChallenge(scheme_, tok);
}
std::string encoded_auth_token;
return net::ParseLaterRoundChallenge(scheme_, tok, &encoded_auth_token,
&decoded_server_auth_token_);
}
int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
const std::string& spn,
const std::string& channel_bindings,
std::string* auth_token,
const NetLogWithSource& net_log,
CompletionOnceCallback /*callback*/) {
DCHECK(auth_token);
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
input_token.length = decoded_server_auth_token_.length();
input_token.value = (input_token.length > 0)
? const_cast<char*>(decoded_server_auth_token_.data())
: nullptr;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
ScopedBuffer scoped_output_token(&output_token, library_);
int rv = GetNextSecurityToken(spn, channel_bindings, &input_token,
&output_token, net_log);
if (rv != OK)
return rv;
// Base64 encode data in output buffer and prepend the scheme.
std::string encode_input(static_cast<char*>(output_token.value),
output_token.length);
std::string encode_output;
base::Base64Encode(encode_input, &encode_output);
*auth_token = scheme_ + " " + encode_output;
return OK;
}
namespace {
// GSSAPI status codes consist of a calling error (essentially, a programmer
// bug), a routine error (defined by the RFC), and supplementary information,
// all bitwise-or'ed together in different regions of the 32 bit return value.
// This means a simple switch on the return codes is not sufficient.
int MapImportNameStatusToError(OM_uint32 major_status) {
if (major_status == GSS_S_COMPLETE)
return OK;
if (GSS_CALLING_ERROR(major_status) != 0)
return ERR_UNEXPECTED;
OM_uint32 routine_error = GSS_ROUTINE_ERROR(major_status);
switch (routine_error) {
case GSS_S_FAILURE:
// Looking at the MIT Kerberos implementation, this typically is returned
// when memory allocation fails. However, the API does not guarantee
// that this is the case, so using ERR_UNEXPECTED rather than
// ERR_OUT_OF_MEMORY.
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_BAD_NAME:
case GSS_S_BAD_NAMETYPE:
return ERR_MALFORMED_IDENTITY;
case GSS_S_DEFECTIVE_TOKEN:
// Not mentioned in the API, but part of code.
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_BAD_MECH:
return ERR_UNSUPPORTED_AUTH_SCHEME;
default:
return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
}
}
int MapInitSecContextStatusToError(OM_uint32 major_status) {
// Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like
// other code just checks if major_status is equivalent to it to indicate
// that there are no other errors included.
if (major_status == GSS_S_COMPLETE || major_status == GSS_S_CONTINUE_NEEDED)
return OK;
if (GSS_CALLING_ERROR(major_status) != 0)
return ERR_UNEXPECTED;
OM_uint32 routine_status = GSS_ROUTINE_ERROR(major_status);
switch (routine_status) {
case GSS_S_DEFECTIVE_TOKEN:
return ERR_INVALID_RESPONSE;
case GSS_S_DEFECTIVE_CREDENTIAL:
// Not expected since this implementation uses the default credential.
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_BAD_SIG:
// Probably won't happen, but it's a bad response.
return ERR_INVALID_RESPONSE;
case GSS_S_NO_CRED:
return ERR_INVALID_AUTH_CREDENTIALS;
case GSS_S_CREDENTIALS_EXPIRED:
return ERR_INVALID_AUTH_CREDENTIALS;
case GSS_S_BAD_BINDINGS:
// This only happens with mutual authentication.
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_NO_CONTEXT:
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_BAD_NAMETYPE:
return ERR_UNSUPPORTED_AUTH_SCHEME;
case GSS_S_BAD_NAME:
return ERR_UNSUPPORTED_AUTH_SCHEME;
case GSS_S_BAD_MECH:
return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
case GSS_S_FAILURE:
// This should be an "Unexpected Security Status" according to the
// GSSAPI documentation, but it's typically used to indicate that
// credentials are not correctly set up on a user machine, such
// as a missing credential cache or hitting this after calling
// kdestroy.
// TODO(cbentzel): Use minor code for even better mapping?
return ERR_MISSING_AUTH_CREDENTIALS;
default:
if (routine_status != 0)
return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
break;
}
OM_uint32 supplemental_status = GSS_SUPPLEMENTARY_INFO(major_status);
// Replays could indicate an attack.
if (supplemental_status & (GSS_S_DUPLICATE_TOKEN | GSS_S_OLD_TOKEN |
GSS_S_UNSEQ_TOKEN | GSS_S_GAP_TOKEN))
return ERR_INVALID_RESPONSE;
// At this point, every documented status has been checked.
return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
}
base::Value ImportNameErrorParams(GSSAPILibrary* library,
base::StringPiece spn,
OM_uint32 major_status,
OM_uint32 minor_status) {
base::Value params{base::Value::Type::DICTIONARY};
params.SetStringKey("spn", spn);
if (major_status != GSS_S_COMPLETE)
params.SetKey("status", GetGssStatusValue(library, "import_name",
major_status, minor_status));
return params;
}
base::Value InitSecContextErrorParams(GSSAPILibrary* library,
gss_ctx_id_t context,
OM_uint32 major_status,
OM_uint32 minor_status) {
base::Value params{base::Value::Type::DICTIONARY};
if (major_status != GSS_S_COMPLETE)
params.SetKey("status", GetGssStatusValue(library, "gss_init_sec_context",
major_status, minor_status));
if (context != GSS_C_NO_CONTEXT)
params.SetKey("context", GetContextStateAsValue(library, context));
return params;
}
} // anonymous namespace
int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn,
const std::string& channel_bindings,
gss_buffer_t in_token,
gss_buffer_t out_token,
const NetLogWithSource& net_log) {
// GSSAPI header files, to this day, require OIDs passed in as non-const
// pointers. Rather than const casting, let's just leave this as non-const.
// Even if the OID pointer is const, the inner |elements| pointer is still
// non-const.
static gss_OID_desc kGSS_C_NT_HOSTBASED_SERVICE = {
10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")};
// Create a name for the principal
// TODO(cbentzel): Just do this on the first pass?
std::string spn_principal = spn;
gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER;
spn_buffer.value = const_cast<char*>(spn_principal.c_str());
spn_buffer.length = spn_principal.size() + 1;
OM_uint32 minor_status = 0;
gss_name_t principal_name = GSS_C_NO_NAME;
OM_uint32 major_status =
library_->import_name(&minor_status, &spn_buffer,
&kGSS_C_NT_HOSTBASED_SERVICE, &principal_name);
net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_IMPORT_NAME, [&] {
return ImportNameErrorParams(library_, spn, major_status, minor_status);
});
int rv = MapImportNameStatusToError(major_status);
if (rv != OK)
return rv;
ScopedName scoped_name(principal_name, library_);
// Continue creating a security context.
net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX);
major_status = library_->init_sec_context(
&minor_status, GSS_C_NO_CREDENTIAL, scoped_sec_context_.receive(),
principal_name, gss_oid_, DelegationTypeToFlag(delegation_type_),
GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token,
nullptr, // actual_mech_type
out_token,
nullptr, // ret flags
nullptr);
net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX, [&] {
return InitSecContextErrorParams(library_, scoped_sec_context_.get(),
major_status, minor_status);
});
return MapInitSecContextStatusToError(major_status);
}
} // namespace net