diff options
Diffstat (limited to 'sasl.rb')
-rw-r--r-- | sasl.rb | 201 |
1 files changed, 0 insertions, 201 deletions
diff --git a/sasl.rb b/sasl.rb deleted file mode 100644 index f871533..0000000 --- a/sasl.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'digest/sha1' -require 'openssl' -require 'xmpp4r/client' -require 'xmpp4r/sasl' - - -module Jabber - class Client - def auth(password) - #begin - if @stream_mechanisms.include? 'SCRAM-SHA-1' - auth_sasl SASL.new(self, 'SCRAM-SHA-1'), password - elsif @stream_mechanisms.include? 'DIGEST-MD5' - auth_sasl SASL.new(self, 'DIGEST-MD5'), password - elsif @stream_mechanisms.include? 'PLAIN' - auth_sasl SASL.new(self, 'PLAIN'), password - else - auth_nonsasl(password) - end - #rescue - # Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}") - # raise ClientAuthenticationFailure.new, $!.to_s - #end - end - end - - module SASL - ## - # Factory function to obtain a SASL helper for the specified mechanism - def SASL.new(stream, mechanism) - case mechanism - when 'SCRAM-SHA-1' - SCRAMSHA1.new(stream) - when 'DIGEST-MD5' - DigestMD5.new(stream) - when 'PLAIN' - Plain.new(stream) - when 'ANONYMOUS' - Anonymous.new(stream) - else - raise "Unknown SASL mechanism: #{mechanism}" - end - end - - ## - # SASL SCRAM-SHA1 authentication helper - class SCRAMSHA1 < Base - ## - # Sends the wished auth mechanism and wait for a challenge - # - # (proceed with SCRAMSHA1#auth) - def initialize(stream) - super - - @nonce = generate_nonce - @client_fm = "n=#{escape @stream.jid.node },r=#{@nonce}" - - challenge = {} - challenge_text = '' - error = nil - @stream.send(generate_auth('SCRAM-SHA-1', text=Base64::strict_encode64('n,,'+@client_fm))) { |reply| - if reply.name == 'challenge' and reply.namespace == NS_SASL - challenge_text = Base64::decode64(reply.text) - challenge = decode_challenge(challenge_text) - else - error = reply.first_element(nil).name - end - true - } - raise error if error - - @server_fm = challenge_text - @cnonce = challenge['r'] - @salt = Base64::decode64(challenge['s']) - @iterations = challenge['i'].to_i - - raise 'SCRAM-SHA-1 protocol error' if @cnonce[0, @nonce.length] != @nonce - end - - def decode_challenge(text) - res = {} - - state = :key - key = '' - value = '' - text.scan(/./) do |ch| - if state == :key - if ch == '=' - state = :value - else - key += ch - end - - elsif state == :value - if ch == ',' - # due to our home-made parsing of the challenge, the key could have - # leading whitespace. strip it, or that would break jabberd2 support. - key = key.strip - res[key] = value - key = '' - value = '' - state = :key - elsif ch == '"' and value == '' - state = :quote - else - value += ch - end - - elsif state == :quote - if ch == '"' - state = :value - else - value += ch - end - end - end - # due to our home-made parsing of the challenge, the key could have - # leading whitespace. strip it, or that would break jabberd2 support. - key = key.strip - res[key] = value unless key == '' - - Jabber::debuglog("SASL SCRAM-SHA-1 challenge:\n#{text}\n#{res.inspect}") - - res - end - - ## - # * Send a response - # * Wait for the server's challenge (which aren't checked) - # * Send a blind response to the server's challenge - def auth(password) - salted_password = hi(password, @salt, @iterations) - client_key = hmac(salted_password, 'Client Key') - stored_key = h(client_key) - - final_message = "c=#{Base64::strict_encode64('n,,')},r=#{@cnonce}" - auth_message = "#{@client_fm},#{@server_fm},#{final_message}" - - client_signature = hmac(stored_key, auth_message) - client_proof = xor(client_key, client_signature) - - - response_text = "#{final_message},p=#{Base64::strict_encode64(client_proof)}" - - Jabber::debuglog("SASL SCRAM-SHA-1 response:\n#{response_text}") - - r = REXML::Element.new('response') - r.add_namespace NS_SASL - r.text = Base64::strict_encode64(response_text) - - error = nil - success = {} - @stream.send(r) { |reply| - if reply.name == 'success' and reply.namespace == NS_SASL - success = decode_challenge(Base64::decode64(reply.text)) - elsif reply.name != 'challenge' - error = reply.first_element(nil).name - end - true - } - - raise error if error - - server_key = hmac(salted_password, 'Server Key') - server_signature = hmac(server_key, auth_message) - - raise "Server authentication failed" if Base64::decode64(success['v']) != server_signature - end - - private - - def xor(a, b) - a.unpack('C*').zip(b.unpack('C*')).collect { | x, y | x ^ y }.pack('C*') - end - - def h(s) - Digest::SHA1.digest(s) - end - - def hmac(key, s) - OpenSSL::HMAC.digest('sha1', key, s) - end - - def hi(s, salt, i) - r = Array.new(size=20, obj=0).pack('C*') - u = salt + [0, 0, 0, 1].pack('C*') - - i.times do |x| - u = hmac(s, u) - r = xor(r, u) - end - - r - end - - def escape(data) - data.gsub(/=/, '=3D').gsub(/,/, '=2C') - end - end - end -end |