Class: Net::IMAP::SASL::ScramAuthenticator

Inherits:
Object
  • Object
show all
Includes:
GS2Header, ScramAlgorithm
Defined in:
lib/net/imap/sasl/scram_authenticator.rb

Overview

Abstract base class for the “SCRAM-*” family of SASL mechanisms, defined in RFC5802. Use via Net::IMAP#authenticate.

Directly supported:

  • SCRAM-SHA-1 — ScramSHA1Authenticator

  • SCRAM-SHA-256 — ScramSHA256Authenticator

New SCRAM-* mechanisms can easily be added for any hash algorithm supported by OpenSSL::Digest. Subclasses need only set an appropriate DIGEST_NAME constant.

SCRAM algorithm

See the documentation and method definitions on ScramAlgorithm for an overview of the algorithm. The different mechanisms differ only by which hash function that is used (or by support for channel binding with -PLUS).

See also the methods on GS2Header.

Server messages

As server messages are received, they are validated and loaded into the various attributes, e.g: #snonce, #salt, #iterations, #verifier, #server_error, etc.

Unlike many other SASL mechanisms, the SCRAM-* family supports mutual authentication and can return server error data in the server messages. If #process raises an Error for the server-final-message, then server_error may contain error details.

TLS Channel binding

The SCRAM-*-PLUS mechanisms and channel binding are not supported yet.

Caching SCRAM secrets

Caching of salted_password, client_key, stored_key, and server_key is not supported yet.

Constant Summary

Constants included from GS2Header

GS2Header::NO_NULL_CHARS, GS2Header::RFC5801_SASLNAME

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ScramAlgorithm

#H, #HMAC, #Hi, #Normalize, #XOR, #auth_message, #client_key, #client_proof, #client_signature, #salted_password, #server_key, #server_signature, #stored_key

Methods included from GS2Header

#gs2_authzid, #gs2_cb_flag, #gs2_header, gs2_saslname_encode

Constructor Details

#initialize(username_arg = nil, password_arg = nil, authcid: nil, username: nil, authzid: nil, password: nil, secret: nil, min_iterations: 4096, cnonce: nil, **options) ⇒ ScramAuthenticator

:call-seq:

new(username,  password,  **options) -> auth_ctx
new(username:, password:, **options) -> auth_ctx
new(authcid:,  password:, **options) -> auth_ctx

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

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

Parameters

  • #authcid ― Identity whose #password is used.

    #username - An alias for #authcid.

  • #password ― Password or passphrase associated with this #username.

  • optional #authzid ― Alternate identity to act as or on behalf of.

  • optional #min_iterations - Overrides the default value (4096).

Any other keyword parameters are quietly ignored.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File '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

Instance Attribute Details

#authzidObject (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. For example, an administrator or superuser might take on another role:

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

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.



126
127
128
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 126

def authzid
  @authzid
end

#cnonceObject (readonly)

The client nonce, generated by SecureRandom



133
134
135
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 133

def cnonce
  @cnonce
end

#iterationsObject (readonly)

The iteration count for the selected hash function and user



142
143
144
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 142

def iterations
  @iterations
end

#min_iterationsObject (readonly)

The minimal allowed iteration count. Lower #iterations will raise an Error.



130
131
132
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 130

def min_iterations
  @min_iterations
end

#passwordObject (readonly) Also known as: secret

A password or passphrase that matches the #username.



111
112
113
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 111

def password
  @password
end

#saltObject (readonly)

The salt used by the server for this user



139
140
141
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 139

def salt
  @salt
end

#server_errorObject (readonly)

An error reported by the server during the SASL exchange.

Does not include errors reported by the protocol, e.g. Net::IMAP::NoResponseError.



148
149
150
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 148

def server_error
  @server_error
end

#snonceObject (readonly)

The server nonce, which must start with #cnonce



136
137
138
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 136

def snonce
  @snonce
end

#usernameObject (readonly) Also known as: authcid

Authentication identity: the identity that matches the #password.

RFC-2831 uses the term username. “Authentication identity” is the generic term used by RFC-4422. RFC-4616 and many later RFCs abbreviate this to authcid.



107
108
109
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 107

def username
  @username
end

Instance Method Details

#digestObject

Returns a new OpenSSL::Digest object, set to the appropriate hash function for the chosen mechanism.

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



155
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 155

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

#done?Boolean

Is the authentication exchange complete?

If false, another server continuation is required.

Returns:

  • (Boolean)


185
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 185

def done?; @state == :done end

#initial_client_responseObject

See RFC5802 §7 client-first-message.



159
160
161
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 159

def initial_client_response
  "#{gs2_header}#{client_first_message_bare}"
end

#process(challenge) ⇒ Object

responds to the server’s challenges



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File '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