class SASL::DigestMD5

RFC 2831: tools.ietf.org/html/rfc2831

Attributes

cnonce[W]

Public Class Methods

new(*a) click to toggle source
Calls superclass method SASL::Mechanism.new
# File lib/sasl/digest_md5.rb, line 10
def initialize(*a)
  super
  @nonce_count = 0
end

Public Instance Methods

receive(message_name, content) click to toggle source
Calls superclass method SASL::Mechanism#receive
# File lib/sasl/digest_md5.rb, line 25
def receive(message_name, content)
  if message_name == 'challenge'
    c = decode_challenge(content)

    unless c['rspauth']
      response = {}
      if defined?(@nonce) && response['nonce'].nil?
        # Could be reauth
      else
        # No reauth:
        @nonce_count = 0
      end
      @nonce ||= c['nonce']
      response['nonce'] = @nonce
      response['charset'] = 'utf-8'
      response['username'] = preferences.username
      response['realm'] = c['realm'] || preferences.realm
      @cnonce = generate_nonce unless defined? @cnonce
      response['cnonce'] = @cnonce
      @nc = next_nc
      response['nc'] = @nc
      @qop = c['qop'] || 'auth'
      response['qop'] = 'auth' #@qop
      response['digest-uri'] = preferences.digest_uri
      response['response'] = response_value(response['nonce'], response['nc'], response['cnonce'], response['qop'], response['realm'])
      ['response', encode_response(response)]
    else
      rspauth_expected = response_value(@nonce, @nc, @cnonce, @qop, '')
      #p :rspauth_received=>c['rspauth'], :rspauth_expected=>rspauth_expected
      if c['rspauth'] == rspauth_expected
        ['response', nil]
      else
        # Bogus server?
        @state = :failure
        ['failure', nil]
      end
    end
  else
    # No challenge? Might be success or failure
    super
  end
end
start() click to toggle source
# File lib/sasl/digest_md5.rb, line 15
def start
  @state = nil
  unless defined? @nonce
    ['auth', nil]
  else
    # reauthentication
    receive('challenge', '')
  end
end

Private Instance Methods

decode_challenge(text) click to toggle source
# File lib/sasl/digest_md5.rb, line 70
def decode_challenge(text)
  challenge = {}
  
  state = :key
  key = ''
  value = ''

  text.scan(/./) do |ch|
    if state == :key
      if ch == '='
        state = :value
      elsif ch =~ /\S/
        key += ch
      end
      
    elsif state == :value
      if ch == ','
        challenge[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
  challenge[key] = value unless key == ''
  
  #p :decode_challenge => challenge
  challenge
end
encode_response(response) click to toggle source
# File lib/sasl/digest_md5.rb, line 111
def encode_response(response)
  #p :encode_response => response
  response.collect do |k,v|
    if ['username', 'cnonce', 'digest-uri', 'authzid','realm','qop'].include? k
      v.sub!('\', '\\')
      v.sub!('"', '\"')
      "#{k}=\"#{v}\""
    else
      "#{k}=#{v}"
    end
  end.join(',')
end
generate_nonce() click to toggle source
# File lib/sasl/digest_md5.rb, line 124
def generate_nonce
  nonce = ''
  while nonce.length < 32
    c = rand(128).chr
    nonce += c if c =~ /^[a-zA-Z0-9]$/
  end
  nonce
end
h(s) click to toggle source

Function from RFC2831

# File lib/sasl/digest_md5.rb, line 135
def h(s); Digest::MD5.digest(s); end
hh(s) click to toggle source

Function from RFC2831

# File lib/sasl/digest_md5.rb, line 138
def hh(s); Digest::MD5.hexdigest(s); end
next_nc() click to toggle source
# File lib/sasl/digest_md5.rb, line 163
def next_nc
  @nonce_count += 1
  s = @nonce_count.to_s
  s = "0#{s}" while s.length < 8
  s
end
response_value(nonce, nc, cnonce, qop, realm, a2_prefix='AUTHENTICATE') click to toggle source

Calculate the value for the response field

# File lib/sasl/digest_md5.rb, line 142
def response_value(nonce, nc, cnonce, qop, realm, a2_prefix='AUTHENTICATE')
  #p :response_value => {:nonce=>nonce,
  #  :cnonce=>cnonce,
  #  :qop=>qop,
  #  :username=>preferences.username,
  #  :realm=>preferences.realm,
  #  :password=>preferences.password,
  #  :authzid=>preferences.authzid}
  a1_h = h("#{preferences.username}:#{realm}:#{preferences.password}")
  a1 = "#{a1_h}:#{nonce}:#{cnonce}"
  if preferences.authzid
    a1 += ":#{preferences.authzid}"
  end
  if qop && (qop.downcase == 'auth-int' || qop.downcase == 'auth-conf')
    a2 = "#{a2_prefix}:#{preferences.digest_uri}:00000000000000000000000000000000"
  else
    a2 = "#{a2_prefix}:#{preferences.digest_uri}"
  end
  hh("#{hh(a1)}:#{nonce}:#{nc}:#{cnonce}:#{qop}:#{hh(a2)}")
end