| Class | Jabber::SASL::DigestMD5 |
| In: |
lib/xmpp4r/sasl.rb
|
| Parent: | Base |
SASL DIGEST-MD5 authentication helper (RFC2831)
Sends the wished auth mechanism and wait for a challenge
(proceed with DigestMD5#auth)
# File lib/xmpp4r/sasl.rb, line 78
78: def initialize(stream)
79: super
80:
81: challenge = {}
82: error = nil
83: @stream.send(generate_auth('DIGEST-MD5')) { |reply|
84: if reply.name == 'challenge' and reply.namespace == NS_SASL
85: challenge = decode_challenge(reply.text)
86: else
87: error = reply.first_element(nil).name
88: end
89: true
90: }
91: raise error if error
92:
93: @nonce = challenge['nonce']
94: @realm = challenge['realm']
95: end
# File lib/xmpp4r/sasl.rb, line 144
144: def auth(password)
145: response = {}
146: response['nonce'] = @nonce
147: response['charset'] = 'utf-8'
148: response['username'] = @stream.jid.node
149: response['realm'] = @realm || @stream.jid.domain
150: response['cnonce'] = generate_nonce
151: response['nc'] = '00000001'
152: response['qop'] = 'auth'
153: response['digest-uri'] = "xmpp/#{@stream.jid.domain}"
154: response['response'] = response_value(@stream.jid.node, @stream.jid.domain, response['digest-uri'], password, @nonce, response['cnonce'], response['qop'])
155: response.each { |key,value|
156: unless %w(nc qop response charset).include? key
157: response[key] = "\"#{value}\""
158: end
159: }
160:
161: response_text = response.collect { |k,v| "#{k}=#{v}" }.join(',')
162: Jabber::debuglog("SASL DIGEST-MD5 response:\n#{response_text}")
163:
164: r = REXML::Element.new('response')
165: r.add_namespace NS_SASL
166: r.text = Base64::encode64(response_text).gsub(/\s/, '')
167:
168: success_already = false
169: error = nil
170: @stream.send(r) { |reply|
171: if reply.name == 'success'
172: success_already = true
173: elsif reply.name != 'challenge'
174: error = reply.first_element(nil).name
175: end
176: true
177: }
178:
179: return if success_already
180: raise error if error
181:
182: # TODO: check the challenge from the server
183:
184: r.text = nil
185: @stream.send(r) { |reply|
186: if reply.name != 'success'
187: error = reply.first_element(nil).name
188: end
189: true
190: }
191:
192: raise error if error
193: end
# File lib/xmpp4r/sasl.rb, line 97
97: def decode_challenge(challenge)
98: text = Base64::decode64(challenge)
99: res = {}
100:
101: state = :key
102: key = ''
103: value = ''
104:
105: text.scan(/./) do |ch|
106: if state == :key
107: if ch == '='
108: state = :value
109: else
110: key += ch
111: end
112:
113: elsif state == :value
114: if ch == ','
115: res[key] = value
116: key = ''
117: value = ''
118: state = :key
119: elsif ch == '"' and value == ''
120: state = :quote
121: else
122: value += ch
123: end
124:
125: elsif state == :quote
126: if ch == '"'
127: state = :value
128: else
129: value += ch
130: end
131: end
132: end
133: res[key] = value unless key == ''
134:
135: Jabber::debuglog("SASL DIGEST-MD5 challenge:\n#{text.inspect}\n#{res.inspect}")
136:
137: res
138: end
Function from RFC2831
# File lib/xmpp4r/sasl.rb, line 202
202: def hh(s); Digest::MD5.hexdigest(s); end
Calculate the value for the response field
# File lib/xmpp4r/sasl.rb, line 206
206: def response_value(username, realm, digest_uri, passwd, nonce, cnonce, qop)
207: a1_h = h("#{username}:#{realm}:#{passwd}")
208: a1 = "#{a1_h}:#{nonce}:#{cnonce}"
209: #a2 = "AUTHENTICATE:#{digest_uri}#{(qop == 'auth') ? '' : ':00000000000000000000000000000000'}"
210: a2 = "AUTHENTICATE:#{digest_uri}"
211:
212: hh("#{hh(a1)}:#{nonce}:00000001:#{cnonce}:#{qop}:#{hh(a2)}")
213: end