| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "dse_auth_gssapi.hpp" |
| #include "logger.hpp" |
| #include "scoped_ptr.hpp" |
| #include "string_ref.hpp" |
| |
| #include <string.h> |
| |
| #include <gssapi/gssapi.h> |
| #include <gssapi/gssapi_generic.h> |
| #include <gssapi/gssapi_krb5.h> |
| |
| #define DSE_AUTHENTICATOR "com.datastax.bdp.cassandra.auth.DseAuthenticator" |
| |
| #define GSSAPI_AUTH_MECHANISM "GSSAPI" |
| #define GSSAPI_AUTH_SERVER_INITIAL_CHALLENGE "GSSAPI-START" |
| |
| #define PLAINTEXT_AUTH_MECHANISM "PLAIN" |
| #define PLAINTEXT_AUTH_SERVER_INITIAL_CHALLENGE "PLAIN-START" |
| |
| using namespace datastax; |
| using namespace datastax::internal::core; |
| using namespace datastax::internal::enterprise; |
| |
| static void dse_gssapi_authenticator_nop_lock(void* data) {} |
| static void dse_gssapi_authenticator_nop_unlock(void* data) {} |
| |
| struct GssapiBuffer { |
| public: |
| gss_buffer_desc buffer; |
| |
| public: |
| GssapiBuffer() { |
| buffer.value = NULL; |
| buffer.length = 0; |
| } |
| |
| ~GssapiBuffer() { release(); } |
| |
| const unsigned char* value() const { return static_cast<const unsigned char*>(buffer.value); } |
| |
| const char* data() const { return static_cast<const char*>(buffer.value); } |
| |
| size_t length() const { return buffer.length; } |
| |
| bool is_empty() const { return buffer.length == 0; } |
| |
| void release() { |
| if (buffer.value) { |
| OM_uint32 min_stat; |
| |
| DseGssapiAuthenticator::lock(); |
| gss_release_buffer(&min_stat, &buffer); |
| DseGssapiAuthenticator::unlock(); |
| } |
| } |
| }; |
| |
| class GssapiName { |
| public: |
| gss_name_t name; |
| |
| public: |
| GssapiName() |
| : name(GSS_C_NO_NAME) {} |
| |
| ~GssapiName() { release(); } |
| |
| void release() { |
| if (name != GSS_C_NO_NAME) { |
| OM_uint32 min_stat; |
| |
| DseGssapiAuthenticator::lock(); |
| gss_release_name(&min_stat, &name); |
| DseGssapiAuthenticator::unlock(); |
| } |
| } |
| }; |
| |
| namespace datastax { namespace internal { namespace enterprise { |
| |
| class GssapiAuthenticatorImpl : public Allocated { |
| public: |
| enum State { NEGOTIATION, AUTHENTICATION, AUTHENTICATED }; |
| |
| enum Result { RESULT_ERROR, RESULT_CONTINUE, RESULT_COMPLETE }; |
| |
| enum Type { AUTH_NONE = 1, AUTH_INTEGRITY = 2, AUTH_CONFIDENTIALITY = 3 }; |
| |
| GssapiAuthenticatorImpl(const String& authorization_id); |
| ~GssapiAuthenticatorImpl(); |
| |
| const String& response() const { return response_; } |
| const String& error() const { return error_; } |
| |
| Result init(const String& service, const String& principal); |
| Result process(const String& token); |
| |
| private: |
| Result negotiate(gss_buffer_t challenge_token); |
| Result authenticate(gss_buffer_t challenge_token); |
| |
| static String display_status(OM_uint32 maj, OM_uint32 min); |
| |
| private: |
| gss_ctx_id_t context_; |
| gss_name_t server_name_; |
| OM_uint32 gss_flags_; |
| gss_cred_id_t client_creds_; |
| String username_; |
| String response_; |
| String error_; |
| State state_; |
| String authorization_id_; |
| }; |
| |
| }}} // namespace datastax::internal::enterprise |
| |
| GssapiAuthenticatorImpl::GssapiAuthenticatorImpl(const String& authorization_id) |
| : context_(GSS_C_NO_CONTEXT) |
| , server_name_(GSS_C_NO_NAME) |
| , gss_flags_(GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG) |
| , client_creds_(GSS_C_NO_CREDENTIAL) |
| , state_(NEGOTIATION) |
| , authorization_id_(authorization_id) {} |
| |
| GssapiAuthenticatorImpl::~GssapiAuthenticatorImpl() { |
| OM_uint32 min_stat; |
| |
| if (context_ != GSS_C_NO_CONTEXT) { |
| DseGssapiAuthenticator::lock(); |
| gss_delete_sec_context(&min_stat, &context_, GSS_C_NO_BUFFER); |
| DseGssapiAuthenticator::unlock(); |
| } |
| |
| if (server_name_ != GSS_C_NO_NAME) { |
| DseGssapiAuthenticator::lock(); |
| gss_release_name(&min_stat, &server_name_); |
| DseGssapiAuthenticator::unlock(); |
| } |
| |
| if (client_creds_ != GSS_C_NO_CREDENTIAL) { |
| DseGssapiAuthenticator::lock(); |
| gss_release_cred(&min_stat, &client_creds_); |
| DseGssapiAuthenticator::unlock(); |
| } |
| } |
| |
| GssapiAuthenticatorImpl::Result GssapiAuthenticatorImpl::init(const String& service, |
| const String& principal) { |
| OM_uint32 maj_stat; |
| OM_uint32 min_stat; |
| gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER; |
| |
| name_token.value = const_cast<void*>(static_cast<const void*>(service.c_str())); |
| name_token.length = service.size(); |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_import_name(&min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &server_name_); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to import server name (gss_import_name()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| GssapiName principal_name; // Initialized to GSS_C_NO_NAME |
| |
| if (!principal.empty()) { |
| gss_buffer_desc principal_token = GSS_C_EMPTY_BUFFER; |
| |
| principal_token.value = const_cast<void*>(static_cast<const void*>(principal.c_str())); |
| principal_token.length = principal.size(); |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = |
| gss_import_name(&min_stat, &principal_token, GSS_C_NT_USER_NAME, &principal_name.name); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to import principal name (gss_import_name()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| } |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_acquire_cred(&min_stat, principal_name.name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, |
| GSS_C_INITIATE, &client_creds_, NULL, NULL); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to acquire principal credentials (gss_acquire_cred()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| return RESULT_COMPLETE; |
| } |
| |
| GssapiAuthenticatorImpl::Result GssapiAuthenticatorImpl::negotiate(gss_buffer_t challenge_token) { |
| OM_uint32 maj_stat; |
| OM_uint32 min_stat; |
| GssapiBuffer output_token; |
| Result result = RESULT_ERROR; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_init_sec_context(&min_stat, client_creds_, &context_, server_name_, GSS_C_NO_OID, |
| gss_flags_, 0, GSS_C_NO_CHANNEL_BINDINGS, challenge_token, NULL, |
| &output_token.buffer, NULL, NULL); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { |
| error_.assign("Failed to initalize security context (gss_init_sec_context()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| result = (maj_stat == GSS_S_COMPLETE) ? RESULT_COMPLETE : RESULT_CONTINUE; |
| |
| if (!output_token.is_empty()) { |
| response_.assign(output_token.data(), output_token.length()); |
| } |
| |
| if (result == RESULT_COMPLETE) { |
| GssapiName user; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = |
| gss_inquire_context(&min_stat, context_, &user.name, NULL, NULL, NULL, NULL, NULL, NULL); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign( |
| "Failed to inquire security context for user principal (gss_inquire_context()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| GssapiBuffer user_token; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_display_name(&min_stat, user.name, &user_token.buffer, NULL); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to get display name for user principal (gss_inquire_context()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } else { |
| username_.assign(user_token.data(), user_token.length()); |
| state_ = AUTHENTICATION; |
| } |
| } |
| |
| return result; |
| } |
| |
| GssapiAuthenticatorImpl::Result |
| GssapiAuthenticatorImpl::authenticate(gss_buffer_t challenge_token) { |
| OM_uint32 maj_stat; |
| OM_uint32 min_stat; |
| OM_uint32 req_output_size; |
| OM_uint32 max_input_size; |
| unsigned char qop; |
| String input; |
| gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; |
| GssapiBuffer output_token; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_unwrap(&min_stat, context_, challenge_token, &output_token.buffer, NULL, NULL); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to get unwrap challenge token (gss_unwrap()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| if (output_token.length() != 4) { |
| return RESULT_ERROR; |
| } |
| |
| qop = output_token.value()[0]; |
| if (qop & AUTH_CONFIDENTIALITY) { |
| qop = AUTH_CONFIDENTIALITY; |
| } else if (qop & AUTH_INTEGRITY) { |
| qop = AUTH_INTEGRITY; |
| } else { |
| qop = AUTH_NONE; |
| } |
| |
| req_output_size = ((output_token.value())[1] << 16) | ((output_token.value())[2] << 8) | |
| ((output_token.value())[3] << 0); |
| |
| req_output_size = req_output_size & 0xFFFFFF; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_wrap_size_limit(&min_stat, context_, 1, GSS_C_QOP_DEFAULT, req_output_size, |
| &max_input_size); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (max_input_size < req_output_size) { |
| req_output_size = max_input_size; |
| } |
| |
| input.push_back(qop); |
| input.push_back((req_output_size >> 16) & 0xFF); |
| input.push_back((req_output_size >> 8) & 0xFF); |
| input.push_back((req_output_size >> 0) & 0xFF); |
| |
| // Send the authorization_id if present (proxy login), otherwise the username. |
| const String& authorization_id = authorization_id_.empty() ? username_ : authorization_id_; |
| input.append(authorization_id); |
| |
| input_token.length = 4 + authorization_id.size(); |
| input_token.value = const_cast<void*>(static_cast<const void*>(input.c_str())); |
| |
| output_token.release(); |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = |
| gss_wrap(&min_stat, context_, 0, GSS_C_QOP_DEFAULT, &input_token, NULL, &output_token.buffer); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error_.assign("Failed to get wrape response token (gss_wrap()): " + |
| display_status(maj_stat, min_stat)); |
| return RESULT_ERROR; |
| } |
| |
| if (!output_token.is_empty()) { |
| response_.assign(output_token.data(), output_token.length()); |
| } |
| |
| state_ = AUTHENTICATED; |
| |
| return RESULT_COMPLETE; |
| } |
| |
| String GssapiAuthenticatorImpl::display_status(OM_uint32 maj, OM_uint32 min) { |
| String error; |
| OM_uint32 message_context; |
| |
| message_context = 0; |
| |
| do { |
| GssapiBuffer message; |
| OM_uint32 maj_stat, min_stat; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_display_status(&min_stat, maj, GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, |
| &message.buffer); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error.append("GSSAPI error: (unable to get major error)"); |
| break; |
| } |
| |
| error.append(message.data(), message.length()); |
| } while (message_context != 0); |
| |
| message_context = 0; |
| |
| error.append(" ("); |
| do { |
| GssapiBuffer message; |
| OM_uint32 maj_stat, min_stat; |
| |
| DseGssapiAuthenticator::lock(); |
| maj_stat = gss_display_status(&min_stat, min, GSS_C_MECH_CODE, GSS_C_NO_OID, &message_context, |
| &message.buffer); |
| DseGssapiAuthenticator::unlock(); |
| |
| if (GSS_ERROR(maj_stat)) { |
| error.append("GSSAPI error: (unable to get minor error)"); |
| break; |
| } |
| |
| error.append(message.data(), message.length()); |
| } while (message_context != 0); |
| error.append(" )"); |
| |
| return error; |
| } |
| |
| GssapiAuthenticatorImpl::Result GssapiAuthenticatorImpl::process(const String& token) { |
| Result result = RESULT_ERROR; |
| gss_buffer_desc challenge_token = GSS_C_EMPTY_BUFFER; |
| |
| response_.clear(); |
| |
| if (!token.empty()) { |
| challenge_token.value = const_cast<char*>(token.c_str()); |
| challenge_token.length = token.length(); |
| } |
| |
| switch (state_) { |
| case NEGOTIATION: |
| result = negotiate(&challenge_token); |
| break; |
| |
| case AUTHENTICATION: |
| result = authenticate(&challenge_token); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| DseGssapiAuthenticatorLockCallback DseGssapiAuthenticator::lock_callback_ = |
| dse_gssapi_authenticator_nop_lock; |
| DseGssapiAuthenticatorUnlockCallback DseGssapiAuthenticator::unlock_callback_ = |
| dse_gssapi_authenticator_nop_unlock; |
| void* DseGssapiAuthenticator::data_ = NULL; |
| |
| CassError |
| DseGssapiAuthenticator::set_lock_callbacks(DseGssapiAuthenticatorLockCallback lock_callback, |
| DseGssapiAuthenticatorUnlockCallback unlock_callback, |
| void* data) { |
| if (lock_callback == NULL || unlock_callback == NULL || data_ == NULL) { |
| lock_callback_ = dse_gssapi_authenticator_nop_lock; |
| unlock_callback_ = dse_gssapi_authenticator_nop_unlock; |
| data_ = NULL; |
| return CASS_ERROR_LIB_BAD_PARAMS; |
| } else { |
| lock_callback_ = lock_callback; |
| unlock_callback_ = unlock_callback; |
| data_ = data; |
| return CASS_OK; |
| } |
| } |
| |
| DseGssapiAuthenticator::DseGssapiAuthenticator(const Address& address, const String& hostname, |
| const String& class_name, const String& service, |
| const String& principal, |
| const String& authorization_id) |
| : address_(address) |
| , hostname_(hostname) |
| , class_name_(class_name) |
| , service_(service) |
| , principal_(principal) |
| , authorization_id_(authorization_id) |
| , impl_(new GssapiAuthenticatorImpl(authorization_id)) {} |
| |
| bool DseGssapiAuthenticator::initial_response(String* response) { |
| |
| String service; |
| |
| if (hostname_.empty()) { |
| service.append(service_); |
| service.append("@"); |
| service.append(address_.to_string()); |
| } else { |
| service.append(service_); |
| service.append("@"); |
| service.append(hostname_); |
| } |
| |
| if (impl_->init(service, principal_) == GssapiAuthenticatorImpl::RESULT_ERROR) { |
| set_error("Unable to initialize GSSAPI: " + impl_->error()); |
| return false; |
| } |
| |
| if (class_name_ == DSE_AUTHENTICATOR) { |
| response->assign(GSSAPI_AUTH_MECHANISM); |
| return true; |
| } else { |
| return evaluate_challenge(GSSAPI_AUTH_SERVER_INITIAL_CHALLENGE, response); |
| } |
| } |
| |
| bool DseGssapiAuthenticator::evaluate_challenge(const String& token, String* response) { |
| if (token == GSSAPI_AUTH_SERVER_INITIAL_CHALLENGE) { |
| if (impl_->process(String()) == GssapiAuthenticatorImpl::RESULT_ERROR) { |
| set_error("GSSAPI initial handshake failed: " + impl_->error()); |
| return false; |
| } |
| } else { |
| if (impl_->process(token) == GssapiAuthenticatorImpl::RESULT_ERROR) { |
| set_error("GSSAPI challenge handshake failed: " + impl_->error()); |
| return false; |
| } |
| } |
| *response = impl_->response(); |
| return true; |
| } |
| |
| bool DseGssapiAuthenticator::success(const String& token) { |
| // no-op |
| return true; |
| } |