class Net::SASL::DigestMD5Authenticator

Authenticator for the “`DIGEST-MD5`” SASL mechanism type, specified in RFC2831(tools.ietf.org/html/rfc2831).

Deprecated

DIGEST-MD5” has been deprecated by RFC6331 and should not be relied on for security. It is included for compatibility with existing servers.

Constants

STAGE_ONE
STAGE_TWO

Attributes

authzid[R]
password[R]
username[R]

Public Class Methods

new(username, password, authzid = nil, **_options) click to toggle source

Provide the username and password credentials. An optional authzid is defined as: “The ”authorization ID“ as per RFC2222, encoded in UTF-8. optional. If present, and the authenticating user has sufficient privilege, and the server supports it, then after authentication the server will use this identity for making all accesses and access checks. If the client specifies it, and the server does not support it, then the response-value will be incorrect, and authentication will fail.”

This should generally be instantiated via Net::SASL.authenticator.

Calls superclass method Net::SASL::Authenticator::new
# File lib/net/sasl/digest_md5_authenticator.rb, line 37
def initialize(username, password, authzid = nil, **_options)
  super
  @username, @password, @authzid = username, password, authzid
  @nc, @stage = {}, STAGE_ONE
end

Public Instance Methods

done?() click to toggle source

returns true after two challenge/response stages

# File lib/net/sasl/digest_md5_authenticator.rb, line 121
def done?
  @stage.nil?
end
process(challenge) click to toggle source

responds to the server's DIGEST-MD5 challenges

# File lib/net/sasl/digest_md5_authenticator.rb, line 46
def process(challenge)
  case @stage
  when STAGE_ONE
    @stage = STAGE_TWO
    sparams = {}
    c = StringScanner.new(challenge)
    while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]+|\\.)*"|[^,]+)\s*/)
      k, v = c[1], c[2]
      if v =~ /^"(.*)"$/
        v = $1
        if v =~ /,/
          v = v.split(",")
        end
      end
      sparams[k] = v
    end

    raise DataFormatError, "Bad Challenge: '#{challenge}'" unless c.rest.empty?
    unless sparams["qop"].include?("auth")
      raise Error,
            "Server does not support auth (qop = #{sparams["qop"].join(",")})"
    end

    response = {
      nonce: sparams["nonce"],
      username: @username,
      realm: sparams["realm"],
      cnonce: Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand,
                                                        Process.pid.to_s,]),
      'digest-uri': "imap/#{sparams["realm"]}",
      qop: "auth",
      maxbuf: 65_535,
      nc: "%08d" % nc(sparams["nonce"]),
      charset: sparams["charset"],
    }

    response[:authzid] = @authzid unless @authzid.nil?

    # now, the real thing
    a0 = Digest::MD5.digest([ response.values_at(:username, :realm),
                              @password, ].join(":"))

    a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":")
    a1 << ":#{response[:authzid]}" unless response[:authzid].nil?

    a2 = "AUTHENTICATE:#{response[:'digest-uri']}"
    if response[:qop] && response[:qop] =~ (/^auth-(?:conf|int)$/)
      a2 << ":00000000000000000000000000000000"
    end

    response[:response] = Digest::MD5.hexdigest(
      [
        Digest::MD5.hexdigest(a1),
        response.values_at(:nonce, :nc, :cnonce, :qop),
        Digest::MD5.hexdigest(a2),
      ].join(":")
    )

    response.keys.map {|key| qdval(key.to_s, response[key]) }.join(",")
  when STAGE_TWO
    @stage = nil
    # if at the second stage, return an empty string
    if challenge =~ /rspauth=/
      ""
    else
      raise ChallengeParseError, challenge
    end
  else
    raise ChallengeParseError, challenge
  end
end

Private Instance Methods

nc(nonce) click to toggle source
# File lib/net/sasl/digest_md5_authenticator.rb, line 127
def nc(nonce)
  @nc[nonce] = if @nc.key? nonce
                 @nc[nonce] + 1
               else
                 1
               end
  @nc[nonce]
end
qdval(k, v) click to toggle source

some responses need quoting

# File lib/net/sasl/digest_md5_authenticator.rb, line 137
def qdval(k, v) # rubocop:disable Naming/MethodParameterName
  return if k.nil? || v.nil?
  if %w[username authzid realm nonce cnonce digest-uri qop].include? k
    v.gsub!(/([\\"])/, "\\\1")
    '%s="%s"' % [k, v]
  else
    "%s=%s" % [k, v]
  end
end