diff options
Diffstat (limited to 'net/http')
-rw-r--r-- | net/http/http_auth_gssapi_posix.cc | 454 | ||||
-rw-r--r-- | net/http/http_auth_gssapi_posix.h | 196 | ||||
-rw-r--r-- | net/http/http_auth_gssapi_posix_unittest.cc | 25 |
3 files changed, 675 insertions, 0 deletions
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc new file mode 100644 index 0000000..7b6faa1 --- /dev/null +++ b/net/http/http_auth_gssapi_posix.cc @@ -0,0 +1,454 @@ +// Copyright (c) 2010 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 "base/base64.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/singleton.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" + +namespace { + +gssapi::gss_OID_desc LOCAL_GSS_C_NT_HOSTBASED_SERVICE_VAL = { + 10, + const_cast<char *>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04") +}; + +gssapi::gss_OID LOCAL_GSS_C_NT_HOSTBASED_SERVICE = + &LOCAL_GSS_C_NT_HOSTBASED_SERVICE_VAL; + +} // namespace + +namespace net { + +GSSAPISharedLibrary::GSSAPISharedLibrary() + : initialized_(false), + gssapi_library_(NULL), + import_name_(NULL), + release_name_(NULL), + release_buffer_(NULL), + display_status_(NULL), + init_sec_context_(NULL), + wrap_size_limit_(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_); + gssapi_library_ = LoadSharedObject(); + if (gssapi_library_ == NULL) + return false; + if (!BindMethods()) + return false; + initialized_ = true; + return true; +} + +base::NativeLibrary GSSAPISharedLibrary::LoadSharedObject() { + static const char* kLibraryNames[] = { +#if defined(OS_MACOSX) + "libgssapi_krb5.dylib" // MIT Kerberos +#else + "libgssapi_krb5.so.2", // MIT Kerberos + "libgssapi.so.4", // Heimdal + "libgssapi.so.1" // Heimdal +#endif + }; + static size_t num_lib_names = arraysize(kLibraryNames); + + for (size_t i = 0; i < num_lib_names; ++i) { + const char* library_name = kLibraryNames[i]; + FilePath file_path(library_name); + base::NativeLibrary lib = base::LoadNativeLibrary(file_path); + if (lib) + return lib; + } + return NULL; +} + +template <typename T> +bool FindAndBind(base::NativeLibrary library, const char* name, T* t) { + void* func = base::GetFunctionPointerFromNativeLibrary(library, name); + if (func == NULL) + return false; + *t = reinterpret_cast<T>(func); + return true; +} + +bool GSSAPISharedLibrary::BindMethods() { + DCHECK(gssapi_library_ != NULL); + if (!FindAndBind(gssapi_library_, "gss_import_name", &import_name_)) + return false; + if (!FindAndBind(gssapi_library_, "gss_release_name", &release_name_)) + return false; + if (!FindAndBind(gssapi_library_, "gss_release_buffer", &release_buffer_)) + return false; + if (!FindAndBind(gssapi_library_, "gss_display_status", &display_status_)) + return false; + if (!FindAndBind(gssapi_library_, "gss_init_sec_context", &init_sec_context_)) + return false; + if (!FindAndBind(gssapi_library_, "gss_wrap_size_limit", &wrap_size_limit_)) + return false; + return true; +} + +gssapi::OM_uint32 GSSAPISharedLibrary::import_name( + gssapi::OM_uint32* minor_status, + const gssapi::gss_buffer_t input_name_buffer, + const gssapi::gss_OID input_name_type, + gssapi::gss_name_t* output_name) { + DCHECK(initialized_); + return import_name_(minor_status, input_name_buffer, input_name_type, + output_name); +} + +gssapi::OM_uint32 GSSAPISharedLibrary::release_name( + gssapi::OM_uint32* minor_status, + gssapi::gss_name_t* input_name) { + DCHECK(initialized_); + return release_name_(minor_status, input_name); +} + +gssapi::OM_uint32 GSSAPISharedLibrary::release_buffer( + gssapi::OM_uint32* minor_status, + gssapi::gss_buffer_t buffer) { + DCHECK(initialized_); + return release_buffer_(minor_status, buffer); +} + +gssapi::OM_uint32 GSSAPISharedLibrary::display_status( + gssapi::OM_uint32* minor_status, + gssapi::OM_uint32 status_value, + int status_type, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32* message_context, + gssapi::gss_buffer_t status_string) { + DCHECK(initialized_); + return display_status_(minor_status, status_value, status_type, mech_type, + message_context, status_string); +} + +gssapi::OM_uint32 GSSAPISharedLibrary::init_sec_context( + gssapi::OM_uint32* minor_status, + const gssapi::gss_cred_id_t initiator_cred_handle, + gssapi::gss_ctx_id_t* context_handle, + const gssapi::gss_name_t target_name, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32 req_flags, + gssapi::OM_uint32 time_req, + const gssapi::gss_channel_bindings_t input_chan_bindings, + const gssapi::gss_buffer_t input_token, + gssapi::gss_OID* actual_mech_type, + gssapi::gss_buffer_t output_token, + gssapi::OM_uint32* ret_flags, + gssapi::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); +} + +gssapi::OM_uint32 GSSAPISharedLibrary::wrap_size_limit( + gssapi::OM_uint32* minor_status, + const gssapi::gss_ctx_id_t context_handle, + int conf_req_flag, + gssapi::gss_qop_t qop_req, + gssapi::OM_uint32 req_output_size, + gssapi::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); +} + +GSSAPILibrary* GSSAPILibrary::GetDefault() { + return Singleton<GSSAPISharedLibrary>::get(); +} + +namespace { + +std::string DisplayStatus(gssapi::OM_uint32 major_status, + gssapi::OM_uint32 minor_status) { + if (major_status == GSS_S_COMPLETE) + return "OK"; + return StringPrintf("0x%08x 0x%08x", major_status, minor_status); +} + +std::string DisplayCode(GSSAPILibrary* gssapi_lib, + gssapi::OM_uint32 status, + gssapi::OM_uint32 status_code_type) { + const int kMaxDisplayIterations = 8; + // msg_ctx needs to be outside the loop because it is invoked multiple times. + gssapi::OM_uint32 msg_ctx = 0; + std::string rv = 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. + for (int i = 0; i < kMaxDisplayIterations; ++i) { + gssapi::OM_uint32 min_stat; + gssapi::gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER; + gssapi_lib->display_status(&min_stat, status, status_code_type, + GSS_C_NULL_OID, + &msg_ctx, &msg); + rv += StringPrintf(" %s", static_cast<char *>(msg.value)); + gssapi_lib->release_buffer(&min_stat, &msg); + if (!msg_ctx) + break; + } + return rv; +} + +std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib, + gssapi::OM_uint32 major_status, + gssapi::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 StringPrintf("Major: %s | Minor: %s", major.c_str(), minor.c_str()); +} + +// ScopedName releases a gssapi::gss_name_t when it goes out of scope. +class ScopedName { + public: + ScopedName(gssapi::gss_name_t name, + GSSAPILibrary* gssapi_lib) + : name_(name), + gssapi_lib_(gssapi_lib) { + DCHECK(gssapi_lib_); + } + + ~ScopedName() { + if (name_ != GSS_C_NO_NAME) { + gssapi::OM_uint32 minor_status = 0; + gssapi::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: + gssapi::gss_name_t name_; + GSSAPILibrary* gssapi_lib_; + + DISALLOW_COPY_AND_ASSIGN(ScopedName); +}; + +// ScopedBuffer releases a gssapi::gss_buffer_t when it goes out of scope. +class ScopedBuffer { + public: + ScopedBuffer(gssapi::gss_buffer_t buffer, + GSSAPILibrary* gssapi_lib) + : buffer_(buffer), + gssapi_lib_(gssapi_lib) { + DCHECK(gssapi_lib_); + } + + ~ScopedBuffer() { + if (buffer_ != GSS_C_NO_BUFFER) { + gssapi::OM_uint32 minor_status = 0; + gssapi::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: + gssapi::gss_buffer_t buffer_; + GSSAPILibrary* gssapi_lib_; + + DISALLOW_COPY_AND_ASSIGN(ScopedBuffer); +}; + +} // namespace + + HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library, + const std::string& scheme, + gssapi::gss_OID gss_oid) + : scheme_(scheme), + gss_oid_(gss_oid), + library_(library), + sec_context_(NULL) { +} + +HttpAuthGSSAPI::~HttpAuthGSSAPI() { +} + +bool HttpAuthGSSAPI::NeedsIdentity() const { + return decoded_server_auth_token_.empty(); +} + +bool HttpAuthGSSAPI::IsFinalRound() const { + return !NeedsIdentity(); +} + +bool HttpAuthGSSAPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) { + // Verify the challenge's auth-scheme. + if (!tok->valid() || + !LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str())) + return false; + + tok->set_expect_base64_token(true); + if (!tok->GetNext()) { + decoded_server_auth_token_.clear(); + return true; + } + + std::string encoded_auth_token = tok->value(); + std::string decoded_auth_token; + bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); + if (!base64_rv) { + LOG(ERROR) << "Base64 decoding of auth token failed."; + return false; + } + decoded_server_auth_token_ = decoded_auth_token; + return true; +} + +int HttpAuthGSSAPI::GenerateAuthToken(const std::wstring* username, + const std::wstring* password, + const std::wstring& spn, + const HttpRequestInfo* request, + const ProxyInfo* proxy, + std::string* out_credentials) { + DCHECK(library_); + DCHECK((username == NULL) == (password == NULL)); + + library_->Init(); + + if (!IsFinalRound()) { + int rv = OnFirstRound(username, password); + if (rv != OK) + return rv; + } + + gssapi::gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + input_token.length = decoded_server_auth_token_.length(); + input_token.value = const_cast<char *>(decoded_server_auth_token_.data()); + gssapi::gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + 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; + bool ok = base::Base64Encode(encode_input, &encode_output); + gssapi::OM_uint32 minor_status = 0; + library_->release_buffer(&minor_status, &output_token); + if (!ok) + return ERR_UNEXPECTED; + *out_credentials = scheme_ + " " + encode_output; + return OK; +} + +int HttpAuthGSSAPI::OnFirstRound(const std::wstring* username, + const std::wstring* password) { + // TODO(cbentzel): Acquire credentials? + DCHECK((username == NULL) == (password == NULL)); + username_.clear(); + password_.clear(); + if (username) { + username_ = *username; + password_ = *password; + } + return OK; +} + +int HttpAuthGSSAPI::GetNextSecurityToken(const std::wstring& spn, + gssapi::gss_buffer_t in_token, + gssapi::gss_buffer_t out_token) { + // Create a name for the principal + // TODO(cbentzel): Should this be username@spn? What about domain? + // TODO(cbentzel): Just do this on the first pass? + const GURL spn_url(WideToASCII(spn)); + std::string spn_principal = GetHostAndPort(spn_url); + gssapi::gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER; + spn_buffer.value = const_cast<char *>(spn_principal.data()); + spn_buffer.length = spn_principal.size() + 1; + gssapi::OM_uint32 minor_status = 0; + gssapi::gss_name_t principal_name; + gssapi::OM_uint32 major_status = library_->import_name( + &minor_status, + &spn_buffer, + LOCAL_GSS_C_NT_HOSTBASED_SERVICE, + &principal_name); + if (major_status != GSS_S_COMPLETE) { + LOG(WARNING) << "Problem importing name. " + << DisplayExtendedStatus(library_, + major_status, + minor_status); + return ERR_UNEXPECTED; + } + ScopedName scoped_name(principal_name, library_); + + // Create a security context. + gssapi::OM_uint32 req_flags = 0; + major_status = library_->init_sec_context( + &minor_status, + GSS_C_NO_CREDENTIAL, + &sec_context_, + 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); + if (major_status != GSS_S_COMPLETE && + major_status != GSS_S_CONTINUE_NEEDED) { + LOG(WARNING) << "Problem initializing context. " + << DisplayExtendedStatus(library_, + major_status, + minor_status); + return ERR_UNEXPECTED; + } + + return (major_status != GSS_S_COMPLETE) ? ERR_IO_PENDING : OK; +} + +} // namespace net + diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h new file mode 100644 index 0000000..0eacbf3 --- /dev/null +++ b/net/http/http_auth_gssapi_posix.h @@ -0,0 +1,196 @@ +// Copyright (c) 2010 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. + +#ifndef NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_ +#define NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_ + +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/native_library.h" +#include "net/http/http_auth.h" + +#define GSS_USE_FUNCTION_POINTERS +#include "net/third_party/gssapi/gssapi.h" + +class GURL; + +namespace net { + +class HttpRequestInfo; +class ProxyInfo; + +// GSSAPILibrary is introduced so unit tests can mock the calls to the GSSAPI +// library. The default implementation attempts to load one of the standard +// GSSAPI library implementations, then simply passes the arguments on to +// that implementation. +class GSSAPILibrary { + public: + virtual ~GSSAPILibrary() {} + + // Initializes the library, including any necessary dynamic libraries. + virtual bool Init() = 0; + + // These methods match the ones in the GSSAPI library. + virtual gssapi::OM_uint32 import_name( + gssapi::OM_uint32* minor_status, + const gssapi::gss_buffer_t input_name_buffer, + const gssapi::gss_OID input_name_type, + gssapi::gss_name_t* output_name) = 0; + virtual gssapi::OM_uint32 release_name( + gssapi::OM_uint32* minor_status, + gssapi::gss_name_t* input_name) = 0; + virtual gssapi::OM_uint32 release_buffer( + gssapi::OM_uint32* minor_status, + gssapi::gss_buffer_t buffer) = 0; + virtual gssapi::OM_uint32 display_status( + gssapi::OM_uint32* minor_status, + gssapi::OM_uint32 status_value, + int status_type, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32* message_contex, + gssapi::gss_buffer_t status_string) = 0; + virtual gssapi::OM_uint32 init_sec_context( + gssapi::OM_uint32* minor_status, + const gssapi::gss_cred_id_t initiator_cred_handle, + gssapi::gss_ctx_id_t* context_handle, + const gssapi::gss_name_t target_name, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32 req_flags, + gssapi::OM_uint32 time_req, + const gssapi::gss_channel_bindings_t input_chan_bindings, + const gssapi::gss_buffer_t input_token, + gssapi::gss_OID* actual_mech_type, + gssapi::gss_buffer_t output_token, + gssapi::OM_uint32* ret_flags, + gssapi::OM_uint32* time_rec) = 0; + virtual gssapi::OM_uint32 wrap_size_limit( + gssapi::OM_uint32* minor_status, + const gssapi::gss_ctx_id_t context_handle, + int conf_req_flag, + gssapi::gss_qop_t qop_req, + gssapi::OM_uint32 req_output_size, + gssapi::OM_uint32* max_input_size) = 0; + + // Get the default GSSPILibrary instance. The object returned is a singleton + // instance, and the caller should not delete it. + static GSSAPILibrary* GetDefault(); +}; + +// GSSAPISharedLibrary class is defined here so that unit tests can access it. +class GSSAPISharedLibrary : public GSSAPILibrary { + public: + GSSAPISharedLibrary(); + virtual ~GSSAPISharedLibrary(); + + // GSSAPILibrary methods: + virtual bool Init(); + virtual gssapi::OM_uint32 import_name( + gssapi::OM_uint32* minor_status, + const gssapi::gss_buffer_t input_name_buffer, + const gssapi::gss_OID input_name_type, + gssapi::gss_name_t* output_name); + virtual gssapi::OM_uint32 release_name( + gssapi::OM_uint32* minor_status, + gssapi::gss_name_t* input_name); + virtual gssapi::OM_uint32 release_buffer( + gssapi::OM_uint32* minor_status, + gssapi::gss_buffer_t buffer); + virtual gssapi::OM_uint32 display_status( + gssapi::OM_uint32* minor_status, + gssapi::OM_uint32 status_value, + int status_type, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32* message_contex, + gssapi::gss_buffer_t status_string); + virtual gssapi::OM_uint32 init_sec_context( + gssapi::OM_uint32* minor_status, + const gssapi::gss_cred_id_t initiator_cred_handle, + gssapi::gss_ctx_id_t* context_handle, + const gssapi::gss_name_t target_name, + const gssapi::gss_OID mech_type, + gssapi::OM_uint32 req_flags, + gssapi::OM_uint32 time_req, + const gssapi::gss_channel_bindings_t input_chan_bindings, + const gssapi::gss_buffer_t input_token, + gssapi::gss_OID* actual_mech_type, + gssapi::gss_buffer_t output_token, + gssapi::OM_uint32* ret_flags, + gssapi::OM_uint32* time_rec); + virtual gssapi::OM_uint32 wrap_size_limit( + gssapi::OM_uint32* minor_status, + const gssapi::gss_ctx_id_t context_handle, + int conf_req_flag, + gssapi::gss_qop_t qop_req, + gssapi::OM_uint32 req_output_size, + gssapi::OM_uint32* max_input_size); + + private: + FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup); + + bool InitImpl(); + static base::NativeLibrary LoadSharedObject(); + bool BindMethods(); + + bool initialized_; + + // Need some way to invalidate the library. + base::NativeLibrary gssapi_library_; + + // Function pointers + gssapi::gss_import_name_type import_name_; + gssapi::gss_release_name_type release_name_; + gssapi::gss_release_buffer_type release_buffer_; + gssapi::gss_display_status_type display_status_; + gssapi::gss_init_sec_context_type init_sec_context_; + gssapi::gss_wrap_size_limit_type wrap_size_limit_; +}; + +// TODO(cbentzel): Share code with HttpAuthSSPI. +class HttpAuthGSSAPI { + public: + HttpAuthGSSAPI(GSSAPILibrary* library, + const std::string& scheme, + const gssapi::gss_OID gss_oid); + ~HttpAuthGSSAPI(); + + bool NeedsIdentity() const; + bool IsFinalRound() const; + + bool ParseChallenge(HttpAuth::ChallengeTokenizer* tok); + + // Generates an authentication token. + // The return value is an error code. If it's not |OK|, the value of + // |*auth_token| is unspecified. + // |spn| is the Service Principal Name of the server that the token is + // being generated for. + // If this is the first round of a multiple round scheme, credentials are + // obtained using |*username| and |*password|. If |username| and |password| + // are NULL, the default credentials are used instead. + int GenerateAuthToken(const std::wstring* username, + const std::wstring* password, + const std::wstring& spn, + const HttpRequestInfo* request, + const ProxyInfo* proxy, + std::string* out_credentials); + + private: + int OnFirstRound(const std::wstring* username, + const std::wstring* password); + int GetNextSecurityToken(const std::wstring& spn, + gssapi::gss_buffer_t in_token, + gssapi::gss_buffer_t out_token); + + std::string scheme_; + std::wstring username_; + std::wstring password_; + gssapi::gss_OID gss_oid_; + GSSAPILibrary* library_; + std::string decoded_server_auth_token_; + gssapi::gss_ctx_id_t sec_context_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_ diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc new file mode 100644 index 0000000..f7a010c --- /dev/null +++ b/net/http/http_auth_gssapi_posix_unittest.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2010 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 "base/native_library.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) { + // TODO(ahendrickson): Manipulate the libraries and paths to test each of the + // libraries we expect, and also whether or not they have the interface + // functions we want. + base::NativeLibrary lib = GSSAPISharedLibrary::LoadSharedObject(); + bool has_library = (lib != NULL); + if (has_library) { + base::UnloadNativeLibrary(lib); + } + GSSAPILibrary* gssapi = GSSAPILibrary::GetDefault(); + EXPECT_EQ(has_library, gssapi->Init()); +} + +} // namespace net |