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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10e07b40466156392cccf8216fdb8ba1fe3a1a3a
4
- data.tar.gz: 4c112a5fb128343ff32cfce1f6896762d7abfcad
3
+ metadata.gz: d6120e3e4c0ff7622a3c9a1fc44b40c15a4383df
4
+ data.tar.gz: 1cd8c81fadca22b24db8c0ecbdbe4f9556864c42
5
5
  SHA512:
6
- metadata.gz: 3579aa92c4f44e391aa21613c1d6398f1548b495e7784da8f9972b2803e0a3836844b02c342359662e86a30437b5004e2f9a2b9aba062fad223bb0822cf6c2d4
7
- data.tar.gz: 6a7e9657313c56e2e5ce2166df38767d3bdfb800dfc3f601e459e3703d7b4234eacf43ea7c3b7ee511df9f6cc1bf46db1961420ab8eff761e98c927a25c42c01
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.0
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
@@ -1,5 +0,0 @@
1
- SASL_PATH = File.dirname(__FILE__) + "/sasl"
2
- require 'sasl/base'
3
- Dir.foreach(SASL_PATH) do |f|
4
- require "#{SASL_PATH}/#{f}" if f =~ /^[^\.].+\.rb$/ && f != 'base.rb'
5
- end
@@ -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
@@ -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
-
@@ -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
@@ -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
@@ -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
@@ -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