Class: Net::IMAP::SASL::DigestMD5Authenticator
Relationships & Source Files | |
Inherits: | Object |
Defined in: | lib/net/imap/sasl/digest_md5_authenticator.rb |
Overview
::Net::IMAP
authenticator for the DIGEST-MD5
::Net::IMAP::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.
Constant Summary
-
AUTH_PARAM =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 254/ (#{TOKEN}) #{LWS} = #{LWS} (#{QUOTED_STR} | #{TOKEN}) #{LIST_DELIM}? /nx
-
DataFormatError =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 12Net::IMAP::DataFormatError
-
LIST_DELIM =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 253/(?:#{LWS} , )+ #{LWS}/nx
-
LWS =
private
less strict than RFC, more strict than ‘s’
/[\r\n \t]*/n
-
NO_MULTIPLES =
private
Directives which must not have multiples. The RFC states:
This directive may appear at most once; if multiple instances are present, the client should abort the authentication exchange.
%w[nonce stale maxbuf charset algorithm].freeze
-
QUOTED_LISTABLE =
private
Directives which are composed of one or more comma delimited tokens
%w[qop cipher].freeze
-
QUOTED_STR =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 252/"(?: [\t\x20-\x7e&&[^"]] | \\[\x00-\x7f] )*"/nx
-
REQUIRED =
private
Required directives which must occur exactly once. The RFC states: >>>
This directive is required and MUST appear exactly once; if not present, or if multiple instances are present, the client should abort the authentication exchange.
%w[nonce algorithm].freeze
-
ResponseParseError =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 13Net::IMAP::ResponseParseError
-
STAGE_DONE =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 18:stage_done
-
STAGE_ONE =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 16:stage_one
-
STAGE_TWO =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 17:stage_two
-
TOKEN =
private
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 251/[^\x00-\x20\x7f()<>@,;:\\"\/\[\]?={}]+/n
Class Method Summary
-
.new(username, password, authzid = nil, **options) ⇒ authenticator
constructor
Creates an Authenticator for the “
DIGEST-MD5
”::Net::IMAP::SASL
mechanism.
Instance Attribute Summary
-
#authcid
readonly
Alias for #username.
-
#authzid
readonly
Authorization identity: an identity to act as or on behalf of.
-
#charset
readonly
The charset sent by the server.
- #done? ⇒ Boolean readonly
-
#host
readonly
Fully qualified canonical DNS host name for the requested service.
- #initial_response? ⇒ Boolean readonly
-
#nonce
readonly
nonce sent by the server.
-
#password
readonly
A password or passphrase that matches the #username.
-
#qop
readonly
qop-options sent by the server.
-
#realm
readonly
A namespace or collection of identities which contains #username.
-
#service
readonly
The service protocol, a registered GSSAPI service name, e.g. “imap”, “ldap”, or “xmpp”.
-
#service_name
readonly
The generic server name when the server is replicated.
-
#sparams
readonly
Parameters sent by the server are stored in this hash.
-
#username
(also: #authcid)
readonly
Authentication identity: the identity that matches the #password.
Instance Method Summary
-
#digest_uri
From RFC-2831: >>>.
-
#process(challenge)
Responds to server challenge in two stages.
- #compute_a0(response) private
- #compute_a1(response) private
- #compute_a2(response) private
- #format_response(response) private
- #nc(nonce) private
- #parse_challenge(challenge) private
-
#qdval(k, v)
private
some responses need quoting.
- #response_value(response) private
- #split_quoted_list(value, challenge) private
Constructor Details
.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
” ::Net::IMAP::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 tofalse
to silence the warning.
Any other keyword arguments are silently ignored.
# 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
#authcid (readonly)
Alias for #username.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 46
alias authcid username
#authzid (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"
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 64
attr_reader :authzid
#charset (readonly)
The charset sent by the server. “UTF-8” (case insensitive) is the only allowed value. nil
should be interpreted as ISO 8859-1.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 111
attr_reader :charset
#done? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 246
def done?; @stage == STAGE_DONE end
#host (readonly)
Fully qualified canonical DNS host name for the requested service.
Defaults to #realm.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 78
attr_reader :host
#initial_response? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 194
def initial_response?; false end
#nonce (readonly)
nonce sent by the server
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 114
attr_reader :nonce
#password (readonly)
A password or passphrase that matches the #username.
The password
will be used to create the response digest.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 51
attr_reader :password
#qop (readonly)
qop-options sent by the server
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 117
attr_reader :qop
#realm (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.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 73
attr_reader :realm
#service (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”.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 90
attr_reader :service
#service_name (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.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 104
attr_reader :service_name
#sparams (readonly)
Parameters sent by the server are stored in this hash.
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 107
attr_reader :sparams
#username (readonly) Also known as: #authcid
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 45
attr_reader :username
Instance Method Details
#compute_a0(response) (private)
[ GitHub ]#compute_a1(response) (private)
[ GitHub ]# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 312
def compute_a1(response) a0 = compute_a0(response) a1 = [ a0, response.values_at(:nonce, :cnonce) ].join(":") a1 << ":#{response[:authzid]}" unless response[:authzid].nil? a1 end
#compute_a2(response) (private)
[ GitHub ]#digest_uri
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”.
# 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
#format_response(response) (private)
[ GitHub ]# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 327
def format_response(response) response.map {|k, v| qdval(k.to_s, v) }.join(",") end
#nc(nonce) (private)
[ GitHub ]#parse_challenge(challenge) (private)
[ GitHub ]# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 259
def parse_challenge(challenge) sparams = Hash.new {|h, k| h[k] = [] } c = StringScanner.new(challenge) c.skip LIST_DELIM while c.scan AUTH_PARAM k, v = c[1], c[2] k = k.downcase if v =~ /\A"(.*)"\z/mn v = $1.gsub(/\\(.)/mn, '\1') v = split_quoted_list(v, challenge) if QUOTED_LISTABLE.include? k end sparams[k] << v end if !c.eos? raise DataFormatError, "Unparsable challenge: %p" % [challenge] elsif sparams.empty? raise DataFormatError, "Empty challenge: %p" % [challenge] end sparams end
#process(challenge)
Responds to server challenge in two stages.
# 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
#qdval(k, v) (private)
some responses need quoting
# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 332
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
#response_value(response) (private)
[ GitHub ]# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 294
def response_value(response) a1 = compute_a1(response) a2 = compute_a2(response) Digest::MD5.hexdigest( [ Digest::MD5.hexdigest(a1), response.values_at(:nonce, :nc, :cnonce, :qop), Digest::MD5.hexdigest(a2) ].join(":") ) end
#split_quoted_list(value, challenge) (private)
[ GitHub ]# File 'lib/net/imap/sasl/digest_md5_authenticator.rb', line 280
def split_quoted_list(value, challenge) value.split(LIST_DELIM).reject(&:empty?).tap do _1.any? or raise DataFormatError, "Bad Challenge: %p" % [challenge] end end