Index: net/http/http_auth_gssapi_posix.cc |
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7c428e6456cea71826c2bd51afcfa7424104efe3 |
--- /dev/null |
+++ b/net/http/http_auth_gssapi_posix.cc |
@@ -0,0 +1,499 @@ |
+// 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 <string> |
+ |
+#include "base/base64.h" |
+#include "base/file_path.h" |
+#include "base/logging.h" |
+#include "base/native_library.h" |
+#include "base/singleton.h" |
+#include "base/string_util.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/net_util.h" |
+#include "net/http/http_auth.h" |
+ |
+namespace net { |
+ |
+gssapi::gss_OID_desc GSS_C_NT_USER_NAME = { |
+ 10, |
+ (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_MACHINE_UID_NAME = { |
+ 10, |
+ (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_STRING_UID_NAME = { |
+ 10, |
+ (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X = { |
+ 6, |
+ (void *) "\x2b\x06\x01\x05\x06\x02" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE = { |
+ 10, |
+ (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_ANONYMOUS = { |
+ 6, |
+ (void *) "\x2b\x06\01\x05\x06\x03" |
+}; |
+gssapi::gss_OID_desc GSS_C_NT_EXPORT_NAME = { |
+ 6, |
+ (void *) "\x2b\x06\x01\x05\x06\x04" |
+}; |
+ |
+GSSAPISharedLibrary::GSSAPISharedLibrary() |
+ : initialized_(false), |
+ cached_initialize_value_(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_) { |
+ cached_initialize_value_ = InitImpl(); |
+ initialized_ = true; |
+ } |
+ return cached_initialize_value_; |
+} |
+ |
+bool GSSAPISharedLibrary::InitImpl() { |
+ DCHECK(!initialized_); |
+ gssapi_library_ = LoadSharedObject(); |
+ if (gssapi_library_ == NULL) |
+ return false; |
+ if (!BindMethods()) |
+ return false; |
+ return true; |
+} |
+ |
+const char* GSSAPISharedLibrary::kLibraryNames[] = { |
+ "libgssapi_krb5.so.2", |
+ "libgssapi.so.4", |
+ "libgssapi.so.1", |
+}; |
+ |
+base::NativeLibrary GSSAPISharedLibrary::LoadSharedObject() { |
+ for (unsigned int i = 0; i < arraysize(kLibraryNames); ++i) { |
+ FilePath file_path(kLibraryNames[i]); |
+ 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) { |
+ string16 error_message = base::GetLibraryErrorMessage(); |
+ LOG(WARNING) << "dlsym failed when finding function pointer for " << name |
+ << ": " << error_message; |
+ 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(cached_initialize_value_); |
+ if (!import_name_) |
+ return gssapi::OM_uint32(-1); |
+ 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(cached_initialize_value_); |
+ if (!release_name_) |
+ return gssapi::OM_uint32(-1); |
+ return release_name_(minor_status, input_name); |
+} |
+ |
+gssapi::OM_uint32 GSSAPISharedLibrary::release_buffer( |
+ gssapi::OM_uint32* minor_status, |
+ gssapi::gss_buffer_t buffer) { |
+ DCHECK(cached_initialize_value_); |
+ if (!release_buffer_) |
+ return gssapi::OM_uint32(-1); |
+ 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(cached_initialize_value_); |
+ if (!display_status_) |
+ return gssapi::OM_uint32(-1); |
+ status_string->length = 0; |
+ status_string->value = NULL; |
+ 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(cached_initialize_value_); |
+ if (!init_sec_context_) |
+ return gssapi::OM_uint32(-1); |
+ 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, |
+ reinterpret_cast<gssapi::gss_buffer_t>(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(cached_initialize_value_); |
+ if (!wrap_size_limit_) |
+ return gssapi::OM_uint32(-1); |
+ 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("%x %x", major_status, minor_status); |
+} |
+ |
+static const int kMaxDisplayIterations = 8; |
+ |
+std::string DisplayCode(GSSAPILibrary* gssapi_lib, |
+ gssapi::OM_uint32 status, |
+ gssapi::OM_uint32 status_code_type) { |
+ // 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(const std::string& scheme, |
+ gssapi::gss_OID gss_oid, |
+ GSSAPILibrary* library) |
+ : 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; |
+} |
+ |
+ |
+static uint8 kHostbasedServiceOidElements[] = { |
+ 0x2a, 0x86, 0x48, 0x86, 0xF7, 0x12, 0x01, 0x02, 0x01, 0x04}; |
+static gssapi::gss_OID_desc kHostbasedServiceOidDesc = { |
+ arraysize(kHostbasedServiceOidElements), |
+ static_cast<void *>(&kHostbasedServiceOidElements)}; |
+ |
+int HttpAuthGSSAPI::GenerateAuthToken(const std::wstring* username, |
+ const std::wstring* password, |
+ const std::wstring& origin, |
+ 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 = static_cast<void *>(const_cast<char *>( |
+ decoded_server_auth_token_.c_str())); |
+ gssapi::gss_buffer_desc output_token1 = GSS_C_EMPTY_BUFFER; |
+ gssapi::gss_buffer_t output_token = &output_token1; |
+ int rv = GetNextSecurityToken(origin, &input_token, &output_token); |
+ if (rv != OK) |
+ return rv; |
+ ScopedBuffer sb_out_token(output_token, library_); |
+ |
+ // 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); |
+ if (!ok) |
+ return rv; |
+ *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_ = L""; |
+ password_ = L""; |
+ if (username) { |
+ username_ = *username; |
+ password_ = *password; |
+ } |
+ return OK; |
+} |
+ |
+int HttpAuthGSSAPI::GetNextSecurityToken(const std::wstring& origin, |
+ gssapi::gss_buffer_t in_token, |
+ gssapi::gss_buffer_t* out_token) { |
+ // Create a name for the principal |
+ // TODO(cbentzel): Should this be username@origin? What about domain? |
+ // TODO(cbentzel): Just do this on the first pass? |
+ const GURL orig(WideToASCII(origin)); |
+ std::string origin_principal = GetHostAndPort(orig); |
+ gssapi::gss_buffer_desc origin_buffer = GSS_C_EMPTY_BUFFER; |
+ origin_buffer.value = static_cast<void *>(const_cast<char *>( |
+ origin_principal.c_str())); |
+ origin_buffer.length = origin_principal.size() + 1; |
+ gssapi::OM_uint32 minor_status_import_name = 0; |
+ gssapi::gss_name_t principal_name; |
+ gssapi::OM_uint32 major_status_import_name = library_->import_name( |
+ &minor_status_import_name, |
+ &origin_buffer, |
+ &kHostbasedServiceOidDesc, |
+ &principal_name); |
+ if (major_status_import_name != GSS_S_COMPLETE) { |
+ LOG(WARNING) << "Problem importing name. " |
+ << DisplayExtendedStatus(library_, |
+ major_status_import_name, |
+ minor_status_import_name); |
+ return ERR_UNEXPECTED; |
+ } |
+ ScopedName scoped_name(principal_name, library_); |
+ |
+ // Create a security context. |
+ gssapi::OM_uint32 minor_status_sec_context; |
+ gssapi::OM_uint32 req_flags = 0; |
+ gssapi::OM_uint32 major_status_sec_context = library_->init_sec_context( |
+ &minor_status_sec_context, |
+ 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_sec_context != GSS_S_COMPLETE && |
+ major_status_sec_context != GSS_S_CONTINUE_NEEDED) { |
+ LOG(WARNING) << "Problem initializing context. " |
+ << DisplayExtendedStatus(library_, |
+ major_status_sec_context, |
+ minor_status_sec_context); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ return (major_status_sec_context != GSS_S_COMPLETE) ? ERR_IO_PENDING : OK; |
+} |
+ |
+} // namespace net |
+ |