// 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/files/file_path.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/http/http_auth_challenge_tokenizer.h" // These are defined for the GSSAPI library: // Paraphrasing the comments from gssapi.h: // "The implementation must reserve static storage for a // gss_OID_desc object for each constant. That constant // should be initialized to point to that gss_OID_desc." // These are encoded using ASN.1 BER encoding. namespace { static gss_OID_desc GSS_C_NT_USER_NAME_VAL = { 10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01") }; static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_VAL = { 10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02") }; static gss_OID_desc GSS_C_NT_STRING_UID_NAME_VAL = { 10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03") }; static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_VAL = { 6, const_cast<char*>("\x2b\x06\x01\x05\x06\x02") }; static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_VAL = { 10, const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04") }; static gss_OID_desc GSS_C_NT_ANONYMOUS_VAL = { 6, const_cast<char*>("\x2b\x06\01\x05\x06\x03") }; static gss_OID_desc GSS_C_NT_EXPORT_NAME_VAL = { 6, const_cast<char*>("\x2b\x06\x01\x05\x06\x04") }; } // namespace // Heimdal >= 1.4 will define the following as preprocessor macros. // To avoid conflicting declarations, we have to undefine these. #undef GSS_C_NT_USER_NAME #undef GSS_C_NT_MACHINE_UID_NAME #undef GSS_C_NT_STRING_UID_NAME #undef GSS_C_NT_HOSTBASED_SERVICE_X #undef GSS_C_NT_HOSTBASED_SERVICE #undef GSS_C_NT_ANONYMOUS #undef GSS_C_NT_EXPORT_NAME gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_VAL; gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_VAL; gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_VAL; gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_VAL; gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_VAL; gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_VAL; gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_VAL; namespace net { // 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; // Debugging helpers. namespace { std::string DisplayStatus(OM_uint32 major_status, OM_uint32 minor_status) { if (major_status == GSS_S_COMPLETE) return "OK"; return base::StringPrintf("0x%08X 0x%08X", major_status, minor_status); } std::string DisplayCode(GSSAPILibrary* gssapi_lib, OM_uint32 status, OM_uint32 status_code_type) { const int kMaxDisplayIterations = 8; const size_t kMaxMsgLength = 4096; // msg_ctx needs to be outside the loop because it is invoked multiple times. OM_uint32 msg_ctx = 0; std::string rv = base::StringPrintf("(0x%08X)", status); // This loop should continue iterating until msg_ctx is 0 after the first // iteration. To be cautious and prevent an infinite loop, it stops after // a finite number of iterations as well. As an added sanity check, no // individual message may exceed |kMaxMsgLength|, and the final result // will not exceed |kMaxMsgLength|*2-1. for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength; ++i) { OM_uint32 min_stat; gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER; OM_uint32 maj_stat = gssapi_lib->display_status(&min_stat, status, status_code_type, GSS_C_NULL_OID, &msg_ctx, &msg); if (maj_stat == GSS_S_COMPLETE) { int msg_len = (msg.length > kMaxMsgLength) ? static_cast<int>(kMaxMsgLength) : static_cast<int>(msg.length); if (msg_len > 0 && msg.value != NULL) { rv += base::StringPrintf(" %.*s", msg_len, static_cast<char*>(msg.value)); } } gssapi_lib->release_buffer(&min_stat, &msg); if (!msg_ctx) break; } return rv; } std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib, OM_uint32 major_status, OM_uint32 minor_status) { if (major_status == GSS_S_COMPLETE) return "OK"; std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE); std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE); return base::StringPrintf("Major: %s | Minor: %s", major.c_str(), minor.c_str()); } // 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) { LOG(WARNING) << "Problem releasing name. " << DisplayStatus(major_status, minor_status); } name_ = GSS_C_NO_NAME; } } private: gss_name_t name_; GSSAPILibrary* gssapi_lib_; DISALLOW_COPY_AND_ASSIGN(ScopedName); }; // 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_); if (major_status != GSS_S_COMPLETE) { LOG(WARNING) << "Problem releasing buffer. " << DisplayStatus(major_status, minor_status); } buffer_ = GSS_C_NO_BUFFER; } } private: gss_buffer_t buffer_; GSSAPILibrary* gssapi_lib_; DISALLOW_COPY_AND_ASSIGN(ScopedBuffer); }; namespace { std::string AppendIfPredefinedValue(gss_OID oid, gss_OID predefined_oid, const char* predefined_oid_name) { DCHECK(oid); DCHECK(predefined_oid); DCHECK(predefined_oid_name); std::string output; if (oid->length != predefined_oid->length) return output; if (0 != memcmp(oid->elements, predefined_oid->elements, predefined_oid->length)) return output; output += " ("; output += predefined_oid_name; output += ")"; return output; } } // namespace std::string DescribeOid(GSSAPILibrary* gssapi_lib, const gss_OID oid) { if (!oid) return "<NULL>"; std::string output; const size_t kMaxCharsToPrint = 1024; OM_uint32 byte_length = oid->length; size_t char_length = byte_length / sizeof(char); if (char_length > kMaxCharsToPrint) { // This might be a plain ASCII string. // Check if the first |kMaxCharsToPrint| characters // contain only printable characters and are NULL terminated. const char* str = reinterpret_cast<const char*>(oid); size_t str_length = 0; for ( ; str_length < kMaxCharsToPrint; ++str_length) { if (!str[str_length] || !isprint(str[str_length])) break; } if (!str[str_length]) { output += base::StringPrintf("\"%s\"", str); return output; } } output = base::StringPrintf("(%u) \"", byte_length); if (!oid->elements) { output += "<NULL>"; return output; } const unsigned char* elements = reinterpret_cast<const unsigned char*>(oid->elements); // Don't print more than |kMaxCharsToPrint| characters. size_t i = 0; for ( ; (i < byte_length) && (i < kMaxCharsToPrint); ++i) { output += base::StringPrintf("\\x%02X", elements[i]); } if (i >= kMaxCharsToPrint) output += "..."; output += "\""; // Check if the OID is one of the predefined values. output += AppendIfPredefinedValue(oid, GSS_C_NT_USER_NAME, "GSS_C_NT_USER_NAME"); output += AppendIfPredefinedValue(oid, GSS_C_NT_MACHINE_UID_NAME, "GSS_C_NT_MACHINE_UID_NAME"); output += AppendIfPredefinedValue(oid, GSS_C_NT_STRING_UID_NAME, "GSS_C_NT_STRING_UID_NAME"); output += AppendIfPredefinedValue(oid, GSS_C_NT_HOSTBASED_SERVICE_X, "GSS_C_NT_HOSTBASED_SERVICE_X"); output += AppendIfPredefinedValue(oid, GSS_C_NT_HOSTBASED_SERVICE, "GSS_C_NT_HOSTBASED_SERVICE"); output += AppendIfPredefinedValue(oid, GSS_C_NT_ANONYMOUS, "GSS_C_NT_ANONYMOUS"); output += AppendIfPredefinedValue(oid, GSS_C_NT_EXPORT_NAME, "GSS_C_NT_EXPORT_NAME"); return output; } std::string DescribeName(GSSAPILibrary* gssapi_lib, const gss_name_t name) { OM_uint32 major_status = 0; OM_uint32 minor_status = 0; gss_buffer_desc_struct output_name_buffer = GSS_C_EMPTY_BUFFER; gss_OID_desc output_name_type_desc = GSS_C_EMPTY_BUFFER; gss_OID output_name_type = &output_name_type_desc; major_status = gssapi_lib->display_name(&minor_status, name, &output_name_buffer, &output_name_type); ScopedBuffer scoped_output_name(&output_name_buffer, gssapi_lib); if (major_status != GSS_S_COMPLETE) { std::string error = base::StringPrintf("Unable to describe name 0x%p, %s", name, DisplayExtendedStatus(gssapi_lib, major_status, minor_status).c_str()); return error; } int len = output_name_buffer.length; std::string description = base::StringPrintf( "%*s (Type %s)", len, reinterpret_cast<const char*>(output_name_buffer.value), DescribeOid(gssapi_lib, output_name_type).c_str()); return description; } std::string DescribeContext(GSSAPILibrary* gssapi_lib, const gss_ctx_id_t context_handle) { 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; if (context_handle == GSS_C_NO_CONTEXT) return std::string("Context: GSS_C_NO_CONTEXT"); major_status = gssapi_lib->inquire_context(&minor_status, context_handle, &src_name, &targ_name, &lifetime_rec, &mech_type, &ctx_flags, &locally_initiated, &open); ScopedName(src_name, gssapi_lib); ScopedName(targ_name, gssapi_lib); if (major_status != GSS_S_COMPLETE) { std::string error = base::StringPrintf("Unable to describe context 0x%p, %s", context_handle, DisplayExtendedStatus(gssapi_lib, major_status, minor_status).c_str()); return error; } std::string source(DescribeName(gssapi_lib, src_name)); std::string target(DescribeName(gssapi_lib, targ_name)); std::string description = base::StringPrintf("Context 0x%p: " "Source \"%s\", " "Target \"%s\", " "lifetime %d, " "mechanism %s, " "flags 0x%08X, " "local %d, " "open %d", context_handle, source.c_str(), target.c_str(), lifetime_rec, DescribeOid(gssapi_lib, mech_type).c_str(), ctx_flags, locally_initiated, open); return description; } } // namespace GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name) : initialized_(false), gssapi_library_name_(gssapi_library_name), gssapi_library_(NULL), import_name_(NULL), release_name_(NULL), release_buffer_(NULL), display_name_(NULL), display_status_(NULL), init_sec_context_(NULL), wrap_size_limit_(NULL), delete_sec_context_(NULL), inquire_context_(NULL) { } GSSAPISharedLibrary::~GSSAPISharedLibrary() { if (gssapi_library_) { base::UnloadNativeLibrary(gssapi_library_); gssapi_library_ = NULL; } } bool GSSAPISharedLibrary::Init() { if (!initialized_) InitImpl(); return initialized_; } bool GSSAPISharedLibrary::InitImpl() { DCHECK(!initialized_); #if defined(DLOPEN_KERBEROS) gssapi_library_ = LoadSharedLibrary(); if (gssapi_library_ == NULL) return false; #endif // defined(DLOPEN_KERBEROS) initialized_ = true; return true; } base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() { 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) "libgssapi_krb5.dylib" // MIT Kerberos #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 = arraysize(kDefaultLibraryNames); } for (size_t i = 0; i < num_lib_names; ++i) { const char* 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, NULL); if (lib) { // Only return this library if we can bind the functions we need. if (BindMethods(lib)) return lib; base::UnloadNativeLibrary(lib); } } LOG(WARNING) << "Unable to find a compatible GSSAPI library"; return NULL; } #if defined(DLOPEN_KERBEROS) #define BIND(lib, x) \ DCHECK(lib); \ gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \ base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \ if (x == NULL) { \ LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \ return false; \ } #else #define BIND(lib, x) gss_##x##_type x = gss_##x #endif bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) { BIND(lib, import_name); BIND(lib, release_name); BIND(lib, release_buffer); BIND(lib, display_name); BIND(lib, display_status); BIND(lib, init_sec_context); BIND(lib, wrap_size_limit); BIND(lib, delete_sec_context); BIND(lib, inquire_context); import_name_ = import_name; release_name_ = release_name; release_buffer_ = release_buffer; display_name_ = display_name; display_status_ = display_status; init_sec_context_ = init_sec_context; wrap_size_limit_ = wrap_size_limit; delete_sec_context_ = delete_sec_context; inquire_context_ = inquire_context; return true; } #undef BIND 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); } 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); if (major_status != GSS_S_COMPLETE) { LOG(WARNING) << "Problem releasing security_context. " << DisplayStatus(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), can_delegate_(false) { DCHECK(library_); } HttpAuthGSSAPI::~HttpAuthGSSAPI() { } bool HttpAuthGSSAPI::Init() { if (!library_) return false; return library_->Init(); } bool HttpAuthGSSAPI::NeedsIdentity() const { return decoded_server_auth_token_.empty(); } bool HttpAuthGSSAPI::AllowsExplicitCredentials() const { return false; } void HttpAuthGSSAPI::Delegate() { can_delegate_ = true; } HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge( HttpAuthChallengeTokenizer* tok) { // Verify the challenge's auth-scheme. if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str())) return HttpAuth::AUTHORIZATION_RESULT_INVALID; std::string encoded_auth_token = tok->base64_param(); if (encoded_auth_token.empty()) { // If a context has already been established, an empty Negotiate challenge // should be treated as a rejection of the current attempt. if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT) return HttpAuth::AUTHORIZATION_RESULT_REJECT; DCHECK(decoded_server_auth_token_.empty()); return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; } else { // If a context has not already been established, additional tokens should // not be present in the auth challenge. if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT) return HttpAuth::AUTHORIZATION_RESULT_INVALID; } // Make sure the additional token is base64 encoded. std::string decoded_auth_token; bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); if (!base64_rv) return HttpAuth::AUTHORIZATION_RESULT_INVALID; decoded_server_auth_token_ = decoded_auth_token; return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; } int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials, const std::string& spn, std::string* auth_token) { 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()) : NULL; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; ScopedBuffer scoped_output_token(&output_token, library_); int rv = GetNextSecurityToken(spn, &input_token, &output_token); 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) { VLOG(1) << "import_name returned 0x" << std::hex << 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) { VLOG(1) << "init_sec_context returned 0x" << std::hex << 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; } } int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn, gss_buffer_t in_token, gss_buffer_t out_token) { // 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, GSS_C_NT_HOSTBASED_SERVICE, &principal_name); int rv = MapImportNameStatusToError(major_status); if (rv != OK) { LOG(ERROR) << "Problem importing name from " << "spn \"" << spn_principal << "\"\n" << DisplayExtendedStatus(library_, major_status, minor_status); return rv; } ScopedName scoped_name(principal_name, library_); // Continue creating a security context. OM_uint32 req_flags = 0; if (can_delegate_) req_flags |= GSS_C_DELEG_FLAG; major_status = library_->init_sec_context( &minor_status, GSS_C_NO_CREDENTIAL, scoped_sec_context_.receive(), principal_name, gss_oid_, req_flags, GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token, NULL, // actual_mech_type out_token, NULL, // ret flags NULL); rv = MapInitSecContextStatusToError(major_status); if (rv != OK) { LOG(ERROR) << "Problem initializing context. \n" << DisplayExtendedStatus(library_, major_status, minor_status) << '\n' << DescribeContext(library_, scoped_sec_context_.get()); } return rv; } } // namespace net