class Net::IMAP::SASL::DigestMD5Authenticator

Net::IMAP authenticator for the “‘DIGEST-MD5`” SASL mechanism type, specified in RFC-2831. See Net::IMAP#authenticate.

Deprecated

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

Constants

STAGE_DONE
STAGE_ONE
STAGE_TWO

Attributes

authcid[R]

Authentication identity: the identity that matches the password.

RFC-2831 uses the term username. “Authentication identity” is the generic term used by RFC-4422. RFC-4616 and many later RFCs abbreviate this to authcid.

authzid[R]

Authorization identity: an identity to act as or on behalf of. The identity form is application protocol specific. If not provided or left blank, the server derives an authorization identity from the authentication identity. The server is responsible for verifying the client’s credentials and verifying that the identity it associates with the client’s authentication identity is allowed to act as (or on behalf of) the authorization identity.

For example, an administrator or superuser might take on another role:

imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user"
password[R]

A password or passphrase that matches the username.

The password will be used to create the response digest.

username[R]

Authentication identity: the identity that matches the password.

RFC-2831 uses the term username. “Authentication identity” is the generic term used by RFC-4422. RFC-4616 and many later RFCs abbreviate this to authcid.

Public Class Methods

new(username, password, authzid = nil, **options) → authenticator click to toggle source
new(username:, password:, authzid: nil, **options) → authenticator
new(authcid:, password:, authzid: nil, **options) → authenticator

Creates an Authenticator for the “DIGEST-MD5” SASL mechanism.

Called by Net::IMAP#authenticate and similar methods on other clients.

Parameters

  • authcid ― Authentication identity that is associated with password.

    username ― An alias for authcid.

  • password ― A password or passphrase associated with this authcid.

  • optional authzid ― Authorization identity to act as or on behalf of.

    When authzid is not set, the server should derive the authorization identity from the authentication identity.

  • optional warn_deprecation — Set to false to silence the warning.

Any other keyword arguments are silently ignored.

# File net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb, line 70
def initialize(user = nil, pass = nil, authz = nil,
               username: nil, password: nil, authzid: nil,
               authcid: nil, secret: nil,
               warn_deprecation: true, **)
  username = authcid || username || user or
    raise ArgumentError, "missing username (authcid)"
  password ||= secret || pass or raise ArgumentError, "missing password"
  authzid  ||= authz
  if warn_deprecation
    warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
    # TODO: recommend SCRAM instead.
  end
  require "digest/md5"
  require "strscan"
  @username, @password, @authzid = username, password, authzid
  @nc, @stage = {}, STAGE_ONE
end

Public Instance Methods

done?() click to toggle source
# File net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb, line 156
def done?; @stage == STAGE_DONE end
initial_response?() click to toggle source
# File net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb, line 88
def initial_response?; false end
process(challenge) click to toggle source

Responds to server challenge in two stages.

# File net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb, line 91
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 Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos? and sparams['qop']
    raise Net::IMAP::Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth")

    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 => 65535,
      :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']
    a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/

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

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

Private Instance Methods

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

some responses need quoting

# File net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb, line 170
def qdval(k, v)
  return if k.nil? or v.nil?
  if %w"username authzid realm nonce cnonce digest-uri qop".include? k
    v = v.gsub(/([\\"])/, "\\\1")
    return '%s="%s"' % [k, v]
  else
    return '%s=%s' % [k, v]
  end
end