gsasl 0.1.0
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.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +41 -0
- data/Rakefile +5 -0
- data/gsasl.gemspec +24 -0
- data/lib/gsasl/context.rb +97 -0
- data/lib/gsasl/native.rb +177 -0
- data/lib/gsasl/peer.rb +115 -0
- data/lib/gsasl/version.rb +3 -0
- data/lib/gsasl.rb +8 -0
- data/spec/authentication_spec.rb +55 -0
- data/spec/context_spec.rb +41 -0
- data/spec/peer_spec.rb +45 -0
- data/spec/spec_helper.rb +14 -0
- metadata +112 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# GNU SASL for Ruby
|
2
|
+
|
3
|
+
This libaray is a lib ffi based wrapper for the [GNU SASL](http://www.gnu.org/software/gsasl/) library. It supports a variaty of different authentication mechanisms. Those are the mechanisms supported for the current versions:
|
4
|
+
|
5
|
+
* **EXTERNAL**: Authentication via out of band information.
|
6
|
+
* **ANONYMOUS**: Mechanism for anonymous access to resources.
|
7
|
+
* **PLAIN**: Clear text username and password.
|
8
|
+
* **LOGIN**: Non-standard clear text username and password.
|
9
|
+
* **CRAM-MD5**: Challenge-Response Authentication Mechanism.
|
10
|
+
* **DIGEST-MD5**: Digest Authentication.
|
11
|
+
* **SCRAM-SHA-1**: SCRAM-SHA-1 authentication.
|
12
|
+
* **NTLM**: Microsoft NTLM authentication.
|
13
|
+
* **SECURID**: Authentication using tokens.
|
14
|
+
* **GSSAPI**: GSSAPI (Kerberos 5) authentication.
|
15
|
+
* **GS2-KRB5**: Improved GSSAPI (Kerberos 5) authentication.
|
16
|
+
* **KERBEROS\_V5**: Experimental KERBEROS\_V5 authentication.
|
17
|
+
* **SAML20**: Experimental SAML20 authentication.
|
18
|
+
* **OPENID20**: Experimental OPENID20 authentication.
|
19
|
+
|
20
|
+
# Use in Ruby
|
21
|
+
|
22
|
+
In the following example the server and the client are on the same machine. If the server is on the remote site, one has to implement a server that will return the next challenge on `server#read` and implements a `server#send` to send the challenge to the server. Also it is possible to not use the `#authenticate` function but to implement the processing individually.
|
23
|
+
|
24
|
+
session = Gsasl::Context.new
|
25
|
+
client = session.create_client("CRAM-MD5")
|
26
|
+
server = session.create_server("CRAM-MD5")
|
27
|
+
|
28
|
+
server.set_callback do |property|
|
29
|
+
if property == Gsasl::GSASL_PASSWORD
|
30
|
+
if server[Gsasl::GSASL_AUTHID] == "joe"
|
31
|
+
server[Gsasl::GSASL_PASSWORD] = "secret"
|
32
|
+
end
|
33
|
+
Gsasl::GSASL_OK
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
client[Gsasl::GSASL_AUTHID] = "joe"
|
38
|
+
client[Gsasl::GSASL_PASSWORD] = "secret"
|
39
|
+
|
40
|
+
client.authenticate(server).should be_true
|
41
|
+
|
data/Rakefile
ADDED
data/gsasl.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "gsasl/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "gsasl"
|
7
|
+
s.version = Gsasl::VERSION
|
8
|
+
s.authors = ["Vincent Landgraf"]
|
9
|
+
s.email = ["vilandgr@googlemail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A lib ffi based wrapper for lib GNU SASL}
|
12
|
+
s.description = %q{A library for doing SASL based authentication mechanisms}
|
13
|
+
|
14
|
+
s.rubyforge_project = "gsasl"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_runtime_dependency "ffi"
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Gsasl
|
2
|
+
class Context
|
3
|
+
# Access the peers of a given session. This is used to find peers for the
|
4
|
+
# global `Gsasl::CALLBACK`.
|
5
|
+
# @api private
|
6
|
+
attr_accessor :peers
|
7
|
+
|
8
|
+
# Create a new gsasl authentication context.
|
9
|
+
def initialize
|
10
|
+
ctx = FFI::MemoryPointer.new :pointer
|
11
|
+
result = Gsasl.gsasl_init(ctx)
|
12
|
+
@context = ctx.get_pointer(0)
|
13
|
+
Gsasl.raise_error!(result)
|
14
|
+
@peers = {}
|
15
|
+
Gsasl.new_context @context.address, self
|
16
|
+
Gsasl.gsasl_callback_set(@context, CALLBACK)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns or checks agains the passed version of GNU SASL.
|
20
|
+
# @param [String] check the version string to check against.
|
21
|
+
# @return [String, nil] the version string if the check was successful, nil
|
22
|
+
# otherwise
|
23
|
+
def version(check = nil)
|
24
|
+
Gsasl.gsasl_check_version check
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks if the client peer supports the passed mechanism
|
28
|
+
# @param [String] mechanism_name the mechnism to check for
|
29
|
+
# @return [Boolean] true if it is supported false otherwise
|
30
|
+
def client_support_for?(mechanism_name)
|
31
|
+
Gsasl.gsasl_client_support_p(@context, mechanism_name) == 1
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks if the server peer supports the passed mechanism
|
35
|
+
# @param [String] mechanism_name the mechnism to check for
|
36
|
+
# @return [Boolean] true if it is supported false otherwise
|
37
|
+
def server_support_for?(mechanism_name)
|
38
|
+
Gsasl.gsasl_server_support_p(@context, mechanism_name) == 1
|
39
|
+
end
|
40
|
+
|
41
|
+
# Closes the sasl peer for the context. Should be called after authenication.
|
42
|
+
def close
|
43
|
+
Gsasl.gsasl_done(@context)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a list of mechanisms for the server peer
|
47
|
+
# @return [Array<String>] the list of possible mechanisms
|
48
|
+
def server_mechanisms
|
49
|
+
mechanisms :server
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a list of mechanisms for the client peer
|
53
|
+
# @return [Array<String>] the list of possible mechanisms
|
54
|
+
def client_mechanisms
|
55
|
+
mechanisms :client
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates the server peer based on the passed mechanism
|
59
|
+
# @param [String] mechanism_name the name of the mechanism
|
60
|
+
# @return [Gsasl::Peer] the server peer
|
61
|
+
# @example
|
62
|
+
# peer = @session.create_server("CRAM-MD5")
|
63
|
+
def create_server(mechanism_name)
|
64
|
+
peer = Peer.new(@context, mechanism_name, :server)
|
65
|
+
@peers[peer.session.address] = peer
|
66
|
+
peer
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates the client peer based on the passed mechanism
|
70
|
+
# @param [String] mechanism_name the name of the mechanism
|
71
|
+
# @return [Gsasl::Peer] the client peer
|
72
|
+
# @example
|
73
|
+
# peer = @session.create_client("CRAM-MD5")
|
74
|
+
def create_client(mechanism_name)
|
75
|
+
peer = Peer.new(@context, mechanism_name, :client)
|
76
|
+
@peers[peer.session.address] = peer
|
77
|
+
peer
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Returns a list of mechanisms for the passed type (type can be :client or
|
83
|
+
# :server)
|
84
|
+
# @param [Symbol] type the type to check mechanisms for
|
85
|
+
# @return [Array<String>] the list of possible mechanisms
|
86
|
+
def mechanisms(type)
|
87
|
+
out = FFI::MemoryPointer.new :pointer
|
88
|
+
result = Gsasl.send("gsasl_#{type}_mechlist", @context, out)
|
89
|
+
Gsasl.raise_error!(result)
|
90
|
+
data = out.get_pointer(0)
|
91
|
+
list = data.read_string.split(/\s/)
|
92
|
+
list
|
93
|
+
ensure
|
94
|
+
Gsasl.gsasl_free(data)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/gsasl/native.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Gsasl
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib "libgsasl"
|
6
|
+
|
7
|
+
# RFC 2222: SASL mechanisms are named by strings, from 1 to 20
|
8
|
+
# characters in length, consisting of upper-case letters, digits,
|
9
|
+
# hyphens, and/or underscores. SASL mechanism names must be
|
10
|
+
# registered with the IANA.
|
11
|
+
GSASL_MIN_MECHANISM_SIZE = 1,
|
12
|
+
GSASL_MAX_MECHANISM_SIZE = 20
|
13
|
+
|
14
|
+
# Error codes for library functions.
|
15
|
+
GSASL_OK = 0
|
16
|
+
GSASL_NEEDS_MORE = 1
|
17
|
+
GSASL_UNKNOWN_MECHANISM = 2
|
18
|
+
GSASL_MECHANISM_CALLED_TOO_MANY_TIMES = 3
|
19
|
+
GSASL_MALLOC_ERROR = 7
|
20
|
+
GSASL_BASE64_ERROR = 8
|
21
|
+
GSASL_CRYPTO_ERROR = 9
|
22
|
+
GSASL_SASLPREP_ERROR = 29
|
23
|
+
GSASL_MECHANISM_PARSE_ERROR = 30
|
24
|
+
GSASL_AUTHENTICATION_ERROR = 31
|
25
|
+
GSASL_INTEGRITY_ERROR = 33
|
26
|
+
GSASL_NO_CLIENT_CODE = 35
|
27
|
+
GSASL_NO_SERVER_CODE = 36
|
28
|
+
GSASL_NO_CALLBACK = 51
|
29
|
+
GSASL_NO_ANONYMOUS_TOKEN = 52
|
30
|
+
GSASL_NO_AUTHID = 53
|
31
|
+
GSASL_NO_AUTHZID = 54
|
32
|
+
GSASL_NO_PASSWORD = 55
|
33
|
+
GSASL_NO_PASSCODE = 56
|
34
|
+
GSASL_NO_PIN = 57
|
35
|
+
GSASL_NO_SERVICE = 58
|
36
|
+
GSASL_NO_HOSTNAME = 59
|
37
|
+
GSASL_NO_CB_TLS_UNIQUE = 65
|
38
|
+
GSASL_NO_SAML20_IDP_IDENTIFIER = 66
|
39
|
+
GSASL_NO_SAML20_REDIRECT_URL = 67
|
40
|
+
GSASL_NO_OPENID20_AUTH_IDENTIFIER = 68
|
41
|
+
|
42
|
+
# Mechanism specific errors.
|
43
|
+
GSASL_GSSAPI_RELEASE_BUFFER_ERROR = 37
|
44
|
+
GSASL_GSSAPI_IMPORT_NAME_ERROR = 38
|
45
|
+
GSASL_GSSAPI_INIT_SEC_CONTEXT_ERROR = 39
|
46
|
+
GSASL_GSSAPI_ACCEPT_SEC_CONTEXT_ERROR = 40
|
47
|
+
GSASL_GSSAPI_UNWRAP_ERROR = 41
|
48
|
+
GSASL_GSSAPI_WRAP_ERROR = 42
|
49
|
+
GSASL_GSSAPI_ACQUIRE_CRED_ERROR = 43
|
50
|
+
GSASL_GSSAPI_DISPLAY_NAME_ERROR = 44
|
51
|
+
GSASL_GSSAPI_UNSUPPORTED_PROTECTION_ERROR = 45
|
52
|
+
GSASL_KERBEROS_V5_INIT_ERROR = 46
|
53
|
+
GSASL_KERBEROS_V5_INTERNAL_ERROR = 47
|
54
|
+
GSASL_SHISHI_ERROR = GSASL_KERBEROS_V5_INTERNAL_ERROR
|
55
|
+
GSASL_SECURID_SERVER_NEED_ADDITIONAL_PASSCODE = 48
|
56
|
+
GSASL_SECURID_SERVER_NEED_NEW_PIN = 49
|
57
|
+
GSASL_GSSAPI_ENCAPSULATE_TOKEN_ERROR = 60
|
58
|
+
GSASL_GSSAPI_DECAPSULATE_TOKEN_ERROR = 61
|
59
|
+
GSASL_GSSAPI_INQUIRE_MECH_FOR_SASLNAME_ERROR = 62
|
60
|
+
GSASL_GSSAPI_TEST_OID_SET_MEMBER_ERROR = 63
|
61
|
+
GSASL_GSSAPI_RELEASE_OID_SET_ERROR = 64
|
62
|
+
|
63
|
+
# Information properties, e.g., username.
|
64
|
+
GSASL_AUTHID = 1
|
65
|
+
GSASL_AUTHZID = 2
|
66
|
+
GSASL_PASSWORD = 3
|
67
|
+
GSASL_ANONYMOUS_TOKEN = 4
|
68
|
+
GSASL_SERVICE = 5
|
69
|
+
GSASL_HOSTNAME = 6
|
70
|
+
GSASL_GSSAPI_DISPLAY_NAME = 7
|
71
|
+
GSASL_PASSCODE = 8
|
72
|
+
GSASL_SUGGESTED_PIN = 9
|
73
|
+
GSASL_PIN = 10
|
74
|
+
GSASL_REALM = 11
|
75
|
+
GSASL_DIGEST_MD5_HASHED_PASSWORD = 12
|
76
|
+
GSASL_QOPS = 13
|
77
|
+
GSASL_QOP = 14
|
78
|
+
GSASL_SCRAM_ITER = 15
|
79
|
+
GSASL_SCRAM_SALT = 16
|
80
|
+
GSASL_SCRAM_SALTED_PASSWORD = 17
|
81
|
+
GSASL_CB_TLS_UNIQUE = 18
|
82
|
+
GSASL_SAML20_IDP_IDENTIFIER = 19
|
83
|
+
GSASL_SAML20_REDIRECT_URL = 20
|
84
|
+
GSASL_OPENID20_AUTH_IDENTIFIER = 21
|
85
|
+
|
86
|
+
# Client callbacks.
|
87
|
+
GSASL_SAML20_AUTHENTICATE_IN_BROWSER = 250
|
88
|
+
|
89
|
+
# Server validation callback properties.
|
90
|
+
GSASL_VALIDATE_SIMPLE = 500
|
91
|
+
GSASL_VALIDATE_EXTERNAL = 501
|
92
|
+
GSASL_VALIDATE_ANONYMOUS = 502
|
93
|
+
GSASL_VALIDATE_GSSAPI = 503
|
94
|
+
GSASL_VALIDATE_SECURID = 504
|
95
|
+
GSASL_VALIDATE_SAML20 = 505
|
96
|
+
|
97
|
+
# Gsasl_cipher
|
98
|
+
GSASL_CIPHER_DES = 1
|
99
|
+
GSASL_CIPHER_3DES = 2
|
100
|
+
GSASL_CIPHER_RC4 = 4
|
101
|
+
GSASL_CIPHER_RC4_40 = 8
|
102
|
+
GSASL_CIPHER_RC4_56 = 16
|
103
|
+
GSASL_CIPHER_AES = 32
|
104
|
+
|
105
|
+
# Quality of Protection types (DIGEST-MD5 and GSSAPI). The
|
106
|
+
# integrity and confidentiality values is about application data
|
107
|
+
# wrapping. We recommend that you use @GSASL_QOP_AUTH with TLS as
|
108
|
+
# that combination is generally more secure and have better chance
|
109
|
+
# of working than the integrity/confidentiality layers of SASL.
|
110
|
+
GSASL_QOP_AUTH = 1,
|
111
|
+
GSASL_QOP_AUTH_INT = 2,
|
112
|
+
GSASL_QOP_AUTH_CONF = 4
|
113
|
+
|
114
|
+
# the callback signature for the global callback
|
115
|
+
callback :gsasl_callback, [ :pointer, :pointer, :int], :int
|
116
|
+
|
117
|
+
# lib ffi mapped functions
|
118
|
+
attach_function :gsasl_init, [ :pointer ], :int
|
119
|
+
attach_function :gsasl_done, [ :pointer ], :void
|
120
|
+
attach_function :gsasl_check_version, [ :string ], :string
|
121
|
+
attach_function :gsasl_strerror, [ :int ], :string
|
122
|
+
attach_function :gsasl_client_support_p, [ :pointer, :string ], :int
|
123
|
+
attach_function :gsasl_server_support_p, [ :pointer, :string ], :int
|
124
|
+
attach_function :gsasl_server_start, [ :pointer, :string, :pointer ], :int
|
125
|
+
attach_function :gsasl_client_start, [ :pointer, :string, :pointer ], :int
|
126
|
+
attach_function :gsasl_finish, [ :pointer ], :void
|
127
|
+
attach_function :gsasl_client_mechlist, [ :pointer, :pointer ], :int
|
128
|
+
attach_function :gsasl_server_mechlist, [ :pointer, :pointer ], :int
|
129
|
+
attach_function :gsasl_free, [ :pointer ], :void
|
130
|
+
attach_function :gsasl_property_set, [ :pointer, :int , :string ], :void
|
131
|
+
attach_function :gsasl_property_get, [ :pointer, :int ], :string
|
132
|
+
attach_function :gsasl_callback_set, [ :pointer, :gsasl_callback ], :void
|
133
|
+
attach_function :gsasl_step64, [ :pointer, :string, :pointer], :int
|
134
|
+
|
135
|
+
# Raises an error if the passed result is not GSASL_OK
|
136
|
+
# @param [Fixnum] result that should be checked
|
137
|
+
# @raises [GsaslError] if a different result occured
|
138
|
+
def self.raise_error!(result)
|
139
|
+
if result != GSASL_OK
|
140
|
+
raise GsaslError, Gsasl.gsasl_strerror(result)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Handles at a global level all callbacks that are made by the gsasl library.
|
145
|
+
# The context and session (or peer) will be used to proxy the events to the
|
146
|
+
# corresponding object.
|
147
|
+
CALLBACK = Proc.new do |context, peer, property|
|
148
|
+
# find the object...
|
149
|
+
if context = find_by_context(context.address)
|
150
|
+
if peer = context.peers[peer.address]
|
151
|
+
# ...and call the callback with the property
|
152
|
+
result = peer.call(property)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# if there is no callback handler (nil) return that information
|
157
|
+
result || Gsasl::GSASL_NO_CALLBACK
|
158
|
+
end
|
159
|
+
|
160
|
+
# Helper to find an context (or session) by the passed id. Used by `CALLBACK`.
|
161
|
+
# @api private
|
162
|
+
# @param [Fixnum] id the id of the sesseion (pointer to context struct)
|
163
|
+
# @return [Gsasl::Context, nil] the session or nil if nothing was found
|
164
|
+
def self.find_by_context(id)
|
165
|
+
@contexts ||= {}
|
166
|
+
@contexts[id]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Registers the context and the session for later use in the global callback.
|
170
|
+
# @api private
|
171
|
+
# @param [Fixnum] id the id of the sesseion (pointer to context struct)
|
172
|
+
# @param [Gsasl::Context] context the session to save for later use
|
173
|
+
def self.new_context(id, context)
|
174
|
+
@contexts ||= {}
|
175
|
+
@contexts[id] = context
|
176
|
+
end
|
177
|
+
end
|
data/lib/gsasl/peer.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module Gsasl
|
2
|
+
# A peer is a client or server side that processes data to do an
|
3
|
+
# authentication.
|
4
|
+
class Peer
|
5
|
+
# give access to the session object (ffi) that is used by the peer
|
6
|
+
# @api private
|
7
|
+
attr_accessor :session
|
8
|
+
|
9
|
+
# Initalize a peer for authentication (either a client or server)
|
10
|
+
# @param [FFI::MemoryPointer] context the pointer to the context used
|
11
|
+
# @param [String] mechanism_name the name of the mechanism to use
|
12
|
+
# @param [Symbol] type either (:server or :client)
|
13
|
+
def initialize(context, mechanism_name, type = :server)
|
14
|
+
@context = context
|
15
|
+
peer = FFI::MemoryPointer.new :pointer
|
16
|
+
result = nil
|
17
|
+
case type
|
18
|
+
when :client
|
19
|
+
result = Gsasl.gsasl_client_start @context, mechanism_name, peer
|
20
|
+
else
|
21
|
+
result = Gsasl.gsasl_server_start @context, mechanism_name, peer
|
22
|
+
end
|
23
|
+
Gsasl.raise_error!(result)
|
24
|
+
ensure
|
25
|
+
@session = peer.get_pointer(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Sets a property for this peer.
|
29
|
+
# @param [Fixnum] property on of the Gsasl API Keys
|
30
|
+
# @param [String] value the value tho set for the api key
|
31
|
+
# @example
|
32
|
+
# peer[Gsasl::GSASL_PASSWORD] = "secret"
|
33
|
+
def []=(property, value)
|
34
|
+
Gsasl.gsasl_property_set(@session, property, value)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reads a property from the peer.
|
38
|
+
# @param [Fixnum] property on of the Gsasl API Keys
|
39
|
+
# @return [String, nil] The value if there is one or nil
|
40
|
+
# @example
|
41
|
+
# peer[Gsasl::GSASL_AUTHID] #=> "joe"
|
42
|
+
def [](property)
|
43
|
+
Gsasl.gsasl_property_get(@session, property)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Registers a callback for the peer. In case a variable is not provided.
|
47
|
+
# @yield [property] The callback that will be calles during the processing.
|
48
|
+
# @yieldparam [Fixnum] property a property for the
|
49
|
+
# @yieldreturn [Fixnum, nil] The return code for the callback or nil
|
50
|
+
def callback(&block)
|
51
|
+
@callback = block
|
52
|
+
end
|
53
|
+
|
54
|
+
# Used as a server hook in the local test environment.
|
55
|
+
# @return [Array<Fixnum, String>] Result code and base64 encoded challenge
|
56
|
+
def read #b64_str
|
57
|
+
process
|
58
|
+
end
|
59
|
+
|
60
|
+
# Used as a server hook in the local test environment.
|
61
|
+
# @param [String] b64_str Base64 encoded challenge
|
62
|
+
# @return [Array<Fixnum, String>] Result code and base64 encoded challenge
|
63
|
+
def send(b64_str)
|
64
|
+
process b64_str
|
65
|
+
end
|
66
|
+
|
67
|
+
# Authenticates against a server that implemennts read and send.
|
68
|
+
# @param [Gsasl::Peer] server a server peer object
|
69
|
+
# @return [Boolean] true if the authentication was successfull,
|
70
|
+
# false otherwise
|
71
|
+
def authenticate(server)
|
72
|
+
result = -1
|
73
|
+
|
74
|
+
begin
|
75
|
+
result, input = server.read
|
76
|
+
result, output = process input
|
77
|
+
|
78
|
+
if (result == GSASL_NEEDS_MORE || result == GSASL_OK)
|
79
|
+
result, output = server.send(output)
|
80
|
+
end
|
81
|
+
end while result == GSASL_NEEDS_MORE
|
82
|
+
|
83
|
+
Gsasl.raise_error!(result) unless Gsasl::GSASL_AUTHENTICATION_ERROR
|
84
|
+
result == Gsasl::GSASL_OK
|
85
|
+
end
|
86
|
+
|
87
|
+
# Close the authentication peer. This should be done after one
|
88
|
+
# authenticaion.
|
89
|
+
def close
|
90
|
+
Gsasl.gsasl_finish(@session)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Process a challenge on the peer. There might be no input at the start.
|
94
|
+
# @param [String] input the inital challenge.
|
95
|
+
# @return [Array<Fixnum, String>] Result code and base64 encoded challenge
|
96
|
+
def process(input = nil)
|
97
|
+
output_ptr = FFI::MemoryPointer.new :pointer
|
98
|
+
result = Gsasl.gsasl_step64(@session, input, output_ptr)
|
99
|
+
if result == GSASL_NEEDS_MORE || result == GSASL_OK
|
100
|
+
output = output_ptr.get_pointer(0)
|
101
|
+
[result, output.read_string.to_s]
|
102
|
+
else
|
103
|
+
[result, nil]
|
104
|
+
end
|
105
|
+
ensure
|
106
|
+
Gsasl.gsasl_free(output)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Call the callback of the peer.
|
110
|
+
# @param [Fixnum] property the api key.
|
111
|
+
def call(property)
|
112
|
+
@callback.call(property) if @callback
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/gsasl.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Authentications" do
|
4
|
+
before(:each) do
|
5
|
+
@session = Gsasl::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "CRAM-MD5" do
|
9
|
+
before(:each) do
|
10
|
+
@client = @session.create_client("CRAM-MD5")
|
11
|
+
@server = @session.create_server("CRAM-MD5")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to authenticate correctly" do
|
15
|
+
@server.callback do |property|
|
16
|
+
if property == Gsasl::GSASL_PASSWORD
|
17
|
+
if @server[Gsasl::GSASL_AUTHID] == "joe"
|
18
|
+
@server[Gsasl::GSASL_PASSWORD] = "secret"
|
19
|
+
end
|
20
|
+
Gsasl::GSASL_OK
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@client[Gsasl::GSASL_AUTHID] = "joe"
|
25
|
+
@client[Gsasl::GSASL_PASSWORD] = "secret"
|
26
|
+
|
27
|
+
@client.authenticate(@server).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should be possible to not authenticate correctly" do
|
31
|
+
@server.callback do |property|
|
32
|
+
if property == Gsasl::GSASL_PASSWORD
|
33
|
+
if @server[Gsasl::GSASL_AUTHID] == "joe"
|
34
|
+
@server[Gsasl::GSASL_PASSWORD] = "test"
|
35
|
+
end
|
36
|
+
Gsasl::GSASL_OK
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@client[Gsasl::GSASL_AUTHID] = "joe"
|
41
|
+
@client[Gsasl::GSASL_PASSWORD] = "secret"
|
42
|
+
|
43
|
+
@client.authenticate(@server).should be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
after(:each) do
|
47
|
+
@client.close
|
48
|
+
@server.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
after(:each) do
|
53
|
+
@session.close
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Gsasl::Context do
|
4
|
+
before(:each) do
|
5
|
+
@session = Gsasl::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be possible to get the version number of gsasl" do
|
9
|
+
@session.version.should =~ /\d.\d.\d/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be possible to check the version" do
|
13
|
+
@session.version("1.7.0").should == nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be possible to check the support for client authentications" do
|
17
|
+
@session.client_support_for?("CRAM-MD5").should be_true
|
18
|
+
@session.client_support_for?("NONE").should be_false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be possible to check the support for server authentications" do
|
22
|
+
@session.server_support_for?("CRAM-MD5").should be_true
|
23
|
+
@session.server_support_for?("NONE").should be_false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should return the supported server mechanisms as list" do
|
27
|
+
@session.server_mechanisms.should include("CRAM-MD5")
|
28
|
+
@session.server_mechanisms.should include("DIGEST-MD5")
|
29
|
+
@session.server_mechanisms.should include("PLAIN")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return the supported client mechanisms as list" do
|
33
|
+
@session.client_mechanisms.should include("CRAM-MD5")
|
34
|
+
@session.client_mechanisms.should include("DIGEST-MD5")
|
35
|
+
@session.client_mechanisms.should include("PLAIN")
|
36
|
+
end
|
37
|
+
|
38
|
+
after(:each) do
|
39
|
+
@session.close
|
40
|
+
end
|
41
|
+
end
|
data/spec/peer_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Gsasl::Context do
|
4
|
+
before(:each) do
|
5
|
+
@session = Gsasl::Context.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be possible to create a client peer" do
|
9
|
+
@session.create_client("CRAM-MD5")
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with peer" do
|
13
|
+
before(:each) do
|
14
|
+
@peer = @session.create_client("CRAM-MD5")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return nil on no values" do
|
18
|
+
@peer[Gsasl::GSASL_AUTHID].should be_nil
|
19
|
+
@peer[Gsasl::GSASL_PASSWORD].should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be possible to set parameter" do
|
23
|
+
@peer[Gsasl::GSASL_AUTHID] = "joe"
|
24
|
+
@peer[Gsasl::GSASL_PASSWORD] = "secret"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be possible to read set parameter" do
|
28
|
+
@peer[Gsasl::GSASL_AUTHID] = "joe"
|
29
|
+
@peer[Gsasl::GSASL_PASSWORD] = "secret"
|
30
|
+
@peer[Gsasl::GSASL_AUTHID].should == "joe"
|
31
|
+
@peer[Gsasl::GSASL_PASSWORD].should == "secret"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should be possible to call a set callback" do
|
35
|
+
test_property = 0
|
36
|
+
@peer.callback { |property| test_property = property }
|
37
|
+
@peer.call(10)
|
38
|
+
test_property.should == 10
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
after(:each) do
|
43
|
+
@session.close
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
8
|
+
require "gsasl"
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.filter_run :focus
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gsasl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vincent Landgraf
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-25 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ffi
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: A library for doing SASL based authentication mechanisms
|
63
|
+
email:
|
64
|
+
- vilandgr@googlemail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rspec
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- gsasl.gemspec
|
75
|
+
- lib/gsasl.rb
|
76
|
+
- lib/gsasl/context.rb
|
77
|
+
- lib/gsasl/native.rb
|
78
|
+
- lib/gsasl/peer.rb
|
79
|
+
- lib/gsasl/version.rb
|
80
|
+
- spec/authentication_spec.rb
|
81
|
+
- spec/context_spec.rb
|
82
|
+
- spec/peer_spec.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: ''
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project: gsasl
|
104
|
+
rubygems_version: 1.8.19
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: A lib ffi based wrapper for lib GNU SASL
|
108
|
+
test_files:
|
109
|
+
- spec/authentication_spec.rb
|
110
|
+
- spec/context_spec.rb
|
111
|
+
- spec/peer_spec.rb
|
112
|
+
- spec/spec_helper.rb
|