Class: Net::IMAP::SASL::ScramAuthenticator
- Inherits:
-
Object
- Object
- Net::IMAP::SASL::ScramAuthenticator
- 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.
Direct Known Subclasses
Constant Summary
Constants included from GS2Header
GS2Header::NO_NULL_CHARS, GS2Header::RFC5801_SASLNAME
Instance Attribute Summary collapse
-
#authzid ⇒ Object
readonly
Authorization identity: an identity to act as or on behalf of.
-
#cnonce ⇒ Object
readonly
The client nonce, generated by SecureRandom.
-
#iterations ⇒ Object
readonly
The iteration count for the selected hash function and user.
-
#max_iterations ⇒ Object
readonly
The maximal allowed iteration count.
-
#min_iterations ⇒ Object
readonly
The minimal allowed iteration count.
-
#password ⇒ Object
(also: #secret)
readonly
A password or passphrase that matches the #username.
-
#salt ⇒ Object
readonly
The salt used by the server for this user.
-
#server_error ⇒ Object
readonly
An error reported by the server during the SASL exchange.
-
#snonce ⇒ Object
readonly
The server nonce, which must start with #cnonce.
-
#username ⇒ Object
(also: #authcid)
readonly
Authentication identity: the identity that matches the #password.
Instance Method Summary collapse
-
#client_key ⇒ Object
Memoized ScramAlgorithm#client_key (needs #salt and #iterations).
-
#digest ⇒ Object
Returns a new OpenSSL::Digest object, set to the appropriate hash function for the chosen mechanism.
-
#done? ⇒ Boolean
Is the authentication exchange complete?.
-
#initial_client_response ⇒ Object
See RFC5802 §7
client-first-message. -
#initialize(username_arg = nil, password_arg = nil, authcid: nil, username: nil, authzid: nil, password: nil, secret: nil, min_iterations: 4096, max_iterations: 2**31 - 1, cnonce: nil, **options) ⇒ ScramAuthenticator
constructor
:call-seq: new(username, password, **options) -> auth_ctx new(username:, password:, **options) -> auth_ctx new(authcid:, password:, **options) -> auth_ctx.
-
#process(challenge) ⇒ Object
responds to the server’s challenges.
-
#salted_password ⇒ Object
Memoized ScramAlgorithm#salted_password (needs #salt and #iterations).
-
#server_key ⇒ Object
Memoized ScramAlgorithm#server_key (needs #salt and #iterations).
Methods included from ScramAlgorithm
#H, #HMAC, #Hi, #Normalize, #XOR, #auth_message, #client_proof, #client_signature, #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, max_iterations: 2**31 - 1, 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).
-
optional #max_iterations - Overrides the default value (2³¹ - 1).
Any other keyword parameters are quietly ignored.
NOTE: It is the user’s responsibility to enforce minimum and maximum iteration counts that are appropriate for their security context.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 85 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 max_iterations: 2**31 - 1, # max int32 cnonce: nil, # must only be set in tests **) @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" @max_iterations = Integer max_iterations.to_int @min_iterations <= @max_iterations or raise ArgumentError, "max_iterations must be more than min_iterations" @cnonce = cnonce || SecureRandom.base64(32) # These attrs are set from the server challenges @server_first_message = @snonce = @salt = @iterations = nil @server_error = nil # Memoized after @salt and @iterations have been sent. @salted_password = @client_key = @server_key = nil # These values are created and cached in response to server challenges @client_first_message_bare = nil @client_final_message_without_proof = nil end |
Instance Attribute Details
#authzid ⇒ Object (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.
147 148 149 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 147 def authzid @authzid end |
#cnonce ⇒ Object (readonly)
The client nonce, generated by SecureRandom
189 190 191 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 189 def cnonce @cnonce end |
#iterations ⇒ Object (readonly)
The iteration count for the selected hash function and user
198 199 200 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 198 def iterations @iterations end |
#max_iterations ⇒ Object (readonly)
The maximal allowed iteration count. Higher #iterations will raise an Error.
As noted in RFC5802
A hostile server can perform a computational denial-of-service attack on clients by sending a big iteration count value.
WARNING: The default value is 2³¹ - 1, the maximum signed 32-bit integer. This is large enough for the computation to take several minutes, and insufficient protection against hostile servers.
Note that OpenSSL::KDF.pbkdf2_hmac is implemented by a blocking C function, and cannot be interrupted by Timeout or Thread.raise. And it keeps the Global VM lock, as of v4.0 of the openssl gem, so other ruby threads will not be able to run.
To prevent a denial of service attack, this must be set to a safe value, depending on hardware and version of OpenSSL. It is the user’s responsibility to enforce minimum and maximum iteration counts that are appropriate for their security context.
186 187 188 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 186 def max_iterations @max_iterations end |
#min_iterations ⇒ Object (readonly)
The minimal allowed iteration count. Lower #iterations will raise an Error.
WARNING: The default value (4096) is set to match guidance from both RFC5802 and RFC7677, but modern recommendations are significantly higher.
It is ultimately the server’s responsibility to securely store password hashes. While this parameter can alert the user to insecure password storage and prevent insecure authentication exchange, updating the iteration count generally requires resetting the password on the server.
163 164 165 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 163 def min_iterations @min_iterations end |
#password ⇒ Object (readonly) Also known as: secret
A password or passphrase that matches the #username.
132 133 134 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 132 def password @password end |
#salt ⇒ Object (readonly)
The salt used by the server for this user
195 196 197 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 195 def salt @salt end |
#server_error ⇒ Object (readonly)
An error reported by the server during the SASL exchange.
Does not include errors reported by the protocol, e.g. Net::IMAP::NoResponseError.
204 205 206 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 204 def server_error @server_error end |
#snonce ⇒ Object (readonly)
The server nonce, which must start with #cnonce
192 193 194 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 192 def snonce @snonce end |
#username ⇒ Object (readonly) Also known as: authcid
128 129 130 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 128 def username @username end |
Instance Method Details
#client_key ⇒ Object
Memoized ScramAlgorithm#client_key (needs #salt and #iterations)
210 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 210 def client_key = @client_key ||= compute_salted { super } |
#digest ⇒ Object
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.
220 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 220 def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end |
#done? ⇒ Boolean
Is the authentication exchange complete?
If false, another server continuation is required.
250 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 250 def done?; @state == :done end |
#initial_client_response ⇒ Object
See RFC5802 §7 client-first-message.
224 225 226 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 224 def initial_client_response "#{gs2_header}#{}" end |
#process(challenge) ⇒ Object
responds to the server’s challenges
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 229 def process(challenge) case (@state ||= :initial_client_response) when :initial_client_response initial_client_response.tap { @state = :server_first_message } when :server_first_message challenge .tap { @state = :server_final_message } when :server_final_message challenge "".tap { @state = :done } else raise Error, "server sent after complete, %p" % [challenge] end rescue Exception => ex @state = ex raise end |
#salted_password ⇒ Object
Memoized ScramAlgorithm#salted_password (needs #salt and #iterations)
207 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 207 def salted_password = @salted_password ||= compute_salted { super } |
#server_key ⇒ Object
Memoized ScramAlgorithm#server_key (needs #salt and #iterations)
213 |
# File 'lib/net/imap/sasl/scram_authenticator.rb', line 213 def server_key = @server_key ||= compute_salted { super } |