kiro-ruby-sasl 0.0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|