Class Jabber::SASL::DigestMD5
In: lib/xmpp4r/sasl.rb
Parent: Base

SASL DIGEST-MD5 authentication helper (RFC2831)

Methods

auth   decode_challenge   h   hh   new   response_value  

Public Class methods

Sends the wished auth mechanism and wait for a challenge

(proceed with DigestMD5#auth)

[Source]

    # 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

Public Instance methods

  • Send a response
  • Wait for the server‘s challenge (which aren‘t checked)
  • Send a blind response to the server‘s challenge

[Source]

     # 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

[Source]

     # 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

Private Instance methods

Function from RFC2831

[Source]

     # File lib/xmpp4r/sasl.rb, line 199
199:       def h(s); Digest::MD5.digest(s); end

Function from RFC2831

[Source]

     # File lib/xmpp4r/sasl.rb, line 202
202:       def hh(s); Digest::MD5.hexdigest(s); end

Calculate the value for the response field

[Source]

     # 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

[Validate]