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

属性

authcid[R]

身份验证标识:与 password 匹配的标识。

RFC-2831 使用术语 username。“身份验证标识”是 RFC-4422 使用的通用术语。RFC-4616 和许多后续 RFC 将其缩写为 authcid

authzid[R]

授权标识:代表或作为其行为的标识。标识形式是特定于应用程序协议的。如果未提供或留空,服务器会从身份验证标识派生授权标识。服务器负责验证客户端的凭据,并验证与客户端的身份验证标识相关联的标识是否允许代表(或作为其行为)授权标识。

例如,管理员或超级用户可能会承担另一个角色

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

服务器发送的字符集。“UTF-8”(不区分大小写)是唯一允许的值。nil 应解释为 ISO 8859-1。

host[R]

所请求服务的完全限定规范 DNS 主机名。

默认为 #realm。

nonce[R]

服务器发送的 nonce

password[R]

username 匹配的密码或密码短语。

password 将用于创建响应摘要。

qop[R]

服务器发送的 qop 选项

realm[R]

包含 username 的命名空间或标识集合。

由 DIGEST-MD5、GSS-API 和 NTLM 使用。这通常是包含执行身份验证的主机名称的域名。

默认为服务器提供的 realm 列表中的最后一个 realm。

service[R]

服务协议,一个 注册的 GSSAPI 服务名称,例如 “imap”、“ldap” 或 “xmpp”。

对于 Net::IMAP,默认值为 “imap”,不应被覆盖。必须正确设置此值才能在其他协议中使用身份验证器。

如果 IANA 注册的名称不可用,GSS-API (RFC-2743) 允许使用通用名称 “host”。

service_name[R]

服务器被复制时的通用服务器名称。

service_namenil 或与 host 相同时,将被忽略。

来自 RFC-2831

如果客户端的服务位置过程涉及使用标准 DNS 查找操作进行解析,并且这些操作涉及将一个 DNS 名称解析为一组其他 DNS 名称的 DNS 记录(例如 SRV 或 MX),则认为该服务已被复制。在这种情况下,客户端使用的初始名称是“serv-name”,最终名称是“host”组件。

sparams[R]

服务器发送的参数存储在此哈希中。

username[R]

身份验证标识:与 password 匹配的标识。

RFC-2831 使用术语 username。“身份验证标识”是 RFC-4422 使用的通用术语。RFC-4616 和许多后续 RFC 将其缩写为 authcid

公共类方法

new(username, password, authzid = nil, **options) → authenticator 单击以切换源代码
new(username:, password:, authzid: nil, **options) → authenticator
new(authcid:, password:, authzid: nil, **options) → authenticator

为 “DIGEST-MD5” SASL 机制创建一个身份验证器。

Net::IMAP#authenticate 和其他客户端上的类似方法调用。

参数

  • authcid ― 与 password 关联的身份验证标识。

    usernameauthcid 的别名。

  • password ― 与此 authcid 关联的密码或密码短语。

  • 可选 authzid ― 代表或作为其行为的授权标识。

    当未设置 authzid 时,服务器应从身份验证标识派生授权标识。

  • 可选 realmusername 的命名空间,例如域名。默认为服务器提供的 realm 列表中的最后一个 realm。

  • 可选 host — 所请求服务的 FQDN。默认为 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

公共实例方法

digest_uri() 单击以切换源代码

来自 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
done?() 单击以切换源代码
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 246
def done?; @stage == STAGE_DONE end
initial_response?() 单击以切换源代码
# File net-imap-0.5.4/lib/net/imap/sasl/digest_md5_authenticator.rb, line 194
def initial_response?; false end
process(challenge) 单击以切换源代码

在两个阶段中响应服务器质询。

# 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

私有实例方法

compute_a0(response) 单击以切换源代码
# 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
compute_a1(response) 单击以切换源代码
# 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
compute_a2(response) 单击以切换源代码
# 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
format_response(response) 单击以切换源代码
# 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
nc(nonce) 单击以切换源代码
# 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
parse_challenge(challenge) 单击以切换源代码
# 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
qdval(k, v) 单击以切换源代码

某些响应需要引用

# 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
response_value(response) 单击以切换源代码
# 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
split_quoted_list(value, challenge) 单击以切换源代码
# 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