class Net::IMAP::SASL::DigestMD5Authenticator
Net::IMAP
认证器,用于 RFC-2831 中指定的 DIGEST-MD5
SASL 机制类型。请参见 Net::IMAP#authenticate
。
已弃用¶ ↑
“DIGEST-MD5
” 已被 RFC-6331 弃用,不应依赖其安全性。包含此机制是为了与现有服务器兼容。
常量
- AUTH_PARAM
- DataFormatError
当数据格式不正确时引发的错误。
- LIST_DELIM
- LWS
- NO_MULTIPLES
不允许有多个的指令。RFC 中指出:
此指令最多只能出现一次;如果存在多个实例,客户端应中止身份验证交换。
- QUOTED_LISTABLE
由一个或多个逗号分隔的标记组成的指令
- QUOTED_STR
- REQUIRED
必须恰好出现一次的必需指令。RFC 中指出: >>>
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.
- ResponseParseError
当服务器的响应无法解析时引发的错误。
- STAGE_DONE
- STAGE_ONE
- STAGE_TWO
- TOKEN
属性
授权标识:代表或作为其行为的标识。标识形式是特定于应用程序协议的。如果未提供或留空,服务器会从身份验证标识派生授权标识。服务器负责验证客户端的凭据,并验证与客户端的身份验证标识相关联的标识是否允许代表(或作为其行为)授权标识。
例如,管理员或超级用户可能会承担另一个角色
imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user"
服务器发送的字符集。“UTF-8”(不区分大小写)是唯一允许的值。nil
应解释为 ISO 8859-1。
所请求服务的完全限定规范 DNS 主机名。
默认为 #realm。
服务器发送的 nonce
与 username
匹配的密码或密码短语。
password
将用于创建响应摘要。
服务器发送的 qop 选项
包含 username
的命名空间或标识集合。
由 DIGEST-MD5、GSS-API 和 NTLM 使用。这通常是包含执行身份验证的主机名称的域名。
默认为服务器提供的 realm 列表中的最后一个 realm。
服务协议,一个 注册的 GSSAPI 服务名称,例如 “imap”、“ldap” 或 “xmpp”。
对于 Net::IMAP
,默认值为 “imap”,不应被覆盖。必须正确设置此值才能在其他协议中使用身份验证器。
如果 IANA 注册的名称不可用,GSS-API (RFC-2743) 允许使用通用名称 “host”。
服务器被复制时的通用服务器名称。
当 service_name
为 nil
或与 host
相同时,将被忽略。
来自 RFC-2831
如果客户端的服务位置过程涉及使用标准 DNS 查找操作进行解析,并且这些操作涉及将一个 DNS 名称解析为一组其他 DNS 名称的 DNS 记录(例如 SRV 或 MX),则认为该服务已被复制。在这种情况下,客户端使用的初始名称是“serv-name”,最终名称是“host”组件。
服务器发送的参数存储在此哈希中。
公共类方法
为 “DIGEST-MD5
” SASL 机制创建一个身份验证器。
由 Net::IMAP#authenticate
和其他客户端上的类似方法调用。
参数¶ ↑
-
authcid
― 与password
关联的身份验证标识。username
―authcid
的别名。 -
可选
authzid
― 代表或作为其行为的授权标识。当未设置
authzid
时,服务器应从身份验证标识派生授权标识。 -
可选
realm
—username
的命名空间,例如域名。默认为服务器提供的 realm 列表中的最后一个 realm。 -
可选
service_name
— 服务器被复制时的通用主机名。 -
可选
service
— 注册的服务协议。例如,“imap”、“smtp”、“ldap”、“xmpp”。对于 Net::IMAP,默认为 “imap”。 -
可选
warn_deprecation
— 设置为false
以静默警告。
任何其他关键字参数都将被静默忽略。
# File net-imap-0.5.4/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
公共实例方法
来自 RFC-2831
指示客户端希望连接的服务的 Principal 名称,该名称由 serv-type、host 和 serv-name 组成。例如,“ftp.example.com” 上的 FTP 服务的 “digest-uri” 值为 “ftp/ftp.example.com”;上面示例中的 SMTP 服务器的 “digest-uri” 值为 “smtp/mail3.example.com/example.com”。
# File net-imap-0.5.4/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
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 246 def done?; @stage == STAGE_DONE end
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 194 def initial_response?; false end
在两个阶段中响应服务器质询。
# File net-imap-0.5.4/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
私有实例方法
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 306 def compute_a0(response) Digest::MD5.digest( [ response.values_at(:username, :realm), password ].join(":") ) end
# File net-imap-0.5.4/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
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 319 def compute_a2(response) a2 = "AUTHENTICATE:#{response[:"digest-uri"]}" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/ a2 << ":00000000000000000000000000000000" end a2 end
# File net-imap-0.5.4/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
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 286 def nc(nonce) if @nc.has_key? nonce @nc[nonce] = @nc[nonce] + 1 else @nc[nonce] = 1 end end
# File net-imap-0.5.4/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
某些响应需要引用
# File net-imap-0.5.4/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
# File net-imap-0.5.4/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
# File net-imap-0.5.4/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