class Net::IMAP::SASL::ScramAuthenticator


is not supported yet.</em>
Caching of salted_password, client_key, stored_key, and server_key<br><br>=== Caching SCRAM secrets<br><br>supported yet.
<em>The SCRAM-*-PLUS mechanisms and channel binding are not
=== TLS Channel binding
server_error may contain error details.
If #process raises an Error for the server-final-message, then
authentication and can return server error data in the server messages.
Unlike many other SASL mechanisms, the SCRAM-* family supports mutual
#server_error, etc.
the various attributes, e.g: #snonce, #salt, #iterations, #verifier,
As server messages are received, they are validated and loaded into
==== Server messages
See also the methods on GS2Header.
-PLUS).
which hash function that is used (or by support for channel binding with
overview of the algorithm. The different mechanisms differ only by
See the documentation and method definitions on ScramAlgorithm for an
=== SCRAM algorithm
Subclasses need only set an appropriate DIGEST_NAME constant.<br>OpenSSL::Digest.
supported by
New SCRAM-* mechanisms can easily be added for any hash algorithm
* SCRAM-SHA-256 — ScramSHA256Authenticator
* SCRAM-SHA-1 — ScramSHA1Authenticator
Directly supported:
Net::IMAP#authenticate.
defined in RFC5802. Use via
Abstract base class for the “SCRAM-*” family of SASL mechanisms,

def client_final_message_without_proof

+client-final-message-without-proof+.
See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7]
def client_final_message_without_proof
  @client_final_message_without_proof ||=
    format_message(c: [cbind_input].pack("m0"), # channel-binding
                   r: snonce)                   # nonce
end

def client_first_message_bare

+client-first-message-bare+.
See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7]
def client_first_message_bare
  @client_first_message_bare ||=
    format_message(n: gs2_saslname_encode(SASL.saslprep(username)),
                   r: cnonce)
end

def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end

algorithm supported by OpenSSL::Digest.
The class's +DIGEST_NAME+ constant must be set to the name of an

function for the chosen mechanism.
Returns a new OpenSSL::Digest object, set to the appropriate hash
def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end

def done?; @state == :done end

If false, another server continuation is required.

Is the authentication exchange complete?
def done?; @state == :done end

def final_message_with_proof

+client-final-message+.
See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7]
def final_message_with_proof
  proof = [client_proof].pack("m0")
  "#{client_final_message_without_proof},p=#{proof}"
end

def format_message(hash) hash.map { _1.join("=") }.join(",") end

def format_message(hash) hash.map { _1.join("=") }.join(",") end

def initial_client_response

+client-first-message+.
See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7]
def initial_client_response
  "#{gs2_header}#{client_first_message_bare}"
end

def initialize(username_arg = nil, password_arg = nil,

Any other keyword parameters are quietly ignored.

* _optional_ #min_iterations - Overrides the default value (4096).
* _optional_ #authzid ― Alternate identity to act as or on behalf of.
* #password ― Password or passphrase associated with this #username.
#username - An alias for #authcid.

* #authcid ― Identity whose #password is used.

=== Parameters

Called by Net::IMAP#authenticate and similar methods on other clients.

Each subclass defines #digest to match a specific mechanism.
Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms.

new(authcid:, password:, **options) -> auth_ctx
new(username:, password:, **options) -> auth_ctx
new(username, password, **options) -> auth_ctx
:call-seq:
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

def parse_challenge(challenge)

repeated keys (violating the spec) will use the last value.
this parses it simply as a hash, without respect to order. Note that
messages is fixed, with the exception of extension attributes", but
RFC5802 specifies "that the order of attributes in client or server
def parse_challenge(challenge)
  challenge.split(/,/).to_h {|pair| pair.split(/=/, 2) }
rescue ArgumentError
  raise Error, "unparsable challenge: %p" % [challenge]
end

def process(challenge)

responds to the server's challenges
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

def recv_server_final_message(server_final_message)

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

def recv_server_first_message(server_first_message)

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