Class: Net::SASL::DigestMD5Authenticator

Inherits:
Authenticator show all
Defined in:
lib/net/sasl/digest_md5_authenticator.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Authenticator

#supports_initial_response?

Constructor Details

#initialize(username, password, authzid = nil, **_options) ⇒ DigestMD5Authenticator

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.



37
38
39
40
41
# 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

Instance Attribute Details

#authzidObject (readonly)

Returns the value of attribute authzid.



24
25
26
# File 'lib/net/sasl/digest_md5_authenticator.rb', line 24

def authzid
  @authzid
end

#passwordObject (readonly)

Returns the value of attribute password.



24
25
26
# File 'lib/net/sasl/digest_md5_authenticator.rb', line 24

def password
  @password
end

#usernameObject (readonly)

Returns the value of attribute username.



24
25
26
# File 'lib/net/sasl/digest_md5_authenticator.rb', line 24

def username
  @username
end

Instance Method Details

#done?Boolean

returns true after two challenge/response stages

Returns:

  • (Boolean)


121
122
123
# File 'lib/net/sasl/digest_md5_authenticator.rb', line 121

def done?
  @stage.nil?
end

#process(challenge) ⇒ Object

responds to the server’s DIGEST-MD5 challenges



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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