Permalink
259 lines (219 sloc) 12.3 KB
=begin
Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
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