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..7b6faa1ae1651bb0d82e9e5c15d76f42389f2d90 |
--- /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); |
wtc
2010/06/04 21:10:34
Nit: be consistent with using either %x or %X in t
|
+} |
+ |
+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 { |
wtc
2010/06/04 21:10:34
ScopedBuffer is not being used. Are you planning
|
+ 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(); |
wtc
2010/06/04 21:10:34
We may need to pass GSS_NO_BUFFER instead of &inpu
|
+ 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()); |
wtc
2010/06/04 21:10:34
Use c_str() instead of data(), since spn_buffer.le
|
+ 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. " |
wtc
2010/06/04 21:10:34
This log message should be at the ERROR level if w
|
+ << DisplayExtendedStatus(library_, |
+ major_status, |
+ minor_status); |
+ return ERR_UNEXPECTED; |
+ } |
+ ScopedName scoped_name(principal_name, library_); |
+ |
+ // Create a security context. |
wtc
2010/06/04 21:10:34
This comment may not be accurate... I think it's
|
+ gssapi::OM_uint32 req_flags = 0; |
+ major_status = library_->init_sec_context( |
+ &minor_status, |
+ GSS_C_NO_CREDENTIAL, |
+ &sec_context_, |
wtc
2010/06/04 21:10:34
The returned sec_context_ value should be deleted
|
+ 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; |
wtc
2010/06/04 21:10:34
Are you sure it's right to return ERR_IO_PENDING i
|
+} |
+ |
+} // namespace net |
+ |