kiro-ruby-sasl 0.0.4.0 → 0.0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|