kerberos_authenticator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c594e9c76894d762d40c03315577382a1202c241
4
+ data.tar.gz: 8ca589fdf26e7183cc11075e3224b7b1737b5af3
5
+ SHA512:
6
+ metadata.gz: 936e9cdefd7562f35f4f583673fc8647bf07361ed934d00486a793b53cfb6b71de327ec118d9c9a0911df14e666d5311513884798e02f8c7e3bae36165b729e1
7
+ data.tar.gz: e54b978206b7d64df2e6dab52f441d7393db2aa4fff387ad92c3377ac8d707058f7e0fe81643f940d36b71c3316122b320322c6616966c0adf095b94c46456b1
@@ -0,0 +1,118 @@
1
+ require 'base64'
2
+ require 'tempfile'
3
+
4
+ require 'kerberos_authenticator/error'
5
+ require 'kerberos_authenticator/krb5'
6
+
7
+ module KerberosAuthenticator
8
+ # @return [Krb5]
9
+ def self.krb5
10
+ Krb5
11
+ end
12
+
13
+ # Supports setting KerberosAuthenticator up using a block.
14
+ def self.setup
15
+ yield self
16
+ end
17
+
18
+ # Authenticates a user using their password.
19
+ # @param username [String] a string representation of the user's principal
20
+ # @param password [String] the user's password
21
+ # @raise [Error] if Kerberos can't understand the principal or contact any KDCs for the principal's realm
22
+ # @raise [Error] if preauthentication fails (usually meaning that the user's password was incorrect)
23
+ # @raise [Error] if the KDC cannot find the user
24
+ # @return [TrueClass] always returns true if authentication succeeds without any error
25
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/init_creds.html Initial credentials
26
+ def self.authenticate!(username, password)
27
+ user = Krb5::Principal.new_with_name(username)
28
+ creds = user.initial_creds_with_password(password, service)
29
+
30
+ with_keytab do |kt|
31
+ creds.verify!(server_princ, kt)
32
+ end
33
+
34
+ true
35
+ end
36
+
37
+ # @!attribute [rw] server
38
+ # @return [String] the server principal name to use when verifying the identity the KDC
39
+
40
+ # @!attribute [rw] service
41
+ # @return [String] the service principal name to request a ticket for when obtaining a user's credentials
42
+
43
+ # @!attribute [rw] keytab_base64
44
+ # @return [String] the keytab to use when verifying the identity the KDC represented as a Base64 encoded string (overrides keytab_path)
45
+
46
+ # @!attribute [rw] keytab_path
47
+ # @return [String] the path to the keytab to use when verifying the identity the KDC
48
+
49
+ @service = nil
50
+
51
+ def self.service
52
+ @service
53
+ end
54
+
55
+ def self.service=(v)
56
+ @service = v
57
+ end
58
+
59
+ @server = nil
60
+
61
+ def self.server
62
+ @server
63
+ end
64
+
65
+ def self.server=(v)
66
+ @server = v
67
+ end
68
+
69
+ @keytab_base64 = nil
70
+ @keytab_path = nil
71
+
72
+ def self.keytab_base64
73
+ @keytab_base64
74
+ end
75
+
76
+ def self.keytab_base64=(v)
77
+ @keytab_base64 = v
78
+ end
79
+
80
+ def self.keytab_path
81
+ @keytab_path
82
+ end
83
+
84
+ def self.keytab_path=(v)
85
+ @keytab_path = v
86
+ end
87
+
88
+ def self.server_princ
89
+ server ? Krb5::Principal.new_with_name(server) : nil
90
+ end
91
+
92
+ def self.new_kt_tmp_file
93
+ return nil unless keytab_base64
94
+
95
+ kt_tmp_file = Tempfile.new('krb5_kt', encoding: 'ascii-8bit')
96
+ kt_tmp_file.write(Base64.decode64(keytab_base64))
97
+ kt_tmp_file.close
98
+
99
+ kt_tmp_file
100
+ end
101
+
102
+ def self.with_keytab
103
+ if keytab_base64
104
+ kt_tmp_file = new_kt_tmp_file
105
+ kt = Krb5::Keytab.new_with_name("FILE:#{kt_tmp_file.path}")
106
+ elsif keytab_path
107
+ kt = Krb5::Keytab.new_with_name("FILE:#{keytab_path}")
108
+ end
109
+
110
+ begin
111
+ yield kt
112
+ ensure
113
+ kt_tmp_file.close! if kt_tmp_file
114
+ end
115
+ end
116
+
117
+ private_class_method :server_princ, :new_kt_tmp_file, :with_keytab
118
+ end
@@ -0,0 +1,4 @@
1
+ module KerberosAuthenticator
2
+ class Error < StandardError; end
3
+ class StandardError < Error; end
4
+ end
@@ -0,0 +1,35 @@
1
+ require 'ffi'
2
+
3
+ module KerberosAuthenticator
4
+ # An FFI wrapper around the Kerberos 5 library.
5
+ # Use the environmental variable FFI_KRB5_LIBRARY_NAME to override the library loaded.
6
+ module Krb5
7
+ extend FFI::Library
8
+
9
+ if ENV['FFI_KRB5_LIBRARY_NAME']
10
+ ffi_lib ENV['FFI_KRB5_LIBRARY_NAME']
11
+ else
12
+ ffi_lib 'krb5'
13
+ end
14
+
15
+ # @!attribute [rw] use_secure_context
16
+ # @return [Boolean] if Context.context should ignore environmental variables when returning a library context
17
+
18
+ @use_secure_context = true
19
+
20
+ def self.use_secure_context
21
+ @use_secure_context
22
+ end
23
+
24
+ def self.use_secure_context=(v)
25
+ @use_secure_context = v
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'kerberos_authenticator/krb5/attach_function'
31
+ require 'kerberos_authenticator/krb5/error'
32
+ require 'kerberos_authenticator/krb5/context'
33
+ require 'kerberos_authenticator/krb5/principal'
34
+ require 'kerberos_authenticator/krb5/creds'
35
+ require 'kerberos_authenticator/krb5/keytab'
@@ -0,0 +1,32 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ # Extends FFI's built-in method to:
4
+ # - drop the krb5_ prefix from function names
5
+ # - wrap any call returning a krb5_error_code with Krb5::Error.raise_if_error
6
+ def self.attach_function(c_name, params, returns, options = {})
7
+ ruby_name = c_name.to_s.gsub(/^krb5_/, '').to_sym
8
+
9
+ super(ruby_name, c_name, params, returns, options)
10
+
11
+ if returns == :krb5_error_code
12
+ no_check_name = "#{ruby_name}_without_catching_error"
13
+
14
+ alias_method(no_check_name, ruby_name)
15
+
16
+ if params.first == :krb5_context
17
+ define_method(ruby_name) do |*args, &block|
18
+ Krb5::Error.raise_if_error(args.first) { public_send(no_check_name, *args, &block) }
19
+ end
20
+ else
21
+ define_method(ruby_name) do |*args, &block|
22
+ Krb5::Error.raise_if_error(nil) { public_send(no_check_name, *args, &block) }
23
+ end
24
+ end
25
+
26
+ module_function no_check_name
27
+ end
28
+
29
+ module_function ruby_name
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,54 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ typedef :pointer, :krb5_context
4
+
5
+ attach_function :krb5_init_context, [:buffer_out], :krb5_error_code
6
+ attach_function :krb5_free_context, [:krb5_context], :void
7
+
8
+ begin
9
+ attach_function :krb5_init_secure_context, [:buffer_out], :krb5_error_code
10
+ rescue FFI::NotFoundError
11
+ # Then we're probably using a version of the Heimdal library
12
+ # that doesn't support init_secure_context (and ignore environmental variables by default)
13
+ alias_method(:init_secure_context, :init_context)
14
+ module_function :init_secure_context
15
+ end
16
+
17
+ # A Kerberos context, holding all per-thread state.
18
+ class Context
19
+ # @return [Context] a fibre-local Context
20
+ def self.context
21
+ if Krb5.use_secure_context
22
+ Thread.current[:krb5_secure_context] ||= new(true)
23
+ else
24
+ Thread.current[:krb5_context] ||= new
25
+ end
26
+ end
27
+
28
+ # @param secure [Boolean] whether to ignore environmental variables when constructing a library context
29
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_init_secure_context.html krb5_init_secure_context
30
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_init_context.html krb5_init_context
31
+ def initialize(secure = false)
32
+ @buffer = FFI::Buffer.new :pointer
33
+
34
+ if secure
35
+ Krb5::Error.raise_if_error { Krb5.init_secure_context(@buffer) }
36
+ else
37
+ Krb5::Error.raise_if_error { Krb5.init_context(@buffer) }
38
+ end
39
+
40
+ ObjectSpace.define_finalizer(self, self.class.finalize(@buffer))
41
+ self
42
+ end
43
+
44
+ def ptr
45
+ @buffer.get_pointer(0)
46
+ end
47
+
48
+ # @api private
49
+ def self.finalize(buffer)
50
+ proc { Krb5.free_context(buffer.get_pointer(0)) }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,89 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ typedef :pointer, :krb5_creds
4
+
5
+ attach_function :krb5_get_init_creds_password, [:krb5_context, :krb5_creds, :krb5_principal, :string, :pointer, :pointer, :int, :string, :pointer], :krb5_error_code
6
+ attach_function :krb5_verify_init_creds, [:krb5_context, :krb5_creds, :krb5_principal, :pointer, :pointer, :pointer], :krb5_error_code
7
+
8
+ attach_function :krb5_verify_init_creds_opt_init, [:pointer], :void
9
+ attach_function :krb5_verify_init_creds_opt_set_ap_req_nofail, [:pointer, :bool], :void
10
+
11
+ attach_function :krb5_free_creds, [:krb5_context, :krb5_creds], :void
12
+ attach_function :krb5_get_init_creds_opt_free, [:krb5_context, :pointer], :void
13
+
14
+ # Credentials, or tickets, provided by a KDC for a user.
15
+ class Creds
16
+ attr_reader :context, :ptr
17
+
18
+ # Requests initial credentials for principal using password from a KDC.
19
+ # @param principal [Principal] the user's Principal
20
+ # @param password [String] the user's password
21
+ # @param service [String] the service name used when requesting the credentials
22
+ # @return [Creds]
23
+ # @raise [Error] if a KDC for the principal can't be contacted
24
+ # @raise [Error] if preauthentication fails
25
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_get_init_creds_password.html krb5_get_init_creds_password
26
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/init_creds.html Initial credentials
27
+ def self.initial_creds_for_principal_with_a_password(principal, password, service = nil)
28
+ raise ArgumentError, 'expected Principal' unless principal.is_a? Principal
29
+
30
+ context = principal.context
31
+ ptr = FFI::MemoryPointer.new :char, 120
32
+
33
+ Krb5.get_init_creds_password(context.ptr, ptr, principal.ptr, password, nil, nil, 0, service, nil)
34
+
35
+ new(context, ptr)
36
+ end
37
+
38
+ def initialize(context, ptr)
39
+ @context = context
40
+ @ptr = ptr
41
+
42
+ @ptr.autorelease = false
43
+ ObjectSpace.define_finalizer(self, self.class.finalize(context, ptr))
44
+
45
+ self
46
+ end
47
+
48
+ # Calls #verify with nofail as true.
49
+ # @see #verify
50
+ def verify!(server_principal = nil, keytab = nil)
51
+ verify(true, server_principal, keytab)
52
+ end
53
+
54
+ # Attempt to verify that these Creds were obtained from a KDC with knowledge of a key in keytab.
55
+ # @param nofail [Boolean] whether to raise an Error if no keytab information is available
56
+ # @param server_principal [Principal] the server principal to use choosing an entry in keytab
57
+ # @param keytab [Keytab] the key table containing a key that the KDC should know
58
+ # @raise [Error] if nofail is true and no keytab information is available
59
+ # @raise [Error] if the KDC did not have knowledge of the key requested
60
+ # @return [TrueClass] always returns true if no error was raised
61
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_verify_init_creds.html krb5_verify_init_creds
62
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_verify_init_creds_opt_set_ap_req_nofail.html krb5_verify_init_creds_opt_set_ap_req_nofail
63
+ def verify(nofail = false, server_principal = nil, keytab = nil)
64
+ verify_creds_opt = FFI::MemoryPointer.new :int, 2
65
+
66
+ Krb5.verify_init_creds_opt_init(verify_creds_opt)
67
+ verify_creds_opt.autorelease = false
68
+
69
+ begin
70
+ Krb5.verify_init_creds_opt_set_ap_req_nofail(verify_creds_opt, nofail)
71
+
72
+ server_princ_ptr = server_principal ? server_principal.ptr : nil
73
+ keytab_ptr = keytab ? keytab.ptr : nil
74
+
75
+ Krb5.verify_init_creds(context.ptr, ptr, server_princ_ptr, keytab_ptr, nil, verify_creds_opt)
76
+ ensure
77
+ Krb5.get_init_creds_opt_free(context.ptr, verify_creds_opt)
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ # @api private
84
+ def self.finalize(context, ptr)
85
+ proc { Krb5.free_creds(context.ptr, ptr) }
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,23 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ typedef :int, :krb5_error_code
4
+ attach_function :krb5_get_error_message, [:pointer, :krb5_error_code], :string
5
+
6
+ # A Kerberos library error
7
+ class Error < StandardError
8
+ attr_reader :error_code
9
+
10
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_get_error_message.html krb5_get_error_message
11
+ def initialize(context_ptr, krb5_error_code)
12
+ @error_code = krb5_error_code
13
+ super(Krb5.get_error_message(context_ptr, krb5_error_code))
14
+ end
15
+
16
+ def self.raise_if_error(context_ptr = nil)
17
+ err = yield
18
+ return 0 if err == 0
19
+ raise Krb5::Error.new(context_ptr, err)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ typedef :pointer, :krb5_keytab
4
+
5
+ attach_function :krb5_kt_resolve, [:krb5_context, :string, :buffer_out], :krb5_error_code
6
+ attach_function :krb5_kt_close, [:krb5_context, :krb5_keytab], :krb5_error_code
7
+ attach_function :krb5_kt_get_type, [:krb5_context, :krb5_keytab], :string
8
+
9
+ # Storage for locally-stored keys.
10
+ class Keytab
11
+ attr_reader :context
12
+
13
+ # @param name [String] a name of the form 'type:residual', where usually type is 'FILE' and residual the path to that file
14
+ # @return [Keytab]
15
+ # @see http://web.mit.edu/Kerberos/krb5-1.14/doc/appdev/refs/api/krb5_kt_resolve.html krb5_kt_resolve
16
+ def self.new_with_name(name, context = Context.context)
17
+ buffer = FFI::Buffer.new :pointer
18
+ Krb5.kt_resolve(context.ptr, name, buffer)
19
+
20
+ new(context, buffer)
21
+ end
22
+
23
+ def initialize(context, buffer)
24
+ @context = context
25
+ @buffer = buffer
26
+
27
+ ObjectSpace.define_finalizer(self, self.class.finalize(context, buffer))
28
+ self
29
+ end
30
+
31
+ def ptr
32
+ @buffer.get_pointer(0)
33
+ end
34
+
35
+ def type
36
+ Krb5.kt_get_type(context.ptr, ptr)
37
+ end
38
+
39
+ # @api private
40
+ def self.finalize(context, buffer)
41
+ proc { Krb5.kt_close(context.ptr, buffer.get_pointer(0)) }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,69 @@
1
+ module KerberosAuthenticator
2
+ module Krb5
3
+ typedef :pointer, :krb5_principal
4
+
5
+ attach_function :krb5_parse_name, [:krb5_context, :string, :krb5_principal], :krb5_error_code
6
+ attach_function :krb5_free_principal, [:krb5_context, :krb5_principal], :void
7
+
8
+ attach_function :krb5_unparse_name, [:krb5_context, :krb5_principal, :pointer], :krb5_error_code
9
+ attach_function :krb5_free_unparsed_name, [:krb5_context, :pointer], :void
10
+
11
+ # A Kerberos principal identifying a user, service or machine.
12
+ class Principal
13
+ attr_reader :context
14
+
15
+ # Convert a string representation of a principal name into a new Principal.
16
+ # @param name [String] a string representation of a principal name
17
+ # @param context [Context] a Kerberos library context
18
+ # @return [Principal]
19
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_parse_name.html krb5_parse_name
20
+ def self.new_with_name(name, context = Context.context)
21
+ raise ArgumentError, 'name cannot be empty' if name.empty?
22
+
23
+ buffer = FFI::Buffer.new :pointer
24
+ Krb5.parse_name(context.ptr, name, buffer)
25
+ new(context, buffer)
26
+ end
27
+
28
+ def initialize(context, buffer)
29
+ @context = context
30
+ @buffer = buffer
31
+
32
+ ObjectSpace.define_finalizer(self, self.class.finalize(context, buffer))
33
+
34
+ self
35
+ end
36
+
37
+ # @param password [String]
38
+ # @param service [String]
39
+ # @return [Creds]
40
+ # @see Creds.initial_creds_for_principal_with_a_password
41
+ def initial_creds_with_password(password, service = nil)
42
+ Creds.initial_creds_for_principal_with_a_password(self, password, service)
43
+ end
44
+
45
+ def ptr
46
+ @buffer.get_pointer(0)
47
+ end
48
+
49
+ # @return [String] a string representation of the principal's name
50
+ # @see http://web.mit.edu/kerberos/krb5-1.14/doc/appdev/refs/api/krb5_unparse_name.html krb5_unparse_name
51
+ def name
52
+ out_ptr = FFI::MemoryPointer.new(:pointer, 1)
53
+ Krb5.unparse_name(context.ptr, ptr, out_ptr)
54
+
55
+ str_ptr = out_ptr.read_pointer
56
+ copy = String.new(str_ptr.read_string)
57
+
58
+ Krb5.free_unparsed_name(context.ptr, str_ptr)
59
+
60
+ copy
61
+ end
62
+
63
+ # @api private
64
+ def self.finalize(context, buffer)
65
+ proc { Krb5.free_principal(context.ptr, buffer.get_pointer(0)) }
66
+ end
67
+ end
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kerberos_authenticator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Watkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/kerberos_authenticator.rb
20
+ - lib/kerberos_authenticator/error.rb
21
+ - lib/kerberos_authenticator/krb5.rb
22
+ - lib/kerberos_authenticator/krb5/attach_function.rb
23
+ - lib/kerberos_authenticator/krb5/context.rb
24
+ - lib/kerberos_authenticator/krb5/creds.rb
25
+ - lib/kerberos_authenticator/krb5/error.rb
26
+ - lib/kerberos_authenticator/krb5/keytab.rb
27
+ - lib/kerberos_authenticator/krb5/principal.rb
28
+ homepage: https://github.com/stupidpupil/kerberos_authenticator
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements:
47
+ - A Kerberos 5 library
48
+ rubyforge_project:
49
+ rubygems_version: 2.5.1
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: An FFI library to support Kerberos authentication of a user, with a password,
53
+ and of the KDC, with a keytab
54
+ test_files: []
55
+ has_rdoc: