Class: Net::IMAP::SASL::DigestMD5Authenticator

Inherits:
Object
  • Object
show all
Defined in:
lib/net/imap/sasl/digest_md5_authenticator.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user = nil, pass = nil, authz = nil, username: nil, password: nil, authzid: nil, authcid: nil, secret: nil, realm: nil, service: "imap", host: nil, service_name: nil, warn_deprecation: true) ⇒ DigestMD5Authenticator

:call-seq:

new(username,  password,  authzid = nil, **options) -> authenticator
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 #realm — A namespace for the #username, e.g. a domain. Defaults to the last realm in the server-provided realms list.

  • optional #host — FQDN for requested service. Defaults to #realm.

  • optional #service_name — The generic host name when the server is replicated.

  • optional #service — the registered service protocol. E.g. “imap”, “smtp”, “ldap”, “xmpp”. For Net::IMAP, this defaults to “imap”.

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

Any other keyword arguments are silently ignored.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 154

def initialize(user = nil, pass = nil, authz = nil,
               username: nil, password: nil, authzid: nil,
               authcid: nil, secret: nil,
               realm: nil, service: "imap", host: nil, service_name: 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.",
         category: :deprecated)
  end

  require "digest/md5"
  require "securerandom"
  require "strscan"
  @username, @password, @authzid = username, password, authzid
  @realm        = realm
  @host         = host
  @service      = service
  @service_name = service_name
  @nc, @stage = {}, STAGE_ONE
end

Instance Attribute Details

#authzidObject (readonly)

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"


64
65
66
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 64

def authzid
  @authzid
end

#charsetObject (readonly)

The charset sent by the server. “UTF-8” (case insensitive) is the only allowed value. nil should be interpreted as ISO 8859-1.



111
112
113
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 111

def charset
  @charset
end

#hostObject (readonly)

Fully qualified canonical DNS host name for the requested service.

Defaults to #realm.



78
79
80
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 78

def host
  @host
end

#nonceObject (readonly)

nonce sent by the server



114
115
116
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 114

def nonce
  @nonce
end

#passwordObject (readonly)

A password or passphrase that matches the #username.

The password will be used to create the response digest.



51
52
53
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 51

def password
  @password
end

#qopObject (readonly)

qop-options sent by the server



117
118
119
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 117

def qop
  @qop
end

#realmObject (readonly)

A namespace or collection of identities which contains username.

Used by DIGEST-MD5, GSS-API, and NTLM. This is often a domain name that contains the name of the host performing the authentication.

Defaults to the last realm in the server-provided list of realms.



73
74
75
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 73

def realm
  @realm
end

#serviceObject (readonly)

The service protocol, a registered GSSAPI service name, e.g. “imap”, “ldap”, or “xmpp”.

For Net::IMAP, the default is “imap” and should not be overridden. This must be set appropriately to use authenticators in other protocols.

If an IANA-registered name isn’t available, GSS-API (RFC-2743) allows the generic name “host”.



90
91
92
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 90

def service
  @service
end

#service_nameObject (readonly)

The generic server name when the server is replicated.

service_name will be ignored when it is nil or identical to host.

From RFC-2831:

The service is considered to be replicated if the client’s service-location process involves resolution using standard DNS lookup operations, and if these operations involve DNS records (such as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case, the initial name used by the client is the “serv-name”, and the final name is the “host” component.



104
105
106
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 104

def service_name
  @service_name
end

#sparamsObject (readonly)

Parameters sent by the server are stored in this hash.



107
108
109
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 107

def sparams
  @sparams
end

#usernameObject (readonly) Also known as: authcid

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.



45
46
47
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 45

def username
  @username
end

Instance Method Details

#digest_uriObject

From RFC-2831:

Indicates the principal name of the service with which the client wishes to connect, formed from the serv-type, host, and serv-name. For example, the FTP service on “ftp.example.com” would have a “digest-uri” value of “ftp/ftp.example.com”; the SMTP server from the example above would have a “digest-uri” value of “smtp/mail3.example.com/example.com”.



186
187
188
189
190
191
192
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 186

def digest_uri
  if service_name && service_name != host
    "#{service}/#{host}/#{service_name}"
  else
    "#{service}/#{host}"
  end
end

#done?Boolean

Returns:

  • (Boolean)


246
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 246

def done?; @stage == STAGE_DONE end

#initial_response?Boolean

Returns:

  • (Boolean)


194
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 194

def initial_response?; false end

#process(challenge) ⇒ Object

Responds to server challenge in two stages.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 197

def process(challenge)
  case @stage
  when STAGE_ONE
    @stage = STAGE_TWO
    @sparams = parse_challenge(challenge)
    @qop     = sparams.key?("qop") ? ["auth"] : sparams["qop"].flatten
    @nonce   = sparams["nonce"]  &.first
    @charset = sparams["charset"]&.first
    @realm ||= sparams["realm"]  &.last
    @host  ||= realm

    if !qop.include?("auth")
      raise DataFormatError, "Server does not support auth (qop = %p)" % [
        sparams["qop"]
      ]
    elsif (emptykey = REQUIRED.find { sparams[_1].empty? })
      raise DataFormatError, "Server didn't send %s (%p)" % [emptykey, challenge]
    elsif (multikey = NO_MULTIPLES.find { sparams[_1].length > 1 })
      raise DataFormatError, "Server sent multiple %s (%p)" % [multikey, challenge]
    end

    response = {
      nonce:        nonce,
      username:     username,
      realm:        realm,
      cnonce:       SecureRandom.base64(32),
      "digest-uri": digest_uri,
      qop:          "auth",
      maxbuf:       65535,
      nc:           "%08d" % nc(nonce),
      charset:      charset,
    }

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

    response[:response] = response_value(response)
    format_response(response)
  when STAGE_TWO
    @stage = STAGE_DONE
    raise ResponseParseError, challenge unless challenge =~ /rspauth=/
    "" # if at the second stage, return an empty string
  else
    raise ResponseParseError, challenge
  end
rescue => error
  @stage = error
  raise
end