kiro-ruby-sasl 0.0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.markdown +64 -0
- data/lib/sasl/anonymous.rb +14 -0
- data/lib/sasl/base.rb +126 -0
- data/lib/sasl/base64.rb +32 -0
- data/lib/sasl/digest_md5.rb +432 -0
- data/lib/sasl/gssapi.rb +92 -0
- data/lib/sasl/gssspnego.rb +38 -0
- data/lib/sasl/plain.rb +14 -0
- data/lib/sasl/socket.rb +88 -0
- data/lib/sasl.rb +5 -0
- data/spec/anonymous_spec.rb +19 -0
- data/spec/digest_md5_spec.rb +244 -0
- data/spec/mechanism_spec.rb +65 -0
- data/spec/plain_spec.rb +39 -0
- data/spec/socket_spec.rb +49 -0
- metadata +64 -0
data/lib/sasl/gssapi.rb
ADDED
@@ -0,0 +1,92 @@
|
|
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
|
+
|
@@ -0,0 +1,38 @@
|
|
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
ADDED
@@ -0,0 +1,14 @@
|
|
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
ADDED
@@ -0,0 +1,88 @@
|
|
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/lib/sasl.rb
ADDED
@@ -0,0 +1,19 @@
|
|
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
|
@@ -0,0 +1,244 @@
|
|
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
|
@@ -0,0 +1,65 @@
|
|
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
ADDED
@@ -0,0 +1,39 @@
|
|
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
ADDED
@@ -0,0 +1,49 @@
|
|
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
|