File: simple.rb

package info (click to toggle)
ruby-gssapi 1.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 216 kB
  • sloc: ruby: 770; makefile: 2
file content (258 lines) | stat: -rw-r--r-- 12,562 bytes parent folder | download | duplicates (3)
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