1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
|
=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
|