kiro-ruby-sasl 0.0.4.0 → 0.0.4.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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- metadata +3 -21
- data/lib/sasl.rb +0 -5
- data/lib/sasl/anonymous.rb +0 -14
- data/lib/sasl/base.rb +0 -126
- data/lib/sasl/base64.rb +0 -32
- data/lib/sasl/digest_md5.rb +0 -432
- data/lib/sasl/gssapi.rb +0 -92
- data/lib/sasl/gssspnego.rb +0 -38
- data/lib/sasl/plain.rb +0 -14
- data/lib/sasl/socket.rb +0 -88
- data/spec/anonymous_spec.rb +0 -19
- data/spec/digest_md5_spec.rb +0 -244
- data/spec/mechanism_spec.rb +0 -65
- data/spec/plain_spec.rb +0 -39
- data/spec/socket_spec.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6120e3e4c0ff7622a3c9a1fc44b40c15a4383df
|
4
|
+
data.tar.gz: 1cd8c81fadca22b24db8c0ecbdbe4f9556864c42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33f868fd65da70183308305655ad62c0f5f35fc8c0c9493019043b2cf043f744557ed8d05564c82be104d7b27d5afc3e91e8c6d07277185ba31b3b4910fd05e8
|
7
|
+
data.tar.gz: 6eb02464ab684c0442e1c20ee325f63f78a63e56bafbd619c1764b73aa2a00718a630a8de3d231d47c50eb3c3e33769d1846b20ee010dcc9d3e6c7c26bf56e4e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kiro-ruby-sasl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.4.
|
4
|
+
version: 0.0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephan Maka
|
@@ -18,21 +18,8 @@ executables: []
|
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
+
- MIT-LICENSE
|
21
22
|
- README.markdown
|
22
|
-
- lib/sasl.rb
|
23
|
-
- lib/sasl/anonymous.rb
|
24
|
-
- lib/sasl/base.rb
|
25
|
-
- lib/sasl/base64.rb
|
26
|
-
- lib/sasl/digest_md5.rb
|
27
|
-
- lib/sasl/gssapi.rb
|
28
|
-
- lib/sasl/gssspnego.rb
|
29
|
-
- lib/sasl/plain.rb
|
30
|
-
- lib/sasl/socket.rb
|
31
|
-
- spec/anonymous_spec.rb
|
32
|
-
- spec/digest_md5_spec.rb
|
33
|
-
- spec/mechanism_spec.rb
|
34
|
-
- spec/plain_spec.rb
|
35
|
-
- spec/socket_spec.rb
|
36
23
|
homepage: http://github.com/luizluca/ruby-sasl/
|
37
24
|
licenses: []
|
38
25
|
metadata: {}
|
@@ -56,9 +43,4 @@ rubygems_version: 2.2.2
|
|
56
43
|
signing_key:
|
57
44
|
specification_version: 4
|
58
45
|
summary: SASL client library
|
59
|
-
test_files:
|
60
|
-
- spec/mechanism_spec.rb
|
61
|
-
- spec/anonymous_spec.rb
|
62
|
-
- spec/plain_spec.rb
|
63
|
-
- spec/digest_md5_spec.rb
|
64
|
-
- spec/socket_spec.rb
|
46
|
+
test_files: []
|
data/lib/sasl.rb
DELETED
data/lib/sasl/anonymous.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module SASL
|
2
|
-
##
|
3
|
-
# SASL ANONYMOUS where you only send a username that may not get
|
4
|
-
# evaluated by the server.
|
5
|
-
#
|
6
|
-
# RFC 4505:
|
7
|
-
# http://tools.ietf.org/html/rfc4505
|
8
|
-
class Anonymous < Mechanism
|
9
|
-
def start
|
10
|
-
@state = nil
|
11
|
-
['auth', preferences.username.to_s]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/lib/sasl/base.rb
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
##
|
2
|
-
# RFC 4422:
|
3
|
-
# http://tools.ietf.org/html/rfc4422
|
4
|
-
module SASL
|
5
|
-
##
|
6
|
-
# You must derive from class Preferences and overwrite methods you
|
7
|
-
# want to implement.
|
8
|
-
class Preferences
|
9
|
-
attr_reader :config
|
10
|
-
# key in config hash
|
11
|
-
# authzid: Authorization identitiy ('username@domain' in XMPP)
|
12
|
-
# realm: Realm ('domain' in XMPP)
|
13
|
-
# digest-uri: : serv-type/serv-name | serv-type/host/serv-name ('xmpp/domain' in XMPP)
|
14
|
-
# username
|
15
|
-
# has_password?
|
16
|
-
# allow_plaintext?
|
17
|
-
# password
|
18
|
-
# want_anonymous?
|
19
|
-
|
20
|
-
def initialize (config)
|
21
|
-
@config = {:has_password? => false, :allow_plaintext? => false, :want_anonymous? => false}.merge(config.dup)
|
22
|
-
end
|
23
|
-
def method_missing(sym, *args, &block)
|
24
|
-
@config.send "[]", sym, &block
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
##
|
29
|
-
# Will be raised by SASL.new_mechanism if mechanism passed to the
|
30
|
-
# constructor is not known.
|
31
|
-
class UnknownMechanism < RuntimeError
|
32
|
-
def initialize(mechanism)
|
33
|
-
@mechanism = mechanism
|
34
|
-
end
|
35
|
-
|
36
|
-
def to_s
|
37
|
-
"Unknown mechanism: #{@mechanism.inspect}"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def SASL.new(mechanisms, preferences)
|
42
|
-
best_mechanism = if preferences.want_anonymous? && mechanisms.include?('ANONYMOUS')
|
43
|
-
'ANONYMOUS'
|
44
|
-
elsif preferences.has_password?
|
45
|
-
if mechanisms.include?('DIGEST-MD5')
|
46
|
-
'DIGEST-MD5'
|
47
|
-
elsif preferences.allow_plaintext?
|
48
|
-
'PLAIN'
|
49
|
-
else
|
50
|
-
raise UnknownMechanism.new(mechanisms)
|
51
|
-
end
|
52
|
-
else
|
53
|
-
raise UnknownMechanism.new(mechanisms)
|
54
|
-
end
|
55
|
-
new_mechanism(best_mechanism, preferences)
|
56
|
-
end
|
57
|
-
|
58
|
-
##
|
59
|
-
# Create a SASL Mechanism for the named mechanism
|
60
|
-
#
|
61
|
-
# mechanism:: [String] mechanism name
|
62
|
-
def SASL.new_mechanism(mechanism, preferences)
|
63
|
-
mechanism_class = case mechanism
|
64
|
-
when 'DIGEST-MD5'
|
65
|
-
DigestMD5
|
66
|
-
when 'PLAIN'
|
67
|
-
Plain
|
68
|
-
when 'ANONYMOUS'
|
69
|
-
Anonymous
|
70
|
-
when 'GSS-SPNEGO'
|
71
|
-
GssSpnego
|
72
|
-
when 'GSSAPI'
|
73
|
-
GssApi
|
74
|
-
else
|
75
|
-
raise UnknownMechanism.new(mechanism)
|
76
|
-
end
|
77
|
-
mechanism_class.new(mechanism, preferences)
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
class AbstractMethod < Exception # :nodoc:
|
82
|
-
def to_s
|
83
|
-
"Abstract method is not implemented"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
##
|
88
|
-
# Common functions for mechanisms
|
89
|
-
#
|
90
|
-
# Mechanisms implement handling of methods start and receive. They
|
91
|
-
# return: [message_name, content] or nil where message_name is
|
92
|
-
# either 'auth' or 'response' and content is either a string which
|
93
|
-
# may transmitted encoded as Base64 or nil.
|
94
|
-
class Mechanism
|
95
|
-
attr_reader :mechanism
|
96
|
-
attr_reader :preferences
|
97
|
-
|
98
|
-
def initialize(mechanism, preferences)
|
99
|
-
@mechanism = mechanism
|
100
|
-
@preferences = preferences
|
101
|
-
@state = nil
|
102
|
-
end
|
103
|
-
|
104
|
-
def success?
|
105
|
-
@state == :success
|
106
|
-
end
|
107
|
-
def failure?
|
108
|
-
@state == :failure
|
109
|
-
end
|
110
|
-
|
111
|
-
def start
|
112
|
-
raise AbstractMethod
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
def receive(message_name, content)
|
117
|
-
case message_name
|
118
|
-
when 'success'
|
119
|
-
@state = :success
|
120
|
-
when 'failure'
|
121
|
-
@state = :failure
|
122
|
-
end
|
123
|
-
nil
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
data/lib/sasl/base64.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
# =XMPP4R - XMPP Library for Ruby
|
2
|
-
# License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
|
3
|
-
# Website::http://home.gna.org/xmpp4r/
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'base64'
|
7
|
-
rescue LoadError
|
8
|
-
##
|
9
|
-
# Ruby 1.9 has dropped the Base64 module,
|
10
|
-
# this is a replacement
|
11
|
-
#
|
12
|
-
# We could replace all call by Array#pack('m')
|
13
|
-
# and String#unpack('m'), but this module
|
14
|
-
# improves readability.
|
15
|
-
module Base64
|
16
|
-
##
|
17
|
-
# Encode a String
|
18
|
-
# data:: [String] Binary
|
19
|
-
# result:: [String] Binary in Base64
|
20
|
-
def self.encode64(data)
|
21
|
-
[data].pack('m')
|
22
|
-
end
|
23
|
-
|
24
|
-
##
|
25
|
-
# Decode a Base64-encoded String
|
26
|
-
# data64:: [String] Binary in Base64
|
27
|
-
# result:: [String] Binary
|
28
|
-
def self.decode64(data64)
|
29
|
-
data64.unpack('m').first
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/sasl/digest_md5.rb
DELETED
@@ -1,432 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
|
3
|
-
module SASL
|
4
|
-
##
|
5
|
-
# RFC 2831:
|
6
|
-
# http://tools.ietf.org/html/rfc2831
|
7
|
-
class DigestMD5 < Mechanism
|
8
|
-
begin
|
9
|
-
require 'openssl'
|
10
|
-
##
|
11
|
-
# Set to +true+ if OpenSSL is available and LDAPS is supported.
|
12
|
-
HasOpenSSL = true
|
13
|
-
rescue LoadError
|
14
|
-
# :stopdoc:
|
15
|
-
HasOpenSSL = false
|
16
|
-
# :startdoc:
|
17
|
-
end
|
18
|
-
|
19
|
-
attr_writer :cnonce
|
20
|
-
|
21
|
-
def initialize(*a)
|
22
|
-
super
|
23
|
-
@nonce_count = 0
|
24
|
-
preferences.config[:secure_layer]=false if preferences.config[:secure_layer]==nil
|
25
|
-
preferences.config[:confidentiality]=preferences.config[:secure_layer] if preferences.config[:confidentiality]==nil
|
26
|
-
preferences.config[:cipher]="rc4" if preferences.config[:confidentiality] and not preferences.config[:cipher]
|
27
|
-
|
28
|
-
if preferences.secure_layer and not HasOpenSSL
|
29
|
-
raise ":secure_layer in #{self.class} depends on Openssl"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def start
|
34
|
-
@state = nil
|
35
|
-
unless defined? @nonce
|
36
|
-
['auth', nil]
|
37
|
-
else
|
38
|
-
# reauthentication
|
39
|
-
receive('challenge', '')
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def receive(message_name, content)
|
44
|
-
case message_name
|
45
|
-
when 'challenge'
|
46
|
-
c = decode_challenge(content)
|
47
|
-
|
48
|
-
unless c['rspauth']
|
49
|
-
response = {}
|
50
|
-
if defined?(@nonce) && response['nonce'].nil?
|
51
|
-
# Could be reauth
|
52
|
-
else
|
53
|
-
# No reauth:
|
54
|
-
@nonce_count = 0
|
55
|
-
end
|
56
|
-
@nonce ||= c['nonce']
|
57
|
-
response['username'] = preferences.username
|
58
|
-
response['realm'] = c['realm'] || preferences.realm
|
59
|
-
response['nonce'] = @nonce
|
60
|
-
@cnonce = generate_nonce unless defined? @cnonce
|
61
|
-
response['cnonce'] = @cnonce
|
62
|
-
@nc = next_nc
|
63
|
-
response['nc'] = @nc
|
64
|
-
@qop="auth"
|
65
|
-
if c['qop']
|
66
|
-
c_qop = c['qop'].split(",")
|
67
|
-
else
|
68
|
-
c_qop = []
|
69
|
-
end
|
70
|
-
if preferences.secure_layer and preferences.confidentiality and c_qop.include?("auth-conf")
|
71
|
-
response['qop'] = "auth-conf"
|
72
|
-
response['cipher'] = preferences.config[:cipher]
|
73
|
-
elsif preferences.secure_layer and not preferences.confidentiality and c_qop.include?("auth-int")
|
74
|
-
response['qop'] = "auth-int"
|
75
|
-
else
|
76
|
-
response['qop'] = 'auth'
|
77
|
-
end
|
78
|
-
@cipher=response['cipher']
|
79
|
-
@qop=response['qop']
|
80
|
-
response['digest-uri'] = preferences.digest_uri
|
81
|
-
response['charset'] = 'utf-8'
|
82
|
-
@algorithm = c['algorithm'] || "md5"
|
83
|
-
response['response'] = response_value(@algorithm, response['nonce'], response['nc'], response['cnonce'], response['qop'], response['realm'])
|
84
|
-
result=['response', encode_response(response)]
|
85
|
-
else
|
86
|
-
rspauth_expected = response_value(@algorithm, @nonce, @nc, @cnonce, @qop, '')
|
87
|
-
#p :rspauth_received=>c['rspauth'], :rspauth_expected=>rspauth_expected
|
88
|
-
if c['rspauth'] == rspauth_expected
|
89
|
-
result=['response', nil]
|
90
|
-
else
|
91
|
-
# Bogus server?
|
92
|
-
@state = :failure
|
93
|
-
result=['failure', nil]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
when 'success'
|
97
|
-
result=super
|
98
|
-
if preferences.secure_layer
|
99
|
-
securelayer_wrapper = proc {|io| DigestMD5SecureLayer.new(io, @ha1, @qop=="auth-conf", @cipher) }
|
100
|
-
result=['securelayer_wrapper', securelayer_wrapper]
|
101
|
-
end
|
102
|
-
else
|
103
|
-
# No challenge? Might be success or failure
|
104
|
-
result=super
|
105
|
-
end
|
106
|
-
result
|
107
|
-
end
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
def decode_challenge(text)
|
112
|
-
challenge = {}
|
113
|
-
|
114
|
-
state = :key
|
115
|
-
key = ''
|
116
|
-
value = ''
|
117
|
-
|
118
|
-
text.scan(/./) do |ch|
|
119
|
-
if state == :key
|
120
|
-
if ch == '='
|
121
|
-
state = :value
|
122
|
-
elsif ch =~ /\S/
|
123
|
-
key += ch
|
124
|
-
end
|
125
|
-
|
126
|
-
elsif state == :value
|
127
|
-
if ch == ','
|
128
|
-
challenge[key] = value
|
129
|
-
key = ''
|
130
|
-
value = ''
|
131
|
-
state = :key
|
132
|
-
elsif ch == '"' and value == ''
|
133
|
-
state = :quote
|
134
|
-
else
|
135
|
-
value += ch
|
136
|
-
end
|
137
|
-
|
138
|
-
elsif state == :quote
|
139
|
-
if ch == '"'
|
140
|
-
state = :value
|
141
|
-
else
|
142
|
-
value += ch
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
challenge[key] = value unless key == ''
|
147
|
-
|
148
|
-
#p :decode_challenge => challenge
|
149
|
-
challenge
|
150
|
-
end
|
151
|
-
|
152
|
-
def encode_response(response)
|
153
|
-
#p :encode_response => response
|
154
|
-
response.collect do |k,v|
|
155
|
-
if ['username', 'cnonce', 'nonce', 'digest-uri', 'authzid','realm','qop'].include? k
|
156
|
-
v.sub!('\\', '\\\\')
|
157
|
-
v.sub!('"', '\\"')
|
158
|
-
"#{k}=\"#{v}\""
|
159
|
-
else
|
160
|
-
"#{k}=#{v}"
|
161
|
-
end
|
162
|
-
end.join(',')
|
163
|
-
end
|
164
|
-
|
165
|
-
def generate_nonce
|
166
|
-
nonce = ''
|
167
|
-
while nonce.length < 32
|
168
|
-
c = rand(128).chr
|
169
|
-
nonce += c if c =~ /^[a-zA-Z0-9]$/
|
170
|
-
end
|
171
|
-
nonce
|
172
|
-
end
|
173
|
-
|
174
|
-
##
|
175
|
-
# Function from RFC2831
|
176
|
-
def self.h(s); Digest::MD5.digest(s); end
|
177
|
-
def h(s) self.class.h(s); end
|
178
|
-
##
|
179
|
-
# Function from RFC2831
|
180
|
-
def self.hh(s); Digest::MD5.hexdigest(s); end
|
181
|
-
def hh(s) self.class.hh(s); end
|
182
|
-
|
183
|
-
##
|
184
|
-
# Calculate the value for the response field
|
185
|
-
def response_value(algorithm, nonce, nc, cnonce, qop, realm, a2_prefix='AUTHENTICATE')
|
186
|
-
#p :response_value => {:nonce=>nonce,
|
187
|
-
# :cnonce=>cnonce,
|
188
|
-
# :qop=>qop,
|
189
|
-
# :username=>preferences.username,
|
190
|
-
# :realm=>preferences.realm,
|
191
|
-
# :password=>preferences.password,
|
192
|
-
# :authzid=>preferences.authzid}
|
193
|
-
a1 = "#{preferences.username}:#{realm}:#{preferences.password}"
|
194
|
-
if algorithm.downcase == "md5-sess"
|
195
|
-
a1 = "#{h(a1)}:#{nonce}:#{cnonce}"
|
196
|
-
end
|
197
|
-
|
198
|
-
if preferences.authzid
|
199
|
-
a1 += ":#{preferences.authzid}"
|
200
|
-
end
|
201
|
-
@ha1=h(a1)
|
202
|
-
|
203
|
-
a2="#{a2_prefix}:#{preferences.digest_uri}"
|
204
|
-
|
205
|
-
qop = "missing" if not qop
|
206
|
-
|
207
|
-
case qop.downcase
|
208
|
-
when "auth-int", "auth-conf"
|
209
|
-
a2 = "#{a2}:00000000000000000000000000000000"
|
210
|
-
end
|
211
|
-
|
212
|
-
case qop.downcase
|
213
|
-
when "auth", "auth-int", "auth-conf"
|
214
|
-
hh("#{hh(a1)}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{hh(a2)}")
|
215
|
-
when "missing"
|
216
|
-
hh("#{hh(a1)}:#{nonce}:#{hh(a2)}")
|
217
|
-
else
|
218
|
-
raise "Unknown qop=#{qop}"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def next_nc
|
223
|
-
@nonce_count += 1
|
224
|
-
s = @nonce_count.to_s
|
225
|
-
s = "0#{s}" while s.length < 8
|
226
|
-
s
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
class DigestMD5SecureLayer < SecureLayer
|
231
|
-
class DigestMD5SecureLayerError < StandardError; end
|
232
|
-
|
233
|
-
DIGEST_SESSKEY_MAGIC_CONS_C2S = "Digest session key to client-to-server signing key magic constant"
|
234
|
-
DIGEST_SESSKEY_MAGIC_CONS_S2C = "Digest session key to server-to-client signing key magic constant"
|
235
|
-
DIGEST_HA1_MAGIC_CONS_C2S = "Digest H(A1) to client-to-server sealing key magic constant"
|
236
|
-
DIGEST_HA1_MAGIC_CONS_S2C = "Digest H(A1) to server-to-client sealing key magic constant"
|
237
|
-
ONE = [1].pack("n")
|
238
|
-
|
239
|
-
# DES does not use the last bit
|
240
|
-
def self.des_key(key)
|
241
|
-
key=key.bytes.to_a
|
242
|
-
(0..(key.size)).map {|i|
|
243
|
-
left = (i>=1 ? ((key[i-1]<<(8-i))%256) : 0)
|
244
|
-
right = (i<key.size ? (key[i]>>i) : 0)
|
245
|
-
(left | right).chr
|
246
|
-
}.join
|
247
|
-
end
|
248
|
-
|
249
|
-
def initialize(io, ha1, confidentiality, cipher, is_server=false)
|
250
|
-
super(io)
|
251
|
-
@localseq=0
|
252
|
-
@remoteseq=0
|
253
|
-
|
254
|
-
@confidentiality=confidentiality
|
255
|
-
|
256
|
-
if is_server
|
257
|
-
@ki_send=self.class.kis(ha1)
|
258
|
-
@ki_recv=self.class.kic(ha1)
|
259
|
-
else
|
260
|
-
@ki_send=self.class.kic(ha1)
|
261
|
-
@ki_recv=self.class.kis(ha1)
|
262
|
-
end
|
263
|
-
|
264
|
-
if @confidentiality
|
265
|
-
cipher.downcase!
|
266
|
-
|
267
|
-
# adapt openssl 3des name
|
268
|
-
ssl_cipher=cipher
|
269
|
-
key_len=nil
|
270
|
-
case cipher
|
271
|
-
when "des"
|
272
|
-
ssl_cipher="des-cbc"
|
273
|
-
when "3des"
|
274
|
-
ssl_cipher="des-ede-cbc"
|
275
|
-
when /rc4-[0-9]*/
|
276
|
-
key_bits=cipher.split("-").last.to_i
|
277
|
-
raise "Non 8-bit multiple for key size: #{key_bits}" if not key_bits%8 == 0
|
278
|
-
key_len=key_bits/8
|
279
|
-
ssl_cipher="rc4"
|
280
|
-
end
|
281
|
-
|
282
|
-
@enc=OpenSSL::Cipher.new(ssl_cipher).encrypt
|
283
|
-
@dec=OpenSSL::Cipher.new(ssl_cipher).decrypt
|
284
|
-
|
285
|
-
# Force keylen size for rc4-* that is not rc-40 or rc4. Does it work?
|
286
|
-
[@enc,@dec].each {|cp| cp.key_len = key_len } if key_len
|
287
|
-
|
288
|
-
case cipher
|
289
|
-
# For cipher "rc4-40" n is 5;
|
290
|
-
when "rc4-40"
|
291
|
-
n=5
|
292
|
-
# for "rc4-56" n is 7;
|
293
|
-
when "rc4-56"
|
294
|
-
n=7
|
295
|
-
# for the rest n is 16
|
296
|
-
else
|
297
|
-
n=16
|
298
|
-
end
|
299
|
-
|
300
|
-
if is_server
|
301
|
-
@kc_send=self.class.kcs(ha1, n)
|
302
|
-
@kc_recv=self.class.kcc(ha1, n)
|
303
|
-
else
|
304
|
-
@kc_send=self.class.kcc(ha1, n)
|
305
|
-
@kc_recv=self.class.kcs(ha1, n)
|
306
|
-
end
|
307
|
-
|
308
|
-
# The key for the "rc-*" ciphers is all 16 bytes of Kcc or Kcs
|
309
|
-
case cipher
|
310
|
-
when /rc.*/
|
311
|
-
key_len=16
|
312
|
-
iv_len=0
|
313
|
-
# the key for "des" is the first 7 bytes
|
314
|
-
when "des"
|
315
|
-
key_len=7
|
316
|
-
iv_len=8
|
317
|
-
when "3des"
|
318
|
-
key_len=14
|
319
|
-
iv_len=8
|
320
|
-
end
|
321
|
-
|
322
|
-
kc_send=@kc_send[0,key_len]
|
323
|
-
kc_recv=@kc_recv[0,key_len]
|
324
|
-
|
325
|
-
case cipher
|
326
|
-
when "des"
|
327
|
-
# (8 bit * 7 bytes) key must be expanded to (7-bit * 8 bytes),
|
328
|
-
# skipping last bit
|
329
|
-
kc_send=self.class.des_key(kc_send)
|
330
|
-
kc_recv=self.class.des_key(kc_recv)
|
331
|
-
key_len = 8
|
332
|
-
# DES does not use padding here
|
333
|
-
[@enc,@dec].each {|cp| cp.padding=0 }
|
334
|
-
when "3des"
|
335
|
-
# (8 bit * 7 bytes) key must be expanded to (7-bit * 8 bytes),
|
336
|
-
# skipping last bit
|
337
|
-
kc_send=self.class.des_key(kc_send[0,7])+self.class.des_key(kc_send[7,7])
|
338
|
-
kc_recv=self.class.des_key(kc_recv[0,7])+self.class.des_key(kc_recv[7,7])
|
339
|
-
key_len = 16
|
340
|
-
# 3DES does not use padding here
|
341
|
-
[@enc,@dec].each {|cp| cp.padding=0 }
|
342
|
-
end
|
343
|
-
|
344
|
-
[@enc,@dec].each {|cp| cp.key_len = key_len } if key_len
|
345
|
-
|
346
|
-
@enc.key=kc_send
|
347
|
-
@enc.iv=@kc_send[-iv_len,iv_len] if iv_len >0
|
348
|
-
@dec.key=kc_recv
|
349
|
-
@dec.iv=@kc_recv[-iv_len,iv_len] if iv_len >0
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def hm(ki, msg)
|
354
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('md5'), ki, msg)
|
355
|
-
end
|
356
|
-
|
357
|
-
def mac(ki, seqnum, msg)
|
358
|
-
hm(ki, (seqnum + msg))[0..9]# + ONE + seqnum
|
359
|
-
end
|
360
|
-
|
361
|
-
def wrap(msg)
|
362
|
-
seqnum=[@localseq].pack("N")
|
363
|
-
if @confidentiality
|
364
|
-
# SEAL(Ki, Kc, SeqNum, msg) = {CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg})[0..9])}), 0x0001, SeqNum}
|
365
|
-
if @enc.block_size==1
|
366
|
-
pad=""
|
367
|
-
else
|
368
|
-
pad_len = @enc.block_size - ((msg.size + 10) % @enc.block_size)
|
369
|
-
pad=pad_len.chr*pad_len
|
370
|
-
end
|
371
|
-
buf=@enc.update(msg + pad + mac(@ki_send, seqnum, msg)) + ONE + seqnum
|
372
|
-
else
|
373
|
-
#MAC(Ki, SeqNum, msg) = (HMAC(Ki, {SeqNum, msg})[0..9], 0x0001, SeqNum)
|
374
|
-
buf=msg + mac(@ki_send, seqnum, msg) + ONE + seqnum
|
375
|
-
end
|
376
|
-
@localseq+=1
|
377
|
-
buf
|
378
|
-
end
|
379
|
-
|
380
|
-
def unwrap(buf)
|
381
|
-
msg_seqnum=buf[-4..-1]
|
382
|
-
# rfc2831 does not ask to check this
|
383
|
-
#exp_seqnum=[@remoteseq].pack("N")
|
384
|
-
#raise DigestMD5SecureLayerError, "Invalid remote sequence field! expected:#{@remoteseq}, got:#{msg_seqnum.unpack("N").first}" if not msg_seqnum == exp_seqnum
|
385
|
-
|
386
|
-
msg_one=buf[-6..-5]
|
387
|
-
raise DigestMD5SecureLayerError, "Invalid one field!" if not msg_one == ONE
|
388
|
-
|
389
|
-
if @confidentiality
|
390
|
-
msg_pad_mac=@dec.update(buf[0..-7])
|
391
|
-
msg_mac=msg_pad_mac[-10..-1]
|
392
|
-
|
393
|
-
if @enc.block_size==1
|
394
|
-
msg=msg_pad_mac[0..-11]
|
395
|
-
else
|
396
|
-
pad_len=msg_pad_mac[-11].ord
|
397
|
-
raise DigestMD5SecureLayerError, "Invalid pad size. Invalid crypto? key?" if not ((1..8).include?(pad_len))
|
398
|
-
msg=msg_pad_mac[0..(-11-(pad_len))]
|
399
|
-
end
|
400
|
-
else
|
401
|
-
msg=buf[0..-17]
|
402
|
-
msg_mac=buf[-16..-7]
|
403
|
-
end
|
404
|
-
exp_mac=mac(@ki_recv, msg_seqnum, msg)
|
405
|
-
raise DigestMD5SecureLayerError, "Invalid mac field!" if not msg_mac == exp_mac
|
406
|
-
|
407
|
-
@remoteseq+=1
|
408
|
-
msg
|
409
|
-
end
|
410
|
-
|
411
|
-
# Kic = MD5({H(A1), "Digest session key to client-to-server signing key magic constant"})
|
412
|
-
def self.kic(ha1)
|
413
|
-
DigestMD5.h(ha1 + DIGEST_SESSKEY_MAGIC_CONS_C2S)
|
414
|
-
end
|
415
|
-
def self.kis(ha1)
|
416
|
-
DigestMD5.h(ha1 + DIGEST_SESSKEY_MAGIC_CONS_S2C)
|
417
|
-
end
|
418
|
-
|
419
|
-
# Kcs = MD5({H(A1)[0..n], "Digest H(A1) to server-to-client sealing key magic constant"})
|
420
|
-
# FYI: Specs do not specify what 0..n means. According to cyrus sasl code, n is length and not position.
|
421
|
-
# More like [0,n] for ruby
|
422
|
-
def self.kcc(ha1,n=16)
|
423
|
-
DigestMD5.h(ha1[0,n] + DIGEST_HA1_MAGIC_CONS_C2S)
|
424
|
-
end
|
425
|
-
def self.kcs(ha1,n=16)
|
426
|
-
DigestMD5.h(ha1[0,n] + DIGEST_HA1_MAGIC_CONS_S2C)
|
427
|
-
end
|
428
|
-
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
|
data/lib/sasl/gssapi.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
require 'sasl/socket'
|
2
|
-
|
3
|
-
module SASL
|
4
|
-
|
5
|
-
class GssApi < Mechanism
|
6
|
-
|
7
|
-
begin
|
8
|
-
# gssapi (1.1.2)
|
9
|
-
require 'gssapi'
|
10
|
-
HasGSSAPI = true
|
11
|
-
rescue LoadError
|
12
|
-
# :stopdoc:
|
13
|
-
HasGSSAPI = false
|
14
|
-
# :startdoc:
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(*args)
|
18
|
-
raise LoadError.new("You need gssapi gem in order to use this class") if not HasGSSAPI
|
19
|
-
super(*args)
|
20
|
-
preferences.config[:gss_opts] = {} if not preferences.config.include? :gss_opts
|
21
|
-
preferences.config[:secure_layer] = false if preferences.config[:secure_layer]==nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def start
|
25
|
-
@state = :authneg
|
26
|
-
(@service,@host)=preferences.digest_uri.split("/")
|
27
|
-
@cli = GSSAPI::Simple.new(@host, @service)
|
28
|
-
tok = @cli.init_context(nil, preferences.gss_opts)
|
29
|
-
['auth', tok ]
|
30
|
-
end
|
31
|
-
|
32
|
-
def receive(message_name, content)
|
33
|
-
case message_name
|
34
|
-
when 'challenge'
|
35
|
-
case @state
|
36
|
-
when :authneg
|
37
|
-
if @cli.init_context(content, preferences.gss_opts)
|
38
|
-
if false #http
|
39
|
-
@state = :waiting_result
|
40
|
-
else
|
41
|
-
@state = :ssfcap
|
42
|
-
end
|
43
|
-
else
|
44
|
-
@state = :failure
|
45
|
-
end
|
46
|
-
response = nil
|
47
|
-
when :ssfcap
|
48
|
-
ssf = @cli.unwrap_message(content)
|
49
|
-
if not ssf.size == 4
|
50
|
-
raise "token too short or long (#{ssf.size}). Should be 4."
|
51
|
-
end
|
52
|
-
|
53
|
-
if not preferences.secure_layer
|
54
|
-
# No security wanted
|
55
|
-
response = @cli.wrap_message(0)
|
56
|
-
else
|
57
|
-
response = @cli.wrap_message(ssf)
|
58
|
-
end
|
59
|
-
@state = :waiting_result
|
60
|
-
else
|
61
|
-
raise "Invalid state #{@state}. Did you called start method?"
|
62
|
-
end
|
63
|
-
result=['response', response]
|
64
|
-
when 'success'
|
65
|
-
result=super
|
66
|
-
if preferences.secure_layer
|
67
|
-
securelayer_wrapper = proc {|io| SASL::GssSecureLayer.new(io,@cli) }
|
68
|
-
result=['securelayer_wrapper', securelayer_wrapper]
|
69
|
-
end
|
70
|
-
else
|
71
|
-
result=super
|
72
|
-
end
|
73
|
-
result
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
class GssSecureLayer < SecureLayer
|
78
|
-
def initialize(io,ctx)
|
79
|
-
super(io)
|
80
|
-
@ctx=ctx
|
81
|
-
end
|
82
|
-
|
83
|
-
def wrap(buf)
|
84
|
-
@ctx.wrap_message(buf)
|
85
|
-
end
|
86
|
-
|
87
|
-
def unwrap(buf)
|
88
|
-
@ctx.unwrap_message(buf)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
data/lib/sasl/gssspnego.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module SASL
|
2
|
-
|
3
|
-
class GssSpnego < Mechanism
|
4
|
-
|
5
|
-
begin
|
6
|
-
# rubyntlm (0.3.3)
|
7
|
-
require 'net/ntlm'
|
8
|
-
HasNTLM = true
|
9
|
-
rescue LoadError
|
10
|
-
# :stopdoc:
|
11
|
-
HasNTLM = false
|
12
|
-
# :startdoc:
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(*args)
|
16
|
-
raise LoadError.new("You need rubyntlm gem in order to use this class") if not HasNTLM
|
17
|
-
super(*args)
|
18
|
-
end
|
19
|
-
|
20
|
-
def start
|
21
|
-
@state = nil
|
22
|
-
['auth', Net::NTLM::Message::Type1.new.serialize]
|
23
|
-
end
|
24
|
-
|
25
|
-
def receive(message_name, content)
|
26
|
-
if message_name == 'challenge'
|
27
|
-
t2_msg = Net::NTLM::Message.parse(content)
|
28
|
-
t3_msg = t2_msg.response({ :user => preferences.username, :password => preferences.password},
|
29
|
-
{ :ntlmv2 => true })
|
30
|
-
p message_name
|
31
|
-
['response', t3_msg.serialize]
|
32
|
-
else
|
33
|
-
super
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
data/lib/sasl/plain.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module SASL
|
2
|
-
##
|
3
|
-
# RFC 4616:
|
4
|
-
# http://tools.ietf.org/html/rfc4616
|
5
|
-
class Plain < Mechanism
|
6
|
-
def start
|
7
|
-
@state = nil
|
8
|
-
message = [preferences.authzid.to_s,
|
9
|
-
preferences.username,
|
10
|
-
preferences.password].join("\000")
|
11
|
-
['auth', message]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/lib/sasl/socket.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
module SASL
|
2
|
-
|
3
|
-
class SecureLayer
|
4
|
-
attr_reader :io
|
5
|
-
|
6
|
-
def initialize(io)
|
7
|
-
@io=io
|
8
|
-
end
|
9
|
-
|
10
|
-
def write(buf)
|
11
|
-
wbuf = wrap(buf)
|
12
|
-
@io.syswrite([wbuf.size].pack("N"))
|
13
|
-
@io.syswrite(wbuf)
|
14
|
-
end
|
15
|
-
|
16
|
-
def read
|
17
|
-
bsize=@io.sysread(4)
|
18
|
-
raise "SASL Buffer size is nil!" if bsize==nil
|
19
|
-
size=bsize.unpack("N")
|
20
|
-
buf=@io.sysread(size.first)
|
21
|
-
unwrap(buf)
|
22
|
-
end
|
23
|
-
|
24
|
-
def wrap(buf)
|
25
|
-
raise AbstractMethod
|
26
|
-
end
|
27
|
-
|
28
|
-
def unwrap(buf)
|
29
|
-
raise AbstractMethod
|
30
|
-
end
|
31
|
-
|
32
|
-
def close
|
33
|
-
@io.close
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
module Buffering
|
38
|
-
begin
|
39
|
-
require 'openssl'
|
40
|
-
HasOpenSSL = true
|
41
|
-
rescue LoadError
|
42
|
-
# :stopdoc:
|
43
|
-
HasOpenSSL = false
|
44
|
-
# :startdoc:
|
45
|
-
end
|
46
|
-
|
47
|
-
# When an object is extended
|
48
|
-
def self.extended(base)
|
49
|
-
class << base
|
50
|
-
Buffering.included(self)
|
51
|
-
end
|
52
|
-
base.initialize_buffering
|
53
|
-
end
|
54
|
-
|
55
|
-
# When a class is extended
|
56
|
-
def self.included(base)
|
57
|
-
raise LoadError.new("SASL::Buffering depends on OpenSSL::Buffering") if not HasOpenSSL
|
58
|
-
base.class_eval do
|
59
|
-
alias_method :nonbuf_read, :read
|
60
|
-
alias_method :nonbuf_write, :write
|
61
|
-
alias_method :nonbuf_close, :close
|
62
|
-
|
63
|
-
# OpenSSL::Buffering replaces initialize. I should save it
|
64
|
-
alias_method :orig_initialize, :initialize
|
65
|
-
include OpenSSL::Buffering
|
66
|
-
alias_method :initialize_buffering, :initialize
|
67
|
-
public :initialize_buffering
|
68
|
-
|
69
|
-
def initialize(*args)
|
70
|
-
orig_initialize(*args)
|
71
|
-
initialize_buffering
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def sysread(size)
|
77
|
-
nonbuf_read
|
78
|
-
end
|
79
|
-
|
80
|
-
def syswrite(buf)
|
81
|
-
nonbuf_write(buf)
|
82
|
-
end
|
83
|
-
|
84
|
-
def sysclose
|
85
|
-
nonbuf_close
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
data/spec/anonymous_spec.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'sasl'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe SASL::Anonymous do
|
5
|
-
class MyAnonymousPreferences < SASL::Preferences
|
6
|
-
def username
|
7
|
-
'bob'
|
8
|
-
end
|
9
|
-
end
|
10
|
-
preferences = MyAnonymousPreferences.new({})
|
11
|
-
|
12
|
-
it 'should authenticate anonymously' do
|
13
|
-
sasl = SASL::Anonymous.new('ANONYMOUS', preferences)
|
14
|
-
sasl.start.should == ['auth', 'bob']
|
15
|
-
sasl.success?.should == false
|
16
|
-
sasl.receive('success', nil).should == nil
|
17
|
-
sasl.success?.should == true
|
18
|
-
end
|
19
|
-
end
|
data/spec/digest_md5_spec.rb
DELETED
@@ -1,244 +0,0 @@
|
|
1
|
-
require 'sasl'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe SASL::DigestMD5 do
|
5
|
-
# Preferences from http://tools.ietf.org/html/rfc2831#section-4
|
6
|
-
class MyDigestMD5Preferences < SASL::Preferences
|
7
|
-
attr_writer :serv_type
|
8
|
-
def realm
|
9
|
-
'elwood.innosoft.com'
|
10
|
-
end
|
11
|
-
def digest_uri
|
12
|
-
"#{@serv_type}/elwood.innosoft.com"
|
13
|
-
end
|
14
|
-
def username
|
15
|
-
'chris'
|
16
|
-
end
|
17
|
-
def has_password?
|
18
|
-
true
|
19
|
-
end
|
20
|
-
def password
|
21
|
-
'secret'
|
22
|
-
end
|
23
|
-
end
|
24
|
-
preferences = MyDigestMD5Preferences.new({})
|
25
|
-
|
26
|
-
it 'should authenticate (1)' do
|
27
|
-
preferences.serv_type = 'imap'
|
28
|
-
sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
|
29
|
-
sasl.start.should == ['auth', nil]
|
30
|
-
sasl.cnonce = 'OA6MHXh6VqTrRk'
|
31
|
-
response = sasl.receive('challenge',
|
32
|
-
'realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
|
33
|
-
algorithm=md5-sess,charset=utf-8')
|
34
|
-
response[0].should == 'response'
|
35
|
-
response[1].should =~ /charset="?utf-8"?/
|
36
|
-
response[1].should =~ /username="?chris"?/
|
37
|
-
response[1].should =~ /realm="?elwood.innosoft.com"?/
|
38
|
-
response[1].should =~ /nonce="?OA6MG9tEQGm2hh"?/
|
39
|
-
response[1].should =~ /nc="?00000001"?/
|
40
|
-
response[1].should =~ /cnonce="?OA6MHXh6VqTrRk"?/
|
41
|
-
response[1].should =~ /digest-uri="?imap\/elwood.innosoft.com"?/
|
42
|
-
response[1].should =~ /response=d388dad90d4bbd760a152321f2143af7"?/
|
43
|
-
response[1].should =~ /"?qop="?auth"?/
|
44
|
-
|
45
|
-
sasl.receive('challenge',
|
46
|
-
'rspauth=ea40f60335c427b5527b84dbabcdfffd').should ==
|
47
|
-
['response', nil]
|
48
|
-
sasl.receive('success', nil).should == nil
|
49
|
-
sasl.success?.should == true
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should authenticate (2)' do
|
53
|
-
preferences.serv_type = 'acap'
|
54
|
-
sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
|
55
|
-
sasl.start.should == ['auth', nil]
|
56
|
-
sasl.cnonce = 'OA9BSuZWMSpW8m'
|
57
|
-
response = sasl.receive('challenge',
|
58
|
-
'realm="elwood.innosoft.com",nonce="OA9BSXrbuRhWay",qop="auth",
|
59
|
-
algorithm=md5-sess,charset=utf-8')
|
60
|
-
response[0].should == 'response'
|
61
|
-
response[1].should =~ /charset="?utf-8"?/
|
62
|
-
response[1].should =~ /username="?chris"?/
|
63
|
-
response[1].should =~ /realm="?elwood.innosoft.com"?/
|
64
|
-
response[1].should =~ /nonce="?OA9BSXrbuRhWay"?/
|
65
|
-
response[1].should =~ /nc="?00000001"?/
|
66
|
-
response[1].should =~ /cnonce="?OA9BSuZWMSpW8m"?/
|
67
|
-
response[1].should =~ /digest-uri="?acap\/elwood.innosoft.com"?/
|
68
|
-
response[1].should =~ /response=6084c6db3fede7352c551284490fd0fc"?/
|
69
|
-
response[1].should =~ /"?qop="?auth"?/
|
70
|
-
|
71
|
-
sasl.receive('challenge',
|
72
|
-
'rspauth=2f0b3d7c3c2e486600ef710726aa2eae').should ==
|
73
|
-
['response', nil]
|
74
|
-
sasl.receive('success', nil).should == nil
|
75
|
-
sasl.success?.should == true
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'should reauthenticate' do
|
79
|
-
preferences.serv_type = 'imap'
|
80
|
-
sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
|
81
|
-
sasl.start.should == ['auth', nil]
|
82
|
-
sasl.cnonce = 'OA6MHXh6VqTrRk'
|
83
|
-
sasl.receive('challenge',
|
84
|
-
'realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
|
85
|
-
algorithm=md5-sess,charset=utf-8')
|
86
|
-
# reauth:
|
87
|
-
response = sasl.start
|
88
|
-
response[0].should == 'response'
|
89
|
-
response[1].should =~ /charset="?utf-8"?/
|
90
|
-
response[1].should =~ /username="?chris"?/
|
91
|
-
response[1].should =~ /realm="?elwood.innosoft.com"?/
|
92
|
-
response[1].should =~ /nonce="?OA6MG9tEQGm2hh"?/
|
93
|
-
response[1].should =~ /nc="?00000002"?/
|
94
|
-
response[1].should =~ /cnonce="?OA6MHXh6VqTrRk"?/
|
95
|
-
response[1].should =~ /digest-uri="?imap\/elwood.innosoft.com"?/
|
96
|
-
response[1].should =~ /response=b0b5d72a400655b8306e434566b10efb"?/ # my own result
|
97
|
-
response[1].should =~ /"?qop="?auth"?/
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'should fail' do
|
101
|
-
sasl = SASL::DigestMD5.new('DIGEST-MD5', preferences)
|
102
|
-
sasl.start.should == ['auth', nil]
|
103
|
-
sasl.receive('failure', 'EPIC FAIL')
|
104
|
-
sasl.failure?.should == true
|
105
|
-
sasl.success?.should == false
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe SASL::DigestMD5SecureLayer do
|
110
|
-
HA1="1234567890ABCDEF" if not defined? HA1
|
111
|
-
MSG="plaintext" if not defined? MSG
|
112
|
-
|
113
|
-
it 'DigestMD5SecureLayer.kic should generate correct KIC' do
|
114
|
-
result = SASL::DigestMD5SecureLayer.kic(HA1)
|
115
|
-
expected = "\xC6 6/\xFD\xD5\x0F\xF6E7=\x82Y\x16\r\x03"
|
116
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'DigestMD5SecureLayer.kcc should generate correct KIS' do
|
120
|
-
result = SASL::DigestMD5SecureLayer.kis(HA1)
|
121
|
-
expected = "\x10\xBED)v\x1E#I\xA5\xF8RR\xF4\x80\t_"
|
122
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'DigestMD5SecureLayer.kcc should generate correct KCC (n=16)' do
|
126
|
-
result = SASL::DigestMD5SecureLayer.kcc(HA1,16)
|
127
|
-
expected = "a\xA2\xF5\x9C\xD2g\x85\xF3\eOZ\x10^\xF9\x97v"
|
128
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'DigestMD5SecureLayer.kcc should generate correct KCC (n=7)' do
|
132
|
-
result = SASL::DigestMD5SecureLayer.kcc(HA1,7)
|
133
|
-
expected = "E{\xC1\xE4\x14W\x12\xE4\x88d=\xA5\xFCY5M"
|
134
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
135
|
-
end
|
136
|
-
|
137
|
-
it 'DigestMD5SecureLayer.kcc should generate correct KCC (n=5)' do
|
138
|
-
result = SASL::DigestMD5SecureLayer.kcc(HA1,5)
|
139
|
-
expected = "\xD3\xBCK\x86\xF4\xC1\xEF\xA9\xAE\xCC\xB9K\xA4KJ\x99"
|
140
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
141
|
-
end
|
142
|
-
|
143
|
-
it 'DigestMD5SecureLayer.kcs should generate correct KCS (n=5)' do
|
144
|
-
result = SASL::DigestMD5SecureLayer.kcs(HA1,16)
|
145
|
-
expected = "\x18g\xDA\xD2\x99\xC41\xD3\x11\x0E\x8B\xA2\xAC&\x82\xA3"
|
146
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
147
|
-
end
|
148
|
-
|
149
|
-
###################
|
150
|
-
# Integrity tests
|
151
|
-
###################
|
152
|
-
function="DigestMD5SecureLayer.wrap (integrity mode, client)"
|
153
|
-
io=StringIO.new("", "w")
|
154
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, false)
|
155
|
-
dg.write(MSG)
|
156
|
-
buf=io.string
|
157
|
-
|
158
|
-
# Client mode (write)
|
159
|
-
it 'should generate correct bufsize' do
|
160
|
-
result = buf[0,4]
|
161
|
-
expected = "\x00\x00\x00\x19"
|
162
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
163
|
-
end
|
164
|
-
# I should tested this twice to see if it increments
|
165
|
-
it 'should generate correct seq number' do
|
166
|
-
result = buf[-4,4]
|
167
|
-
expected = "\x00\x00\x00\x00"
|
168
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
169
|
-
end
|
170
|
-
it 'should generate one number' do
|
171
|
-
result = buf[-6,2]
|
172
|
-
expected = "\x00\x01"
|
173
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
174
|
-
end
|
175
|
-
it 'should generate correct MAC field ' do
|
176
|
-
result = buf[-16,10]
|
177
|
-
expected = "b\xD6\xD1#\xCF\xCE7\x97\x1D\xD4"
|
178
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
179
|
-
end
|
180
|
-
it 'should preserve original msg content' do
|
181
|
-
result = buf[4,9]
|
182
|
-
expected = MSG
|
183
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
184
|
-
end
|
185
|
-
|
186
|
-
# Server mode (read)
|
187
|
-
it 'should return only the original msg content' do
|
188
|
-
io=StringIO.new(buf, "r")
|
189
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, true)
|
190
|
-
result = dg.read
|
191
|
-
expected = MSG
|
192
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
193
|
-
end
|
194
|
-
it 'should raise exception when msgsize is changed' do
|
195
|
-
buf_def=buf.clone
|
196
|
-
buf_def[4]="\x18"
|
197
|
-
io=StringIO.new(buf_def, "r")
|
198
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, true)
|
199
|
-
expect { dg.read }.to raise_error SASL::DigestMD5SecureLayer::DigestMD5SecureLayerError
|
200
|
-
end
|
201
|
-
it 'should raise exception when one field is missing' do
|
202
|
-
buf_def=buf.clone
|
203
|
-
buf_def[-5]="\x02"
|
204
|
-
io=StringIO.new(buf_def, "r")
|
205
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, true)
|
206
|
-
expect { dg.read }.to raise_error SASL::DigestMD5SecureLayer::DigestMD5SecureLayerError
|
207
|
-
end
|
208
|
-
it 'should raise exception when MAC is changed' do
|
209
|
-
buf_def=buf.clone
|
210
|
-
buf_def[-16]="\x0F"
|
211
|
-
io=StringIO.new(buf_def, "r")
|
212
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, true)
|
213
|
-
expect { dg.read }.to raise_error SASL::DigestMD5SecureLayer::DigestMD5SecureLayerError
|
214
|
-
end
|
215
|
-
it 'should raise exception when msg content is changed' do
|
216
|
-
buf_def=buf.clone
|
217
|
-
buf_def[4]="P"
|
218
|
-
io=StringIO.new(buf_def, "r")
|
219
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, false, nil, true)
|
220
|
-
expect { dg.read }.to raise_error SASL::DigestMD5SecureLayer::DigestMD5SecureLayerError
|
221
|
-
end
|
222
|
-
|
223
|
-
###########################
|
224
|
-
# Confidentiality tests
|
225
|
-
###########################
|
226
|
-
["rc4","rc4-40","rc4-56","des","3des"].each do
|
227
|
-
|cipher|
|
228
|
-
it "should encrypt and decrypt messages with #{cipher} cipher" do
|
229
|
-
# client
|
230
|
-
io=StringIO.new("", "w")
|
231
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, true, cipher, false)
|
232
|
-
dg.write(MSG)
|
233
|
-
buf=io.string
|
234
|
-
|
235
|
-
# server
|
236
|
-
io=StringIO.new(buf, "r")
|
237
|
-
dg=SASL::DigestMD5SecureLayer.new(io, HA1, true, cipher, true)
|
238
|
-
result = dg.read
|
239
|
-
expected = MSG
|
240
|
-
expected.bytes.to_a.should == result.bytes.to_a
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
data/spec/mechanism_spec.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'sasl'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe SASL do
|
5
|
-
it 'should know DIGEST-MD5' do
|
6
|
-
sasl = SASL.new_mechanism('DIGEST-MD5', SASL::Preferences.new({}))
|
7
|
-
sasl.should be_an_instance_of SASL::DigestMD5
|
8
|
-
end
|
9
|
-
it 'should know PLAIN' do
|
10
|
-
sasl = SASL.new_mechanism('PLAIN', SASL::Preferences.new({}))
|
11
|
-
sasl.should be_an_instance_of SASL::Plain
|
12
|
-
end
|
13
|
-
it 'should know ANONYMOUS' do
|
14
|
-
sasl = SASL.new_mechanism('ANONYMOUS', SASL::Preferences.new({}))
|
15
|
-
sasl.should be_an_instance_of SASL::Anonymous
|
16
|
-
end
|
17
|
-
it 'should know GSSAPI' do
|
18
|
-
sasl = SASL.new_mechanism('GSSAPI', SASL::Preferences.new({}))
|
19
|
-
sasl.should be_an_instance_of SASL::GssApi
|
20
|
-
end
|
21
|
-
it 'should know GSS-SPNEGO' do
|
22
|
-
sasl = SASL.new_mechanism('GSS-SPNEGO', SASL::Preferences.new({}))
|
23
|
-
sasl.should be_an_instance_of SASL::GssSpnego
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'should choose ANONYMOUS' do
|
27
|
-
preferences = SASL::Preferences.new({})
|
28
|
-
class << preferences
|
29
|
-
def want_anonymous?
|
30
|
-
true
|
31
|
-
end
|
32
|
-
end
|
33
|
-
SASL.new(%w(PLAIN DIGEST-MD5 ANONYMOUS), preferences).should be_an_instance_of SASL::Anonymous
|
34
|
-
end
|
35
|
-
it 'should choose DIGEST-MD5' do
|
36
|
-
preferences = SASL::Preferences.new({})
|
37
|
-
class << preferences
|
38
|
-
def has_password?
|
39
|
-
true
|
40
|
-
end
|
41
|
-
end
|
42
|
-
SASL.new(%w(PLAIN DIGEST-MD5 ANONYMOUS), preferences).should be_an_instance_of SASL::DigestMD5
|
43
|
-
end
|
44
|
-
it 'should choose PLAIN' do
|
45
|
-
preferences = SASL::Preferences.new({})
|
46
|
-
class << preferences
|
47
|
-
def has_password?
|
48
|
-
true
|
49
|
-
end
|
50
|
-
def allow_plaintext?
|
51
|
-
true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
SASL.new(%w(PLAIN ANONYMOUS), preferences).should be_an_instance_of SASL::Plain
|
55
|
-
end
|
56
|
-
it 'should disallow PLAIN by default' do
|
57
|
-
preferences = SASL::Preferences.new({})
|
58
|
-
class << preferences
|
59
|
-
def has_password?
|
60
|
-
true
|
61
|
-
end
|
62
|
-
end
|
63
|
-
lambda { SASL.new(%w(PLAIN ANONYMOUS), preferences) }.should raise_error(SASL::UnknownMechanism)
|
64
|
-
end
|
65
|
-
end
|
data/spec/plain_spec.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'sasl'
|
2
|
-
require 'rspec'
|
3
|
-
|
4
|
-
describe SASL::Plain do
|
5
|
-
class MyPlainPreferences < SASL::Preferences
|
6
|
-
def authzid
|
7
|
-
'bob@example.com'
|
8
|
-
end
|
9
|
-
def username
|
10
|
-
'bob'
|
11
|
-
end
|
12
|
-
def has_password?
|
13
|
-
true
|
14
|
-
end
|
15
|
-
def password
|
16
|
-
's3cr3t'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
preferences = MyPlainPreferences.new({})
|
20
|
-
|
21
|
-
it 'should authenticate' do
|
22
|
-
sasl = SASL::Plain.new('PLAIN', preferences)
|
23
|
-
sasl.start.should == ['auth', "bob@example.com\000bob\000s3cr3t"]
|
24
|
-
sasl.success?.should == false
|
25
|
-
sasl.receive('success', nil).should == nil
|
26
|
-
sasl.failure?.should == false
|
27
|
-
sasl.success?.should == true
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should recognize failure' do
|
31
|
-
sasl = SASL::Plain.new('PLAIN', preferences)
|
32
|
-
sasl.start.should == ['auth', "bob@example.com\000bob\000s3cr3t"]
|
33
|
-
sasl.success?.should == false
|
34
|
-
sasl.failure?.should == false
|
35
|
-
sasl.receive('failure', 'keep-idiots-out').should == nil
|
36
|
-
sasl.failure?.should == true
|
37
|
-
sasl.success?.should == false
|
38
|
-
end
|
39
|
-
end
|
data/spec/socket_spec.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
require "sasl"
|
2
|
-
require 'socket'
|
3
|
-
require 'rspec'
|
4
|
-
|
5
|
-
describe SASL::SecureLayer do
|
6
|
-
|
7
|
-
class PlainSecureLayer < SASL::SecureLayer
|
8
|
-
def wrap(buf)
|
9
|
-
buf
|
10
|
-
end
|
11
|
-
def unwrap(buf)
|
12
|
-
buf
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
MSG="plaintext" if not defined? MSG
|
17
|
-
io=StringIO.new("","w")
|
18
|
-
sl=PlainSecureLayer.new(io)
|
19
|
-
sl.write(MSG)
|
20
|
-
buf=io.string
|
21
|
-
|
22
|
-
it 'should send msg with correct size' do
|
23
|
-
buf[0,4].unpack("N").first.should == MSG.size
|
24
|
-
end
|
25
|
-
it 'should send msg with the correct content' do
|
26
|
-
msg=buf[4..-1]
|
27
|
-
msg.bytes.to_a.should == MSG.bytes.to_a
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should recv msg with the correct content' do
|
31
|
-
io=StringIO.new(buf,"r")
|
32
|
-
sl=PlainSecureLayer.new(io)
|
33
|
-
msg=sl.read
|
34
|
-
msg.bytes.to_a.should == MSG.bytes.to_a
|
35
|
-
end
|
36
|
-
|
37
|
-
class PlainSecureLayerBuffered < PlainSecureLayer
|
38
|
-
include SASL::Buffering
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'should recv msg with the correct content, even when buffering' do
|
42
|
-
io=StringIO.new(buf,"r")
|
43
|
-
sl=PlainSecureLayerBuffered.new(io)
|
44
|
-
MSG.each_char {|chr|
|
45
|
-
sl.getc.ord.should == chr.ord
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|