class Mongo::Auth::SCRAM::Conversation
Defines behaviour around a single SCRAM-SHA-1 conversation between the client and server.
@since 2.0.0
Constants
- CLIENT_CONTINUE_MESSAGE
The base client continue message.
@since 2.0.0
- CLIENT_FIRST_MESSAGE
The base client first message.
@since 2.0.0
- CLIENT_KEY
The client key string.
@since 2.0.0
- DIGEST
The digest to use for encryption.
@since 2.0.0
- DONE
The key for the done field in the responses.
@since 2.0.0
- ID
The conversation id field.
@since 2.0.0
- ITERATIONS
The iterations key in the responses.
@since 2.0.0
- PAYLOAD
The payload field.
@since 2.0.0
- RNONCE
The rnonce key in the responses.
@since 2.0.0
- SALT
The salt key in the responses.
@since 2.0.0
- SERVER_KEY
The server key string.
@since 2.0.0
- VERIFIER
The server signature verifier in the response.
@since 2.0.0
Attributes
@return [ String ] nonce The initial user nonce.
@return [ Protocol::Reply ] reply The current reply in the
conversation.
@return [ User ] user The user for the conversation.
Public Class Methods
Create the new conversation.
@example Create the new coversation.
Conversation.new(user)
@param [ Auth::User ] user The user to converse about.
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 181 def initialize(user) @user = user @nonce = SecureRandom.base64 end
Public Instance Methods
Continue the SCRAM conversation. This sends the client final message to the server after setting the reply from the previous server communication.
@example Continue the conversation.
conversation.continue(reply)
@param [ Protocol::Reply ] reply The reply of the previous
message.
@return [ Protocol::Query ] The next message to send.
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 111 def continue(reply) validate_first_message!(reply) Protocol::Query.new( user.auth_source, Database::COMMAND, CLIENT_CONTINUE_MESSAGE.merge(payload: client_final_message, conversationId: id), limit: -1 ) end
Finalize the SCRAM conversation. This is meant to be iterated until the provided reply indicates the conversation is finished.
@example Finalize the conversation.
conversation.finalize(reply)
@param [ Protocol::Reply ] reply The reply of the previous
message.
@return [ Protocol::Query ] The next message to send.
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 133 def finalize(reply) validate_final_message!(reply) Protocol::Query.new( user.auth_source, Database::COMMAND, CLIENT_CONTINUE_MESSAGE.merge(payload: client_empty_message, conversationId: id), limit: -1 ) end
Get the id of the conversation.
@example Get the id of the conversation.
conversation.id
@return [ Integer ] The conversation id.
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 169 def id reply.documents[0][ID] end
Start the SCRAM conversation. This returns the first message that needs to be send to the server.
@example Start the conversation.
conversation.start
@return [ Protocol::Query ] The first SCRAM conversation message.
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 152 def start Protocol::Query.new( user.auth_source, Database::COMMAND, CLIENT_FIRST_MESSAGE.merge(payload: client_first_message, mechanism: SCRAM::MECHANISM), limit: -1 ) end
Private Instance Methods
Auth message algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 195 def auth_message @auth_message ||= "#{first_bare},#{reply.documents[0][PAYLOAD].data},#{without_proof}" end
Get the empty client message.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 204 def client_empty_message BSON::Binary.new('') end
Client final implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 237 def client_final @client_final ||= client_proof(client_key, client_signature(stored_key(client_key), auth_message)) end
Get the final client message.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 215 def client_final_message BSON::Binary.new("#{without_proof},p=#{client_final}") end
Get the client first message
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 226 def client_first_message BSON::Binary.new("n,,#{first_bare}") end
Client key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 248 def client_key @client_key ||= hmac(salted_password, CLIENT_KEY) end
Client proof algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 259 def client_proof(key, signature) @client_proof ||= Base64.strict_encode64(xor(key, signature)) end
Client signature algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 270 def client_signature(key, message) @client_signature ||= hmac(key, message) end
# File lib/mongo/auth/scram/conversation.rb, line 432 def compare_digest(a, b) check = a.bytesize ^ b.bytesize a.bytes.zip(b.bytes){ |x, y| check |= x ^ y.to_i } check == 0 end
First bare implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 281 def first_bare @first_bare ||= "n=#{user.encoded_name},r=#{nonce}" end
H algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-2.2
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 292 def h(string) DIGEST.digest(string) end
HI algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-2.2
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 303 def hi(data) OpenSSL::PKCS5.pbkdf2_hmac_sha1( data, Base64.strict_decode64(salt), iterations, DIGEST.size ) end
HMAC algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-2.2
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 319 def hmac(data, key) OpenSSL::HMAC.digest(DIGEST, data, key) end
Get the iterations from the server response.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 328 def iterations @iterations ||= payload_data.match(ITERATIONS)[1].to_i end
Get the data from the returned payload.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 337 def payload_data reply.documents[0][PAYLOAD].data end
Get the server nonce from the payload.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 346 def rnonce @rnonce ||= payload_data.match(RNONCE)[1] end
Gets the salt from the server response.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 355 def salt @salt ||= payload_data.match(SALT)[1] end
Salted password algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 366 def salted_password @salted_password ||= hi(user.hashed_password) end
Server key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 377 def server_key @server_key ||= hmac(salted_password, SERVER_KEY) end
Server signature algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 388 def server_signature @server_signature ||= Base64.strict_encode64(hmac(server_key, auth_message)) end
Stored key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 399 def stored_key(key) h(key) end
# File lib/mongo/auth/scram/conversation.rb, line 450 def validate!(reply) raise Unauthorized.new(user) unless reply.documents[0][Operation::Result::OK] == 1 @reply = reply end
# File lib/mongo/auth/scram/conversation.rb, line 438 def validate_final_message!(reply) validate!(reply) unless compare_digest(verifier, server_signature) raise Error::InvalidSignature.new(verifier, server_signature) end end
# File lib/mongo/auth/scram/conversation.rb, line 445 def validate_first_message!(reply) validate!(reply) raise Error::InvalidNonce.new(nonce, rnonce) unless rnonce.start_with?(nonce) end
Get the verifier token from the server response.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 408 def verifier @verifier ||= payload_data.match(VERIFIER)[1] end
Get the without proof message.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 419 def without_proof @without_proof ||= "c=biws,r=#{rnonce}" end
XOR operation for two strings.
@api private
@since 2.0.0
# File lib/mongo/auth/scram/conversation.rb, line 428 def xor(first, second) first.bytes.zip(second.bytes).map{ |(a,b)| (a ^ b).chr }.join('') end