class Mongo::Auth::ScramConversationBase
Defines common behavior around authentication conversations between the client and the server.
@api private
Constants
- MIN_ITER_COUNT
-
The minimum iteration count for SCRAM-SHA-1 and SCRAM-SHA-256.
Attributes
Auth
message algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
@return [ String ] client_nonce
The client nonce.
Get the id of the conversation.
@example Get the id of the conversation.
conversation.id
@return [ Integer ] The conversation id.
Get the iterations from the server response.
@api private
@since 2.0.0
Get the data from the returned payload.
@api private
@since 2.0.0
Gets the salt from the server response.
@api private
@since 2.0.0
Get the server nonce from the payload.
@api private
@since 2.0.0
Public Class Methods
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 35 def initialize(user, connection, client_nonce: nil) super @client_nonce = client_nonce || SecureRandom.base64 end
Create the new conversation.
@param [ Auth::User
] user The user to converse about. @param [ String | nil ] client_nonce
The client nonce to use.
If this conversation is created for a connection that performed speculative authentication, this client nonce must be equal to the client nonce used for speculative authentication; otherwise, the client nonce must not be specified.
Mongo::Auth::ConversationBase::new
Public Instance Methods
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 70 def continue(reply_document, connection) @id = reply_document['conversationId'] payload_data = reply_document['payload'].data parsed_data = parse_payload(payload_data) @server_nonce = parsed_data.fetch('r') @salt = Base64.strict_decode64(parsed_data.fetch('s')) @iterations = parsed_data.fetch('i').to_i.tap do |i| if i < MIN_ITER_COUNT raise Error::InsufficientIterationCount.new( Error::InsufficientIterationCount.message(MIN_ITER_COUNT, i)) end end @auth_message = "#{first_bare},#{payload_data},#{without_proof}" validate_server_nonce! selector = CLIENT_CONTINUE_MESSAGE.merge( payload: client_final_message, conversationId: id, ) if connection && connection.features.op_msg_enabled? selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source cluster_time = connection.mongos? && connection.cluster_time selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time Protocol::Msg.new([], {}, selector) else Protocol::Query.new( user.auth_source, Database::COMMAND, selector, limit: -1, ) end end
Continue the SCRAM conversation. This sends the client final message to the server after setting the reply from the previous server communication.
@param [ BSON::Document ] reply_document The reply document of the
previous message.
@param [ Server::Connection
] connection The connection being
authenticated.
@return [ Protocol::Message
] The next message to send.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 120 def finalize(connection) if connection && connection.features.op_msg_enabled? selector = CLIENT_CONTINUE_MESSAGE.merge( payload: client_empty_message, conversationId: id, ) selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source cluster_time = connection.mongos? && connection.cluster_time selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time Protocol::Msg.new([], {}, selector) else Protocol::Query.new( user.auth_source, Database::COMMAND, CLIENT_CONTINUE_MESSAGE.merge( payload: client_empty_message, conversationId: id, ), limit: -1, ) end end
Finalize the SCRAM conversation. This is meant to be iterated until the provided reply indicates the conversation is finished.
@param [ Server::Connection
] connection The connection being authenticated.
@return [ Protocol::Query
] The next message to send.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 109 def process_continue_response(reply_document) payload_data = parse_payload(reply_document['payload'].data) check_server_signature(payload_data) end
Processes the second response from the server.
@param [ BSON::Document ] reply_document The reply document of the
continue response.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 56 def server_verified? !!@server_verified end
Whether the client verified the ServerSignature from the server.
@see jira.mongodb.org/browse/SECURITY-621
@return [ true | fase ] Whether the server’s signature was verified.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 150 def speculative_auth_document client_first_document.merge(db: user.auth_source) end
Returns the hash to provide to the server in the handshake as value of the speculativeAuthenticate key.
If the auth mechanism does not support speculative authentication, this method returns nil.
@return [ Hash | nil ] Speculative authentication document.
Private Instance Methods
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 336 def cache_key(*extra) [user.password, salt, iterations, @mechanism] + extra end
@api private
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 229 def check_server_signature(payload_data) if verifier = payload_data['v'] if compare_digest(verifier, server_signature) @server_verified = true else raise Error::InvalidSignature.new(verifier, server_signature) end end end
Looks for field ‘v’ in payload data, if it is present verifies the server signature. If verification succeeds, sets @server_verified to true. If verification fails, raises InvalidSignature.
This method can be called from different conversation steps depending on whether the short SCRAM conversation is used.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 195 def client_empty_message BSON::Binary.new('') end
Get the empty client message.
@api private
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 217 def client_final @client_final ||= client_proof(client_key, client_signature(stored_key(client_key), auth_message)) end
Client
final implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 206 def client_final_message BSON::Binary.new("#{without_proof},p=#{client_final}") end
Get the final client message.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 172 def client_first_message_options {skipEmptyExchange: true} end
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 177 def client_first_payload "n,,#{first_bare}" end
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 246 def client_key @client_key ||= CredentialCache.cache(cache_key(:client_key)) do hmac(salted_password, 'Client Key') end end
Client
key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 259 def client_proof(key, signature) @client_proof ||= Base64.strict_encode64(xor(key, signature)) end
Client
proof algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 270 def client_signature(key, message) @client_signature ||= hmac(key, message) end
Client
signature algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 395 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
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 281 def first_bare @first_bare ||= "n=#{user.encoded_name},r=#{client_nonce}" end
First bare implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 292 def h(string) digest.digest(string) end
H algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-2.2
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 303 def hmac(data, key) OpenSSL::HMAC.digest(digest, data, key) end
HMAC algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-2.2
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 162 def parse_payload(payload) Hash[payload.split(',').reject { |v| v == '' }.map do |pair| k, v, = pair.split('=', 2) if k == '' raise Error::InvalidServerAuthResponse, 'Payload malformed: missing key' end [k, v] end] end
Parses a payload like a=value,b=value2 into a hash like {‘a’ => ‘value’, ‘b’ => ‘value2’}.
@param [ String ] payload The payload to parse.
@return [ Hash ] Parsed key-value pairs.
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 347 def server_key @server_key ||= CredentialCache.cache(cache_key(:server_key)) do hmac(salted_password, 'Server Key') end end
Server
key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 360 def server_signature @server_signature ||= Base64.strict_encode64(hmac(server_key, auth_message)) end
Server
signature algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 371 def stored_key(key) h(key) end
Stored key algorithm implementation.
@api private
@see tools.ietf.org/html/rfc5802#section-3
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 382 def without_proof @without_proof ||= "c=biws,r=#{server_nonce}" end
Get the without proof message.
@api private
@see tools.ietf.org/html/rfc5802#section-7
@since 2.0.0
Source
# File lib/mongo/auth/scram_conversation_base.rb, line 391 def xor(first, second) first.bytes.zip(second.bytes).map{ |(a,b)| (a ^ b).chr }.join('') end
XOR operation for two strings.
@api private
@since 2.0.0