=begin Copyright © 2010 Dan Wanek Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php =end module GSSAPI # This class is a simple wrapper around the most common usage of GSSAPI. If you are looking at doing # something a bit more advanced you may want to check out the LibGSSAPI module. class Simple attr_reader :context attr_reader :delegated_credentials # Initialize a new GSSAPI::Simple object # @param [String] host_name the fully qualified host name # @param [String] service_name The service name. This can either be a # GSS_KRB5_NT_PRINCIPAL_NAME in the form of srvc/fqdn@REALM # or # GSS_C_NT_HOSTBASED_SERVICE in the form of srvc@fqdn # If there is no '@fqdn' part, the host_name will be appended. # If no service_name is given at all the default service of 'host@fqdn' will be used. def initialize(host_name, service_name=nil, keytab=nil) @host = host_name @service = service_name.nil? ? "host@#{@host}" : (service_name.include?('@') ? service_name : "#{service_name}@#{@host}") @int_svc_name = import_name(@service) @context = nil # the security context @scred = nil # the service credentials. really only used for the server-side via acquire_credentials set_keytab(keytab) unless keytab.nil? @delegated_credentials = nil end # Convert a String to a GSSAPI usable buffer (gss_buffer_desc) # @param [String] str the string to convert def import_name(str) buff_str = LibGSSAPI::UnManagedGssBufferDesc.new buff_str.value = str # Choose the appropriate mechanism based on the string passed. if (str =~ /[A-Za-z0-9]+\/[^@]+@.+$/) mech = LibGSSAPI::GssOID.gss_c_no_oid else mech = LibGSSAPI::GSS_C_NT_HOSTBASED_SERVICE end name = FFI::MemoryPointer.new :pointer # gss_name_t min_stat = FFI::MemoryPointer.new :OM_uint32 maj_stat = LibGSSAPI.gss_import_name(min_stat, buff_str.pointer, mech, name) raise GssApiError.new(maj_stat, min_stat), "gss_import_name did not return GSS_S_COMPLETE" if maj_stat != 0 LibGSSAPI::GssNameT.new(name.get_pointer(0)) end # Initialize the GSS security context (client initiator). If there was a previous call that issued a # continue you can pass the continuation token in via the token param. # If no flags are set the default flags are LibGSSAPI::GSS_C_MUTUAL_FLAG | LibGSSAPI::GSS_C_SEQUENCE_FLAG # @param [String] in_token an input token sent from the remote service in a continuation. # @param [Hash] opts misc opts to be set # @option opts [Fixnum] :flags override all other flags. If you set the :delegate option this option will override it. # @see http://tools.ietf.org/html/rfc4121#section-4.1.1.1 # @option opts [Boolean] :delegate if true set the credential delegate flag # @return [String, true] if a continuation flag is set it will return the output token that is needed to send # to the remote host. Otherwise it returns true and the GSS security context has been established. def init_context(in_token = nil, opts = {}) min_stat = FFI::MemoryPointer.new :OM_uint32 pctx = (@context.nil? ? LibGSSAPI::GssCtxIdT.gss_c_no_context.address_of : @context.address_of) mech = LibGSSAPI::GssOID.gss_c_no_oid if(opts[:flags]) flags = opts[:flags] else flags = (LibGSSAPI::GSS_C_MUTUAL_FLAG | LibGSSAPI::GSS_C_SEQUENCE_FLAG | LibGSSAPI::GSS_C_CONF_FLAG | LibGSSAPI::GSS_C_INTEG_FLAG) flags |= LibGSSAPI::GSS_C_DELEG_FLAG if opts[:delegate] flags |= LibGSSAPI::GSS_C_DELEG_POLICY_FLAG if opts[:delegate] end in_tok = LibGSSAPI::UnManagedGssBufferDesc.new in_tok.value = in_token out_tok = LibGSSAPI::ManagedGssBufferDesc.new ret_flags = FFI::MemoryPointer.new :OM_uint32 maj_stat = LibGSSAPI.gss_init_sec_context(min_stat, nil, pctx, @int_svc_name, mech, flags, 0, nil, in_tok.pointer, nil, out_tok.pointer, ret_flags, nil) raise GssApiError.new(maj_stat, min_stat), "gss_init_sec_context did not return GSS_S_COMPLETE" if maj_stat > 1 # The returned context may be equal to the passed in @context. If so, we # must not create another AutoPointer to the same gss_buffer_t. If we do # we will double delete it. ctx = pctx.get_pointer(0) @context = LibGSSAPI::GssCtxIdT.new(ctx) if ctx != @context maj_stat == 1 ? out_tok.value : true end # Accept a security context that was initiated by a remote peer. # @param [String] in_token The token sent by the remote client to initiate the context # @return [String, true] If this is part of a continuation it will return a token to be passed back to the remote # otherwise it will simply return true. def accept_context(in_token) raise GssApiError, "No credentials yet acquired. Call #{self.class.name}#acquire_credentials first" if @scred.nil? min_stat = FFI::MemoryPointer.new :OM_uint32 pctx = (@context.nil? ? LibGSSAPI::GssCtxIdT.gss_c_no_context.address_of : @context.address_of) no_chn_bind = LibGSSAPI::GSS_C_NO_CHANNEL_BINDINGS @client = FFI::MemoryPointer.new :pointer # Will hold the initiating client name after the call mech = FFI::MemoryPointer.new :pointer # Will hold the mech being used after the call in_tok = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new in_tok.value = in_token out_tok = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new ret_flags = FFI::MemoryPointer.new :OM_uint32 delegated_cred_handle = FFI::MemoryPointer.new :pointer maj_stat = LibGSSAPI.gss_accept_sec_context(min_stat, pctx, @scred, in_tok.pointer, no_chn_bind, @client, mech, out_tok.pointer, ret_flags, nil, delegated_cred_handle) raise GssApiError.new(maj_stat, min_stat), "gss_accept_sec_context did not return GSS_S_COMPLETE" if maj_stat > 1 if (ret_flags.read_uint32 & LibGSSAPI::GSS_C_DELEG_FLAG) != 0 @delegated_credentials = LibGSSAPI::GssCredIdT.new(delegated_cred_handle.get_pointer(0)) end # The returned context may be equal to the passed in @context. If so, we # must not create another AutoPointer to the same gss_buffer_t. If we do # we will double delete it. ctx = pctx.get_pointer(0) @context = LibGSSAPI::GssCtxIdT.new(ctx) if ctx != @context out_tok.length > 0 ? out_tok.value : true end def get_mic(token) min_stat = FFI::MemoryPointer.new :OM_uint32 qop_req = GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT in_buff = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new in_buff.value = token out_buff = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new maj_stat = GSSAPI::LibGSSAPI.gss_get_mic(min_stat, @context, qop_req, in_buff.pointer, out_buff.pointer) raise GssApiError.new(maj_stat, min_stat), "Failed to gss_get_mic" if maj_stat != 0 out_buff.value end # Get textual representation of internal GSS name # @return [String] textual representation of internal GSS name def display_name raise GssApiError.new(), "No context accepted yet. Call #{self.class.name}#accept_context(in_token) first" if @client.nil? output_name = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new min_stat = FFI::MemoryPointer.new :OM_uint32 maj_stat = LibGSSAPI.gss_display_name(min_stat, @client.get_pointer(0), output_name.pointer, nil) if maj_stat != GSSAPI::LibGSSAPI::GSS_S_COMPLETE raise GssApiError.new(maj_stat, min_stat), "gss_display_name did not return GSS_S_COMPLETE but #{ maj_stat }" end output_name.value end # Acquire security credentials. This does not log you in. It grabs the credentials from a cred cache or keytab. # @param [Hash] opts options to pass to the gss_acquire_cred function. # @option opts [String] :usage The credential usage type (:accept, :initiate, :both). It defaults to 'accept' since # this method is most usually called on the server only. # @return [true] It will return true if everything succeeds and the @scred variable will be set for future methods. If # an error ocurrs an exception will be raised. def acquire_credentials(princ = @int_svc_name, opts = {:usage => :accept}) min_stat = FFI::MemoryPointer.new :OM_uint32 scred = FFI::MemoryPointer.new :pointer case opts[:usage] when :accept usage = LibGSSAPI::GSS_C_ACCEPT when :initiate usage = LibGSSAPI::GSS_C_INITIATE when :both usage = LibGSSAPI::GSS_C_BOTH else raise GssApiError, "Bad option passed to #{self.class.name}#acquire_credentials" end maj_stat = LibGSSAPI.gss_acquire_cred(min_stat, princ, 0, LibGSSAPI::GSS_C_NO_OID_SET, usage, scred, nil, nil) raise GssApiError.new(maj_stat, min_stat), "gss_acquire_cred did not return GSS_S_COMPLETE" if maj_stat != 0 @scred = LibGSSAPI::GssCredIdT.new(scred.get_pointer(0)) true end # Wrap a message using gss_wrap. It can either encrypt the message (confidentiality) or simply sign it (integrity). # @param [String] msg The message to wrap # @param [Boolean] encrypt Whether or not to encrypt the message or just sign it. The default is to encrypt. # @return [String] The wrapped message. It will raise an exception on error def wrap_message(msg, encrypt = true) min_stat = FFI::MemoryPointer.new :OM_uint32 conf_req = (encrypt ? 1 : 0) qop_req = GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT in_buff = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new in_buff.value = msg conf_state = FFI::MemoryPointer.new :OM_uint32 out_buff = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new maj_stat = GSSAPI::LibGSSAPI.gss_wrap(min_stat, @context, conf_req, qop_req, in_buff.pointer, conf_state, out_buff.pointer) raise GssApiError.new(maj_stat, min_stat), "Failed to gss_wrap message" if maj_stat != 0 out_buff.value end # Unwrap a message previously wrapped with gss_wrap. # @param [String] msg The message to unwrap # @param [Boolean] encrypted Whether or not this message was encrypted (true) or just signed (false) def unwrap_message(msg, encrypted = true) min_stat = FFI::MemoryPointer.new :OM_uint32 in_buff = GSSAPI::LibGSSAPI::UnManagedGssBufferDesc.new in_buff.value = msg out_buff = GSSAPI::LibGSSAPI::ManagedGssBufferDesc.new conf_state = FFI::MemoryPointer.new :int conf_state.write_int((encrypted ? 1 : 0)) q_op = FFI::MemoryPointer.new :OM_uint32 q_op.write_int(0) maj_stat = GSSAPI::LibGSSAPI.gss_unwrap(min_stat, @context, in_buff.pointer, out_buff.pointer, conf_state, q_op) raise GssApiError.new(maj_stat, min_stat), "Failed to gss_unwrap message" if maj_stat != 0 out_buff.value end # Add a path to a custom keytab file # @param [String] keytab the path to the keytab def set_keytab(keytab) maj_stat = LibGSSAPI.krb5_gss_register_acceptor_identity(keytab) raise GssApiError.new(maj_stat, min_stat), "krb5_gss_register_acceptor_identity did not return GSS_S_COMPLETE" if maj_stat != 0 true end end # Simple end # GSSAPI