class Net::IMAP::SASL::ScramAuthenticator

SCRAM-*”系列 SASL 机制的抽象基类,定义在 RFC5802 中。通过 Net::IMAP#authenticate 使用。

直接支持

对于 OpenSSL::Digest 支持的任何哈希算法,都可以轻松添加新的 SCRAM-* 机制。子类只需要设置一个适当的 DIGEST_NAME 常量。

SCRAM 算法

请参阅 ScramAlgorithm 上的文档和方法定义,了解该算法的概述。不同的机制仅在使用哪个哈希函数方面有所不同(或通过对具有 -PLUS 的通道绑定的支持而有所不同)。

另请参阅 GS2Header 上的方法。

服务器消息

收到服务器消息后,它们将被验证并加载到各种属性中,例如:snoncesaltiterations、verifier、server_error 等。

与许多其他 SASL 机制不同,SCRAM-* 系列支持相互身份验证,并且可以在服务器消息中返回服务器错误数据。如果 process 为服务器最终消息引发 Error,则 server_error 可能包含错误详细信息。

TLS 通道绑定

目前不支持 SCRAM-*-PLUS 机制和通道绑定。

缓存 SCRAM 密钥

目前不支持缓存 salted_password、client_key、stored_key 和 server_key。

属性

authcid[R]

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

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

authzid[R]

授权身份:代表或充当的身份。身份形式是特定于应用程序协议的。如果未提供或留空,服务器将从身份验证身份派生授权身份。例如,管理员或超级用户可能会承担另一个角色。

imap.authenticate "SCRAM-SHA-256", "root", passwd, authzid: "user"

服务器负责验证客户端的凭据,并验证与其客户端身份验证身份关联的身份是否允许充当(或代表)授权身份。

cnonce[R]

由 SecureRandom 生成的客户端随机数

iterations[R]

所选哈希函数和用户的迭代计数

min_iterations[R]

允许的最小迭代计数。较低的 iterations 将引发 Error

password[R]

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

salt[R]

服务器为此用户使用的盐

secret[R]

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

server_error[R]

SASL 交换期间服务器报告的错误。

不包括协议报告的错误,例如 Net::IMAP::NoResponseError

server_first_message[R]

需要存储此项以用于 auth_message

snonce[R]

服务器随机数,必须以 cnonce 开头

username[R]

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

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

公共类方法

new(username, password, **options) → auth_ctx 点击切换源代码
new(username:, password:, **options) → auth_ctx
new(authcid:, password:, **options) → auth_ctx

为“SCRAM-*SASL 机制之一创建身份验证器。每个子类都定义 digest 以匹配特定的机制。

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

参数

任何其他关键字参数都会被忽略。

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 80
def initialize(username_arg = nil, password_arg = nil,
               authcid: nil, username: nil,
               authzid: nil,
               password: nil, secret: nil,
               min_iterations: 4096, # see both RFC5802 and RFC7677
               cnonce: nil, # must only be set in tests
               **options)
  @username = username || username_arg || authcid or
    raise ArgumentError, "missing username (authcid)"
  @password = password || secret || password_arg or
    raise ArgumentError, "missing password"
  @authzid = authzid

  @min_iterations = Integer min_iterations
  @min_iterations.positive? or
    raise ArgumentError, "min_iterations must be positive"

  @cnonce = cnonce || SecureRandom.base64(32)
end

公共实例方法

digest() 点击切换源代码

返回一个新的 OpenSSL::Digest 对象,设置为所选机制的适当哈希函数。

该类的 DIGEST_NAME 常量必须设置为 OpenSSL::Digest 支持的算法的名称。

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 155
def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end
done?() 点击切换源代码

身份验证交换是否完成?

如果为 false,则需要另一个服务器继续。

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 185
def done?; @state == :done end
initial_client_response() 点击切换源代码

请参阅 RFC5802 §7 client-first-message

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 159
def initial_client_response
  "#{gs2_header}#{client_first_message_bare}"
end
process(challenge) 点击切换源代码

响应服务器的质询

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 164
def process(challenge)
  case (@state ||= :initial_client_response)
  when :initial_client_response
    initial_client_response.tap { @state = :server_first_message }
  when :server_first_message
    recv_server_first_message challenge
    final_message_with_proof.tap { @state = :server_final_message }
  when :server_final_message
    recv_server_final_message challenge
    "".tap { @state = :done }
  else
    raise Error, "server sent after complete, %p" % [challenge]
  end
rescue Exception => ex
  @state = ex
  raise
end

私有实例方法

client_final_message_without_proof() 点击切换源代码

请参阅 RFC5802 §7 client-final-message-without-proof

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 240
def client_final_message_without_proof
  @client_final_message_without_proof ||=
    format_message(c: [cbind_input].pack("m0"), # channel-binding
                   r: snonce)                   # nonce
end
client_first_message_bare() 点击切换源代码

请参阅 RFC5802 §7 client-first-message-bare

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 225
def client_first_message_bare
  @client_first_message_bare ||=
    format_message(n: gs2_saslname_encode(SASL.saslprep(username)),
                   r: cnonce)
end
final_message_with_proof() 点击切换源代码

请参阅 RFC5802 §7 client-final-message

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 233
def final_message_with_proof
  proof = [client_proof].pack("m0")
  "#{client_final_message_without_proof},p=#{proof}"
end
format_message(hash) 点击切换源代码
# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 192
def format_message(hash) hash.map { _1.join("=") }.join(",") end
parse_challenge(challenge) 点击切换源代码

RFC5802 指定“客户端或服务器消息中的属性顺序是固定的,但扩展属性除外”,但此代码仅将其解析为哈希,而无需考虑顺序。请注意,重复的键(违反规范)将使用最后一个值。

# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 257
def parse_challenge(challenge)
  challenge.split(/,/).to_h {|pair| pair.split(/=/, 2) }
rescue ArgumentError
  raise Error, "unparsable challenge: %p" % [challenge]
end
recv_server_final_message(server_final_message) 点击切换源代码
# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 211
def recv_server_final_message(server_final_message)
  sparams = parse_challenge server_final_message
  @server_error = sparams["e"] and
    raise Error, "server error: %s" % [server_error]
  verifier = sparams["v"].unpack1("m") or
    raise Error, "server did not send verifier"
  verifier == server_signature or
    raise Error, "server verify failed: %p != %p" % [
      server_signature, verifier
    ]
end
recv_server_first_message(server_first_message) 点击切换源代码
# File net-imap-0.5.4/lib/net/imap/sasl/scram_authenticator.rb, line 194
def recv_server_first_message(server_first_message)
  @server_first_message = server_first_message
  sparams = parse_challenge server_first_message
  @snonce = sparams["r"] or
    raise Error, "server did not send nonce"
  @salt = sparams["s"]&.unpack1("m") or
    raise Error, "server did not send salt"
  @iterations = sparams["i"]&.then {|i| Integer i } or
    raise Error, "server did not send iteration count"
  min_iterations <= iterations or
    raise Error, "too few iterations: %d" % [iterations]
  mext = sparams["m"] and
    raise Error, "mandatory extension: %p" % [mext]
  snonce.start_with? cnonce or
    raise Error, "invalid server nonce"
end