| // 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/macros.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/http/http_auth_multi_round_parse.h" |
| #include "starboard/memory.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 scoped_src_name(src_name, gssapi_lib); |
| ScopedName scoped_targ_name(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) |
| "/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 = 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::NativeLibraryLoadError load_error; |
| base::NativeLibrary lib = base::LoadNativeLibrary(file_path, &load_error); |
| if (lib) { |
| // Only return this library if we can bind the functions we need. |
| if (BindMethods(lib)) |
| return lib; |
| base::UnloadNativeLibrary(lib); |
| } else { |
| // If this is the only library available, log the reason for failure. |
| LOG_IF(WARNING, num_lib_names == 1) << load_error.ToString(); |
| } |
| } |
| 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); |
| } |
| |
| 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); |
| 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() = default; |
| |
| 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) { |
| 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, |
| 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()) : |
| NULL; |
| 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); |
| 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; |
| } |
| |
| } // anonymous namespace |
| |
| int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn, |
| const std::string& channel_bindings, |
| 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, |
| nullptr, // actual_mech_type |
| out_token, |
| nullptr, // ret flags |
| nullptr); |
| 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 |